#pragma once
#include <optional>
#include <string>
#include <unordered_map>
#include <vector>
#include "torlinkc/engine/history.hpp"
#include "torlinkc/engine/types.hpp"
#include "torlinkc/sources/registry.hpp"
#include "torlinkc/sources/types.hpp"
#include "torlinkc/ui/sort.hpp"
namespace torlinkc::ui {
// Per-source status for the "N sources down" UI. Ported from
// useConcurrentSearch.ts's SourceState.
struct SourceState {
bool loading = false;
std::optional<std::string> error;
std::optional<std::string> code;
int count = 0;
};
// Ported from store.ts.
enum class View { Splash, Browser };
// Merges store.ts's Category ("all"/"games"/"movies"/"tv"/"anime") with its
// Section ("downloads"/"seeding") into one enum, since the C++ port has no
// separate synced category+section pair to keep in step.
enum class Section { All, Games, Movies, TV, Anime, Downloads, Seeding };
enum class Region { Sidebar, Content };
enum class DownloadFocus { Downloading, Paused, Failed, Recent };
enum class SeedFocus { Seeding, Paused, Missing, Idle };
// nullopt = the given category doesn't filter sources (Downloads/Seeding).
std::optional<Category> sectionToCategory(Section s);
std::string sectionLabel(Section s);
// The single source of truth for the TUI, mutated only on the UI thread --
// either directly inside an FTXUI event handler, or via a closure a
// background thread posts through ftxui::ScreenInteractive::Post(). Ported
// conceptually from ui/store.ts, but as a plain struct rather than a
// React-context-plus-useState bundle, since FTXUI has no equivalent
// reactivity to hang hooks off of (see the Phase 2 section of the plan).
// No internal locking: that invariant (UI-thread-only mutation) is what
// makes locking unnecessary, not something this struct enforces itself.
struct AppState {
// Navigation (ported from store.ts's view/section/region/*Focus).
View view = View::Splash;
Section section = Section::All;
Region region = Region::Content;
std::optional<DownloadFocus> downloadFocus;
std::optional<SeedFocus> seedFocus;
// Index of the currently focused child in main.cpp's top-level
// Container::Vertical -- see kFocus* constants in main.cpp. Kept in sync
// with view/section/region by main.cpp's syncFocus(), which also runs
// whenever SearchAggregator lands the first results of a search (so a
// hotkey pressed right after Enter reaches the results list, not the
// still-focused search box).
int focusedIndex = 0;
int sidebarCursor = 0;
int downloadsCursor = 0;
int seedingCursor = 0;
std::vector<std::string> sidebarLabels; // rebuilt each render; badges depend on live counts
bool showHelp = false;
bool editingFolder = false;
bool editingTrackers = false;
std::string folderPromptText;
std::string trackersPromptText;
// Search, across all sources (Phase 3's SearchAggregator).
std::string query;
std::vector<TorrentResult> results; // deduped, default-ordered, unfiltered -- SearchAggregator's output
std::unordered_map<std::string, SourceState> perSource;
int doneSources = 0;
int totalSources = 0;
bool searching = false;
// Display: section-as-category/hideDead/sort applied to `results`,
// recomputed into `visibleResults` by main.cpp's refreshVisible() whenever
// `results` or any of the three change. main.cpp's results table renders
// this directly (one row per entry, in a fixed-width column layout), so
// there's no separate label cache to keep in sync.
bool hideDead = false;
Sort sort;
std::vector<TorrentResult> visibleResults;
int selectedResult = 0;
// Elapsed seconds while View::Browser is showing (see ui/clock.hpp) --
// drives the downloads progress-bar sheen.
float uiClock = 0.0f;
// Downloads / seeds, replaced wholesale each engine-thread tick.
std::vector<QueueItem> items;
std::vector<HistoryItem> history;
std::vector<SeedItem> seeds;
std::string notice;
};
} // namespace torlinkc::ui