/* ─────────────────────────────────────────────────────────
   app.jsx — main dashboard composition + Tweaks integration
   ───────────────────────────────────────────────────────── */

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "todayState": "auto",
  "density": "regular",
  "logo": "candlesticks"
}/*EDITMODE-END*/;

function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [selectedAcct, setSelectedAcct] = React.useState(0);
  const D = window.DATA;
  const acct = D.accounts[selectedAcct];
  const clock = useETClock();

  // Inject the active account's accent into a CSS var so the chart line / fill follow
  document.documentElement.style.setProperty("--acct-accent", acct.accentColor);

  // Decide which "today" plan to show. Tweak takes precedence (for demo +
  // testing). When tweak is "auto" we use the REAL position state from
  // samples.today.position_state (set by generate_dashboard_data.py based
  // on the bot's actual log), with clock-based overrides only for:
  //   - weekend (cron paused, show weekend tile)
  //   - after-hours of a market day (show tomorrow's plan)
  //   - log isn't from today (bot hasn't fired yet -> pre_market)
  // This replaces the old hardcoded 10:45-13:00 "holding window" which
  // was wrong when the bot's actual buy/sell times differ from those slots.
  const todayDateET = (() => {
    // Today's ET date as YYYY-MM-DD
    const d = new Date();
    const opts = { timeZone: "America/New_York" };
    const y = d.toLocaleString("en-US", { ...opts, year: "numeric" });
    const m = d.toLocaleString("en-US", { ...opts, month: "2-digit" });
    const day = d.toLocaleString("en-US", { ...opts, day: "2-digit" });
    return `${y}-${m}-${day}`;
  })();
  const logIsToday = acct.actualDate === todayDateET;
  const isFirstRun = !!acct.firstRun;
  const resolvedState = (() => {
    if (t.todayState !== "auto") return t.todayState;
    // A LIVE open position (current_position_ticker from GET /accounts) means the
    // account is holding RIGHT NOW — show it immediately, before the hourly
    // generator refreshes the blob. Same real-time source as the Settings strategy
    // lock, so the "Live · Holding" badge never lags. Checked before firstRun so a
    // brand-new account's very first buy shows "holding", not "awaiting first trade".
    if (acct.livePositionTicker) return "holding";
    // Brand-new account (never traded): always show the awaiting-first-trade
    // plan — in EVERY clock window, not just after-hours — so a pilot who just
    // linked their account sees their balance + "here's what happens next",
    // not a bare "no trade today" tile.
    if (isFirstRun) return "next_day";
    if (clock.parts.weekday === "Sat" || clock.parts.weekday === "Sun") return "weekend";
    if (clock.status.isAfterHours) return "next_day";
    if (!logIsToday) return "pre_market";  // bot hasn't fired yet today
    // Use actual position state from samples
    return acct.actualPositionState || "no_trade";
  })();

  const plan = (() => {
    const base = acct.todayPlans[resolvedState] || acct.todayPlans.closed;
    // For next_day, embed today's settled result so the hero can surface it
    if (resolvedState === "next_day") {
      // First-run: no settled trade exists yet — flag it so the hero shows the
      // welcome copy instead of an empty "today's result" tile.
      if (isFirstRun) return { ...base, first_run: true };
      const today = acct.todayPlans.closed;
      return {
        ...base,
        today_settled: {
          ticker: today.ticker,
          ticker_long: today.ticker_long,
          buy_time: today.buy_time,
          sell_time: today.sell_time,
          buy_price: today.buy_price,
          sell_price: today.sell_price,
          shares: today.shares,
          realized_pnl: today.realized_pnl,
          realized_pnl_pct: today.realized_pnl_pct,
        },
      };
    }
    return base;
  })();
  const equitySpark = acct.equityCurve.slice(-15).map(d => d.portfolio_value);

  // Account/position visibility: no_trade / weekend / next_day zeros out today's P&L + positions
  const flatState = ["no_trade", "weekend", "next_day"].includes(resolvedState);
  const account = flatState
    ? { ...acct.account, day_pnl_dollars: 0, open_positions_count: 0 }
    : resolvedState === "holding"
      ? { ...acct.account, open_positions_count: 1 }
      : acct.account;

  const shellStyle = t.density === "compact"
    ? { padding: "20px 32px 56px" }
    : {};

  // Bot status pill (ACTIVE green / STANDING BY red) — derived from the resolved
  // today-state + live open position + clock. Holding/open position ⇒ mid-cycle
  // ⇒ ACTIVE; everything else (after the sell, no-trade, weekend, overnight,
  // pre-cron) ⇒ STANDING BY. See botState() in components.jsx.
  const botStatus = botState({
    resolvedState,
    openPositions: account.open_positions_count,
    livePositionTicker: acct.livePositionTicker,
  }, clock.parts);

  return (
    <div className="app">
      <div className="shell" style={shellStyle}>
        <Topbar botHealth={D.botHealth} asOf={D.asOf} clock={clock} logoVariant={t.logo} botStatus={botStatus} />

        <AccountTabs
          accounts={D.accounts}
          selected={selectedAcct}
          onSelect={setSelectedAcct}
        />

        <TodayHero
          plan={plan}
          account={account}
          streakHistory={acct.streakHistory}
          currentStreak={acct.currentStreak}
          botHealth={D.botHealth}
          equitySpark={equitySpark}
          asOf={D.asOf}
          acct={acct}
          clock={clock}
        />

        <section className="section">
          <div className="section-head">
            <div>
              <div className="section-label">Performance</div>
              <h2 className="section-title">Is this strategy actually making money?</h2>
            </div>
            <div className="section-meta">window · {acct.equityCurve.length} trading days</div>
          </div>
          <StatsRow perf={acct.perf} />
          <MainChart
            equityCurve={acct.equityCurve}
            dailyPnl={acct.dailyPnl}
            tradeByDate={acct.tradeByDate}
            tickerMeta={D.tickerMeta}
            accentColor={acct.accentColor}
          />
          <TradesTable trades={acct.recentTrades} tickerMeta={D.tickerMeta} />
        </section>

        <AboutSection strategy={acct.strategy} acct={acct} />

        <footer className="foot">
          <div>thetradingoptimizer.com</div>
          <div>© {new Date().getFullYear()}</div>
        </footer>
      </div>

      <TweaksPanel title="Tweaks">
        <TweakSection label="Logo" />
        <TweakSelect
          label="Variant"
          value={t.logo}
          onChange={v => setTweak("logo", v)}
          options={[
            { value: "candlesticks", label: "Candlesticks + arrow" },
            { value: "monogram",    label: "Monogram tile (tO)" },
            { value: "ticker",      label: "$TO ticker chip" },
            { value: "target",      label: "Target / crosshair" },
            { value: "network",     label: "Network constellation" },
            { value: "bracket",     label: "Bracketed schedule" },
          ]}
        />
        <TweakSection label="Today's state" />
        <TweakSelect
          label="Scenario"
          value={t.todayState}
          onChange={v => setTweak("todayState", v)}
          options={[
            { value: "auto",       label: "Auto (follow ET clock)" },
            { value: "closed",     label: "Closed (today, real log)" },
            { value: "holding",    label: "Holding (mid-trade)" },
            { value: "pre_market", label: "Pre-market (queued)" },
            { value: "next_day",   label: "After-hours · tomorrow" },
            { value: "no_trade",   label: "No trade today" },
            { value: "weekend",    label: "Weekend / closed" },
          ]}
        />
        <TweakSection label="Density" />
        <TweakRadio
          label="Padding"
          value={t.density}
          onChange={v => setTweak("density", v)}
          options={[
            { value: "regular", label: "Regular" },
            { value: "compact", label: "Compact" },
          ]}
        />
      </TweaksPanel>
    </div>
  );
}

/* ── Error boundary — a render bug in any widget (e.g. a chart) shows a graceful
   fallback card instead of throwing and blanking the whole #root. Wraps <App/>
   for BOTH the public demo and the authenticated dashboard (same render path). */
function DashboardErrorFallback({ error, onRetry }) {
  const btn = { appearance: "none", cursor: "pointer", borderRadius: 9, padding: "10px 18px",
    font: '600 13px/1 "Inter", system-ui, sans-serif' };
  const primary = { ...btn, border: "none", color: "#1a1407",
    background: "linear-gradient(135deg, #f6e7bb, #e3c47e, #cda85c)", boxShadow: "0 4px 14px rgba(205,168,92,.25)" };
  const secondary = { ...btn, background: "#1E2229", border: "1px solid #2C333D", color: "#E7EAEE" };
  return (
    <div style={{ minHeight: "70vh", display: "flex", alignItems: "center", justifyContent: "center",
                  padding: 24, background: "#0B0D10", color: "#E7EAEE",
                  fontFamily: '"Inter", system-ui, sans-serif' }}>
      <div style={{ maxWidth: 460, width: "100%", background: "#14171C", border: "1px solid #232830",
                    borderRadius: 14, padding: "30px 26px", textAlign: "center",
                    boxShadow: "0 20px 60px rgba(0,0,0,.45)" }}>
        <div style={{ width: 52, height: 52, margin: "0 auto 16px", borderRadius: "50%", background: "#FF5C6F18",
                      display: "flex", alignItems: "center", justifyContent: "center" }}>
          <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="#FF5C6F"
               strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
            <path d="M10.3 3.9l-8.1 14A2 2 0 0 0 3.9 21h16.2a2 2 0 0 0 1.7-3.1l-8.1-14a2 2 0 0 0-3.4 0z" />
            <path d="M12 9v4" /><path d="M12 17h.01" />
          </svg>
        </div>
        <div style={{ fontSize: 18, fontWeight: 700, marginBottom: 8 }}>Something went wrong</div>
        <div style={{ fontSize: 14, color: "#8B95A1", lineHeight: 1.55, marginBottom: 22 }}>
          The dashboard hit an unexpected error rendering this view. Your data is safe — this is just a display hiccup.
        </div>
        <div style={{ display: "flex", gap: 10, justifyContent: "center", flexWrap: "wrap" }}>
          <button type="button" style={primary} onClick={onRetry}>Try again</button>
          <button type="button" style={secondary} onClick={() => window.location.reload()}>Reload page</button>
        </div>
        {error && error.message ? (
          <div style={{ marginTop: 18, fontFamily: '"JetBrains Mono", ui-monospace, monospace',
                        fontSize: 11, color: "#565E68", wordBreak: "break-word" }}>
            {String(error.message)}
          </div>
        ) : null}
      </div>
    </div>
  );
}

class ErrorBoundary extends React.Component {
  constructor(props) { super(props); this.state = { error: null }; }
  static getDerivedStateFromError(error) { return { error }; }
  componentDidCatch(error, info) {
    console.error("[dashboard] render error caught by ErrorBoundary:", error,
                  info && info.componentStack);
  }
  render() {
    if (this.state.error) {
      return <DashboardErrorFallback error={this.state.error}
               onRetry={() => this.setState({ error: null })} />;
    }
    return this.props.children;
  }
}

// ── Render entrypoint ────────────────────────────────────────────────────────
// Two callers:
//   1. Public demo (dashboard.thetradingoptimizer.com / local mockup) — data.js
//      sets window.DATA synchronously from window.SAMPLES, so the guard below
//      fires immediately on load.
//   2. Authenticated app (/app/dashboard) — app.html sets
//      window.TTO_DEFER_RENDER = true BEFORE this script runs, so the guard is
//      skipped. auth-loader.js fetches the API, builds window.DATA via
//      window.TTO.buildDataFromAccountBlobs(...), then calls window.TTO_renderApp().
// Cache the root so background refreshes (auth-loader's 60s poll / focus refetch)
// re-render IN PLACE instead of calling createRoot() on the same node every time
// (which warns in React 18 and discards component state). Reusing one root lets
// React reconcile the new window.DATA while preserving useState — the selected
// account, tweaks, and scroll position survive each refresh.
window._ttoAppRoot = window._ttoAppRoot || null;
window.TTO_renderApp = function () {
  if (!window._ttoAppRoot) {
    window._ttoAppRoot = ReactDOM.createRoot(document.getElementById("root"));
  }
  window._ttoAppRoot.render(
    <ErrorBoundary><App /></ErrorBoundary>
  );
};

if (window.DATA && !window.TTO_DEFER_RENDER) {
  window.TTO_renderApp();
}
