/* events.jsx — Signal Events tab.
   Full feed of all parsed events, with channel-category filter and analyst filter.
   Click any row → flyout with parser internals.
*/

// A single event's price = the fill stated in that message, plus a "→ mark" when
// the message also gave a current price. Mirrors TradeTimeline so the Events feed
// reads the same way as the position detail (and positions' Entry → Now column).
function eventFillMark(e) {
  const exitAction = ["close", "cut", "trim"].includes(e.action);
  const fill = e.price != null ? e.price : (exitAction ? e.currentPrice : null);
  const mark = (!exitAction && e.currentPrice != null) ? e.currentPrice : null;
  return { fill, mark };
}

function ChannelDot({ cat }) {
  const color = cat === "signals" ? "var(--accent)" :
                cat === "analysis" ? "var(--c-watch)" :
                cat === "chat" ? "var(--fg-3)" : "var(--fg-3)";
  return <span className="dot" style={{ color, background: color, width: 6, height: 6, marginRight: 8 }} />;
}

function CategoryFilter({ value, onChange }) {
  const opts = [["all", "All"], ["signals", "Signals"], ["analysis", "Analysis"], ["chat", "Chat"]];
  return (
    <div className="seg">
      {opts.map(([v, l]) => (
        <button key={v} className={cx("seg-btn", value === v && "on")} onClick={() => onChange(v)}>{l}</button>
      ))}
    </div>
  );
}

// Mobile Signal Events — compact accordion (matches Open Positions). Each row:
// ticker · instrument · analyst · action · time; tap to reveal details + the raw
// Discord message, plus parser/delete actions.
function MobileEventList({ rows, analysts, onShowParser, onDelete, canDeleteRow, selectable, selected, onToggle, grouped, onEditEvent, canEditEvent }) {
  const canDelRow = (e) => !canDeleteRow || canDeleteRow(e);
  const [open, setOpen] = useState(null);
  if (!rows.length) return <Empty msg="No events match." />;
  return (
    <div className="mpos-list">
      {rows.map((e, idx) => {
        const isOpen = open === e.id;
        const opt = isOption(e);
        const rcy = recencyBucket(e.ts);
        const showSep = grouped && (idx === 0 || dayKey(e.ts) !== dayKey(rows[idx - 1].ts));
        return (
          <Fragment key={e.id}>
          {showSep && <div className="mpos-day"><span className="mpos-day-t">{fmtDayGroup(e.ts)}</span></div>}
          <div className={cx("mpos", isOpen && "open", rcy === "live" && "row-fresh", selectable && selected.has(e.id) && "msel")}>
            <div className="mpos-head" onClick={() => setOpen(isOpen ? null : e.id)}>
              <div className="mpos-r1">
                <span className="mpos-tk">
                  {selectable && <input type="checkbox" className="mpos-cb" checked={selected.has(e.id)} onClick={ev => ev.stopPropagation()} onChange={() => onToggle(e.id)} />}
                  <span className="ticker">${e.ticker}</span>
                  <InstrumentTag event={e} />
                  {rcy === "live" && <span className="rcy-new">NEW</span>}
                </span>
                <span className="mpos-chev">{I(isOpen ? "chevDown" : "chevRight", { size: 16 })}</span>
              </div>
              <div className="mpos-r2">
                <AnalystChip analyst={e.analyst} analysts={analysts} />
                <span className="mpos-r2r"><ActionPill kind={e.action} /><span className="mpos-meta mono">{fmtTime(e.ts, { short: true })}</span></span>
              </div>
            </div>
            {isOpen && (
              <div className="mpos-body">
                <dl className="mpos-grid">
                  <div><dt>Price</dt><dd>{(() => {
                    const { fill, mark } = eventFillMark(e);
                    if (fill == null && mark == null) return "—";
                    return <>{fill != null ? fmtNum(fill) : "—"}{mark != null && <span className="mpos-now"> → {fmtNum(mark)}</span>}</>;
                  })()}</dd></div>
                  <div><dt>Conf</dt><dd><ConfPill level={e.confidence} /></dd></div>
                  <div><dt>Channel</dt><dd><ChannelDot cat={e.channelCategory} />{e.channel}</dd></div>
                  {opt && <div><dt>Expiry</dt><dd>{e.expiry ? fmtExpiry(e.expiry) : "—"}</dd></div>}
                  {opt && <div><dt>Strike</dt><dd>{e.strike != null ? e.strike : "—"}</dd></div>}
                  {e.stopLoss != null && <div><dt>Stop</dt><dd><span className="mpos-dn">{fmtNum(e.stopLoss)}</span></dd></div>}
                </dl>
                <div className="mpos-sec">
                  <span className="mpos-lbl">Raw message</span>
                  <p className="mpos-note">{e.raw}</p>
                </div>
                {(onShowParser || (onEditEvent && canEditEvent && canEditEvent(e)) || (onDelete && canDelRow(e))) && (
                  <div className="mpos-btns">
                    {onEditEvent && canEditEvent && canEditEvent(e) && <button className="btn" onClick={() => onEditEvent(e)}>{I("edit", { size: 15 })} Override</button>}
                    {onShowParser && <button className="btn" onClick={() => onShowParser(e)}>{I("brain", { size: 15 })} Parser</button>}
                    {onDelete && canDelRow(e) && <button className="btn danger" onClick={async () => { if (await confirmDialog({ title: "Delete event", message: `Delete this ${e.action.toUpperCase()} $${e.ticker} event? It will also be removed from positions, recaps and charts.`, confirmLabel: "Delete", danger: true })) onDelete(e.id); }}>{I("trash", { size: 14 })} Delete</button>}
                  </div>
                )}
              </div>
            )}
          </div>
          </Fragment>
        );
      })}
    </div>
  );
}

function SignalEvents({ events, analysts, onShowParser, onDelete, onDeleteMany, canDeleteRow, onEditEvent, canEditEvent, serverIds, onRestore, tickerFilter, onTickerFilter }) {
  const canDelRow = (e) => !canDeleteRow || canDeleteRow(e);
  const isMobile = useIsMobile();
  const [cat, setCat] = useState("all");
  const [action, setAction] = useState("all");
  const [inst, setInst] = useState("all");
  const [anFilter, setAnFilter] = useState(() => new Set());
  const [filter, setFilter] = useState(tickerFilter || "");
  // Arriving from a To-Do's "Find" — adopt the ticker it sent, then clear the
  // request so navigating back to Events later doesn't silently re-apply it.
  useEffect(() => {
    if (!tickerFilter) return;
    setFilter(tickerFilter);
    if (onTickerFilter) onTickerFilter("");
  }, [tickerFilter]);
  const [selected, setSelected] = useState(() => new Set());
  const [expanded, setExpanded] = useState(null); // event id whose raw note is open
  const [showDeleted, setShowDeleted] = useState(false); // recycle-bin view
  const selectable = !!onDeleteMany;

  const availableAnalysts = useMemo(() => {
    const set = new Set();
    for (const e of events) if (e.analyst) set.add(e.analyst);
    return [...set].sort();
  }, [events]);

  const filtered = useMemo(() => {
    const fl = filter.toLowerCase().trim();
    return events.filter(e => {
      if (cat !== "all" && e.channelCategory !== cat) return false;
      if (action !== "all" && e.action !== action) return false;
      if (!matchesInstrument(e, inst)) return false;
      if (!analystMatch(e.analyst, anFilter)) return false;
      if (!fl) return true;
      return e.ticker.toLowerCase().includes(fl);
    });
  }, [events, cat, action, inst, anFilter, filter]);

  // Newest-first feed + per-day counts, so the table can break into Today /
  // Yesterday / … sections — making "what's new vs old" obvious at a glance.
  const feed = useMemo(() => [...filtered].sort((a, b) => b.ts - a.ts), [filtered]);
  const dayCounts = useMemo(() => {
    const m = new Map();
    for (const e of feed) { const k = dayKey(e.ts); m.set(k, (m.get(k) || 0) + 1); }
    return m;
  }, [feed]);

  const actionOpts = ["all", "open", "add", "trim", "cut", "close", "hold", "watch", "expire"];

  // ── Multi-select ──
  const filteredIds = filtered.map(e => e.id);
  const allSelected = selectable && filteredIds.length > 0 && filteredIds.every(id => selected.has(id));
  function toggle(id) {
    setSelected(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n; });
  }
  function toggleAll() {
    setSelected(prev => {
      const n = new Set(prev);
      if (allSelected) filteredIds.forEach(id => n.delete(id)); else filteredIds.forEach(id => n.add(id));
      return n;
    });
  }
  async function bulkDelete() {
    const ids = [...selected];
    if (!ids.length) return;
    if (await confirmDialog({ title: "Delete events", message: `Delete ${ids.length} selected event${ids.length === 1 ? "" : "s"}? They'll be removed from positions, recaps and charts too.`, confirmLabel: `Delete ${ids.length}`, danger: true })) {
      onDeleteMany(ids);
      setSelected(new Set());
    }
  }
  const colCount = 13 + (selectable ? 1 : 0);

  // Live | Deleted toggle — only for users who can delete (they own a bin).
  const showBinToggle = !!onDelete;
  const binToggle = showBinToggle && (
    <div className="seg" role="group" aria-label="Recycle bin">
      <button className={cx("seg-btn", !showDeleted && "on")} onClick={() => setShowDeleted(false)}>Live</button>
      <button className={cx("seg-btn", showDeleted && "on")} onClick={() => setShowDeleted(true)}>Deleted</button>
    </div>
  );

  if (showDeleted && showBinToggle) {
    return (
      <div>
        <div className="filterbar" style={{ gridTemplateColumns: "1fr auto" }}>
          <div className="muted" style={{ alignSelf: "center", fontSize: "0.8125rem" }}>Recycle bin — restore a deleted event or remove it for good</div>
          {binToggle}
        </div>
        <div className="panel">
          <div className="panel-hdr"><div className="panel-title">Recycle bin</div></div>
          <div className="scroll-y feed-scroll" style={{ maxHeight: "calc(100vh - 250px)", minHeight: 400 }}>
            <TrashList source="tape_trade_events" serverIds={serverIds} onRestored={onRestore} noun="event" />
          </div>
        </div>
      </div>
    );
  }

  return (
    <div>
      <div className="filterbar" style={{ gridTemplateColumns: "1fr auto auto auto auto" + (showBinToggle ? " auto" : "") }}>
        <input className="input search" placeholder="Filter by ticker…" value={filter} onChange={e => setFilter(e.target.value)} />
        <AnalystFilter available={availableAnalysts} selected={anFilter} onChange={setAnFilter} analysts={analysts} />
        <InstrumentFilter value={inst} onChange={setInst} />
        <CategoryFilter value={cat} onChange={setCat} />
        <select className="select" value={action} onChange={e => setAction(e.target.value)} style={{ fontFamily: "var(--f-mono)", fontSize: "0.75rem", textTransform: "uppercase", letterSpacing: ".06em" }}>
          {actionOpts.map(a => <option key={a} value={a}>{a}</option>)}
        </select>
        {binToggle}
      </div>

      {selectable && selected.size > 0 && (
        <div className="bulk-bar">
          <span><strong>{selected.size}</strong> selected</span>
          <span style={{ flex: 1 }} />
          <button className="btn" onClick={() => setSelected(new Set())}>Clear</button>
          <button className="btn danger" onClick={bulkDelete}>{I("trash", { size: 14 })} Delete {selected.size}</button>
        </div>
      )}

      <div className="panel">
        <div className="panel-hdr">
          <div className="panel-title">Signal Events feed</div>
          <div className="panel-meta">{filtered.length} of {events.length} · live</div>
        </div>
        {selectable && (
          <label className="cards-selectall">
            <input type="checkbox" checked={allSelected} onChange={toggleAll} />
            <span>Select all</span>
            {selected.size > 0 && <span className="cards-selectall-n mono">{selected.size} selected</span>}
          </label>
        )}
        <div className="scroll-y feed-scroll" style={{ maxHeight: "calc(100vh - 250px)", minHeight: 400 }}>
          {isMobile ? (
            <MobileEventList rows={feed} analysts={analysts} onShowParser={onShowParser} onDelete={onDelete} canDeleteRow={canDeleteRow} selectable={selectable} selected={selected} onToggle={toggle} grouped onEditEvent={onEditEvent} canEditEvent={canEditEvent} />
          ) : (
          <table className="tbl tbl-fixed tbl-cards tbl-wide">
            <colgroup>
              {selectable && <col style={{ width: "3%" }} />}
              <col style={{ width: "8%" }} />
              <col style={{ width: "10%" }} />
              <col style={{ width: "6%" }} />
              <col style={{ width: "7%" }} />
              <col style={{ width: "8%" }} />
              <col style={{ width: "6%" }} />
              <col style={{ width: "6%" }} />
              <col style={{ width: "8%" }} />
              <col style={{ width: "7%" }} />
              <col style={{ width: "6%" }} />
              <col style={{ width: "9%" }} />
              <col style={{ width: "9%" }} />
              <col style={{ width: "10%" }} />
            </colgroup>
            <thead>
              <tr>
                {selectable && <th style={{ textAlign: "center" }}><input type="checkbox" checked={allSelected} onChange={toggleAll} title="Select all" /></th>}
                <th>Time</th>
                <th>Analyst</th>
                <th>Ticker</th>
                <th>Instrument</th>
                <th>Expiry</th>
                <th>Strike</th>
                <th>Stop</th>
                <th style={{ textAlign: "right" }}>Price</th>
                <th>Action</th>
                <th>Conf</th>
                <th>Channel</th>
                <th>Raw note</th>
                <th></th>
              </tr>
            </thead>
            <tbody>
              {(() => {
                const out = [];
                let lastDK = null;
                for (const e of feed) {
                  const isOpen = expanded === e.id;
                  const rcy = recencyBucket(e.ts);
                  const dk = dayKey(e.ts);
                  if (dk !== lastDK) {
                    lastDK = dk;
                    out.push(
                      <tr key={"sep" + dk} className="day-sep">
                        <td colSpan={colCount}><span className="day-sep-title">{fmtDayGroup(e.ts)}</span><span className="day-sep-meta mono">{dayCounts.get(dk)} signal{dayCounts.get(dk) === 1 ? "" : "s"}</span></td>
                      </tr>
                    );
                  }
                  out.push(
                  <Fragment key={e.id}>
                    <tr onClick={() => setExpanded(isOpen ? null : e.id)} style={{ cursor: "pointer" }} className={cx(selected.has(e.id) && "row-selected", isOpen && "expanded", rcy === "live" && "row-fresh")}>
                      {selectable && (
                        <td className="cell-check" style={{ textAlign: "center" }} onClick={(ev) => ev.stopPropagation()}>
                          <input type="checkbox" checked={selected.has(e.id)} onChange={() => toggle(e.id)} />
                        </td>
                      )}
                      <td data-label="Time" className="muted mono">{rcy === "live" && <span className="rcy-new">NEW</span>}{fmtTime(e.ts, {short:true})}</td>
                      <td data-label="Analyst"><AnalystChip analyst={e.analyst} analysts={analysts} /></td>
                      <td data-label="Ticker"><span className="ticker">${e.ticker}</span></td>
                      <td data-label="Instrument"><span className="inst"><InstrumentTag event={e} /></span></td>
                      <td data-label="Expiry" className="mono" style={{ fontSize: "0.71875rem", whiteSpace: "nowrap" }}>{isOption(e) && e.expiry ? <span className="inst-detail">{fmtExpiry(e.expiry)}</span> : <span style={{ color: "var(--fg-3)" }}>—</span>}</td>
                      <td data-label="Strike" className="mono">{isOption(e) && e.strike != null ? <span className="inst-detail">{e.strike}</span> : <span style={{ color: "var(--fg-3)" }}>—</span>}</td>
                      <td data-label="Stop" className="mono">{e.stopLoss != null ? <span className="inst-detail" style={{ color: "var(--c-down)" }}>{fmtNum(e.stopLoss)}</span> : <span style={{ color: "var(--fg-3)" }}>—</span>}</td>
                      <td data-label="Price" className="num mono" style={{ whiteSpace: "nowrap" }}>{(() => {
                        const { fill, mark } = eventFillMark(e);
                        if (fill == null && mark == null) return <span style={{ color: "var(--fg-3)" }}>—</span>;
                        return <>{fill != null ? fmtNum(fill) : <span style={{ color: "var(--fg-3)" }}>—</span>}{mark != null && <span style={{ color: "var(--fg-2)", marginLeft: 4 }}>→ {fmtNum(mark)}</span>}</>;
                      })()}</td>
                      <td data-label="Action"><ActionPill kind={e.action} /></td>
                      <td data-label="Conf"><ConfPill level={e.confidence} /></td>
                      <td data-label="Channel" className="muted mono" style={{ fontSize: "0.6875rem", whiteSpace: "nowrap" }}><ChannelDot cat={e.channelCategory} />{e.channel}</td>
                      <td data-label="Raw note" className="muted cell-note" style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}><span className="cell-text">{e.raw}</span></td>
                      <td className="cell-actions" style={{ textAlign: "right", whiteSpace: "nowrap" }}>
                        {onEditEvent && canEditEvent && canEditEvent(e) && (
                          <button
                            className="icon-btn"
                            title="Override / correct this event"
                            onClick={(ev) => { ev.stopPropagation(); onEditEvent(e); }}
                          >{I("edit", { size: 15 })}<span className="btn-label">Override</span></button>
                        )}
                        {onShowParser && (
                          <button
                            className="icon-btn"
                            title="Parser reasoning"
                            onClick={(ev) => { ev.stopPropagation(); onShowParser(e); }}
                          >{I("brain", { size: 15 })}<span className="btn-label">Parser</span></button>
                        )}
                        {onDelete && canDelRow(e) && (
                          <button
                            className="icon-btn danger"
                            title="Delete event"
                            onClick={async (ev) => { ev.stopPropagation(); if (await confirmDialog({ title: "Delete event", message: `Delete this ${e.action.toUpperCase()} $${e.ticker} event? It will also be removed from positions, recaps and charts.`, confirmLabel: "Delete", danger: true })) onDelete(e.id); }}
                          >{I("trash", { size: 14 })}<span className="btn-label">Delete</span></button>
                        )}
                      </td>
                    </tr>
                    {isOpen && (
                      <tr className="wl-raw-row">
                        <td colSpan={colCount}>
                          <div className="wl-raw">{e.raw}</div>
                        </td>
                      </tr>
                    )}
                  </Fragment>
                  );
                }
                return out;
              })()}
              {feed.length === 0 && <tr className="is-empty"><td colSpan={colCount}><Empty msg="No events match." /></td></tr>}
            </tbody>
          </table>
          )}
        </div>
      </div>
    </div>
  );
}

// ─── Override modal: correct a mis-parsed event. Owner can edit any event;
// an analyst can edit their own. Prefilled from the current parse; on save it
// PATCHes the event and every derived view (positions/recaps/charts) recomputes.
function EventOverride({ event, onClose, onSubmit }) {
  const e = event;
  const [ticker, setTicker] = useState(e.ticker || "");
  const [action, setAction] = useState(e.action || "open");
  const [kind, setKind] = useState(isOption(e) ? "option" : "shares");
  const [optType, setOptType] = useState(e.optionType || "call");
  const [dir, setDir] = useState(e.direction === "short" ? "short" : "long");
  const [strike, setStrike] = useState(e.strike != null ? String(e.strike) : "");
  const [expiry, setExpiry] = useState(e.expiry || "");
  const [price, setPrice] = useState(e.price != null ? String(e.price) : "");
  const [mark, setMark] = useState(e.currentPrice != null ? String(e.currentPrice) : "");
  const [avg, setAvg] = useState(e.avgPrice != null ? String(e.avgPrice) : "");
  const [stop, setStop] = useState(e.stopLoss != null ? String(e.stopLoss) : "");
  const [conf, setConf] = useState(e.confidence || "medium");
  const [note, setNote] = useState(e.raw || "");
  // "Posted at", in MARKET time. A mis-timed event sits in the wrong trading
  // session and therefore the wrong recap bucket, and until now the only way to
  // move it was editing the database by hand. ET wall-clock in, ET wall-clock
  // out — etWallClockToEpoch resolves EST vs EDT itself.
  const [when, setWhen] = useState(epochToEtWallClock(e.ts) || "");
  const [busy, setBusy] = useState(false);
  useEffect(() => {
    const onKey = (ev) => { if (ev.key === "Escape" && !busy) onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [busy]);

  const isOpt = kind === "option";
  const ACTIONS = ["open", "add", "trim", "cut", "close", "hold", "watch", "expire"];

  async function submit() {
    if (busy) return;
    const tk = ticker.trim().toUpperCase().replace(/^\$/, "");
    if (!tk) { toast.error("Enter a ticker."); return; }
    const n = (v) => v.trim() === "" ? null : Number(v);
    const contract = isOpt
      ? `${expiry.trim() ? expiry.trim() + " " : ""}${strike.trim()}${optType === "put" ? "P" : "C"}`.trim()
      : null;
    const patch = {
      ticker: tk,
      action,
      instrument: isOpt ? "option" : "shares",
      direction: isOpt ? (optType === "put" ? "short" : "long") : dir,
      contract,
      strike: isOpt && strike.trim() !== "" ? Number(strike) : null,
      expiry: isOpt ? (expiry.trim() || null) : null,
      option_type: isOpt ? optType : null,
      price: n(price),
      current_price: n(mark),
      avg_price: n(avg),
      stop_loss: n(stop),
      confidence: conf,
      raw: note,
    };
    if (when) {
      const ms = etWallClockToEpoch(when);
      if (ms == null) { toast.error("Posted-at isn't a valid date/time."); return; }
      // Only send it when it actually moved, so an untouched field can never
      // nudge a timestamp by a rounding second.
      if (Math.abs(ms - Number(e.ts)) > 60000) patch.ts = ms;
    }
    setBusy(true);
    const ok = await onSubmit(e.id, patch);
    setBusy(false);
    if (ok) onClose();
  }

  return (
    <div className="modal-back" onMouseDown={() => !busy && onClose()}>
    <div className="modal manage-modal" role="dialog" aria-modal="true" onMouseDown={(ev) => ev.stopPropagation()}>
      <div className="calc-hdr">
        <div>
          <div className="calc-title">{I("edit", { size: 16 })} Override event</div>
          <div className="calc-sub mono">Correct a mis-parsed signal · {e.analyst} · ${e.ticker}</div>
        </div>
        <button className="icon-btn" title="Close" onClick={onClose}>{I("close", { size: 16 })}</button>
      </div>
      <div className="manage-body">
        <div className="manage-fields">
          <label className="calc-field">
            <span>Ticker</span>
            <div className="calc-input"><span className="calc-pre">$</span><input value={ticker} onChange={ev => setTicker(ev.target.value)} placeholder="NVDA" /></div>
          </label>
          <label className="calc-field">
            <span>Action</span>
            <select className="select" value={action} onChange={ev => setAction(ev.target.value)} style={{ textTransform: "uppercase", fontFamily: "var(--f-mono)", fontSize: "0.75rem" }}>
              {ACTIONS.map(a => <option key={a} value={a}>{a}</option>)}
            </select>
          </label>
          <label className="calc-field">
            <span>Confidence</span>
            <select className="select" value={conf} onChange={ev => setConf(ev.target.value)}>
              {["high", "medium", "low"].map(c => <option key={c} value={c}>{c}</option>)}
            </select>
          </label>
          <label className="calc-field">
            <span>Posted at <span className="calc-hint">market time (ET)</span></span>
            <div className="calc-input"><input type="datetime-local" value={when} onChange={ev => setWhen(ev.target.value)} /></div>
          </label>
        </div>

        <div className="manage-twoseg">
          <div className="seg">
            <button className={cx("seg-btn", isOpt && "on")} onClick={() => setKind("option")}>Option</button>
            <button className={cx("seg-btn", !isOpt && "on")} onClick={() => setKind("shares")}>Shares</button>
          </div>
          {isOpt ? (
            <div className="seg">
              <button className={cx("seg-btn", optType === "call" && "on")} onClick={() => setOptType("call")}>Call</button>
              <button className={cx("seg-btn", optType === "put" && "on")} onClick={() => setOptType("put")}>Put</button>
            </div>
          ) : (
            <div className="seg">
              <button className={cx("seg-btn", dir === "long" && "on")} onClick={() => setDir("long")}>Long</button>
              <button className={cx("seg-btn", dir === "short" && "on")} onClick={() => setDir("short")}>Short</button>
            </div>
          )}
        </div>

        {isOpt && (
          <div className="manage-fields">
            <label className="calc-field">
              <span>Strike</span>
              <div className="calc-input"><span className="calc-pre">$</span><input type="number" min="0" step="0.5" value={strike} onChange={ev => setStrike(ev.target.value)} placeholder="210" /></div>
            </label>
            <label className="calc-field">
              <span>Expiry</span>
              <div className="calc-input"><input value={expiry} onChange={ev => setExpiry(ev.target.value)} placeholder="6/20 or 6/20/26" /></div>
            </label>
          </div>
        )}

        <div className="manage-fields">
          <label className="calc-field">
            <span>Fill price</span>
            <div className="calc-input"><span className="calc-pre">$</span><input type="number" min="0" step="0.01" value={price} onChange={ev => setPrice(ev.target.value)} placeholder="—" /></div>
          </label>
          <label className="calc-field">
            <span>Current mark</span>
            <div className="calc-input"><span className="calc-pre">$</span><input type="number" min="0" step="0.01" value={mark} onChange={ev => setMark(ev.target.value)} placeholder="—" /></div>
          </label>
          <label className="calc-field">
            <span>Avg</span>
            <div className="calc-input"><span className="calc-pre">$</span><input type="number" min="0" step="0.01" value={avg} onChange={ev => setAvg(ev.target.value)} placeholder="—" /></div>
          </label>
          <label className="calc-field">
            <span>Stop</span>
            <div className="calc-input"><span className="calc-pre">$</span><input type="number" min="0" step="0.01" value={stop} onChange={ev => setStop(ev.target.value)} placeholder="—" /></div>
          </label>
        </div>

        <label className="calc-field">
          <span>Raw message</span>
          <textarea className="manage-note" rows={2} value={note} onChange={ev => setNote(ev.target.value)} />
        </label>

        <div className="manage-foot">
          <button className="btn" onClick={onClose} disabled={busy}>Cancel</button>
          <button className="btn primary" onClick={submit} disabled={busy}>{busy ? "Saving…" : "Save override"}</button>
        </div>
        <div className="manage-disclaimer">Corrects this parsed event everywhere — positions, recaps &amp; charts recompute, and the change is recorded in the audit log.</div>
      </div>
    </div>
    </div>
  );
}

// ─── Flyout: full parser internals for a single event
function EventFlyout({ event, onClose, onDelete, onEdit, analysts }) {
  if (!event) return null;
  const evAnalyst = (analysts || []).find(x => x.handle === event.analyst);
  useEffect(() => {
    const handler = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, []);
  return [
    <div key="back" className="flyout-back" onClick={onClose} />,
    <div key="flyout" className="flyout" role="dialog" aria-modal="true">
      <div className="flyout-hdr">
          <div>
            <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
              <span className="ticker" style={{ fontSize: "1.125rem" }}>${event.ticker}</span>
              <ActionPill kind={event.action} />
              <ConfPill level={event.confidence} />
            </div>
            <div className="mono" style={{ fontSize: "0.6875rem", color: "var(--fg-2)", marginTop: 7 }}>
              {event.analyst} · {fmtTime(event.ts)}
            </div>
            {window.AnalystStyleCaption && <AnalystStyleCaption analyst={evAnalyst} />}
          </div>
          <div style={{ display: "flex", gap: 6 }}>
            {onEdit && (
              <button
                className="icon-btn"
                title="Override / correct this event"
                onClick={() => onEdit(event)}
              >{I("edit", { size: 16 })}</button>
            )}
            {onDelete && (
              <button
                className="icon-btn danger"
                title="Delete event"
                onClick={async () => { if (await confirmDialog({ title: "Delete event", message: `Delete this ${event.action.toUpperCase()} $${event.ticker} event? It will also be removed from positions, recaps and charts.`, confirmLabel: "Delete", danger: true })) onDelete(event.id); }}
              >{I("trash", { size: 16 })}</button>
            )}
            <button className="btn" onClick={onClose} style={{ padding: 6 }}>{I("close", { size: 16 })}</button>
          </div>
        </div>
        <div className="flyout-body">
          <div className="field">
            <h5>Raw message</h5>
            <div className="raw-msg" style={{ background: "var(--bg-2)", border: "1px solid var(--border-1)", borderRadius: 6, padding: "12px 14px" }}>{event.raw}</div>
          </div>
          <div className="field">
            <h5>Source</h5>
            <div className="mono" style={{ fontSize: "0.75rem" }}>
              <ChannelDot cat={event.channelCategory} />{event.channel}
              <span style={{ color: "var(--fg-3)", margin: "0 8px" }}>·</span>
              <span style={{ color: "var(--fg-2)" }}>category</span> <strong style={{ color: "var(--fg-0)" }}>{event.channelCategory}</strong>
              <span style={{ color: "var(--fg-3)", margin: "0 8px" }}>·</span>
              <span style={{ color: "var(--fg-2)" }}>id</span> <span style={{ color: "var(--fg-1)" }}>{event.messageId}</span>
            </div>
          </div>
          <div className="field">
            <h5>Extracted event</h5>
            <pre style={{ background: "var(--bg-0)", border: "1px solid var(--border-1)", borderRadius: 6, padding: "12px 14px", fontFamily: "var(--f-mono)", fontSize: "0.75rem", color: "var(--fg-mono)", margin: 0, lineHeight: 1.6 }}>
{`{
  "ticker":      "${event.ticker}",
  "instrument":  "${isOption(event) ? "option" : "shares"}",
  "action":      "${event.action}",
  "direction":   "${event.direction}",
  "contract":    ${event.contract ? `"${event.contract}"` : "null"},
  "strike":      ${event.strike == null ? "null" : event.strike},
  "expiry":      ${event.expiry ? `"${event.expiry}"` : "null"},
  "option_type": ${event.optionType ? `"${event.optionType}"` : "null"},
  "price":         ${event.price == null ? "null" : event.price.toFixed(2)},
  "current_price": ${event.currentPrice == null ? "null" : event.currentPrice},
  "stop_loss":     ${event.stopLoss == null ? "null" : event.stopLoss},
  "confidence":    "${event.confidence}",
  "raw_note":    "${event.raw.replace(/"/g, '\\"')}"
}`}
            </pre>
          </div>
          <div className="field">
            <h5>Parser reasoning</h5>
            <div style={{ fontSize: "0.75rem", color: "var(--fg-1)", lineHeight: 1.6 }}>
              {event.reasoning.summary.split(/("[^"]+")/).map((part, i) =>
                part.startsWith('"') ? <span key={i} style={{ background: "var(--accent-glow)", color: "var(--accent)", padding: "1px 4px", borderRadius: 2 }}>{part}</span> : <span key={i}>{part}</span>
              )}
            </div>
          </div>
          <div className="field">
            <h5>Pipeline</h5>
            <div style={{ display: "grid", gridTemplateColumns: "1fr", gap: 6, fontFamily: "var(--f-mono)", fontSize: "0.6875rem" }}>
              <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 8 }}>
                <span style={{ color: "var(--fg-2)" }}>scraper</span>
                <span style={{ color: "var(--fg-0)" }}>discord.py-self · {event.channel}</span>
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 8 }}>
                <span style={{ color: "var(--fg-2)" }}>parser</span>
                <span style={{ color: "var(--fg-0)" }}>claude-haiku-4-5 · 220ms</span>
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 8 }}>
                <span style={{ color: "var(--fg-2)" }}>storage</span>
                <span style={{ color: "var(--fg-0)" }}>trade_events · row {event.id}</span>
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "120px 1fr", gap: 8 }}>
                <span style={{ color: "var(--fg-2)" }}>aggregator</span>
                <span style={{ color: "var(--fg-0)" }}>positions[{event.analyst}|{event.ticker}|{event.contract || "shares"}]</span>
              </div>
            </div>
          </div>
        </div>
      </div>
  ];
}

Object.assign(window, { SignalEvents, EventFlyout, EventOverride, ChannelDot });
