/* Custex editor — motif, layout, output node bodies. */
const { useState: uS, useRef: uR, useEffect: uE, useMemo: uM } = React;
const { NODE_W, PORT_FIRST, PORT_STRIDE } = window.CxGeom;

const { motifRasterHrefForLayoutFeed, motifsConnectedToLayout } = window.CxMotifPipeline;
const { svgInnerFromSchema: cxMotifSvgInnerFromSchema, catalogAbsHref: cxMotifCatalogImageAbsHref, rasterHrefToSvgImage: cxMotifRasterHrefToSvgImage } = window.CxMotifHelpers;

function MotifBody({ node, updateNode, graph }) {
  const fileRef = uR(null);
  const motifUploadRef = uR(null);
  const [gen, setGen] = uS(false);
  const [uploadBusy, setUploadBusy] = uS(false);
  const [err, setErr] = uS('');
  const d = node.data || {};
  const motifRefUrl = d.refImageDataUrl || null;
  const targetColorCount = Math.max(1, Math.min(10, Number(d.targetColorCount) || 5));
  const craftType = graph?.nodes?.craft?.data?.type || '';
  const showColorCount = craftType !== 'printing';
  const selected = new Set(node.data.motifIds && node.data.motifIds.length ? node.data.motifIds : [node.data.motifId].filter(Boolean));
  const hasAI = !!window.CxAI?.generateMotif;
  const busy = gen || uploadBusy;
  const onFile = (e) => {
    const f = e.target.files && e.target.files[0];
    if (!f || !/^image\//.test(f.type)) return;
    const reader = new FileReader();
    reader.onload = () => {
      updateNode(node.id, { refImageDataUrl: reader.result, refImageName: f.name });
    };
    reader.readAsDataURL(f);
    e.target.value = '';
  };
  const onMotifPngUpload = (e) => {
    const f = e.target.files && e.target.files[0];
    e.target.value = '';
    if (!f) return;
    const isPng = f.type === 'image/png' || /\.png$/i.test(f.name || '');
    if (!isPng) {
      setErr('Please choose a PNG file.');
      return;
    }
    setErr('');
    setUploadBusy(true);
    const reader = new FileReader();
    reader.onload = async () => {
      try {
        const dataUrl = typeof reader.result === 'string' ? reader.result : '';
        if (!dataUrl) throw new Error('Could not read file');
        const baseName = (f.name || 'Uploaded motif').replace(/\.png$/i, '') || 'Uploaded motif';
        const uploadId = 'user_upload';
        if (graph.nodes?.colorSplit?.id) {
          updateNode(graph.nodes.colorSplit.id, { layers: [], mergedPreviewDataUrl: '', splitError: '' });
        }
        updateNode(node.id, {
          motifId: uploadId,
          motifIds: [uploadId],
          generated: true,
          generatedSvg: '',
          generatedImageDataUrl: dataUrl,
          generatedImageUrl: '',
          generatedName: baseName,
          motifSource: 'upload',
          generationError: '',
        });
      } catch (ex) {
        const msg = ex?.message || 'PNG upload failed';
        setErr(msg);
        updateNode(node.id, { generationError: msg });
      } finally {
        setUploadBusy(false);
      }
    };
    reader.onerror = () => {
      setUploadBusy(false);
      setErr('Could not read PNG file');
    };
    reader.readAsDataURL(f);
  };
  const clearMotifOutput = () => {
    setErr('');
    updateNode(node.id, {
      generatedSvg: '',
      generatedImageDataUrl: '',
      generatedImageUrl: '',
      generatedName: '',
      motifSource: '',
      generationError: '',
      generated: false,
    });
  };
  const toggleMotif = (id) => {
    const next = new Set(selected);
    if (next.has(id) && next.size > 1) next.delete(id);
    else next.add(id);
    const motifIds = [...next];
    updateNode(node.id, { motifIds, motifId: motifIds[0] });
  };
  const fallbackGenerate = () => {
    setGen(true);
    setTimeout(() => {
      const list = window.CxSchema.MOTIFS;
      if (list.length) {
        const next = list[(list.findIndex(m => m.id === node.data.motifId) + 1) % list.length];
        const motifIds = node.data.motifIds && node.data.motifIds.length ? [...node.data.motifIds] : [node.data.motifId].filter(Boolean);
        const rest = motifIds.filter(id => id !== next.id);
        updateNode(node.id, { motifId: next.id, motifIds: [next.id, ...rest].slice(0, 4), generated: true });
      }
      setGen(false);
    }, 700);
  };
  const generate = async () => {
    if (!hasAI) {
      fallbackGenerate();
      return;
    }
    if (gen) return;
    setErr('');
    setGen(true);
    try {
      const product = window.CxSchema.PRODUCTS.find((p) => p.id === graph?.nodes?.product?.data?.selected);
      const craft = window.CxSchema.CRAFTS.find((c) => c.id === graph?.nodes?.craft?.data?.type);
      const material = graph?.nodes?.fabric
        ? window.CxSchema.MATERIALS.find((m) => m.id === graph.nodes.fabric.data.materialId)
        : null;
      const targetColorCount = Math.max(1, Math.min(10, Number(node.data.targetColorCount) || 5));
      const result = await window.CxAI.generateMotif({
        prompt: node.data.prompt || '',
        referenceImageDataUrl: motifRefUrl,
        currentMotifId: node.data.motifId || '',
        selectedMotifIds: node.data.motifIds || [node.data.motifId].filter(Boolean),
        context: {
          productId: product?.id || '',
          productName: product?.name || '',
          craftId: craft?.id || '',
          craftName: craft?.label || '',
          materialId: material?.id || '',
          materialName: material?.name || '',
          maxColors: craft?.id !== 'printing' ? targetColorCount : 0,
          targetColorCount: craft?.id !== 'printing' ? targetColorCount : 0,
        },
      });
      const list = window.CxSchema.MOTIFS;
      const hasMotifId = !!(result.motifId && list.find((m) => m.id === result.motifId));
      if (hasMotifId) {
        const motifIds = node.data.motifIds && node.data.motifIds.length ? [...node.data.motifIds] : [node.data.motifId].filter(Boolean);
        const rest = motifIds.filter((id) => id !== result.motifId);
        updateNode(node.id, {
          motifId: result.motifId,
          motifIds: [result.motifId, ...rest].slice(0, 4),
          generated: true,
          generatedSvg: '',
          generatedImageDataUrl: '',
          generatedImageUrl: '',
          generatedName: '',
          motifSource: 'catalog',
          generationError: '',
        });
      } else if (result.imageDataUrl || result.imageUrl) {
        const existingIds =
          node.data.motifIds && node.data.motifIds.length
            ? [...node.data.motifIds]
            : [node.data.motifId].filter(Boolean);
        const syntheticId = 'ai_generated';
        const motifIds = existingIds.length ? existingIds : [syntheticId];
        const motifId = node.data.motifId || motifIds[0] || syntheticId;
        updateNode(node.id, {
          motifId,
          motifIds,
          generated: true,
          generatedSvg: '',
          generatedImageDataUrl: result.imageDataUrl || '',
          generatedImageUrl: result.imageUrl || '',
          generatedName: result.name || 'AI print motif',
          motifSource: 'ai',
          generationError: '',
        });
      } else if (result.svg) {
        updateNode(node.id, {
          generated: true,
          generatedSvg: result.svg,
          generatedImageDataUrl: '',
          generatedImageUrl: '',
          generatedName: result.name || 'AI motif',
          motifSource: 'ai',
          generationError: '',
        });
      } else {
        throw new Error('Motif API returned no image or svg');
      }
    } catch (e) {
      const msg = e?.message || 'Motif generate failed';
      setErr(msg);
      updateNode(node.id, { generationError: msg });
    } finally {
      setGen(false);
    }
  };
  const motif = window.CxSchema.MOTIFS.find(m => m.id === node.data.motifId);
  const motifSvg = node.data.generatedSvg || cxMotifSvgInnerFromSchema(motif);
  const motifImgSrc = d.generatedImageDataUrl || d.generatedImageUrl || '';
  const hasAiRaster = !!(d.generatedImageDataUrl || d.generatedImageUrl);
  return (
    <>
      <div className="cx-field-l">Reference for AI <span className="cx-field-optional">optional</span></div>
      <div className="cx-render-upload">
        <button type="button" className="cx-render-filebtn" onClick={() => fileRef.current && fileRef.current.click()}>
          Upload image
        </button>
        <input ref={fileRef} type="file" accept="image/*" className="cx-hidden-file" onChange={onFile} />
        {motifRefUrl && (
          <div className="cx-render-preview">
            <img src={motifRefUrl} alt="" className="cx-render-thumb" />
            <button
              type="button"
              className="cx-render-clear"
              onClick={() => updateNode(node.id, { refImageDataUrl: null, refImageName: '' })}
            >
              Remove
            </button>
          </div>
        )}
      </div>
      <div className="cx-field-l">Text prompt <span className="cx-field-optional">optional</span></div>
      <textarea
        className="cx-textarea"
        rows={2}
        value={node.data.prompt}
        onChange={e => updateNode(node.id, { prompt: e.target.value })}
      />
      {showColorCount ? (
        <>
          <label className="cx-field-l cx-mt">Color count <span className="cx-field-v">{targetColorCount}</span></label>
          <input
            type="range"
            min="1"
            max="10"
            step="1"
            value={targetColorCount}
            onChange={(e) => updateNode(node.id, { targetColorCount: Number(e.target.value) })}
            className="cx-range"
          />
        </>
      ) : null}
      <div className="cx-field-l">Motif output</div>
      <div className="cx-motif-preview">
        {busy
          ? <div className="cx-spinner" />
          : motifImgSrc
            ? <img src={motifImgSrc} alt="" className="cx-motif-svg cx-motif-raster" />
            : <svg viewBox="0 0 80 80" className="cx-motif-svg" dangerouslySetInnerHTML={{__html: motifSvg}} />}
      </div>
      <input
        ref={motifUploadRef}
        type="file"
        accept="image/png,.png"
        className="cx-hidden-file"
        onChange={onMotifPngUpload}
      />
      <div className="cx-row">
        <button type="button" className="cx-btn-ghost" disabled={busy} onClick={generate}>
          {gen ? '…' : 'Regenerate'}
        </button>
        <button
          type="button"
          className="cx-btn-ghost"
          disabled={busy}
          onClick={() => motifUploadRef.current && motifUploadRef.current.click()}
        >
          {uploadBusy ? '…' : 'Upload PNG'}
        </button>
        <button
          type="button"
          className="cx-btn-ghost"
          disabled={busy || (!node.data.generatedSvg && !hasAiRaster)}
          onClick={clearMotifOutput}
        >
          Clear
        </button>
      </div>
      {err ? <p className="cx-render-note cx-render-note-err">{err}</p> : null}
    </>
  );
}

function LayoutBody({ node, updateNode, graph }) {
  const linkedMotifs = motifsConnectedToLayout(graph);
  const motifNode = graph?.nodes?.motif;
  const selected = new Set(
    motifNode?.data?.motifIds && motifNode.data.motifIds.length
      ? motifNode.data.motifIds
      : [motifNode?.data?.motifId].filter(Boolean)
  );
  const toggleMotif = (id) => {
    if (!motifNode) return;
    const next = new Set(selected);
    if (next.has(id) && next.size > 1) next.delete(id);
    else next.add(id);
    const motifIds = [...next];
    updateNode(motifNode.id, { motifIds, motifId: motifIds[0] });
  };
  const controls = node.data.motifControls || {};
  const getCtl = (id) => ({ scale: 100, rotate: 0, ox: 0, oy: 0, ...(controls[id] || {}) });
  const setCtl = (id, patch) => {
    const next = { ...controls, [id]: { ...getCtl(id), ...patch } };
    updateNode(node.id, { motifControls: next });
  };
  return (
    <>
      <label className="cx-field-l">Repeat</label>
      <div className="cx-seg cx-seg-layout">
        {(window.CxLayoutTiles?.OPTIONS || [
          { id: 'grid', label: 'Grid' },
          { id: 'halfdrop', label: 'Half drop' },
          { id: 'brick', label: 'Brick' },
          { id: 'mirror', label: 'Mirror' },
        ]).map(({ id, label }) => {
          const active = (window.CxLayoutTiles?.normalizeTile || ((t) => t))(node.data.tile || 'grid') === id;
          return (
            <button
              key={id}
              type="button"
              className={`cx-seg-b ${active ? 'sel' : ''}`}
              title={label}
              onClick={() => updateNode(node.id, { tile: id })}
            >
              {label}
            </button>
          );
        })}
      </div>
      <label className="cx-field-l cx-mt">Scale <span className="cx-field-v">{node.data.scale}%</span></label>
      <input type="range" min="20" max="200" value={node.data.scale}
             onChange={e => updateNode(node.id, { scale: Number(e.target.value) })}
             className="cx-range" />
      <label className="cx-field-l">Rotate <span className="cx-field-v">{node.data.rotate}°</span></label>
      <input type="range" min="0" max="180" value={node.data.rotate}
             onChange={e => updateNode(node.id, { rotate: Number(e.target.value) })}
             className="cx-range" />
      {window.CxSchema.MOTIFS.length > 0 ? (
        <>
          <div className="cx-craft-note">tile motifs (2+ supported in one repeat tile)</div>
          <div className="cx-motif-picks">
            {window.CxSchema.MOTIFS.map((m) => (
              <button
                key={m.id}
                className={`cx-chip ${selected.has(m.id) ? 'sel' : ''}`}
                onClick={() => toggleMotif(m.id)}
              >
                {m.name}
              </button>
            ))}
          </div>
        </>
      ) : null}
      {linkedMotifs.length > 0 && (
        <>
          <label className="cx-field-l cx-mt">Linked motifs <span className="cx-field-v">{linkedMotifs.length}</span></label>
          <div className="cx-layout-motif-controls">
            {linkedMotifs.map((m) => {
              const c = getCtl(m.id);
              return (
                <div key={m.id} className="cx-layout-motif-card">
                  <div className="cx-layout-motif-h">{m.id}</div>
                  <label className="cx-field-l">Scale <span className="cx-field-v">{c.scale}%</span></label>
                  <input type="range" min="40" max="260" value={c.scale}
                    onChange={(e) => setCtl(m.id, { scale: Number(e.target.value) })}
                    className="cx-range" />
                  <label className="cx-field-l">Rotate <span className="cx-field-v">{c.rotate}°</span></label>
                  <input type="range" min="-180" max="180" value={c.rotate}
                    onChange={(e) => setCtl(m.id, { rotate: Number(e.target.value) })}
                    className="cx-range" />
                  <label className="cx-field-l">Offset X <span className="cx-field-v">{c.ox}%</span></label>
                  <input type="range" min="-70" max="70" value={c.ox}
                    onChange={(e) => setCtl(m.id, { ox: Number(e.target.value) })}
                    className="cx-range" />
                  <label className="cx-field-l">Offset Y <span className="cx-field-v">{c.oy}%</span></label>
                  <input type="range" min="-70" max="70" value={c.oy}
                    onChange={(e) => setCtl(m.id, { oy: Number(e.target.value) })}
                    className="cx-range" />
                </div>
              );
            })}
          </div>
        </>
      )}
    </>
  );
}

function workshopsForCraft(craftId) {
  return ({ embroidery: 4, printing: 6, weaving: 3, knitting: 2, tufting: 3 })[craftId] || 0;
}

function OutputBody({ node, updateNode, graph }) {
  const matchedShops = workshopsForCraft(graph.nodes.craft.data.type);
  return (
    <>
      <label className="cx-field-l">Quantity</label>
      <div className="cx-qty">
        <button onClick={() => updateNode(node.id, { qty: Math.max(1, (node.data.qty||1) - 1) })}>−</button>
        <input value={node.data.qty} type="number" min={1}
               onChange={e => updateNode(node.id, { qty: Number(e.target.value) || 1 })}/>
        <button onClick={() => updateNode(node.id, { qty: (node.data.qty||1) + 1 })}>+</button>
      </div>
      <div className="cx-mini-stat warm">
        <div className="cx-mini-stat-v">{matchedShops} workshops</div>
        <div className="cx-mini-stat-k">accept · est. 7 days</div>
      </div>
      <button className="cx-btn-primary cx-mt">Open Tech-Pack →</button>
    </>
  );
}
window.CxNodeBodiesMotifLayout = { MotifBody, LayoutBody, OutputBody };
