/* global React, lucide */
const { useState, useEffect, useMemo, useRef, useCallback } = React;

// ─── parsing helpers ────────────────────────────────────────────
function extractBlock(text, tag) {
  if (!text) return null;
  const re = new RegExp(`<${tag}>\\s*([\\s\\S]*?)\\s*</${tag}>`, 'i');
  const m = text.match(re);
  return m ? m[1].trim() : null;
}
function parseModelResponse(content) {
  const think = extractBlock(content, 'think');
  const dagText = extractBlock(content, 'dag');
  const code = extractBlock(content, 'code');
  let dag = null;
  if (dagText) {
    try { dag = JSON.parse(dagText); } catch (e) {}
  }
  return { think, dag, code, hasAll: !!(think && dagText && code) };
}

// ─── tiny SQL highlighter ───────────────────────────────────────
function highlightSQL(sql) {
  if (!sql) return [];
  const KW = /\b(WITH|AS|SELECT|FROM|WHERE|JOIN|INNER|LEFT|RIGHT|OUTER|ON|GROUP|BY|ORDER|LIMIT|HAVING|UNION|DISTINCT|AND|OR|NOT|IS|NULL|CASE|WHEN|THEN|ELSE|END|IN|EXISTS|COUNT|SUM|AVG|MIN|MAX|CAST|INTEGER|REAL|TEXT|DESC|ASC|EXCEPT|INTERSECT|ALL)\b/gi;
  const lines = sql.split('\n');
  return lines.map((line, i) => {
    const isComment = /^\s*--/.test(line);
    if (isComment) {
      return <div key={i}><span className="com">{line || ' '}</span></div>;
    }
    // Tokenize
    const parts = [];
    let rest = line;
    let safety = 0;
    while (rest.length && safety++ < 200) {
      // strings
      let m = rest.match(/^'([^']*)'/);
      if (m) { parts.push({t:'str', v:`'${m[1]}'`}); rest = rest.slice(m[0].length); continue; }
      // numbers
      m = rest.match(/^\b(\d+(?:\.\d+)?)\b/);
      if (m) { parts.push({t:'num', v:m[0]}); rest = rest.slice(m[0].length); continue; }
      // keyword
      m = rest.match(KW);
      if (m && rest.indexOf(m[0]) === 0) { parts.push({t:'kw', v:m[0]}); rest = rest.slice(m[0].length); continue; }
      // word
      m = rest.match(/^[A-Za-z_][\w.]*/);
      if (m) { parts.push({t:'id', v:m[0]}); rest = rest.slice(m[0].length); continue; }
      // single
      parts.push({t:'p', v: rest[0]}); rest = rest.slice(1);
    }
    return (
      <div key={i}>
        {parts.length ? parts.map((p, j) => p.t === 'p' || p.t === 'id'
          ? <span key={j}>{p.v}</span>
          : <span key={j} className={p.t}>{p.v}</span>) : ' '}
      </div>
    );
  });
}

// ─── think text mini-format (markdown-lite for **bold**) ────────
function formatThink(t) {
  if (!t) return null;
  // Convert **x** to <strong>x</strong>, `x` to <em>x</em> (re-purpose)
  const parts = [];
  const re = /(\*\*[^*]+\*\*|`[^`]+`)/g;
  let last = 0;
  let m;
  let key = 0;
  while ((m = re.exec(t)) !== null) {
    if (m.index > last) parts.push(t.slice(last, m.index));
    if (m[0].startsWith('**')) parts.push(<strong key={key++}>{m[0].slice(2, -2)}</strong>);
    else parts.push(<em key={key++}>{m[0].slice(1, -1)}</em>);
    last = m.index + m[0].length;
  }
  if (last < t.length) parts.push(t.slice(last));
  return parts;
}

// ─── DAG layout (simple longest-path layered) ───────────────────
function layoutDAG(dag) {
  if (!dag || !dag.nodes || !dag.edges) return null;
  const nodes = dag.nodes.map(n => ({ ...n }));
  const edges = dag.edges.map(e => ({ from: e.from ?? e[0], to: e.to ?? e[1] }));
  const byId = new Map(nodes.map(n => [n.id, n]));
  // longest path layering
  const incoming = new Map(nodes.map(n => [n.id, 0]));
  edges.forEach(e => incoming.set(e.to, (incoming.get(e.to) || 0) + 1));
  const layer = new Map();
  // BFS-ish
  const queue = nodes.filter(n => (incoming.get(n.id) || 0) === 0).map(n => n.id);
  queue.forEach(id => layer.set(id, 0));
  // Iteratively assign max(parent layer)+1
  let changed = true, guard = 0;
  while (changed && guard++ < 100) {
    changed = false;
    edges.forEach(e => {
      const fl = layer.get(e.from);
      if (fl == null) return;
      const cur = layer.get(e.to);
      const want = fl + 1;
      if (cur == null || want > cur) {
        layer.set(e.to, want);
        changed = true;
      }
    });
  }
  // place
  const layers = {};
  nodes.forEach(n => {
    const l = layer.get(n.id) ?? 0;
    (layers[l] = layers[l] || []).push(n);
  });
  const layerKeys = Object.keys(layers).map(Number).sort((a,b)=>a-b);
  const colW = 200;
  const rowH = 86;
  const padX = 30;
  const padY = 30;
  let maxRows = 0;
  const positions = new Map();
  layerKeys.forEach((l, ci) => {
    const arr = layers[l];
    arr.forEach((n, ri) => {
      positions.set(n.id, { x: padX + ci * colW, y: padY + ri * rowH });
    });
    maxRows = Math.max(maxRows, arr.length);
  });
  const width = padX * 2 + Math.max(1, layerKeys.length) * colW - 60;
  const height = padY * 2 + Math.max(1, maxRows) * rowH - 30;
  return { nodes, edges, byId, positions, width, height };
}

function DAGView({ dag, activeId }) {
  const layout = useMemo(() => layoutDAG(dag), [dag]);
  if (!layout) {
    return <div style={{padding:'24px', fontFamily:'var(--font-mono)', fontSize:12, color:'var(--fg-3)'}}>DAG not parseable</div>;
  }
  const { nodes, edges, positions, width, height } = layout;
  const nodeW = 158, nodeH = 56;
  return (
    <div className="dag-canvas">
      <svg className="dag-svg" viewBox={`0 0 ${width} ${height}`} preserveAspectRatio="xMidYMid meet">
        <defs>
          <marker id="dag-arrow" viewBox="0 0 10 10" refX="9" refY="5"
                  markerWidth="6" markerHeight="6" orient="auto-start-reverse">
            <path d="M0,1 L9,5 L0,9" fill="none" stroke="rgba(246,243,236,0.4)" strokeWidth="1.2"/>
          </marker>
        </defs>
        {edges.map((e, i) => {
          const a = positions.get(e.from);
          const b = positions.get(e.to);
          if (!a || !b) return null;
          const x1 = a.x + nodeW;
          const y1 = a.y + nodeH/2;
          const x2 = b.x;
          const y2 = b.y + nodeH/2;
          const mx = (x1 + x2) / 2;
          const path = `M ${x1} ${y1} C ${mx} ${y1}, ${mx} ${y2}, ${x2} ${y2}`;
          return <path key={i} d={path} className="dag-edge" />;
        })}
        {nodes.map(n => {
          const p = positions.get(n.id);
          if (!p) return null;
          const type = (n.type || 'select').toLowerCase().replace('agg','aggregate');
          const isActive = activeId === n.id;
          const label = n.id.length > 18 ? n.id.slice(0,17) + '…' : n.id;
          return (
            <g key={n.id} transform={`translate(${p.x}, ${p.y})`}>
              <rect className={`dag-node ${type} ${isActive?'dag-node-active':''}`}
                    width={nodeW} height={nodeH} rx="6" />
              <text className="dag-node-type" x="12" y="18">{type}</text>
              <text className="dag-node-label" x="12" y="38">{label}</text>
            </g>
          );
        })}
      </svg>
    </div>
  );
}

// ─── Result table ──────────────────────────────────────────────
function ResultTable({ columns, rows, max = 8 }) {
  if (!columns || !rows) return null;
  const shown = rows.slice(0, max);
  return (
    <div className="result-table-wrap">
      <table className="result-table">
        <thead>
          <tr>{columns.map((c, i) => <th key={i}>{c}</th>)}</tr>
        </thead>
        <tbody>
          {shown.map((r, i) => (
            <tr key={i}>
              {(Array.isArray(r) ? r : [r]).map((v, j) => (
                <td key={j}>{v == null ? <span style={{color:'var(--fg-4)'}}>NULL</span> : String(v)}</td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
      {rows.length > max && (
        <div className="result-more">+ {rows.length - max} more rows · showing first {max}</div>
      )}
    </div>
  );
}

// ─── Lucide icon ───────────────────────────────────────────────
function Icon({ name, size = 16, stroke = 1.5, ...rest }) {
  // Use span wrapper so React owns the node; inject SVG via innerHTML
  const ref = useRef(null);
  useEffect(() => {
    if (!ref.current) return;
    const node = window.lucide?.icons?.[name] || window.lucide?.icons?.[toCamel(name)];
    if (node && node.toSvg) {
      ref.current.innerHTML = node.toSvg({ width: size, height: size, 'stroke-width': stroke });
    } else if (window.lucide?.createIcons) {
      // fallback: stamp <i> and let lucide.createIcons replace it
      ref.current.innerHTML = `<i data-lucide="${name}" width="${size}" height="${size}" stroke-width="${stroke}"></i>`;
      window.lucide.createIcons();
    }
  }, [name, size, stroke]);
  return <span ref={ref} className="icon-slot" style={{display:'inline-flex',alignItems:'center',justifyContent:'center',width:size,height:size,verticalAlign:'middle'}} {...rest}></span>;
}
function toCamel(s) { return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase()).replace(/^(.)/, m => m.toUpperCase()); }

// ─── Step block ────────────────────────────────────────────────
function Step({ num, title, meta, state, open, onToggle, children }) {
  return (
    <div className="step" data-state={state} data-open={open}>
      <div className="step-header" onClick={onToggle}>
        <div className="step-header-left">
          <span className="step-num">
            {state === 'done' ? <Icon name="check" size={12} stroke={2.5}/> : num}
          </span>
          <span className="step-title">
            {title}
            {meta && <span className="meta">· {meta}</span>}
            {state === 'active' && <span className="blinker" style={{display:'inline-block', width:6, height:10, marginLeft:6, background:'var(--signal)', animation:'blink 1s steps(2) infinite'}}/>}
          </span>
        </div>
        <div className="step-toggle">
          <Icon name="chevron-down" size={14}/>
        </div>
      </div>
      {open && (
        <div className="step-body">{children}</div>
      )}
    </div>
  );
}

// expose
Object.assign(window, {
  parseModelResponse, highlightSQL, formatThink,
  DAGView, ResultTable, Icon, Step,
});
