/* Opening tree — adapted to FEN-keyed nodes from the API. */

function severityColor(cpl) {
  if (cpl == null) return 'transparent';
  const t = Math.min(1, cpl / 80);
  const alpha = 0.15 + t * 0.85;
  return `color-mix(in oklab, var(--accent) ${Math.round(alpha * 100)}%, transparent)`;
}
function severityText(cpl) {
  if (cpl == null) return '—';
  if (cpl < 8) return 'fine';
  if (cpl < 20) return 'soft';
  if (cpl < 40) return 'leak';
  return 'crit';
}

function pathFromRoot(nodes, fen) {
  const arr = [];
  let cur = nodes[fen];
  while (cur && cur.parent) {
    arr.unshift(cur);
    cur = nodes[cur.parent];
  }
  return arr;
}

function fullMoveLine(nodes, fen) {
  return pathFromRoot(nodes, fen)
    .map((n) => {
      if (n.ply % 2 === 1) {
        const num = Math.ceil(n.ply / 2);
        return `${num}. ${n.san}`;
      }
      return n.san;
    })
    .join(' ');
}

function winRate(n) {
  if (!n || !n.games) return null;
  return (n.wins + n.draws * 0.5) / n.games;
}

/* Flow tree — columns by ply */
function FlowTree({ nodes, rootFen, activeFen, onPick, onHoverNode, maxDepth = 5 }) {
  if (!rootFen || !nodes[rootFen]) return null;

  const activePathFens = new Set(pathFromRoot(nodes, activeFen).map((n) => n.fen));
  activePathFens.add(rootFen);

  const cols = [];
  function collect(fen, depth) {
    if (depth >= maxDepth) return;
    const n = nodes[fen];
    if (!n) return;
    if (!cols[depth]) cols[depth] = [];
    for (const cFen of n.children) cols[depth].push(cFen);
    for (const cFen of n.children) {
      if (activePathFens.has(cFen)) collect(cFen, depth + 1);
    }
  }
  collect(rootFen, 0);

  // Extend with highest-volume branch beneath active node if shallow
  if (activeFen && nodes[activeFen]) {
    let cur = activeFen;
    let depth = pathFromRoot(nodes, activeFen).length;
    while (depth < maxDepth && nodes[cur] && nodes[cur].children.length) {
      const kids = nodes[cur].children
        .slice()
        .sort((a, b) => nodes[b].games - nodes[a].games);
      cols[depth] = kids;
      cur = kids[0];
      depth++;
    }
  }

  if (!cols.length) return <div style={{ color: 'var(--fg-muted)', fontSize: 13 }}>No moves to display.</div>;

  // Dedupe columns (children reoccur due to our recursion)
  const dedupedCols = cols.map((arr) => Array.from(new Set(arr)));

  return (
    <div className="tree-scroll">
      <div className="flow">
        {dedupedCols.map((fens, d) => (
          <div className="flow-col" key={d}>
            <div className="flow-col-head">
              {d % 2 === 0 ? `W · ply ${d + 1}` : `B · ply ${d + 1}`}
            </div>
            {fens.map((fen) => {
              const n = nodes[fen];
              if (!n) return null;
              const isActive = activeFen === fen;
              const onActivePath = activePathFens.has(fen);
              const sev = n.myAvgLossCp;
              const cls = ['node'];
              if (isActive) cls.push('active');
              if (!onActivePath && activeFen) cls.push('dim');
              const wr = winRate(n);
              return (
                <div
                  key={fen}
                  className={cls.join(' ')}
                  style={{ '--sev-color': severityColor(sev) }}
                  onClick={() => onPick(fen)}
                  onMouseEnter={(e) => onHoverNode && onHoverNode(fen, e)}
                  onMouseLeave={() => onHoverNode && onHoverNode(null)}
                >
                  <span className="mv">{n.san || 'start'}</span>
                  <span className="cnt">{n.games}</span>
                  <span className="drop">
                    <span>{sev != null ? `−${sev}cp` : 'opp'}</span>
                    <span className="wr">
                      {wr != null ? `${Math.round(wr * 100)}%` : ''}
                    </span>
                  </span>
                </div>
              );
            })}
          </div>
        ))}
      </div>
    </div>
  );
}

/* Indented tree */
function IndentedTree({ nodes, rootFen, activeFen, onPick, maxDepth = 6 }) {
  const rows = [];
  function walk(fen, depth) {
    if (depth > maxDepth) return;
    const n = nodes[fen];
    if (!n) return;
    if (fen !== rootFen) {
      let label;
      if (n.ply % 2 === 1) {
        label = (
          <span className="mv">
            <span className="muted">{Math.ceil(n.ply / 2)}.</span> {n.san}
          </span>
        );
      } else {
        label = (
          <span className="mv">
            <span className="muted">{Math.ceil(n.ply / 2)}…</span> {n.san}
          </span>
        );
      }
      rows.push({ fen, depth, n, label });
    }
    const kids = [...n.children].sort((a, b) => nodes[b].games - nodes[a].games);
    for (const cFen of kids) walk(cFen, depth + 1);
  }
  walk(rootFen, 0);

  return (
    <div className="indent">
      <div className="indent-head">
        <span></span>
        <span>LINE</span>
        <span className="val">GAMES</span>
        <span className="val">WIN%</span>
        <span className="val">Ø CPL</span>
      </div>
      {rows.map(({ fen, depth, n, label }) => {
        const wr = winRate(n);
        return (
          <div
            key={fen}
            className={'indent-row' + (activeFen === fen ? ' active' : '')}
            onClick={() => onPick(fen)}
            style={{ paddingLeft: 8 + (depth - 1) * 16 }}
          >
            <span
              className="tw"
              style={{ color: severityColor(n.myAvgLossCp), filter: 'saturate(2)' }}
            >
              {n.myAvgLossCp != null && n.myAvgLossCp >= 20 ? '●' : ''}
            </span>
            {label}
            <span className="val">{n.games}</span>
            <span className="val">{wr != null ? Math.round(wr * 100) + '%' : '—'}</span>
            <span className={'val' + (n.myAvgLossCp >= 20 ? ' bad' : '')}>
              {n.myAvgLossCp != null ? n.myAvgLossCp : '—'}
            </span>
          </div>
        );
      })}
    </div>
  );
}

Object.assign(window, {
  FlowTree, IndentedTree, pathFromRoot, fullMoveLine, winRate, severityColor, severityText,
});
