/* Custex editor — node chrome, collapsed summary, body dispatch. */
const { useState: uS, useRef: uR, useEffect: uE, useMemo: uM } = React;
const { NODE_W, PORT_FIRST, PORT_STRIDE } = window.CxGeom;

const { nodeNumFor, nodeTitleFor, cxCollapseTruncate } = window.CxEditorShared;
const { ProductBody, CraftBody, FabricBody, YarnBody } = window.CxNodeBodiesParams;
const { MatteBody, ColorSplitBody } = window.CxNodeBodiesMatteColor;
const { MotifBody, LayoutBody, OutputBody } = window.CxNodeBodiesMotifLayout;
const { AiRenderBody } = window.CxNodeBodiesAi;

function CxNode({ node, selected, graph, validation, onHeaderMouseDown, onPortMouseDown, onPortMouseUp, updateNode, updateCraftParam, changeCraftType, changeProduct, onDuplicate, onDelete, onSelect, onSetCollapsed, connect, wireTool = null, theme }) {
  const issues = validation.filter(v => v.node === node.id);
  const hasError = issues.some(i => i.level === 'error');
  const collapsed = !!node.collapsed;
  return (
    <div
      className={`cx-node cx-node-${node.role} ${selected?'sel':''} ${hasError?'err':''} ${collapsed ? 'cx-node-collapsed' : ''} ${wireTool === 'scissors' ? 'cx-node-wire-scissors' : ''}`}
      style={{ left: node.x, top: node.y, width: NODE_W }}
      onMouseDown={(e) => { e.stopPropagation(); onSelect(); }}
    >
      <div className="cx-node-header" onMouseDown={(e) => onHeaderMouseDown(e, node.id)}>
        <button
          type="button"
          className="cx-node-collapse"
          aria-expanded={!collapsed}
          aria-label={collapsed ? 'Expand node' : 'Collapse node'}
          title={collapsed ? 'Expand' : 'Collapse'}
          onMouseDown={(e) => e.stopPropagation()}
          onClick={(e) => { e.stopPropagation(); onSetCollapsed && onSetCollapsed(!collapsed); }}
        >
          <svg className="cx-node-collapse-ico" viewBox="0 0 16 16" fill="none" aria-hidden="true">
            <path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" strokeLinejoin="round" />
          </svg>
        </button>
        <span className="cx-node-num">{nodeNumFor(node.role)}</span>
        <span className="cx-node-title">{nodeTitleFor(node.role)}</span>
        <span className="cx-node-type">{node.role === 'airender' ? 'AI' : node.role.toUpperCase()}</span>
        <div className="cx-node-actions">
          <button className="cx-node-act" onMouseDown={(e) => e.stopPropagation()} onClick={(e) => { e.stopPropagation(); onDuplicate && onDuplicate(); }} title="Copy node" aria-label="Copy node">
            <svg viewBox="0 0 16 16" className="cx-node-act-ico" fill="none" aria-hidden="true">
              <rect x="6" y="3" width="7" height="9" rx="1.4" />
              <path d="M4 13H3a1 1 0 0 1-1-1V5.8a1 1 0 0 1 1-1h1.8" />
            </svg>
          </button>
          <button className="cx-node-act danger" onMouseDown={(e) => e.stopPropagation()} onClick={(e) => { e.stopPropagation(); onDelete && onDelete(); }} title="Delete node" aria-label="Delete node">
            <svg viewBox="0 0 16 16" className="cx-node-act-ico" fill="none" aria-hidden="true">
              <path d="M3 4.6h10" />
              <path d="M6.2 4.6V3.7c0-.5.4-.9.9-.9h1.8c.5 0 .9.4.9.9v.9" />
              <rect x="4.6" y="4.6" width="6.8" height="8.6" rx="1.2" />
              <path d="M6.8 7v4.4M9.2 7v4.4" />
            </svg>
          </button>
        </div>
        <span className={`cx-node-badge ${!hasError?'on':''}`} />
      </div>
      {collapsed ? <CxNodeCollapsedSummary node={node} graph={graph} /> : null}
      {!collapsed && (
      <div className="cx-node-body">
        <CxNodeBody node={node} updateNode={updateNode} updateCraftParam={updateCraftParam} changeCraftType={changeCraftType} changeProduct={changeProduct} graph={graph} />
        {issues.length > 0 && (
          <div className="cx-node-issues">
            {issues.map((iss, i) => (
              <div key={i} className={`cx-node-issue ${iss.level}`}>! {iss.msg}</div>
            ))}
          </div>
        )}
      </div>
      )}
      {/* input ports */}
      {(() => {
        const portBase = typeof window.CxGeom?.portFirstY === 'function' ? window.CxGeom.portFirstY(node) : PORT_FIRST;
        return (
      <>
      {node.inputs.map((p, i) => {
        const cn = graph.connections.find(c => c.to === node.id && c.toPort === p.name);
        const inPeer = cn ? (graph.nodes[cn.from]?.role ?? null) : null;
        const isHover = connect && connect.hover && connect.hover.id === node.id && connect.hover.port === p.name;
        const isHoverInvalid = connect && connect.hoverInvalid && connect.hoverInvalid.id === node.id && connect.hoverInvalid.port === p.name;
        return (
          <div key={'in'+p.name}
            className={`cx-port in cx-port-${p.type}${inPeer ? ` cx-port-peer-${inPeer}` : ''} ${cn?'connected':''} ${isHover?'hover-valid':''} ${isHoverInvalid?'hover-invalid':''}`}
            style={{ top: portBase + i * PORT_STRIDE }}
            title={`In · ${p.type}`}
            aria-label={`Input port ${p.type}`}
            onMouseDown={(e) => onPortMouseDown(e, node.id, p.name, 'in', p.type)}
            onMouseUp={(e)   => onPortMouseUp(e, node.id, p.name, 'in', p.type)}
          />
        );
      })}
      {/* output ports */}
      {node.outputs.map((p, i) => {
        const outC = graph.connections.find((c) => c.from === node.id && c.fromPort === p.name);
        const outPeer = outC ? (graph.nodes[outC.to]?.role ?? null) : null;
        return (
        <div key={'out'+p.name}
          className={`cx-port out cx-port-${p.type}${outPeer ? ` cx-port-peer-${outPeer}` : ''}`}
          style={{ top: portBase + i * PORT_STRIDE }}
          title={`Out · ${p.type}`}
          aria-label={`Output port ${p.type}`}
          onMouseDown={(e) => onPortMouseDown(e, node.id, p.name, 'out', p.type)}
          onMouseUp={(e)   => onPortMouseUp(e, node.id, p.name, 'out', p.type)}
        />
      );})}
      </>
        );
      })()}
    </div>
  );
}
/** One- or two-line summary when a node is collapsed (selected values only). */
function CxNodeCollapsedSummary({ node, graph }) {
  if (!node) return null;
  const d = node.data || {};
  let primary = '';
  let secondary = '';
  const Sch = window.CxSchema;
  switch (node.role) {
    case 'product': {
      const p = Sch.PRODUCTS.find((x) => x.id === d.selected);
      const cs = d.customSize || { w: 42, h: 42, unit: 'cm' };
      primary = p?.name || d.selected || '—';
      secondary = `${cs.w} × ${cs.h} ${cs.unit}`;
      break;
    }
    case 'craft': {
      const c = Sch.CRAFTS.find((x) => x.id === d.type);
      primary = c?.label || c?.short || d.type || '—';
      const params = d.params || {};
      if (c?.params) {
        const bits = [];
        for (const [k, def] of Object.entries(c.params)) {
          const v = params[k] ?? def?.default;
          if (v == null) continue;
          const lab = window.CxNodeBodiesParams?.paramFieldLabel
            ? window.CxNodeBodiesParams.paramFieldLabel(k, def)
            : (def.label || k.replace(/_/g, ' '));
          let disp = v;
          if (def.kind === 'range') disp = `${Math.round(Number(v) * 100)}%`;
          else if (def.kind === 'int') disp = `${v}${def.unit || ''}`;
          bits.push(`${lab}: ${disp}`);
          if (bits.length >= 2) break;
        }
        secondary = bits.join(' · ');
      }
      break;
    }
    case 'fabric': {
      const m = Sch.MATERIALS.find((x) => x.id === d.materialId);
      primary = m?.name || d.materialId || '—';
      if (m) secondary = [m.gsm ? `${m.gsm} gsm` : '', m.hand].filter(Boolean).join(' · ');
      break;
    }
    case 'yarn': {
      const sel = d.selected || [];
      const yarns = sel.map((id) => Sch.YARNS.find((y) => y.id === id)).filter(Boolean);
      primary = yarns.map((y) => y.name).join(' · ') || '—';
      break;
    }
    case 'motif': {
      const ids = d.motifIds?.length ? d.motifIds : [d.motifId].filter(Boolean);
      const names = ids.map((id) => Sch.MOTIFS.find((m) => m.id === id)?.name).filter(Boolean);
      if (d.motifSource === 'upload' && d.generatedName) {
        primary = d.generatedName;
        secondary = 'Uploaded PNG';
      } else {
        primary = names.length ? names.join(' · ') : (d.generatedName || 'Motif');
        secondary = `${d.targetColorCount ?? 5} colors`;
        const pr = (d.prompt || '').trim();
        if (pr) secondary = `${secondary} · ${cxCollapseTruncate(pr, 32)}`;
      }
      break;
    }
    case 'matte': {
      const outUrl = d.strippedImageDataUrl || d.strippedImageUrl;
      primary = outUrl ? 'Background stripped' : 'No output';
      const inc = graph.connections?.find((x) => x.to === node.id && x.toPort === 'in');
      const up = inc ? graph.nodes?.[inc.from] : null;
      if (up) secondary = `From ${nodeTitleFor(up.role)}`;
      break;
    }
    case 'colorSplit': {
      const layers = (d.layers || []).filter((l) => l.visible !== false);
      primary = layers.length ? `${layers.length} color layer(s)` : 'No layers';
      secondary = `Target ${d.targetColorCount ?? graph.nodes?.motif?.data?.targetColorCount ?? 5} colors`;
      break;
    }
    case 'layout': {
      const tileLabel = window.CxLayoutTiles?.label ? window.CxLayoutTiles.label(d.tile) : (d.tile || 'grid');
      primary = `${tileLabel} · ${d.scale ?? 60}%`;
      secondary = `${d.rotate ?? 0}°`;
      break;
    }
    case 'airender': {
      const hasRender = !!(d.renderImageDataUrl || d.renderImageUrl);
      const pr = (d.renderPrompt || '').trim();
      if (hasRender) {
        primary = 'Render output';
        secondary = pr ? cxCollapseTruncate(pr, 42) : (d.refImageName || '');
      } else {
        primary = pr ? cxCollapseTruncate(pr, 52) : 'No scene brief';
        if (d.refImageName) secondary = `Ref: ${d.refImageName}`;
      }
      break;
    }
    case 'output': {
      primary = `Qty ${d.qty ?? 1}`;
      break;
    }
    default:
      return null;
  }
  if (!primary && !secondary) return null;
  return (
    <div className="cx-node-collapsed-summary">
      {primary ? <div className="cx-node-collapsed-primary">{primary}</div> : null}
      {secondary ? <div className="cx-node-collapsed-secondary">{secondary}</div> : null}
    </div>
  );
}

// ─────────── node bodies (dispatch by role) ───────────
function CxNodeBody({ node, updateNode, updateCraftParam, changeCraftType, changeProduct, graph }) {
  if (node.role === 'product') return <ProductBody node={node} updateNode={updateNode} changeProduct={changeProduct} graph={graph} />;
  if (node.role === 'craft')   return <CraftBody   node={node} updateCraftParam={updateCraftParam} changeCraftType={changeCraftType} graph={graph} />;
  if (node.role === 'fabric')  return <FabricBody  node={node} updateNode={updateNode} graph={graph} />;
  if (node.role === 'yarn')    return <YarnBody    node={node} updateNode={updateNode} graph={graph} />;
  if (node.role === 'motif')   return <MotifBody   node={node} updateNode={updateNode} graph={graph} />;
  if (node.role === 'matte')  return <MatteBody   node={node} updateNode={updateNode} graph={graph} />;
  if (node.role === 'colorSplit') return <ColorSplitBody node={node} updateNode={updateNode} graph={graph} />;
  if (node.role === 'layout')  return <LayoutBody  node={node} updateNode={updateNode} graph={graph} />;
  if (node.role === 'airender') return <AiRenderBody node={node} updateNode={updateNode} graph={graph} />;
  if (node.role === 'output')  return <OutputBody  node={node} updateNode={updateNode} graph={graph} />;
  return null;
}
window.CxNodeUi = { CxNode, CxNodeCollapsedSummary, CxNodeBody };
