/**
 * Horizon chart primitives shared by the dashboard and ticker page.
 * All colors come from the :root tokens in globals.css; the data marks
 * (--up / --down) are validated against the dark surface.
 */
import { useState } from 'react';
import type { OHLCVBar } from '../../lib/types';

function fmt(n: number, digits = 2): string {
  return n.toLocaleString('en-US', { minimumFractionDigits: digits, maximumFractionDigits: digits });
}

function signedR(r: number): string {
  return `${r > 0 ? '+' : r < 0 ? '−' : ''}${fmt(Math.abs(r))}`;
}

/* ------------------------------------------------------------------ */
/* RBar — diverging R-multiple bar growing from a centered zero line    */
/* ------------------------------------------------------------------ */

export function RBar({ r, max = 1.6 }: { r: number | null; max?: number }) {
  const pct = r == null ? 0 : Math.min(Math.abs(r) / Math.max(max, 0.01), 1) * 50;
  return (
    <div className="hz-rbar" role="img" aria-label={r == null ? 'no R value' : `${signedR(r)} R`}>
      <span className="hz-rbar-zero" />
      {r != null && (
        <span
          className="hz-rbar-fill"
          style={{
            left: r >= 0 ? '50%' : `${50 - pct}%`,
            width: `${pct}%`,
            background: r >= 0 ? 'var(--up)' : 'var(--down)',
            borderRadius: r >= 0 ? '0 4px 4px 0' : '4px 0 0 4px',
          }}
        />
      )}
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* PriceRail — stop → entry → now → target laid out spatially           */
/* ------------------------------------------------------------------ */

export function PriceRail({
  direction, entry, stop, target, current, scaleTo,
}: {
  direction: string;
  entry: number;
  stop: number;
  target: number;
  current: number | null;
  /** Extra prices the scale must span (e.g. every target in the ladder), so
   *  switching targets moves the target marker, not stop/entry/now. */
  scaleTo?: number[];
}) {
  const points = [entry, stop, target, ...(current != null ? [current] : []), ...(scaleTo ?? [])];
  const span = Math.max(...points) - Math.min(...points) || 1;
  const lo = Math.min(...points) - span * 0.06;
  const hi = Math.max(...points) + span * 0.06;
  const pct = (v: number) => Math.min(100, Math.max(0, ((v - lo) / (hi - lo)) * 100));
  const risk = Math.abs(entry - stop);
  const rNow = current != null && risk > 0
    ? (direction === 'long' ? current - entry : entry - current) / risk
    : null;
  const rTarget = risk > 0 ? Math.abs(target - entry) / risk : null;
  const inProfit = rNow != null && rNow >= 0;
  const progressLeft = current != null ? Math.min(pct(entry), pct(current)) : 0;
  const progressWidth = current != null ? Math.abs(pct(current) - pct(entry)) : 0;
  return (
    <div className="hz-rail" role="img" aria-label={
      `Stop ${fmt(stop)}, entry ${fmt(entry)}, now ${current != null ? fmt(current) : 'unknown'}, target ${fmt(target)}`
    }>
      <div className="hz-rail-track" />
      <div
        className="hz-rail-risk"
        style={{ left: `${Math.min(pct(stop), pct(entry))}%`, width: `${Math.abs(pct(entry) - pct(stop))}%` }}
      />
      {current != null && (
        <div
          className="hz-rail-progress"
          style={{
            left: `${progressLeft}%`,
            width: `${progressWidth}%`,
            background: inProfit ? 'var(--up)' : 'var(--down)',
          }}
        />
      )}
      <div className="hz-rail-mark" style={{ left: `${pct(stop)}%` }}>
        <span className="hz-rail-tick" style={{ background: 'var(--down)' }} />
        <span className="hz-rail-label">
          <em>stop</em>
          <b>{fmt(stop)}</b>
          <i>−1R</i>
        </span>
      </div>
      <div className="hz-rail-mark" style={{ left: `${pct(entry)}%` }}>
        <span className="hz-rail-tick" style={{ background: 'var(--ink-2)' }} />
        <span className="hz-rail-label">
          <em>entry</em>
          <b>{fmt(entry)}</b>
        </span>
      </div>
      {current != null && (
        <div className="hz-rail-mark" style={{ left: `${pct(current)}%` }}>
          <span className="hz-rail-dot" style={{ background: inProfit ? 'var(--up)' : 'var(--down)' }} />
          <span className="hz-rail-label hz-rail-label-top">
            <em>now</em>
            <b style={{ color: inProfit ? 'var(--up-text)' : 'var(--down-text)' }}>{fmt(current)}</b>
            {rNow != null && <i>{signedR(rNow)}R</i>}
          </span>
        </div>
      )}
      <div className="hz-rail-mark" style={{ left: `${pct(target)}%` }}>
        <span className="hz-rail-ring" />
        <span className="hz-rail-label">
          <em>target</em>
          <b>{fmt(target)}</b>
          {rTarget != null && <i>+{fmt(rTarget, 1)}R</i>}
        </span>
      </div>
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* RadarChart — score fingerprint over n axes (0–100), hover for values */
/* ------------------------------------------------------------------ */

export interface RadarAxis {
  label: string;
  value: number;
  /** Longer name for the tooltip / screen reader (falls back to label). */
  full?: string;
}

/** Canonical axis order — mirrors the backend's DIMENSIONS list
 *  (app/services/scoring_service.py). Every radar in the app must use this so
 *  fingerprint shapes stay comparable between the dashboard and ticker pages. */
const RADAR_DIMENSION_ORDER = ['technical', 'sr_quality', 'sentiment', 'fundamental', 'momentum'];

export function radarAxesFromDimensions(
  dimensions: { dimension: string; score: number }[],
): RadarAxis[] {
  const rank = (d: string) => {
    const i = RADAR_DIMENSION_ORDER.indexOf(d.toLowerCase());
    return i === -1 ? RADAR_DIMENSION_ORDER.length : i;
  };
  return [...dimensions]
    .sort((a, b) => rank(a.dimension) - rank(b.dimension) || a.dimension.localeCompare(b.dimension))
    .map((d) => ({
      label: d.dimension.length > 9 ? `${d.dimension.slice(0, 8)}.` : d.dimension,
      full: d.dimension,
      value: d.score,
    }));
}

export function RadarChart({
  axes, size = 120, labels = true,
}: { axes: RadarAxis[]; size?: number; labels?: boolean }) {
  const [hover, setHover] = useState<number | null>(null);
  const n = axes.length;
  if (n < 3) return null;
  // Label pad must fit "momentum 100" fully outside the left/right vertices.
  const pad = labels ? 96 : 10;
  const vb = size + pad * 2;
  const c = vb / 2;
  const r = size / 2;
  const angle = (i: number) => ((-90 + (i * 360) / n) * Math.PI) / 180;
  const pt = (v: number, i: number): [number, number] => {
    const a = angle(i);
    return [c + (Math.cos(a) * r * Math.max(0, Math.min(100, v))) / 100, c + (Math.sin(a) * r * Math.max(0, Math.min(100, v))) / 100];
  };
  const poly = (vals: number[]) =>
    vals.map((v, i) => pt(v, i).map((x) => x.toFixed(1)).join(',')).join(' ');
  const tip = hover !== null ? pt(axes[hover].value, hover) : null;
  return (
    <div className="hz-radar-wrap" style={{ width: vb, height: vb }}>
      <svg
        viewBox={`0 0 ${vb} ${vb}`}
        width={vb}
        height={vb}
        role="img"
        aria-label={`Score fingerprint: ${axes.map((a) => `${a.full ?? a.label} ${Math.round(a.value)}`).join(', ')} (0–100)`}
      >
        {[50, 100].map((ring) => (
          <polygon key={ring} points={poly(Array(n).fill(ring))} fill="none" stroke="var(--grid)" strokeWidth="1" />
        ))}
        {axes.map((a, i) => {
          const [x, y] = pt(100, i);
          return <line key={a.label} x1={c} y1={c} x2={x} y2={y} stroke="var(--grid)" strokeWidth="1" />;
        })}
        <polygon
          points={poly(axes.map((a) => a.value))}
          fill="var(--up)" fillOpacity="0.13"
          stroke="var(--up)" strokeWidth="2" strokeLinejoin="round"
        />
        {axes.map((a, i) => {
          const [x, y] = pt(a.value, i);
          return (
            <circle
              key={a.label} cx={x} cy={y} r={hover === i ? 4.5 : 3}
              fill="var(--up)" stroke="var(--surface)" strokeWidth="1.5"
            />
          );
        })}
        {labels && axes.map((a, i) => {
          const ang = angle(i);
          const x = c + Math.cos(ang) * (r + 13);
          const y = c + Math.sin(ang) * (r + 13);
          const anchor = Math.cos(ang) > 0.3 ? 'start' : Math.cos(ang) < -0.3 ? 'end' : 'middle';
          const dy = Math.sin(ang) < -0.3 ? -2 : Math.sin(ang) > 0.3 ? 9 : 4;
          return (
            <text key={a.label} x={x} y={y + dy} textAnchor={anchor} className="hz-radar-axis">
              {a.label} <tspan className="hz-radar-axisval">{Math.round(a.value)}</tspan>
            </text>
          );
        })}
        {/* hit targets on top — comfortably larger than the dots, keyboard-reachable */}
        {axes.map((a, i) => {
          const [x, y] = pt(a.value, i);
          return (
            <circle
              key={`hit-${a.label}`} cx={x} cy={y} r="13"
              fill="transparent" style={{ cursor: 'default', outline: 'none' }}
              tabIndex={0}
              aria-label={`${a.full ?? a.label}: ${Math.round(a.value)} of 100`}
              onMouseEnter={() => setHover(i)}
              onMouseLeave={() => setHover(null)}
              onFocus={() => setHover(i)}
              onBlur={() => setHover(null)}
            />
          );
        })}
      </svg>
      {hover !== null && tip && (
        <div
          className="hz-radar-tip"
          style={{ left: `${(tip[0] / vb) * 100}%`, top: `${(tip[1] / vb) * 100}%` }}
        >
          <b>{axes[hover].full ?? axes[hover].label}</b>
          <span className="num">{Math.round(axes[hover].value)} / 100</span>
        </div>
      )}
    </div>
  );
}

/* ------------------------------------------------------------------ */
/* TradeChart — close path since entry + entry / stop trail / target    */
/* ------------------------------------------------------------------ */

/** Wilder ATR series matching paper_trade_service._atr_series_from_rows. */
function wilderAtrSeries(bars: OHLCVBar[], period = 14): (number | null)[] {
  const n = bars.length;
  const out: (number | null)[] = Array(n).fill(null);
  if (n < period + 1) return out;
  const tr: number[] = Array(n).fill(0);
  for (let i = 1; i < n; i++) {
    const h = bars[i].high;
    const l = bars[i].low;
    const pc = bars[i - 1].close;
    tr[i] = Math.max(h - l, Math.abs(h - pc), Math.abs(l - pc));
  }
  let running = 0;
  for (let i = 1; i <= period; i++) running += tr[i];
  running /= period;
  let rounded = Math.round(running * 10000) / 10000;
  out[period] = rounded > 0 ? rounded : null;
  for (let j = period + 1; j < n; j++) {
    running = (running * (period - 1) + tr[j]) / period;
    rounded = Math.round(running * 10000) / 10000;
    out[j] = rounded > 0 ? rounded : null;
  }
  return out;
}

/**
 * Per-bar stop level after that bar's update — mirrors the production trail
 * ratchet so the chart can show a *moving* trail, not a flat line at the
 * current level.
 */
function buildStopPath(
  bars: OHLCVBar[],
  direction: string,
  entry: number,
  initStop: number,
  openedDate: string,
  mode: 'atr_trailing' | 'trailing' | 'fixed',
  atrMultiplier = 3,
  trailingPct = 12,
): number[] {
  const long = direction !== 'short';
  const out = bars.map(() => initStop);
  if (mode === 'fixed') return out;

  let stop = initStop;
  let anchor = entry;
  const atr = mode === 'atr_trailing' ? wilderAtrSeries(bars) : null;
  const trailFrac = trailingPct / 100;

  for (let i = 0; i < bars.length; i++) {
    const d = bars[i].date.slice(0, 10);
    if (d <= openedDate) {
      out[i] = initStop;
      continue;
    }
    if (mode === 'atr_trailing' && atr) {
      const close = bars[i].close;
      if (long) {
        anchor = Math.max(anchor, close);
        const a = atr[i];
        if (a != null) {
          const next = anchor - atrMultiplier * a;
          if (next < close) stop = Math.max(stop, next);
        }
      } else {
        anchor = Math.min(anchor, close);
        const a = atr[i];
        if (a != null) {
          const next = anchor + atrMultiplier * a;
          if (next > close) stop = Math.min(stop, next);
        }
      }
    } else {
      // % trailing: peak on high/low, ratchet only.
      if (long) {
        anchor = Math.max(anchor, bars[i].high);
        stop = Math.max(initStop, anchor * (1 - trailFrac));
      } else {
        anchor = Math.min(anchor, bars[i].low);
        stop = Math.min(initStop, anchor * (1 + trailFrac));
      }
    }
    out[i] = stop;
  }
  return out;
}

export function TradeChart({
  direction,
  entry,
  initialStop,
  target,
  bars,
  openedAt,
  exitMode = 'atr_trailing',
  atrMultiplier = 3,
  trailingPct = 12,
}: {
  direction: string;
  entry: number;
  /** Hard stop from the setup (floor for the trail). */
  initialStop: number;
  target: number;
  bars: OHLCVBar[];
  openedAt: string;
  exitMode?: 'time' | 'trailing' | 'atr_trailing' | 'target';
  atrMultiplier?: number;
  trailingPct?: number;
}) {
  const openedDate = openedAt.slice(0, 10);
  const firstIdx = bars.findIndex((b) => b.date.slice(0, 10) >= openedDate);
  // Fresh trades have few bars since entry — pad with context so the chart
  // still reads (gray before entry, colored after).
  const CONTEXT_BARS = 10;
  const start = firstIdx === -1
    ? Math.max(0, bars.length - CONTEXT_BARS)
    : Math.max(0, firstIdx - CONTEXT_BARS);
  const window = bars.slice(start);
  const series = window.map((b) => b.close);
  const entryIdx = firstIdx === -1 ? series.length - 1 : firstIdx - start;
  if (series.length < 2) return null;

  const trailMode: 'atr_trailing' | 'trailing' | 'fixed' =
    exitMode === 'atr_trailing' || exitMode === 'trailing' ? exitMode : 'fixed';
  const stopPathFull = buildStopPath(
    bars,
    direction,
    entry,
    initialStop,
    openedDate,
    trailMode,
    atrMultiplier,
    trailingPct,
  );
  const stopPath = stopPathFull.slice(start);
  const trailActive = trailMode !== 'fixed';
  // Target is a screening artifact under trailing policies — only draw when
  // it is an actual exit.
  const showTarget = exitMode === 'target';

  const w = 560; const h = 150;
  const padL = 8; const padR = 96; const padT = 10; const padB = 12;
  const isShort = direction === 'short';
  const vals = [...series, ...stopPath, entry, initialStop];
  if (showTarget) vals.push(target);
  let lo = Math.min(...vals);
  let hi = Math.max(...vals);
  const range = hi - lo || 1;
  if (showTarget) {
    const targetFits = isShort ? lo - target <= range * 0.9 : target - hi <= range * 0.9;
    if (targetFits) { lo = Math.min(lo, target); hi = Math.max(hi, target); }
  }
  const pad = (hi - lo) * 0.07;
  lo -= pad; hi += pad;
  const plotW = w - padL - padR;
  const plotH = h - padT - padB;
  const px = (i: number) => padL + (i / (series.length - 1)) * plotW;
  const py = (v: number) => padT + plotH - ((v - lo) / (hi - lo)) * plotH;
  const seg = (from: number, to: number) =>
    series
      .slice(from, to + 1)
      .map((v, i) => `${i === 0 ? 'M' : 'L'}${px(from + i).toFixed(1)},${py(v).toFixed(1)}`)
      .join(' ');
  // Step-style trail path so ratchet days read as jumps, not smoothed slopes.
  const stopSeg = (from: number, to: number) => {
    if (to < from) return '';
    let d = `M${px(from).toFixed(1)},${py(stopPath[from]).toFixed(1)}`;
    for (let i = from + 1; i <= to; i++) {
      d += `H${px(i).toFixed(1)}V${py(stopPath[i]).toFixed(1)}`;
    }
    return d;
  };
  const contextPath = entryIdx > 0 ? seg(0, entryIdx) : null;
  const tradePath = seg(entryIdx, series.length - 1);
  const trailPath = stopSeg(entryIdx, series.length - 1);
  const last = series[series.length - 1];
  const liveStop = stopPath[stopPath.length - 1] ?? initialStop;
  const trailMoved = Math.abs(liveStop - initialStop) > 1e-6;
  const perShare = isShort ? entry - last : last - entry;
  const col = perShare >= 0 ? 'var(--up)' : 'var(--down)';
  const labelX = w - padR + 10;
  const entryY = py(entry);
  let stopLabelY = py(liveStop);
  if (Math.abs(stopLabelY - entryY) < 11) {
    stopLabelY = entryY + (stopLabelY >= entryY ? 11 : -11);
  }
  const stopLabel = trailActive && trailMoved ? 'trail' : 'stop';

  return (
    <svg
      viewBox={`0 0 ${w} ${h}`}
      className="hz-tradechart"
      role="img"
      aria-label={
        `Price since entry ${fmt(entry)}, now ${fmt(last)}, ${stopLabel} ${fmt(liveStop)}`
        + (showTarget ? `, target ${fmt(target)}` : '')
      }
    >
      {/* Initial hard stop as a quiet dashed reference once the trail has lifted */}
      {trailActive && trailMoved && (
        <>
          <line
            x1={padL}
            x2={w - padR}
            y1={py(initialStop)}
            y2={py(initialStop)}
            stroke="var(--down)"
            strokeWidth="1"
            strokeDasharray="3 3"
            opacity="0.35"
          />
        </>
      )}
      <line x1={padL} x2={w - padR} y1={entryY} y2={entryY} stroke="var(--ink-3)" strokeWidth="1" opacity="0.5" />
      <text x={labelX} y={entryY + 3.5} className="hz-lvl">entry {fmt(entry)}</text>

      {/* Moving trail (or flat initial stop when no trailing policy) */}
      {trailPath && (
        <path
          d={trailPath}
          fill="none"
          stroke="var(--down)"
          strokeWidth="1.5"
          strokeLinejoin="round"
          strokeLinecap="round"
          opacity="0.9"
        />
      )}
      <text x={labelX} y={stopLabelY + 3.5} className="hz-lvl hz-lvl-down">
        {stopLabel} {fmt(liveStop)}
      </text>

      {showTarget && (() => {
        const tFits = isShort
          ? py(target) <= h - padB && py(target) >= padT
          : py(target) <= h - padB && py(target) >= padT;
        const inRange = isShort
          ? lo - target <= (hi - lo) * 2
          : target - hi <= (hi - lo) * 2;
        if (inRange && tFits) {
          return (
            <>
              <line x1={padL} x2={w - padR} y1={py(target)} y2={py(target)} stroke="var(--up)" strokeWidth="1" opacity="0.6" />
              <text x={labelX} y={py(target) + 3.5} className="hz-lvl hz-lvl-up">target {fmt(target)}</text>
            </>
          );
        }
        return (
          <text x={labelX} y={isShort ? h - padB : padT + 4} className="hz-lvl hz-lvl-up">
            target {fmt(target)} {isShort ? '↓' : '↑'}
          </text>
        );
      })()}

      {contextPath && (
        <path d={contextPath} fill="none" stroke="var(--ink-3)" strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" opacity="0.7" />
      )}
      <path d={tradePath} fill="none" stroke={col} strokeWidth="2" strokeLinejoin="round" strokeLinecap="round" />
      <circle cx={px(entryIdx)} cy={py(entry)} r="3.5" fill="var(--ink-2)" stroke="var(--surface)" strokeWidth="1.5" />
      <circle cx={px(series.length - 1)} cy={py(last)} r="4" fill={col} stroke="var(--surface)" strokeWidth="2" />
    </svg>
  );
}
