// row.jsx — one ticker per table row, plus the shared column registry.
//
// The table is column-driven: COLUMNS is the registry of value columns, each
// knowing how to render its own <td>. app.jsx owns the *order* (drag-and-drop,
// persisted) and renders the matching <thead>; StockRow renders the cells in
// that same order. The Symbol column (left) is fixed — only the value columns
// to its right reorder. Deletion lives in the drawer, not the row.

// ── Column registry ──────────────────────────────────────────────────────
// Each cell(ctx) returns a complete <td>. ctx = { quote, d, direction, r }
// where d = resolveDisplayQuote(quote) and r = quote.returns.
const COLUMNS = [
  {
    id: "lastMarketPrice",
    label: "Last Price",
    // The last regular-session price, with a Market / Market closed badge so
    // you can tell at a glance whether this number is live or a frozen close.
    // While the regular session is open this equals Current Price; once it
    // closes, Current Price moves on to post/pre and this one stays put.
    cell: ({ quote }) => {
      const live = quote.marketState === "REGULAR";
      return (
        <td className="ld__price-cell">
          <div className="ld__price-line">
            <span className="ld__price">
              {formatPrice(quote.regularMarketPrice, quote.currency)}
            </span>
          </div>
          <div className={"ld__sess ld__sess--" + (live ? "market" : "market-closed")}>
            {live ? "Market" : "Market closed"}
          </div>
        </td>
      );
    },
  },
  {
    id: "currentPrice",
    label: "Current Price",
    // The active-session price (pre / market / post / closed) — carries the
    // session dot + label, since this is the column whose session varies.
    cell: ({ quote, d, direction }) => {
      const sessionKey = d.sessionLabel.toLowerCase().replace(/\s/g, "-");
      return (
        <td className="ld__price-cell">
          <div className="ld__price-line">
            <span className={"ld__dot ld__dot--" + sessionKey + " ld__dot--" + direction} />
            <span className="ld__price">{formatPrice(d.displayPrice, quote.currency)}</span>
          </div>
          <div className={"ld__sess ld__sess--" + sessionKey}>
            {d.sessionLabel}
            {d.secondaryLabel && (
              <span className="ld__sess-sec"> · {d.secondaryLabel}</span>
            )}
          </div>
        </td>
      );
    },
  },
  {
    id: "delta",
    label: "Δ",
    cell: ({ quote, d, direction }) => (
      <td className={"ld__num ld__delta ld__delta--" + direction}>
        {formatChange(d.displayChange, quote.currency)}
      </td>
    ),
  },
  // 1D tracks the active session — regular % while the market is open, then
  // post/pre % once it closes — so it keeps moving when the bands are frozen.
  { id: "r1d", label: "1D", cell: ({ d }) => <ReturnCell value={d.displayChangePercent} /> },
  { id: "r1m", label: "1M", cell: ({ r }) => <ReturnCell value={r["1M"]} /> },
  { id: "r6m", label: "6M", cell: ({ r }) => <ReturnCell value={r["6M"]} /> },
  { id: "rytd", label: "YTD", cell: ({ r }) => <ReturnCell value={r["YTD"]} /> },
  { id: "r1y", label: "1Y", cell: ({ r }) => <ReturnCell value={r["1Y"]} /> },
  {
    id: "afterHoursVol",
    label: "After Hrs Vol",
    // Extended-hours share volume — "n/a" while the regular session is open
    // (there's no after-hours figure yet), otherwise the live pre/post volume:
    // a real number, "0" for covered-but-untraded, or "—" for no data.
    cell: ({ quote }) => (
      <td className="ld__num ld__muted">
        {quote.marketState === "REGULAR"
          ? "n/a"
          : formatCompact(quote.afterHoursVolume)}
      </td>
    ),
  },
  {
    id: "volume",
    label: "Volume",
    cell: ({ quote }) => (
      <td className="ld__num ld__muted">{quote.volume || "—"}</td>
    ),
  },
  {
    id: "avgVol3m",
    label: "Avg Vol 3M",
    cell: ({ quote }) => (
      <td className="ld__num ld__muted">
        {quote.averageVolume3M || quote.averageVolume || "—"}
      </td>
    ),
  },
  {
    id: "marketCap",
    label: "Market cap",
    cell: ({ quote }) => (
      <td className="ld__num ld__muted">{quote.marketCap || "—"}</td>
    ),
  },
];

const COLUMN_BY_ID = Object.fromEntries(COLUMNS.map((c) => [c.id, c]));
const DEFAULT_COLUMN_ORDER = COLUMNS.map((c) => c.id);

// Resolve a persisted order against the live registry: keep known ids in the
// saved order, drop unknown ones, append any columns added since.
function normalizeColumnOrder(saved) {
  if (!Array.isArray(saved)) return [...DEFAULT_COLUMN_ORDER];
  const known = saved.filter((id) => COLUMN_BY_ID[id]);
  const missing = DEFAULT_COLUMN_ORDER.filter((id) => !known.includes(id));
  return known.length ? [...known, ...missing] : [...DEFAULT_COLUMN_ORDER];
}

// ── StockRow ─────────────────────────────────────────────────────────────
// The whole <tr> is a pointer-drag handle (App owns the reorder logic) and a
// click target — App decides click-vs-drag from the pointer movement, then
// either opens the drawer or commits a reorder. `style` / `extraClass` carry
// the live drag transform; deletion lives in the drawer now, not the row.
function StockRow({ ticker, columnOrder, onOpen, onPointerDown, style, extraClass }) {
  const [quote, setQuote] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [flashing, setFlashing] = React.useState(false);
  const prevPriceRef = React.useRef(null);

  React.useEffect(() => {
    let unsubscribe;
    let cancelled = false;
    fetchQuote(ticker)
      .then((q) => {
        if (cancelled) return;
        setQuote({ ...q });
        prevPriceRef.current = resolveDisplayQuote(q).displayPrice;
        unsubscribe = subscribeToQuoteUpdates(ticker, (next) => {
          const d = resolveDisplayQuote(next);
          const prev = prevPriceRef.current;
          if (prev != null && d.displayPrice !== prev) {
            setFlashing(true);
            setTimeout(() => setFlashing(false), 700);
          }
          prevPriceRef.current = d.displayPrice;
          setQuote({ ...next });
        });
      })
      .catch((e) => !cancelled && setError(e.message));
    return () => {
      cancelled = true;
      if (unsubscribe) unsubscribe();
    };
  }, [ticker]);

  const span = columnOrder.length;

  if (error) {
    return (
      <tr className={"ld__row ld__row--err" + (extraClass || "")}
          style={style}
          onPointerDown={(e) => onPointerDown(e, ticker)}>
        <td className="ld__sym">
          <CompanyLogo ticker={ticker} name={ticker} />
          <div className="ld__sym-text">
            <div className="ld__sym-ticker">{ticker}</div>
            <div className="ld__sym-name">Couldn't load</div>
          </div>
        </td>
        <td colSpan={span} className="ld__err">{error}</td>
      </tr>
    );
  }

  if (!quote) {
    return (
      <tr className={"ld__row ld__row--loading" + (extraClass || "")}
          style={style}
          onPointerDown={(e) => onPointerDown(e, ticker)}>
        <td className="ld__sym">
          <CompanyLogo ticker={ticker} name={ticker} />
          <div className="ld__sym-text">
            <div className="ld__sym-ticker">{ticker}</div>
            <div className="ld__sym-name shimmer">&nbsp;</div>
          </div>
        </td>
        <td colSpan={span} className="ld__loading">Loading…</td>
      </tr>
    );
  }

  const d = resolveDisplayQuote(quote);
  const direction = d.displayChange > 0 ? "up" : d.displayChange < 0 ? "down" : "flat";
  const r = quote.returns || {};
  const ctx = { quote, d, direction, r };

  return (
    <tr
      className={"ld__row ld__row--" + direction + (flashing ? " flash-" + direction : "") + (extraClass || "")}
      style={style}
      onPointerDown={(e) => onPointerDown(e, ticker)}
      role="button" tabIndex="0"
      onKeyDown={(e) => (e.key === "Enter" || e.key === " ") && onOpen()}>
      <td className="ld__sym">
        <CompanyLogo ticker={quote.ticker} name={quote.name} domain={quote.logoDomain} />
        <div className="ld__sym-text">
          <div className="ld__sym-row">
            <span className="ld__sym-ticker">{quote.ticker}</span>
            <span className="ld__sym-exch">{quote.exchange}</span>
          </div>
          <div className="ld__sym-name">{quote.name}</div>
        </div>
      </td>
      {columnOrder.map((id) => {
        const col = COLUMN_BY_ID[id];
        return col ? React.cloneElement(col.cell(ctx), { key: id }) : null;
      })}
    </tr>
  );
}

function ReturnCell({ value }) {
  if (value == null || Number.isNaN(value)) {
    return <td className="ld__num ld__muted">—</td>;
  }
  const dir = value > 0 ? "up" : value < 0 ? "down" : "flat";
  const sign = value > 0 ? "+" : value < 0 ? "−" : "";
  return (
    <td className={"ld__num ld__return ld__return--" + dir}>
      {sign + Math.abs(value).toFixed(2) + "%"}
    </td>
  );
}

window.StockRow = StockRow;
window.COLUMNS = COLUMNS;
window.COLUMN_BY_ID = COLUMN_BY_ID;
window.DEFAULT_COLUMN_ORDER = DEFAULT_COLUMN_ORDER;
window.normalizeColumnOrder = normalizeColumnOrder;
