/* =============================================================================
   App — Options Scanner

   Cleaned-up version of the design's app.jsx:
     - Removed `TWEAK_DEFAULTS`, `useTweaks`, `applyDensity`, `applyAccent`,
       `ACCENTS` palette, and the entire <TweaksPanel> tree. Those were the
       artifact's design-tool plumbing, not runtime UX. The mint accent and
       balanced density become the only built-in look.
     - `window.IDEAS` and `window.SCAN` are loaded from `data.js`, which
       `dashboard.py` writes alongside `index.html` on every build.
   ===========================================================================*/

const { useState, useEffect, useMemo, useRef } = React;

// Per-column comparators for the click-to-sort headers. Each one returns
// a positive/negative number per the standard JS sort contract; the App's
// sort runner multiplies by ±1 for the desc/asc direction. Nulls are
// pushed to the end of an ascending sort (so "missing" data isn't
// accidentally ranked as "best" or "worst").
const SORT_COMPARATORS = {
  ticker:     (a, b) => a.ticker.localeCompare(b.ticker),
  underlying: (a, b) => (a.underlying || 0) - (b.underlying || 0),
  strategy:   (a, b) => a.strategy.localeCompare(b.strategy),
  exp:        (a, b) => (a.exp || "").localeCompare(b.exp || ""),
  dte:        (a, b) => (a.dte || 0) - (b.dte || 0),
  credit:     (a, b) => (a.credit || 0) - (b.credit || 0),
  maxProfit:  (a, b) => {
    // null = unbounded (LC). Treat as +Infinity so it sorts to the top
    // of a descending-by-profit view; the "Unlimited" label communicates this.
    const av = a.maxProfit === null || a.maxProfit === undefined ? Infinity : a.maxProfit;
    const bv = b.maxProfit === null || b.maxProfit === undefined ? Infinity : b.maxProfit;
    return av - bv;
  },
  pop:        (a, b) => (a.pop || 0) - (b.pop || 0),
  ivRank:     (a, b) => (a.ivRank || 0) - (b.ivRank || 0),
  confluence: (a, b) => (a.confluence || 0) - (b.confluence || 0),
};

// Default direction when a column is first clicked. Numeric/quantitative
// columns default to desc (operator wants the highest first). String /
// date columns default to asc (alphabetical / chronological).
const SORT_DEFAULTS = {
  ticker: "asc", underlying: "desc", strategy: "asc", exp: "asc",
  dte: "asc", credit: "desc", maxProfit: "desc",
  pop: "desc", ivRank: "desc", confluence: "desc",
};

function App() {
  const [theme, setTheme] = useState(() => {
    const saved = localStorage.getItem("os-theme");
    if (saved) return saved;
    return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
  });
  // Top-level tab: "ideas" (the original scanner) or "positions" (manual
  // open-position tracker). Persists across reloads via localStorage.
  const [tab, setTab] = useState(() => localStorage.getItem("os-tab") || "ideas");
  useEffect(() => { localStorage.setItem("os-tab", tab); }, [tab]);

  // For the Add-position form: when an idea row's "Track" button is
  // clicked we pre-fill these fields. ``null`` = form is closed.
  const [addPositionPrefill, setAddPositionPrefill] = useState(null);
  // For mark-as-closed: id of the position being closed. ``null`` =
  // close modal not open.
  const [closingPositionId, setClosingPositionId] = useState(null);
  // For the position drawer (Pass 2): id of the position whose
  // detail panel is open. Null = drawer closed.
  const [selectedPositionId, setSelectedPositionId] = useState(null);

  const [activeStrat, setActiveStrat] = useState("ALL");
  // Sort is column-aware so the table headers can act as the sort UI:
  // first click on a column sets its default direction (desc for numerics
  // where the operator wants high values first; asc for alpha/date),
  // second click on the same column toggles, click on a different column
  // resets to that column's default.
  const [sort, setSort] = useState({ column: "confluence", direction: "desc" });
  const [view, setView] = useState("list");
  const [query, setQuery] = useState("");
  const [selectedId, setSelectedId] = useState(null);

  useEffect(() => {
    document.body.dataset.theme = theme;
    localStorage.setItem("os-theme", theme);
  }, [theme]);

  // Live refresh controller — talks to scripts/refresh_server.py.
  // Falls through silently if the dashboard is being served by a plain
  // static file server (e.g. python -m http.server) without the FastAPI
  // refresh API; buttons just won't do anything in that mode.
  const refresh = useRefreshController();

  const ideas = useMemo(() => {
    let list = (window.IDEAS || []).slice();
    if (activeStrat !== "ALL") list = list.filter(i => i.strategy === activeStrat);
    if (query.trim()) list = list.filter(i => i.ticker.toLowerCase().includes(query.trim().toLowerCase()));
    const cmp = SORT_COMPARATORS[sort.column];
    if (cmp) {
      const sign = sort.direction === "desc" ? -1 : 1;
      list.sort((a, b) => sign * cmp(a, b));
    }
    return list;
  }, [activeStrat, sort, query]);

  // Toggle direction if clicking the same column; otherwise switch to the
  // new column with its column-type default direction. Numerics start
  // descending (highest value first), alpha/date start ascending.
  const onSort = (column) => {
    setSort(prev => {
      if (prev.column === column) {
        return { column, direction: prev.direction === "desc" ? "asc" : "desc" };
      }
      return { column, direction: SORT_DEFAULTS[column] || "desc" };
    });
  };

  const selected = useMemo(
    () => (window.IDEAS || []).find(i => i.id === selectedId) || null,
    [selectedId]
  );

  return (
    <>
      <TopBar
        theme={theme}
        onToggleTheme={() => setTheme(theme === "dark" ? "light" : "dark")}
        refresh={refresh}
        tab={tab}
        onTabChange={setTab}
      />

      <main className="shell">
        {tab === "history" ? (
          <HistoryView />
        ) : tab === "positions" ? (
          <PositionsView
            onAddClick={() => setAddPositionPrefill({})}
            onCloseClick={(id) => setClosingPositionId(id)}
            onSelectPosition={(id) => setSelectedPositionId(id)}
          />
        ) : (
          <>
            <Hero ideas={window.IDEAS || []} onPick={setSelectedId} />

            <div className="section-head">
              <div className="section-title">Top ideas</div>
              <div className="section-sub">
                ranked by confluence — click any row for the full breakdown
              </div>
            </div>

            <Toolbar
              activeStrat={activeStrat} setActiveStrat={setActiveStrat}
              view={view} setView={setView}
              query={query} setQuery={setQuery}
            />

            {ideas.length === 0 ? (
              <div className="ideas-card" style={{ padding: "60px 20px", textAlign: "center", color: "var(--text-3)" }}>
                No ideas match those filters.
              </div>
            ) : view === "list" ? (
              <IdeasTable ideas={ideas} selectedId={selectedId} onSelect={setSelectedId}
                          sort={sort} onSort={onSort}
                          onTrackPosition={(idea) => setAddPositionPrefill({
                            ticker: idea.ticker,
                            strategy: idea.strategy.toLowerCase(),
                            longK: idea.longK, shortK: idea.shortK,
                            expiration: idea.exp,
                            // Suggested entry: signed credit/debit. Idea's
                            // `credit` is already signed (PCS/CSP positive,
                            // LC/CDS negative).
                            entry_price: idea.credit,
                          })} />
            ) : (
              <GroupedView ideas={ideas} selectedId={selectedId} onSelect={setSelectedId} />
            )}
          </>
        )}

        <div className="foot">
          read-only analysis · execution is manual · scan {(window.SCAN && window.SCAN.id) || ""}
        </div>
      </main>

      {/* Modals + drawers are mounted at the App root so they overlay
          either tab. The position drawer slides in from the right; the
          modals are centered overlays. */}
      <PositionDrawer
        position={(window.POSITIONS || []).find(p => p.id === selectedPositionId) || null}
        onClose={() => setSelectedPositionId(null)}
        onCloseClick={(id) => {
          // Closing the position implicitly closes the drawer too.
          setSelectedPositionId(null);
          setClosingPositionId(id);
        }}
      />
      {addPositionPrefill !== null && (
        <AddPositionModal
          prefill={addPositionPrefill}
          onClose={() => setAddPositionPrefill(null)}
        />
      )}
      {closingPositionId !== null && (
        <ClosePositionModal
          positionId={closingPositionId}
          onClose={() => setClosingPositionId(null)}
        />
      )}

      <Drawer idea={selected} onClose={() => setSelectedId(null)} />
    </>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
