// Side panels: Signals, Right rail, Multi-TF delta
const { useState: useStateP, useEffect: useEffectP } = React;

// ─── i18n ───────────────────────────────────────────────────────────
const STR_panels = {
  en: {
    signals: 'Signals', all: 'All', apr: 'Apr', rej: 'Rej', filled: 'Filled',  // PR-SIGNALS-UX-1 (2026-05-25)
    session_summary: 'Session Summary',
    live: 'Live',
    offline: 'Offline',
    open_pl: 'Open P&L', realized: 'Realized', gross_fees: 'Gross / Fees',
    strategy: 'Strategy',
    mode: 'Mode', htf_bias: 'HTF Bias',
    approved_rejected: 'Approved / Rejected',
    safety: 'Safety',
    sxs_title: 'Session-by-Session P&L',
    sess: 'Session', net: 'Net', trades: 'Trades', wr: 'WR',
    asia: 'Asia', london: 'London', new_york: 'New York', extended: 'Extended',
    multi_tf_delta: 'Multi-TF Delta',
    trade_log: 'Trade Log',
    trade_log_empty: 'No trades yet today.',
    time: 'Time', side: 'Side', pnl_short: 'P&L',
    entry: 'Entry', exit: 'Exit',
  },
  es: {
    signals: 'Señales', all: 'Todas', apr: 'Apr', rej: 'Rec', filled: 'Ejecut',  // PR-SIGNALS-UX-1 (2026-05-25)
    session_summary: 'Resumen de Sesión',
    live: 'En Vivo',
    offline: 'Sin Conexión',
    open_pl: 'G/P Abierto', realized: 'Realizado', gross_fees: 'Bruto / Comisiones',
    strategy: 'Estrategia',
    mode: 'Modo', htf_bias: 'Sesgo HTF',
    approved_rejected: 'Aprobadas / Rechazadas',
    safety: 'Seguridad',
    sxs_title: 'G/P por Sesión',
    sess: 'Sesión', net: 'Neto', trades: 'Ops', wr: '%V',
    asia: 'Asia', london: 'Londres', new_york: 'Nueva York', extended: 'Extendido',
    multi_tf_delta: 'Delta Multi-TF',
    trade_log: 'Registro de Operaciones',
    trade_log_empty: 'Sin operaciones hoy.',
    time: 'Hora', side: 'Lado', pnl_short: 'G/P',
    entry: 'Entrada', exit: 'Salida',
  },
};
function t_panels(key) {
  const lang = (document.documentElement.lang || 'en').toLowerCase().split('-')[0];
  return (STR_panels[lang] && STR_panels[lang][key]) || STR_panels.en[key] || key;
}

// Slice B: re-render hook — listens for mmm-live-update from live-data.js
function useLiveTickP() {
  const [, setT] = useStateP(0);
  useEffectP(() => {
    const onUpdate = () => setT(t => t + 1);
    window.addEventListener('mmm-live-update', onUpdate);
    return () => window.removeEventListener('mmm-live-update', onUpdate);
  }, []);
}

// Phase 13A: staleness hook for the right-rail Session Summary pill.
// Mirrors chrome.jsx's BOT_OFFLINE_STALE_THRESHOLD_MS exactly so both
// pills flip in lockstep. Driven by a 5s setInterval so the pill goes
// OFFLINE even when no mmm-live-update events fire (the bot can be dead
// for hours; we still need the UI to update).
const PANELS_STALE_THRESHOLD_MS = 180 * 1000;
function useStaleP() {
  const [stale, setStale] = useStateP(false);
  useEffectP(() => {
    const check = () => {
      const live = (typeof window !== 'undefined') ? window.MMM_LIVE : null;
      const updatedMs = live && live.updated ? Date.parse(live.updated) : 0;
      const ageMs = updatedMs > 0 ? (Date.now() - updatedMs) : Infinity;
      setStale(ageMs > PANELS_STALE_THRESHOLD_MS);
    };
    check();
    const id = setInterval(check, 5000);
    const onUpdate = () => check();
    window.addEventListener('mmm-live-update', onUpdate);
    return () => {
      clearInterval(id);
      window.removeEventListener('mmm-live-update', onUpdate);
    };
  }, []);
  return stale;
}

function SignalsRail({ compact = false }) {
  useLiveTickP();
  // PR-SIGNALS-UX-1 (2026-05-25): default filter is FILLED so the rail starts
  // quiet -- showing only real trades, not the firehose of considered signals.
  // Toggle ALL / APPROVED / REJECTED for debugging the funnel.
  const [filter, setFilter] = useStateP('FILLED');
  const liveSignals = (typeof window !== 'undefined' && window.SIGNALS_DATA) || [];
  // Newest signal at the top. SIGNALS_DATA is delivered chronological
  // (oldest first) by live-data.js, so reverse a copy for display.
  const filtered = liveSignals
    .filter(s => {
      if (filter === 'ALL') return true;
      // FILLED = decision approved AND execution actually filled at IBKR.
      // The tag field is set to 'FILLED' / 'CANCELLED' / 'BLOCKED' by
      // live-data.js when execution_status is present (PR-SIGNALS-STATUS-1).
      if (filter === 'FILLED') {
        return s.status === 'approved' && (s.tag || '').toString().toUpperCase() === 'FILLED';
      }
      return s.status === filter.toLowerCase();
    })
    .slice()
    .reverse();
  return (
    <div className="signals-rail">
      <div className="rail-header">
        <span>{t_panels('signals')}</span>
        <span className="count">{filtered.length}</span>
      </div>
      <div className="signals-filters">
        {['FILLED', 'ALL', 'APPROVED', 'REJECTED'].map(f => (
          <button key={f} className={filter === f ? 'active':''} onClick={() => setFilter(f)}>
            {f === 'FILLED' ? t_panels('filled')
              : f === 'ALL' ? t_panels('all')
              : f === 'APPROVED' ? t_panels('apr')
              : t_panels('rej')}
          </button>
        ))}
      </div>
      <div className="signals-list">
        {filtered.map((s, i) => (
          <div className="signal-row" key={i}>
            <span className="time">{s.time}</span>
            <span className={`side ${s.side.toLowerCase()}`}>{s.side}</span>
            <span className={`tag ${s.status}`}>{s.tag}</span>
            <span className="muted" style={{fontSize:'var(--fs-xxs)'}}>{s.note}</span>
          </div>
        ))}
      </div>
      <MultiTFDelta />
    </div>
  );
}


// ─── CollapsibleSection — open/closed state persisted to localStorage ──
function CollapsibleSection({ title, badge, defaultOpen = true, storageKey, children }) {
  const [open, setOpen] = useStateP(() => {
    if (!storageKey || typeof window === 'undefined' || !window.localStorage) return defaultOpen;
    try {
      const v = localStorage.getItem('mmm.rail.' + storageKey);
      if (v === null) return defaultOpen;
      return v === '1';
    } catch (e) { return defaultOpen; }
  });
  const toggle = () => {
    setOpen(prev => {
      const next = !prev;
      try {
        if (storageKey) localStorage.setItem('mmm.rail.' + storageKey, next ? '1' : '0');
      } catch (e) { /* private mode etc — ignore */ }
      return next;
    });
  };
  return (
    <div className={'rail-section rail-collapsible ' + (open ? 'is-open' : 'is-collapsed')}>
      <button type="button" className="rail-section__title rail-section__toggle" onClick={toggle} aria-expanded={open}>
        <span className="rail-section__title-text">{title}</span>
        <span className="rail-section__controls">
          {badge}
          <span className="rail-section__chevron" aria-hidden="true">{open ? '\u25BE' : '\u25B8'}</span>
        </span>
      </button>
      {open && <div className="rail-section__body">{children}</div>}
    </div>
  );
}

// ─── TradeLogSection — reads window.MMM_LIVE.trade_log ──────────────
function TradeLogSection() {
  useLiveTickP();
  const live = (typeof window !== 'undefined' && window.MMM_LIVE) || {};
  const trades = Array.isArray(live.trade_log) ? live.trade_log : [];
  const fmtTLMoney = (v) => {
    if (v === null || v === undefined || Number.isNaN(v)) return '\u2014';
    const sign = v < 0 ? '\u2212' : (v > 0 ? '+' : '');
    return sign + '$' + Math.abs(v).toFixed(2);
  };
  return (
    <CollapsibleSection title={t_panels('trade_log')} storageKey="trade_log" defaultOpen={true}>
      {trades.length === 0 ? (
        <div className="rail-empty mono">{t_panels('trade_log_empty')}</div>
      ) : (
        <table className="trade-log-table">
          <thead><tr><th>{t_panels('time')}</th><th>{t_panels('side')}</th><th>{t_panels('pnl_short')}</th></tr></thead>
          <tbody>
            {trades.slice().reverse().slice(0, 12).map((t, i) => (
              <tr key={i}>
                <td className="time">{t.time_et || t.time || '\u2014'}</td>
                <td className={'side ' + (t.direction ? t.direction.toLowerCase() : '')}>{t.direction || '\u2014'}</td>
                <td className={t.pnl < 0 ? 'down' : (t.pnl > 0 ? 'up' : 'muted')}>{fmtTLMoney(t.pnl)}</td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </CollapsibleSection>
  );
}


function RightRail() {
  useLiveTickP();
  // Phase 13A: when the publisher hasn't written for >180s, the Session
  // Summary header pill flips from LIVE to OFFLINE -- same threshold as
  // the topbar pill so visitors get one consistent answer.
  const rrStale = useStaleP();
  // Session-by-Session P&L: pull from live_stats.json::session_breakdown
  // when present; otherwise show all rows as zero/dashed for "no data
  // today." Active-session highlight is driven by window.MMM_LIVE_SESSION
  // (set by chart.jsx's currentSessionET).
  const live_sxs_data = (typeof window !== 'undefined' && window.MMM_LIVE) || {};
  const breakdown = live_sxs_data.session_breakdown || {};
  const currentSess = (typeof window !== 'undefined' && window.MMM_LIVE_SESSION) || '';
  const fmtSxsMoney = (v) => {
    if (v === null || v === undefined || Number.isNaN(v)) return '—';
    if (v === 0) return '—';
    const sign = v < 0 ? '−' : '+';
    return sign + '$' + Math.abs(v).toFixed(2);
  };
  const sxs = ['asia', 'london', 'ny', 'extended'].map(sKey => {
    const data = breakdown[sKey] || {};
    const isActive = sKey === currentSess;
    const trades = data.trades !== undefined ? data.trades : 0;
    const wr = (data.win_rate !== undefined && trades > 0)
      ? Math.round(data.win_rate * 100) + '%'
      : '—';
    return {
      sKey,
      net: fmtSxsMoney(data.net),
      t: trades,
      wr,
      active: isActive,
      dim: !isActive && trades === 0,
    };
  });
  return (
    <div className="right-rail">
      <CollapsibleSection
        title={t_panels('session_summary')}
        badge={rrStale
          ? <span className="pill stale"><span className="dot"></span>{t_panels('offline')}</span>
          : <span className="pill live"><span className="dot"></span>{t_panels('live')}</span>}
        storageKey="session_summary"
        defaultOpen
      >
        {(() => {
          const live = (typeof window !== 'undefined' && window.MMM_LIVE) || {};
          const dollars = live.session_pnl_dollars;
          const pct = live.session_pnl_pct;
          const isDown = (dollars || 0) < 0;
          const cls = isDown ? 'down' : (dollars > 0 ? 'up' : 'muted');
          const fmtD = dollars === undefined || dollars === null
            ? '—'
            : (isDown ? '−' : (dollars > 0 ? '+' : '')) + '$' + Math.abs(dollars).toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2});
          const fmtP = pct === undefined || pct === null
            ? '—'
            : ((pct > 0 ? '+' : pct < 0 ? '−' : '') + Math.abs(pct).toFixed(3) + '%');
          return (
            <div className="session-pl">
              <div className={`session-pl__big ${cls}`}>{fmtD}</div>
              <div className={`session-pl__delta ${cls}`}>{fmtP}</div>
            </div>
          );
        })()}
        {(() => {
          // Read Open / Realized / Gross / Fees from live_stats.json. Fall
          // back to "—" (unknown) when the bot hasn't published the field
          // yet — never invent numbers.
          const live = (typeof window !== 'undefined' && window.MMM_LIVE) || {};
          const fmtMoney = (v) => {
            if (v === null || v === undefined || Number.isNaN(v)) return '—';
            const sign = v < 0 ? '−' : (v > 0 ? '+' : '');
            return sign + '$' + Math.abs(v).toLocaleString('en-US', {minimumFractionDigits:2, maximumFractionDigits:2});
          };
          const cls = (v) => (v === null || v === undefined) ? 'muted' : (v < 0 ? 'down' : (v > 0 ? 'up' : ''));
          const open = live.open_pnl;
          const realized = live.realized_pnl;
          const gross = live.gross_pnl;
          const fees = live.fees;
          const grossFeesText =
            (gross === null || gross === undefined) && (fees === null || fees === undefined)
              ? '—'
              : `${fmtMoney(gross)} / ${fmtMoney(fees != null ? Math.abs(fees) : fees)}`;
          return (
            <>
              <div className="kv" style={{marginTop:10}}>
                <span className="kv__k">{t_panels('open_pl')}</span>
                <span className={`kv__v ${cls(open)}`}>{fmtMoney(open)}</span>
              </div>
              <div className="kv">
                <span className="kv__k">{t_panels('realized')}</span>
                <span className={`kv__v ${cls(realized)}`}>{fmtMoney(realized)}</span>
              </div>
              <div className="kv">
                <span className="kv__k">{t_panels('gross_fees')}</span>
                <span className="kv__v">{grossFeesText}</span>
              </div>
            </>
          );
        })()}
      </CollapsibleSection>

      {(() => {
        const live = (typeof window !== 'undefined' && window.MMM_LIVE) || {};
        const mode = (live.state && live.state.mode) || live.trading_mode || '—';
        const bias = live.htf_bias || '—';
        const approved = live.signals_approved !== undefined ? live.signals_approved : '—';
        const rejected = live.signals_rejected !== undefined ? live.signals_rejected : '—';
        const safety = live.safety_rails || '—';
        // PR-BOT-STATE-1 (2026-05-22): publisher now emits richer strings like
        // "HALTED: CONSEC_LOSS 5/5". Anything that starts with "OK" or equals
        // "GREEN" is green; anything else (HALTED:..., ALERT, WARN, etc) is red.
        const safetyOk = (typeof safety === 'string') && (safety.indexOf('OK') === 0 || safety === 'GREEN');
        return (
          <CollapsibleSection title={t_panels('strategy')} storageKey="strategy" defaultOpen>
            <div className="kv"><span className="kv__k">{t_panels('mode')}</span><span className="kv__v">{mode}</span></div>
            <div className="kv"><span className="kv__k">{t_panels('htf_bias')}</span><span className="kv__v">{bias}</span></div>
            <div className="kv"><span className="kv__k">{t_panels('approved_rejected')}</span><span className="kv__v"><span className="up">{approved}</span> / <span className="down">{rejected}</span></span></div>
            <div className="kv"><span className="kv__k">{t_panels('safety')}</span><span className={`kv__v ${safetyOk ? 'up' : 'down'}`}>{safety}</span></div>
          </CollapsibleSection>
        );
      })()}

      <CollapsibleSection title={t_panels('sxs_title')} storageKey="session_by_session" defaultOpen>
        <table className="sxs-table">
          <thead><tr><th>{t_panels('sess')}</th><th>{t_panels('net')}</th><th>{t_panels('trades')}</th><th>{t_panels('wr')}</th></tr></thead>
          <tbody>
            {sxs.map(r => (
              <tr key={r.sKey} className={r.dim ? 'dim' : ''}>
                <td>{t_panels(r.sKey === 'ny' ? 'new_york' : r.sKey)}{r.active && <span className="accent" style={{marginLeft:4}}>•</span>}</td>
                <td className={r.net.startsWith('−') ? 'down' : r.net === '—' ? '' : 'up'}>{r.net}</td>
                <td>{r.t}</td>
                <td>{r.wr}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </CollapsibleSection>
      <TradeLogSection />
    </div>
  );
}

// PR-MTD-1 (2026-05-22): the six fixed timeframes the widget tracks.
// Values are NOT hardcoded -- they come from live.multi_tf_delta (publisher
// will populate in PR-MTD-2). If a value is missing, the row renders "—"
// rather than fabricating a number. See note on MultiTFDelta below.
const MTD_TIMEFRAMES = ['1m', '5m', '15m', '1h', '4h', 'D'];

function MultiTFDelta() {
  useLiveTickP();
  // PR-MTD-1 (2026-05-22): read real values from live.multi_tf_delta when
  // the publisher emits them. Field shape expected: { '1m': 0.32, '5m': ... }.
  // Until the publisher populates this field, every row renders "—" -- no
  // fabricated numbers reach the operator's eyes. The aggregate badge follows
  // the same rule: real number when at least one TF value exists, "—" otherwise.
  const live = (typeof window !== 'undefined' && window.MMM_LIVE) || {};
  const mtd = (live && typeof live.multi_tf_delta === 'object' && live.multi_tf_delta) || {};
  const rows = MTD_TIMEFRAMES.map(tf => {
    const raw = mtd[tf];
    const val = (typeof raw === 'number' && isFinite(raw)) ? raw : null;
    return { tf, val };
  });
  const present = rows.filter(r => r.val !== null).map(r => r.val);
  const haveAny = present.length > 0;
  const agg = haveAny ? (present.reduce((s, v) => s + v, 0) / present.length) : null;
  const aggUp = agg !== null && agg >= 0;
  const aggLabel = agg === null ? '—' : (aggUp ? `+${agg.toFixed(2)}` : agg.toFixed(2));
  const aggClass = agg === null ? '' : (aggUp ? 'up' : 'down');
  return (
    <CollapsibleSection
      title={t_panels('multi_tf_delta')}
      badge={<span className={`mono ${aggClass}`} style={{fontWeight:600, fontSize:'var(--fs-sm)'}}>{aggLabel}</span>}
      storageKey="multi_tf_delta"
      defaultOpen={false}
    >
      <div className="mtd-rows">
        {rows.map(r => {
          if (r.val === null) {
            // Honest empty state -- no fabricated bar fill, no false direction.
            return (
              <div className="mtd-row" key={r.tf}>
                <span className="tf">{r.tf}</span>
                <div className="bar"><div className="bar__center"></div></div>
                <span className="val muted">—</span>
              </div>
            );
          }
          const pct = Math.abs(r.val) / 0.5;
          const pctW = Math.min(1, pct) * 50;
          const isUp = r.val >= 0;
          return (
            <div className="mtd-row" key={r.tf}>
              <span className="tf">{r.tf}</span>
              <div className="bar">
                <div className="bar__center"></div>
                <div
                  className="bar__fill"
                  style={{
                    left: isUp ? '50%' : `calc(50% - ${pctW}%)`,
                    width: `${pctW}%`,
                    background: isUp ? 'var(--gain)' : 'var(--loss)',
                  }}
                ></div>
              </div>
              <span className={`val ${isUp ? 'up':'down'}`}>{isUp ? '+':''}{r.val.toFixed(2)}</span>
            </div>
          );
        })}
      </div>
    </CollapsibleSection>
  );
}

Object.assign(window, { SignalsRail, RightRail, MultiTFDelta });
