// CardDetail.jsx — modal that opens when a kanban card is tapped.
//
// Shows a Lightweight Chart of the card's pair/timeframe with the matched
// strategy's indicator overlay + trigger markers, plus the "advance to
// next column" button that used to fire from tapping the card itself.
//
// Strategy selection is delegated to window.TW_pickStrategy(cell). If no
// template matches the archetype/TF, we render the chart only.
const { useState: useStateCD, useEffect: useEffectCD, useRef: useRefCD } = React;

function CardDetail({ story, onClose, onAdvance, canAdvance }) {
  const cell = storyToCell(story);
  const strategy = (window.TW_pickStrategy && window.TW_pickStrategy(cell)) || null;
  const [status, setStatus] = useStateCD("loading");
  const [error, setError] = useStateCD(null);
  const [result, setResult] = useStateCD(null);
  const [candleCount, setCandleCount] = useStateCD(0);
  const chartHostRef = useRefCD(null);
  const chartRef = useRefCD(null);
  const seriesRef = useRefCD({});

  // Fetch candles + HTF context + run the strategy.
  useEffectCD(() => {
    let cancelled = false;
    if (!cell.coin || !cell.timeframe) {
      setStatus("error"); setError("Missing coin/timeframe on this card");
      return;
    }
    const wanted = [{ key: "primary", interval: cell.timeframe, limit: 250 }];
    if (strategy) {
      for (const ctx of strategy.htfContext || []) {
        wanted.push({ key: ctx.interval, interval: ctx.interval, limit: 200 });
      }
    }
    Promise.all(wanted.map((w) =>
      fetch(`/api/candles?coin=${encodeURIComponent(cell.coin)}&interval=${encodeURIComponent(w.interval)}&limit=${w.limit}`)
        .then((r) => r.ok ? r.json() : Promise.reject(`http_${r.status}`))
        .then((j) => ({ key: w.key, interval: w.interval, candles: j.candles || [] }))
    ))
      .then((rows) => {
        if (cancelled) return;
        const byKey = Object.fromEntries(rows.map((r) => [r.key, r.candles]));
        const primary = byKey.primary || [];
        if (primary.length === 0) {
          setStatus("error"); setError("No candle data returned from Hyperliquid");
          return;
        }
        setCandleCount(primary.length);
        if (strategy) {
          const contextCandles = {};
          for (const ctx of strategy.htfContext || []) contextCandles[ctx.interval] = byKey[ctx.interval] || [];
          const r = strategy.compute(primary, contextCandles, null);
          setResult({ primary, strategyResult: r });
        } else {
          setResult({ primary, strategyResult: null });
        }
        setStatus("ready");
      })
      .catch((e) => {
        if (cancelled) return;
        setError(String(e?.message || e));
        setStatus("error");
      });
    return () => { cancelled = true; };
  }, []);

  // Mount/teardown the chart whenever data is ready or the dark theme flips.
  useEffectCD(() => {
    if (status !== "ready" || !result || !chartHostRef.current) return;
    const dark = document.documentElement.dataset.theme === "dark";
    const lwc = window.LightweightCharts;
    if (!lwc) { setError("LightweightCharts library failed to load"); setStatus("error"); return; }

    const chart = lwc.createChart(chartHostRef.current, {
      layout: {
        background: { type: "solid", color: dark ? "#101012" : "#ffffff" },
        textColor: dark ? "#cccccc" : "#222222",
      },
      grid: {
        vertLines: { color: dark ? "#1f1f24" : "#eeeeee" },
        horzLines: { color: dark ? "#1f1f24" : "#eeeeee" },
      },
      rightPriceScale: { borderVisible: false },
      timeScale: { borderVisible: false, timeVisible: true, secondsVisible: false },
      crosshair: { mode: 0 },
      height: 320,
    });
    chartRef.current = chart;

    const candleSeries = chart.addCandlestickSeries({
      upColor: "#22c55e", downColor: "#ef4444",
      borderVisible: false, wickUpColor: "#22c55e", wickDownColor: "#ef4444",
    });
    candleSeries.setData(result.primary);
    seriesRef.current.candle = candleSeries;

    // Strategy overlays. Supported types:
    //   - "line": time-series — values[] aligned to candles
    //   - "hline": constant horizontal line — single value drawn as a
    //              priceLine on the candle series (no extra series needed)
    if (strategy && result.strategyResult) {
      for (const ov of strategy.overlays(result.strategyResult) || []) {
        if (ov.type === "line") {
          const line = chart.addLineSeries({
            color: ov.color, lineWidth: ov.lineWidth || 1,
            priceLineVisible: false, lastValueVisible: false,
          });
          const points = result.primary
            .map((c, i) => isFinite(ov.values[i]) ? { time: c.time, value: ov.values[i] } : null)
            .filter(Boolean);
          line.setData(points);
          seriesRef.current[ov.id] = line;
        } else if (ov.type === "hline" && isFinite(ov.value)) {
          candleSeries.createPriceLine({
            price: ov.value,
            color: ov.color,
            lineWidth: ov.lineWidth || 1,
            lineStyle: 2, // dashed
            axisLabelVisible: true,
            title: ov.label || "",
          });
        }
      }
      const markers = strategy.markers(result.strategyResult) || [];
      if (markers.length) candleSeries.setMarkers(markers);
    }

    chart.timeScale().fitContent();

    const onResize = () => chart.applyOptions({ width: chartHostRef.current?.clientWidth || 320 });
    chart.applyOptions({ width: chartHostRef.current.clientWidth });
    window.addEventListener("resize", onResize);
    return () => {
      window.removeEventListener("resize", onResize);
      chart.remove();
      chartRef.current = null;
      seriesRef.current = {};
    };
  }, [status, result]);

  return (
    <div
      onClick={onClose}
      style={{
        position: "absolute", inset: 0, zIndex: 60,
        background: "rgba(0,0,0,0.55)",
        display: "flex", alignItems: "flex-end",
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: "100%",
          maxHeight: "92%",
          background: "var(--color-surface-default)",
          borderTopLeftRadius: "var(--radius-lg)",
          borderTopRightRadius: "var(--radius-lg)",
          boxShadow: "var(--shadow-4)",
          padding: "14px 16px 16px",
          display: "flex", flexDirection: "column", gap: 10,
          overflowY: "auto",
        }}
      >
        <div aria-hidden style={{
          alignSelf: "center", width: 36, height: 4,
          background: "var(--color-border-subtle)", borderRadius: 2, marginTop: -2,
        }} />

        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 8 }}>
          <div style={{ display: "flex", flexDirection: "column", gap: 2, minWidth: 0 }}>
            <span className="ds-overline" style={{ color: "var(--color-brand-primary)", fontSize: 10 }}>
              {story.ref || cell.pair}
            </span>
            <span style={{
              fontFamily: "var(--font-family-sans)", fontWeight: 700, fontSize: 15,
              color: "var(--color-text-primary)", letterSpacing: "-0.005em",
            }}>
              {story.want}
            </span>
            <span className="ds-caption" style={{ color: "var(--color-text-muted)" }}>
              {strategy ? strategy.name : "No matching strategy template"}
              {candleCount ? ` · ${candleCount} bars` : ""}
            </span>
          </div>
          <button onClick={onClose} aria-label="Close" style={{
            background: "none", border: "none", padding: 4, cursor: "pointer",
            color: "var(--color-text-muted)", display: "flex",
          }}>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
              <line x1="18" y1="6" x2="6" y2="18" />
              <line x1="6" y1="6" x2="18" y2="18" />
            </svg>
          </button>
        </div>

        {status === "loading" && (
          <div style={{
            height: 320, display: "flex", alignItems: "center", justifyContent: "center",
            background: "var(--color-surface-subtle)", borderRadius: "var(--radius-sm)",
            color: "var(--color-text-muted)", fontSize: 12,
          }}>
            Loading {cell.coin || "?"} {cell.timeframe || ""}…
          </div>
        )}
        {status === "error" && (
          <div style={{
            padding: "12px 14px", borderRadius: "var(--radius-sm)",
            background: "var(--color-surface-promo)",
            borderLeft: "4px solid var(--color-danger, #b03a2e)",
            color: "var(--color-text-primary)", fontSize: 13,
          }}>
            {error || "Could not load chart."}
          </div>
        )}
        <div ref={chartHostRef} style={{
          width: "100%",
          minHeight: status === "ready" ? 320 : 0,
          display: status === "ready" ? "block" : "none",
        }} />

        {strategy && result?.strategyResult && (
          <StrategySummary
            strategy={strategy}
            result={result.strategyResult}
            story={story}
            cell={cell}
          />
        )}

        <div style={{ display: "flex", gap: 10, marginTop: 4 }}>
          <button
            onClick={onClose}
            style={{
              flex: 1, height: 44,
              background: "var(--color-surface-default)",
              color: "var(--color-text-primary)",
              border: "1px solid var(--color-border-subtle)",
              borderRadius: "var(--radius-sm)",
              fontFamily: "var(--font-family-sans)", fontWeight: 600, fontSize: 14, cursor: "pointer",
            }}
          >
            Close
          </button>
          {canAdvance && (
            <button
              onClick={() => onAdvance?.(story.id)}
              style={{
                flex: 1.3, height: 44,
                background: "var(--color-brand-primary)",
                color: "var(--color-text-on-brand)",
                border: "none",
                borderRadius: "var(--radius-sm)",
                fontFamily: "var(--font-family-sans)", fontWeight: 600, fontSize: 14, cursor: "pointer",
                display: "inline-flex", alignItems: "center", justifyContent: "center", gap: 8,
              }}
            >
              Advance to {nextColumnLabel(story.column)}
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
                <polyline points="9 18 15 12 9 6" />
              </svg>
            </button>
          )}
        </div>
      </div>
    </div>
  );
}

function StrategySummary({ strategy, result, story, cell }) {
  const longCount = (result.longMarkers || []).length;
  const shortCount = (result.shortMarkers || []).length;
  const trend = result.htfTrend;
  // Two summary styles depending on which strategy fired:
  //   - htfTrend present (range-fade-15m): trend-gated state line
  //   - bandWidthPct present (range-fade-1h): regime gate readout
  let stateLabel;
  if (typeof result.bandWidthPct === "number") {
    const bw = result.bandWidthPct.toFixed(2);
    const adxStr = isFinite(result.adx) ? result.adx.toFixed(1) : "n/a";
    stateLabel = result.isSafeRange
      ? `Regime: SAFE RANGE (bw ${bw}% · ADX ${adxStr}) — fades armed · ${result.buyPower}🟢 / ${result.sellPower}🔴 last ${result.lookbackBars} bars`
      : `Regime: TRENDING (bw ${bw}% · ADX ${adxStr}) — fades disabled`;
  } else {
    stateLabel = !trend
      ? "HTF trend: not yet computed"
      : trend.isStrongUp
        ? "HTF trend: STRONG UP — long fades blocked"
        : trend.isStrongDown
          ? "HTF trend: STRONG DOWN — short fades blocked"
          : "HTF trend: mixed — both sides armed";
  }
  const trendLabel = stateLabel;
  return (
    <div style={{
      padding: "10px 12px",
      borderRadius: "var(--radius-sm)",
      background: "var(--color-surface-subtle)",
      border: "1px solid var(--color-border-subtle)",
      display: "flex", flexDirection: "column", gap: 6,
    }}>
      <div className="ds-overline" style={{ color: "var(--color-text-muted)", fontSize: 10 }}>
        {strategy.name}
      </div>
      <div style={{ display: "flex", gap: 12, fontSize: 12, color: "var(--color-text-secondary)" }}>
        <span><b style={{ color: "#22c55e" }}>{longCount}</b> long historical</span>
        <span><b style={{ color: "#ef4444" }}>{shortCount}</b> short historical</span>
      </div>
      <div className="ds-caption" style={{ color: "var(--color-text-muted)", fontSize: 11 }}>
        {trendLabel}
      </div>
      <AlertsControl story={story} cell={cell} />
    </div>
  );
}

// Arm / disarm toggle + email field + recent fires log. Fetches state from
// /api/alerts/card/:id on mount and on toggle. The email is sticky per
// device (localStorage) so the user doesn't retype it for every card.
const EMAIL_LOCAL_KEY = "tradewizard:alert-email";

function AlertsControl({ story, cell }) {
  const [state, setState] = useStateCD({ loading: true, armed: null, fires: [] });
  const [email, setEmail] = useStateCD(() => {
    try { return localStorage.getItem(EMAIL_LOCAL_KEY) || ""; } catch { return ""; }
  });
  const [pending, setPending] = useStateCD(false);
  const [error, setError] = useStateCD(null);

  const refresh = () =>
    fetch(`/api/alerts/card/${encodeURIComponent(story.id)}`, { headers: { accept: "application/json" } })
      .then((r) => r.ok ? r.json() : Promise.reject(`http_${r.status}`))
      .then((d) => setState({ loading: false, armed: d.armed, fires: d.fires || [] }))
      .catch((e) => setState({ loading: false, armed: null, fires: [], error: String(e) }));

  useEffectCD(() => { refresh(); }, []);

  const arm = async () => {
    if (!cell.coin || !cell.timeframe) {
      setError("This card is missing coin/timeframe — can't arm");
      return;
    }
    setPending(true); setError(null);
    try { localStorage.setItem(EMAIL_LOCAL_KEY, email || ""); } catch {}
    const r = await fetch("/api/alerts/arm", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({
        card_id: story.id, coin: cell.coin, timeframe: cell.timeframe,
        archetype: cell.archetype, pair: cell.pair, email: email || null,
      }),
    });
    const body = await r.json().catch(() => ({}));
    if (!r.ok) { setError(body.error || `http_${r.status}`); setPending(false); return; }
    await refresh();
    setPending(false);
  };

  const disarm = async () => {
    setPending(true); setError(null);
    const r = await fetch("/api/alerts/disarm", {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ card_id: story.id }),
    });
    if (!r.ok) { const body = await r.json().catch(() => ({})); setError(body.error || `http_${r.status}`); }
    await refresh();
    setPending(false);
  };

  if (state.loading) {
    return <div className="ds-caption" style={{ color: "var(--color-text-muted)", fontSize: 11 }}>Loading alert state…</div>;
  }

  const armed = !!state.armed;
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 6, marginTop: 4 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <span style={{
          display: "inline-block", width: 8, height: 8, borderRadius: "50%",
          background: armed ? "var(--color-success, #22c55e)" : "var(--color-text-muted)",
        }} />
        <span style={{ fontSize: 12, fontWeight: 600, color: "var(--color-text-primary)" }}>
          {armed ? "Alerts armed" : "Alerts off"}
        </span>
        {armed && state.armed?.armed_at && (
          <span className="ds-caption" style={{ color: "var(--color-text-muted)", fontSize: 10 }}>
            since {formatAge(state.armed.armed_at)}
          </span>
        )}
        <button
          onClick={armed ? disarm : arm}
          disabled={pending}
          style={{
            marginLeft: "auto",
            padding: "4px 12px",
            borderRadius: "var(--radius-pill)",
            border: armed ? "1px solid var(--color-border-subtle)" : "none",
            background: armed ? "var(--color-surface-default)" : "var(--color-brand-primary)",
            color: armed ? "var(--color-text-primary)" : "var(--color-text-on-brand)",
            fontFamily: "var(--font-family-sans)",
            fontWeight: 600, fontSize: 11, cursor: pending ? "wait" : "pointer",
          }}
        >
          {pending ? "…" : armed ? "Disarm" : "Arm alerts"}
        </button>
      </div>
      {!armed && (
        <input
          type="email"
          placeholder="email for notifications (optional)"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          style={{
            padding: "6px 10px",
            background: "var(--color-surface-default)",
            border: "1px solid var(--color-border-subtle)",
            borderRadius: "var(--radius-sm)",
            fontFamily: "var(--font-family-sans)",
            fontSize: 12,
            color: "var(--color-text-primary)",
          }}
        />
      )}
      {error && (
        <div className="ds-caption" style={{ color: "var(--color-danger, #b03a2e)", fontSize: 11 }}>
          {error}
        </div>
      )}
      {state.fires.length > 0 && (
        <div style={{ marginTop: 4 }}>
          <div className="ds-overline" style={{ color: "var(--color-text-muted)", fontSize: 9 }}>
            Recent fires
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 2, marginTop: 3 }}>
            {state.fires.slice(0, 5).map((f) => (
              <div key={`${f.bar_time}-${f.side}`} style={{
                display: "flex", gap: 8, fontSize: 11, color: "var(--color-text-secondary)",
                fontFamily: "var(--font-family-mono, monospace)",
              }}>
                <span style={{
                  color: f.side === "long" ? "var(--color-success, #22c55e)" : "var(--color-danger, #ef4444)",
                  fontWeight: 700, width: 50,
                }}>
                  {f.side.toUpperCase()}
                </span>
                <span>{new Date(f.bar_time * 1000).toLocaleString()}</span>
                <span style={{ color: "var(--color-text-muted)" }}>{f.reason}</span>
                {f.delivery_status && f.delivery_status !== "delivered" && (
                  <span style={{ color: "var(--color-text-muted)", marginLeft: "auto" }}>
                    {f.delivery_status === "skipped" ? "(no email)" : `(${f.delivery_status})`}
                  </span>
                )}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

function formatAge(iso) {
  const ms = Date.now() - new Date(iso).getTime();
  if (!isFinite(ms) || ms < 0) return "";
  const min = Math.floor(ms / 60000);
  if (min < 1) return "now";
  if (min < 60) return `${min}m ago`;
  const hr = Math.floor(min / 60);
  if (hr < 24) return `${hr}h ago`;
  return `${Math.floor(hr / 24)}d ago`;
}

// Map a Featureban story back to the {coin, pair, timeframe, archetype}
// shape the strategy registry expects. story.ref = "BTC/USDC · 15m";
// story.who holds the archetype name we stamped at seed time.
function storyToCell(story) {
  if (!story) return {};
  const refStr = String(story.ref || "");
  const m = refStr.match(/^([A-Z0-9]+)\b.*·\s*([0-9]+[mhdMHD])$/);
  return {
    coin: m ? m[1] : "",
    pair: refStr.split(" · ")[0] || refStr,
    timeframe: m ? m[2].toLowerCase() : "",
    archetype: story.who || "",
  };
}

function nextColumnLabel(col) {
  return ({ todo: "Paper trade", doing: "Live run", review: "Closed" })[col] || "next";
}

Object.assign(window, { CardDetail });
