/* Custex editor — matte strip + color split node bodies. */
const { useState: uS, useRef: uR, useEffect: uE, useMemo: uM } = React;
const { NODE_W, PORT_FIRST, PORT_STRIDE } = window.CxGeom;

function MatteBody({ node, updateNode, graph }) {
  const d = node.data || {};
  const outUrl = d.strippedImageDataUrl || d.strippedImageUrl || '';
  const err = d.stripError || '';
  const inc = graph.connections.find((x) => x.to === node.id && x.toPort === 'in');
  const upstream = inc ? graph.nodes[inc.from] : null;
  const srcUrl =
    upstream && upstream.role === 'motif'
      ? upstream.data?.generatedImageDataUrl || upstream.data?.generatedImageUrl || ''
      : '';
  const [busy, setBusy] = uS(false);
  const strip = async () => {
    if (!window.CxAI?.stripMotifMatteBackdrop) return;
    let url = srcUrl;
    if (!url) {
      updateNode(node.id, {
        stripError: 'Connect Motif → Matte, then generate or pick a motif with a raster.',
        strippedImageDataUrl: '',
        strippedImageUrl: '',
      });
      return;
    }
    if (/^https?:\/\//i.test(url) && window.CxAI.tryFetchImageAsDataUrl) {
      const fetched = await window.CxAI.tryFetchImageAsDataUrl(url);
      if (fetched) url = fetched;
    }
    setBusy(true);
    try {
      const next = await window.CxAI.stripMotifMatteBackdrop(url);
      updateNode(node.id, { strippedImageDataUrl: next, strippedImageUrl: '', stripError: '' });
    } catch (e) {
      updateNode(node.id, { stripError: e?.message || 'Strip failed' });
    } finally {
      setBusy(false);
    }
  };
  const isUserUpload = upstream?.data?.motifSource === 'upload';
  uE(() => {
    if (!srcUrl) return undefined;
    if (isUserUpload) {
      if (outUrl !== srcUrl) {
        updateNode(node.id, { strippedImageDataUrl: srcUrl, strippedImageUrl: '', stripError: '' });
      }
      return undefined;
    }
    const h = window.setTimeout(() => {
      strip();
    }, 480);
    return () => window.clearTimeout(h);
    // eslint-disable-next-line react-hooks/exhaustive-deps -- strip when upstream raster identity changes
  }, [srcUrl, upstream?.id, isUserUpload]);

  return (
    <>
      <p className="cx-render-note">Algorithmic matte removal (white / gray / fake checker). Runs after Motif AI or uses catalog + AI raster upstream.</p>
      <div className="cx-field-l">Upstream <span className="cx-field-v">{upstream?.role === 'motif' ? 'Motif' : '—'}</span></div>
      <div className="cx-motif-preview">
        {busy ? (
          <div className="cx-spinner" />
        ) : outUrl ? (
          <img src={outUrl} alt="" className="cx-motif-svg cx-motif-raster" />
        ) : (
          <span className="cx-motif-matte-placeholder">No output yet</span>
        )}
      </div>
      <div className="cx-row">
        <button type="button" className="cx-btn-ghost" disabled={busy} onClick={() => strip()}>
          {busy ? '…' : 'Run strip'}
        </button>
        <button
          type="button"
          className="cx-btn-ghost"
          disabled={!outUrl}
          onClick={() => updateNode(node.id, { strippedImageDataUrl: '', strippedImageUrl: '', stripError: '' })}
        >
          Clear
        </button>
      </div>
      {err ? <p className="cx-render-note cx-render-note-err">{err}</p> : null}
    </>
  );
}

function colorSplitSourceUrl(graph, colorSplitNodeId) {
  const inc = graph.connections.find((x) => x.to === colorSplitNodeId && x.toPort === 'in');
  const upstream = inc ? graph.nodes[inc.from] : null;
  if (!upstream) return '';
  if (upstream.role === 'matte') {
    return upstream.data?.strippedImageDataUrl || upstream.data?.strippedImageUrl || '';
  }
  if (upstream.role === 'motif') {
    return upstream.data?.generatedImageDataUrl || upstream.data?.generatedImageUrl || '';
  }
  return '';
}

function ColorSplitBody({ node, updateNode, graph }) {
  const d = node.data || {};
  const layers = d.layers || [];
  const outUrl = d.mergedPreviewDataUrl || '';
  const err = d.splitError || '';
  const motifNode = graph.nodes?.motif;
  const targetColors = Math.max(1, Math.min(10, Number(motifNode?.data?.targetColorCount) || 5));
  const srcUrl = colorSplitSourceUrl(graph, node.id);
  const [busy, setBusy] = uS(false);
  const [previewBusy, setPreviewBusy] = uS(false);
  const recomposeTimerRef = uR(null);
  const recomposeGenRef = uR(0);

  const applyLayers = async (nextLayers, opts) => {
    const immediate = opts && opts.immediate;
    if (!window.CxAI?.recomposeColorSplitLayers) {
      updateNode(node.id, { layers: nextLayers });
      return;
    }
    const visible = nextLayers.filter((l) => l.visible !== false && l.maskDataUrl);
    if (!visible.length) {
      updateNode(node.id, { layers: nextLayers, mergedPreviewDataUrl: '', splitError: '' });
      return;
    }
    const gen = ++recomposeGenRef.current;
    setPreviewBusy(true);
    try {
      const merged = await window.CxAI.recomposeColorSplitLayers(nextLayers);
      if (gen !== recomposeGenRef.current) return;
      updateNode(node.id, {
        layers: nextLayers,
        mergedPreviewDataUrl: merged || '',
        splitError: merged ? '' : 'Preview rebuild failed',
      });
    } catch (e) {
      if (gen !== recomposeGenRef.current) return;
      updateNode(node.id, {
        layers: nextLayers,
        splitError: e?.message || 'Preview rebuild failed',
      });
    } finally {
      if (gen === recomposeGenRef.current) setPreviewBusy(false);
    }
  };

  const scheduleRecompose = (nextLayers, immediate) => {
    if (recomposeTimerRef.current) {
      clearTimeout(recomposeTimerRef.current);
      recomposeTimerRef.current = null;
    }
    if (immediate) {
      applyLayers(nextLayers, { immediate: true });
      return;
    }
    recomposeTimerRef.current = setTimeout(() => {
      recomposeTimerRef.current = null;
      applyLayers(nextLayers);
    }, 48);
  };

  const patchLayerColor = (layerId, hexRaw) => {
    const hex = window.CxAI?.normalizeHex
      ? window.CxAI.normalizeHex(hexRaw, '#000000')
      : hexRaw;
    const rgb = window.CxAI?.hexToRgb ? window.CxAI.hexToRgb(hex) : null;
    if (!rgb) return;
    const next = layers.map((l) =>
      l.id === layerId ? { ...l, hex, r: rgb[0], g: rgb[1], b: rgb[2] } : l
    );
    updateNode(node.id, { layers: next });
    scheduleRecompose(next);
  };

  const runSplit = async () => {
    if (!window.CxAI?.splitMotifColors) return;
    if (!srcUrl) {
      updateNode(node.id, {
        splitError: 'Connect Matte → Color split and generate a motif raster first.',
        layers: [],
        mergedPreviewDataUrl: '',
        targetColorCount: targetColors,
      });
      return;
    }
    let url = srcUrl;
    if (/^https?:\/\//i.test(url) && window.CxAI.tryFetchImageAsDataUrl) {
      const fetched = await window.CxAI.tryFetchImageAsDataUrl(url);
      if (fetched) url = fetched;
    }
    setBusy(true);
    try {
      const result = await window.CxAI.splitMotifColors(url, { targetColors });
      updateNode(node.id, {
        layers: result.layers || [],
        mergedPreviewDataUrl: result.mergedPreviewDataUrl || '',
        targetColorCount: targetColors,
        splitError: '',
      });
    } catch (e) {
      updateNode(node.id, { splitError: e?.message || 'Color split failed' });
    } finally {
      setBusy(false);
    }
  };

  const isUserUpload = motifNode?.data?.motifSource === 'upload';
  uE(() => {
    if (node.hidden) return undefined;
    if (isUserUpload) {
      if (layers.length || outUrl) {
        updateNode(node.id, { layers: [], mergedPreviewDataUrl: '', splitError: '' });
      }
      return undefined;
    }
    if (!srcUrl) return undefined;
    const h = window.setTimeout(() => {
      runSplit();
    }, 520);
    return () => window.clearTimeout(h);
    // eslint-disable-next-line react-hooks/exhaustive-deps -- re-split when upstream raster or target count changes
  }, [srcUrl, targetColors, node.hidden, isUserUpload]);

  const toggleLayer = (id) => {
    const next = layers.map((l) =>
      l.id === id ? { ...l, visible: l.visible === false } : l
    );
    scheduleRecompose(next, true);
  };

  const previewBusyOrSplit = busy || previewBusy;

  return (
    <>
      <p className="cx-render-note">Quantizes the motif into flat color layers for embroidery, tufting, weave &amp; knit (skipped for print).</p>
      <label className="cx-field-l">Target colors <span className="cx-field-v">{targetColors}</span></label>
      <p className="cx-render-note">Set on Motif node · auto-split after matte strip</p>
      <div className="cx-motif-preview">
        {previewBusyOrSplit ? (
          <div className="cx-spinner" />
        ) : outUrl ? (
          <img src={outUrl} alt="" className="cx-motif-svg cx-motif-raster" />
        ) : (
          <span className="cx-motif-matte-placeholder">No layers yet</span>
        )}
      </div>
      {layers.length > 0 ? (
        <>
          <div className="cx-field-l">Layers · edit color</div>
          <div className="cx-colorsplit-list">
            {layers.map((layer) => {
              const hex = layer.hex || '#000000';
              const on = layer.visible !== false;
              return (
                <div key={layer.id} className={`cx-colorsplit-row ${on ? 'on' : 'off'}`}>
                  <label className="cx-colorsplit-swatch" title="Pick thread color">
                    <input
                      type="color"
                      className="cx-colorsplit-color-input"
                      value={hex}
                      onChange={(e) => patchLayerColor(layer.id, e.target.value)}
                    />
                    <span className="cx-colorsplit-swatch-fill" style={{ background: hex }} />
                  </label>
                  <div className="cx-colorsplit-meta">
                    <input
                      type="text"
                      className="cx-colorsplit-hex"
                      value={hex}
                      spellCheck={false}
                      onChange={(e) => patchLayerColor(layer.id, e.target.value)}
                    />
                    <span className="cx-colorsplit-px">
                      {layer.areaPx ? `${layer.areaPx.toLocaleString()} px` : 'layer'}
                    </span>
                  </div>
                  <button
                    type="button"
                    className={`cx-colorsplit-vis ${on ? 'on' : ''}`}
                    title={on ? 'Hide layer' : 'Show layer'}
                    onClick={() => toggleLayer(layer.id)}
                  >
                    {on ? 'On' : 'Off'}
                  </button>
                </div>
              );
            })}
          </div>
        </>
      ) : null}
      <div className="cx-row">
        <button type="button" className="cx-btn-ghost" disabled={busy} onClick={() => runSplit()}>
          {busy ? '…' : 'Run split'}
        </button>
        <button
          type="button"
          className="cx-btn-ghost"
          disabled={!outUrl && !layers.length}
          onClick={() => updateNode(node.id, { layers: [], mergedPreviewDataUrl: '', splitError: '' })}
        >
          Clear
        </button>
      </div>
      {err ? <p className="cx-render-note cx-render-note-err">{err}</p> : null}
    </>
  );
}
window.CxNodeBodiesMatteColor = { MatteBody, colorSplitSourceUrl, ColorSplitBody };
