// ============================================================
// App — data loader + tab composition
// ============================================================

const GITHUB_USER = 'StaithValanthis';
const GITHUB_REPO = 'btc-agents';
const GITHUB_BRANCH = 'main';

const STATE_FILES = {
  portfolio: 'portfolio.json',
  strategies: 'strategies.json',
  research: 'research.json',
  signals: 'signals.json',
  directive: 'orchestrator-directive.json',
  syslog: 'system-log.json',
  watchlist: 'watchlist.json',
  lessons: 'lessons.json',
  actions: 'orchestrator-strategy-actions.json',
};

async function loadState(path) {
  try {
    const res = await fetch(`state/${path}`);
    if (res.ok) return await res.json();
  } catch (e) {}
  const token = localStorage.getItem('gh_token') || '';
  const headers = { Accept: 'application/vnd.github.v3.raw' };
  if (token) headers.Authorization = `token ${token}`;
  const res = await fetch(
    `https://api.github.com/repos/${GITHUB_USER}/${GITHUB_REPO}/contents/state/${path}?ref=${GITHUB_BRANCH}&t=${Date.now()}`,
    { headers }
  );
  if (res.status === 401 || res.status === 403) throw new Error('AUTH_REQUIRED');
  if (!res.ok) throw new Error(`Failed: ${path} (${res.status})`);
  return res.json();
}

const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "accent": "#3dfba1",
  "density": "default"
}/*EDITMODE-END*/;

const TABS = [
  { id: 'today', label: 'Today' },
  { id: 'strategies', label: 'Strategies' },
  { id: 'market', label: 'Market' },
  { id: 'performance', label: 'Performance' },
  { id: 'operations', label: 'Operations' },
];

function App() {
  const [data, setData] = useState({});
  const [loading, setLoading] = useState(true);
  const [lastUpdated, setLastUpdated] = useState('—');
  const [dataMode, setDataMode] = useState('LOCAL');
  const [activeTab, setActiveTab] = useState(() => {
    return localStorage.getItem('btc_tab') || 'today';
  });
  const [drawer, setDrawer] = useState(null);
  const [needsAuth, setNeedsAuth] = useState(false);
  const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);

  // Parse ?token=... from the URL synchronously, save to localStorage,
  // and strip the token from the address bar before any other code runs.
  // Runs once via lazy useState init so it executes before the first refresh().
  useState(() => {
    try {
      const params = new URLSearchParams(window.location.search);
      const urlToken = params.get('token');
      if (urlToken) {
        localStorage.setItem('gh_token', urlToken.trim());
        params.delete('token');
        const newSearch = params.toString();
        const newUrl = window.location.pathname + (newSearch ? '?' + newSearch : '') + window.location.hash;
        window.history.replaceState({}, '', newUrl);
      }
    } catch (e) { /* non-fatal — fall through to existing auth flow */ }
    return null;
  });

  // persist tab choice
  useEffect(() => { localStorage.setItem('btc_tab', activeTab); }, [activeTab]);

  // apply tweaks
  useEffect(() => {
    document.body.className =
      t.density === 'compact' ? 'density-compact' :
      t.density === 'spacious' ? 'density-spacious' : '';
    document.documentElement.style.setProperty('--accent', t.accent);
    document.documentElement.style.setProperty('--accent-soft', hexToRGBA(t.accent, 0.12));
    document.documentElement.style.setProperty('--accent-glow', hexToRGBA(t.accent, 0.35));
  }, [t.density, t.accent]);

  // keyboard shortcuts — 1..4 tab swap, R refresh
  useEffect(() => {
    const h = (e) => {
      if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
      if (e.key >= '1' && e.key <= '5') {
        setActiveTab(TABS[parseInt(e.key) - 1].id);
      } else if (e.key.toLowerCase() === 'r' && !e.metaKey && !e.ctrlKey) {
        refresh();
      }
    };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, []);

  async function refresh() {
    try {
      const probe = await fetch('state/portfolio.json');
      if (probe.ok) setDataMode('LOCAL');

      const keys = Object.keys(STATE_FILES);
      const results = await Promise.allSettled(keys.map((k) => loadState(STATE_FILES[k])));

      // If every remote load failed with AUTH_REQUIRED and we have no local data,
      // surface the token panel instead of silently rendering an empty dashboard.
      if (!probe.ok) {
        const anyAuthFailure = results.some(
          (r) => r.status === 'rejected' && r.reason?.message === 'AUTH_REQUIRED'
        );
        const allFailed = results.every((r) => r.status === 'rejected');
        if (anyAuthFailure && allFailed) {
          // Clear any bad/expired token so the user gets a clean slate
          localStorage.removeItem('gh_token');
          setNeedsAuth(true);
          setLoading(false);
          return;
        }
      }

      const next = {};
      results.forEach((r, i) => {
        if (r.status === 'fulfilled') next[keys[i]] = r.value;
      });

      // hydrate bootstrap portfolio from latest trader log + directive
      if (next.portfolio?.last_updated === 'BOOTSTRAP' && next.syslog?.entries) {
        const lastTrader = [...next.syslog.entries].reverse()
          .find((e) => e.agent === 'trader' && e.btc_equivalent_total != null);
        const sysHealth = next.directive?.system_health;
        if (lastTrader || sysHealth) {
          const btcEq = lastTrader?.btc_equivalent_total ?? sysHealth?.current_btc_held ?? 0;
          const hodl = sysHealth?.hodl_btc_benchmark ?? btcEq;
          next.portfolio = {
            ...next.portfolio,
            btc_holdings: btcEq,
            btc_equivalent_total: btcEq,
            current_btc_price_usdt: lastTrader?.btc_price_at_run ?? next.portfolio.current_btc_price_usdt,
            hodl_btc_benchmark: hodl,
            peak_btc_equivalent: sysHealth?.peak_btc_equivalent ?? btcEq,
            btc_denominated_drawdown_pct: lastTrader?.btc_denominated_drawdown_pct ?? 0,
            paper_trading: true,
            performance: {
              ...next.portfolio.performance,
              btc_equivalent_total: btcEq,
              hodl_btc_benchmark: hodl,
              vs_hodl_btc_pct: lastTrader?.vs_hodl_btc_pct ?? 0,
              sats_ahead_of_hodl: Math.round((btcEq - hodl) * 1e8),
            },
            _hydrated: true,
          };
        }
      }

      setData(next);
      setLastUpdated(new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
      setLoading(false);
    } catch (e) {
      setLoading(false);
    }
  }

  useEffect(() => {
    refresh();
    const id = setInterval(refresh, 5 * 60 * 1000);
    return () => clearInterval(id);
  }, []);

  if (loading) {
    return (
      <div className="loading-screen">
        <div className="spinner"></div>
        <div>Loading agent state…</div>
      </div>
    );
  }

  if (needsAuth) {
    return <TokenPanel onTokenSaved={() => {
      setNeedsAuth(false);
      setLoading(true);
      refresh();
    }} />;
  }

  const { portfolio, strategies, signals, directive, syslog, watchlist } = data;

  // compute tab badges
  const signalCount = (signals?.signals || []).length;
  const openCount = (portfolio?.open_positions || []).length;
  const activeStratCount = (strategies?.strategies || []).filter((s) => s.status !== 'retired').length;
  const lastEntry = (syslog?.entries || []).slice().sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp))[0];

  const tabs = [
    { id: 'today', label: 'Today',
      badge: signalCount > 0 ? signalCount : (openCount > 0 ? `${openCount} pos` : null),
      badgeKind: signalCount > 0 ? 'active' : 'neutral' },
    { id: 'strategies', label: 'Strategies',
      badge: activeStratCount, badgeKind: 'neutral' },
    { id: 'market', label: 'Market',
      badge: watchlist?.candidates?.length > 0 ? `${watchlist.candidates.length} alt` : null,
      badgeKind: 'active' },
    { id: 'performance', label: 'Performance',
      badge: (portfolio?.closed_trades || []).length > 0 ? (portfolio.closed_trades.length + ' trades') : null,
      badgeKind: 'neutral' },
    { id: 'operations', label: 'Operations',
      badge: lastEntry ? BTC.relTime(lastEntry.timestamp) : null,
      badgeKind: 'neutral' },
  ];

  // status summary line for tabbar right side
  const cs = directive?.cold_start;
  const liveBlocked = directive?.live_execution_authorized === false;
  const summary = (
    <React.Fragment>
      {cs && (
        <span className="tabbar-chip">
          <span className="tabbar-chip-dot cool"></span>
          Cold-start {directive.cold_start_day}/14
        </span>
      )}
      {liveBlocked && (
        <span className="tabbar-chip warn">
          <span className="tabbar-chip-dot warn"></span>
          Live blocked
        </span>
      )}
      <span className="tabbar-chip">
        {portfolio?.paper_trading === false ? 'LIVE' : 'PAPER'}
      </span>
    </React.Fragment>
  );

  return (
    <React.Fragment>
      <div className="shell">
        <TopBar
          portfolio={portfolio}
          directive={directive}
          lastUpdated={lastUpdated}
          dataMode={dataMode}
          actions={<ActionMenu onRefresh={refresh} dataMode={dataMode} />}
        />

        <TickerBar research={data.research} portfolio={portfolio} directive={directive} />

        <TabBar
          tabs={tabs}
          active={activeTab}
          onChange={setActiveTab}
          summary={summary}
        />

        <main className="tab-content">
          {activeTab === 'today' && <TodayView data={data} onOpenStrategy={setDrawer} />}
          {activeTab === 'strategies' && <StrategiesView data={data} onOpenStrategy={setDrawer} />}
          {activeTab === 'market' && <MarketView data={data} />}
          {activeTab === 'performance' && <PerformanceView data={data} />}
          {activeTab === 'operations' && <OperationsView data={data} />}
        </main>

        <div className="foot">
          <span>btc-agents · sats are the only score</span>
          <span>press 1–5 to switch tabs · R to refresh</span>
        </div>
      </div>

      <StrategyDrawer strategy={drawer} research={data.research} onClose={() => setDrawer(null)} />

      <TweaksPanelMount t={t} setTweak={setTweak} />
    </React.Fragment>
  );
}

function TokenPanel({ onTokenSaved }) {
  const [token, setToken] = useState('');
  const [error, setError] = useState('');
  const [testing, setTesting] = useState(false);

  async function submit() {
    const trimmed = token.trim();
    if (!trimmed) {
      setError('Paste a GitHub personal access token (starts with ghp_).');
      return;
    }
    setTesting(true);
    setError('');
    try {
      const res = await fetch(
        `https://api.github.com/repos/${GITHUB_USER}/${GITHUB_REPO}/contents/state/portfolio.json?ref=${GITHUB_BRANCH}`,
        {
          headers: {
            Accept: 'application/vnd.github.v3.raw',
            Authorization: `token ${trimmed}`,
          },
        }
      );
      if (res.status === 401) {
        setError('GitHub rejected that token — check it was copied correctly and has the repo scope.');
        setTesting(false);
        return;
      }
      if (res.status === 404) {
        setError(`Token is valid but the repo '${GITHUB_USER}/${GITHUB_REPO}' or file was not found.`);
        setTesting(false);
        return;
      }
      if (!res.ok) {
        setError(`GitHub returned ${res.status}. Try again or check token scopes.`);
        setTesting(false);
        return;
      }
      localStorage.setItem('gh_token', trimmed);
      onTokenSaved();
    } catch (e) {
      setError(`Connection error: ${e.message}`);
      setTesting(false);
    }
  }

  return (
    <div className="loading-screen" style={{ flexDirection: 'column', padding: 32 }}>
      <div style={{
        maxWidth: 520, width: '100%',
        background: 'var(--surface, #111)',
        border: '1px solid var(--border, rgba(255,255,255,0.08))',
        borderRadius: 12, padding: 28,
      }}>
        <div style={{
          fontSize: 11, letterSpacing: '0.12em', textTransform: 'uppercase',
          color: 'var(--muted, #888)', marginBottom: 16,
        }}>Connect to private repo</div>
        <div style={{ fontSize: 13, lineHeight: 1.6, color: 'var(--text-dim, #ccc)', marginBottom: 20 }}>
          The dashboard needs a GitHub personal access token to read state files
          from <strong>{GITHUB_USER}/{GITHUB_REPO}</strong>. The token is stored only
          in this browser's localStorage and is never sent anywhere except to GitHub's API.
        </div>
        <div style={{ display: 'flex', gap: 8 }}>
          <input
            type="password"
            placeholder="ghp_xxxxxxxxxxxxxxxxxxxx"
            value={token}
            onChange={(e) => setToken(e.target.value)}
            onKeyDown={(e) => { if (e.key === 'Enter') submit(); }}
            disabled={testing}
            style={{
              flex: 1, padding: '10px 12px',
              background: 'var(--surface2, #1a1a1a)',
              border: '1px solid var(--border, rgba(255,255,255,0.08))',
              borderRadius: 6,
              color: 'var(--text, #fff)',
              fontFamily: 'inherit', fontSize: 13, outline: 'none',
            }}
          />
          <button
            onClick={submit}
            disabled={testing}
            style={{
              padding: '10px 20px',
              background: 'var(--accent, #3dfba1)',
              border: 'none', borderRadius: 6,
              color: '#000', fontSize: 12, fontWeight: 500,
              cursor: testing ? 'wait' : 'pointer',
            }}
          >{testing ? 'Testing…' : 'Connect'}</button>
        </div>
        {error && (
          <div style={{ color: 'var(--red, #ef4444)', fontSize: 12, marginTop: 10 }}>{error}</div>
        )}
        <div style={{ fontSize: 11, color: 'var(--muted, #888)', marginTop: 16, lineHeight: 1.6 }}>
          Create one at GitHub → Settings → Developer settings → Personal access tokens
          → Tokens (classic) → Generate new → select <strong>repo</strong> scope.
          You can reuse the same token set as <code>GITHUB_TOKEN</code> in your routine environment.
        </div>
      </div>
    </div>
  );
}

function TweaksPanelMount({ t, setTweak }) {
  if (!window.TweaksPanel) return null;
  const { TweaksPanel, TweakSection, TweakRadio, TweakColor } = window;
  return (
    <TweaksPanel title="Tweaks">
      <TweakSection label="Layout">
        <TweakRadio
          label="Density"
          value={t.density}
          options={[
            { value: 'compact', label: 'Compact' },
            { value: 'default', label: 'Default' },
            { value: 'spacious', label: 'Spacious' },
          ]}
          onChange={(v) => setTweak('density', v)}
        />
      </TweakSection>
      <TweakSection label="Theme">
        <TweakColor
          label="Accent"
          value={t.accent}
          options={['#3dfba1', '#f7931a', '#5bb8ff', '#ff7ec8', '#b08aff']}
          onChange={(v) => setTweak('accent', v)}
        />
      </TweakSection>
    </TweaksPanel>
  );
}

function hexToRGBA(hex, alpha) {
  const h = hex.replace('#', '');
  const r = parseInt(h.slice(0, 2), 16);
  const g = parseInt(h.slice(2, 4), 16);
  const b = parseInt(h.slice(4, 6), 16);
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
