/* Custex Node Editor — main React shell (studio canvas + routing). */
const { useState, useRef, useEffect, useMemo, useCallback } = React;
const C = window.CxEditorConstants;
const G = window.CxGeom;
const GM = window.CxGraphModel;
const {
  NODE_W, HEADER_H, CX_PALETTE_DRAG_MIME,
  PREVIEW_PANE_STORAGE_KEY, PREVIEW_PANE_MIN, clampPreviewPaneWidth,
  STUDIO_PROJECTS_STORAGE_KEY, NEW_PROJECT_DOT_COLORS,
  getInitialStudioProjects,
  normalizeTheme, readStoredCanvasPattern,
  appearanceSelectValue, applyAppearanceSelect,
  CX_TEXTILE_CHROME_THEMES,
} = C;
const {
  portPos, bezier, fitCanvasToView, findNearestInputPort, allowsMultipleInputs, nextNodeId,
  nodesOnProductOutputPath, connectionIndicesCrossedBySlash, polylineLengthWorld,
  FIT_SCALE_MIN, FIT_SCALE_MAX, SCISSOR_SLASH_MIN_LEN,
} = G;
const { portsCompatible, defaultGraph, reshapeForCraft, syncColorSplitPipeline } = GM;
const computePriceQuote = window.computePriceQuote;

function cxInitialAppRoute() {
  try {
    const q = new URLSearchParams(window.location.search || '');
    const r = (q.get('route') || '').toLowerCase();
    if (r === 'studio') return 'studio';
    const h = window.location.hash || '';
    if (/^#(studio|route=studio)\b/i.test(h)) return 'studio';
    const p = window.location.pathname || '';
    if (/\/(?:studio|workflow(?:\.html)?)\/?$/i.test(p)) return 'studio';
  } catch (_) { /* ignore */ }
  return 'home';
}

// ─────────── main NodeEditor component ───────────
function NodeEditor({ theme: initialTheme = 'atelier', density='regular', tone='warm', height=720, projectName='Untitled / Studio Holland' }) {
  const {
    CxTopbar, CxSidebar, CxNode, CxPreview, CxTechpackModal, CxHint, CxWireTools,
    CxMinimap, CxZoomCtrl, CxStudioAiChat, CxCanvasGrid, CxFabricTag, CxMoodboard, CxValidation,
  } = window.CxComponents || {};
  const [theme, setTheme] = useState(() => normalizeTheme(initialTheme));
  const [canvasPattern, setCanvasPattern] = useState(readStoredCanvasPattern);
  const [route, setRoute] = useState(cxInitialAppRoute);
  const routeRef = useRef(route);
  routeRef.current = route;
  const [graph, setGraph] = useState(defaultGraph);
  const [selected, setSelected] = useState('craft');
  const [pan, setPan] = useState({ x:0, y:0 });
  const [scale, setScale] = useState(1);
  const [showTechpack, setShowTechpack] = useState(false);
  const [previewCollapsed, setPreviewCollapsed] = useState(false);
  const [previewPaneWidthPx, setPreviewPaneWidthPx] = useState(() => {
    try {
      const v = parseInt(localStorage.getItem(PREVIEW_PANE_STORAGE_KEY), 10);
      return Number.isFinite(v) && v >= PREVIEW_PANE_MIN ? v : null;
    } catch {
      return null;
    }
  });
  const [validation, setValidation] = useState([]);
  const [studioProjects, setStudioProjects] = useState(getInitialStudioProjects);
  const [activeStudioProjectId, setActiveStudioProjectId] = useState(
    () => getInitialStudioProjects()[0]?.id ?? 'studio-wei-spring'
  );
  const studioProjectsRef = useRef(studioProjects);
  studioProjectsRef.current = studioProjects;

  useEffect(() => {
    setGraph((g) => syncColorSplitPipeline(g, g.nodes.craft?.data?.type || 'embroidery'));
  }, []);

  // ── derived
  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 yarns    = graph.nodes.yarn ? (graph.nodes.yarn.data.selected || []).map(id => window.CxSchema.YARNS.find(y => y.id === id)).filter(Boolean) : [];
  const motif    = graph.nodes.motif ? window.CxSchema.MOTIFS.find(m => m.id === graph.nodes.motif.data.motifId) : null;
  const visibleNodes = useMemo(
    () => Object.fromEntries(Object.entries(graph.nodes).filter(([, n]) => !n.hidden)),
    [graph.nodes]
  );
  const visibleGraph = useMemo(
    () => ({
      ...graph,
      nodes: visibleNodes,
      connections: graph.connections.filter((c) => visibleNodes[c.from] && visibleNodes[c.to]),
    }),
    [graph, visibleNodes]
  );
  const workflowNodeIds = useMemo(() => nodesOnProductOutputPath(visibleGraph), [visibleGraph]);

  /* Recompute every render so quote stays fresh if script load order bypasses useMemo. */
  const priceQuote = computePriceQuote(graph, product, craft);

  const activeStudioProject = useMemo(
    () => studioProjects.find((p) => p.id === activeStudioProjectId),
    [studioProjects, activeStudioProjectId]
  );
  const sidebarProjectTitle = activeStudioProject?.name ?? projectName;

  useEffect(() => {
    try {
      localStorage.setItem(STUDIO_PROJECTS_STORAGE_KEY, JSON.stringify(studioProjects));
    } catch {
      /* ignore */
    }
  }, [studioProjects]);

  useEffect(() => {
    if (!studioProjects.some((p) => p.id === activeStudioProjectId)) {
      const fallback = studioProjects[0]?.id;
      if (fallback) {
        setActiveStudioProjectId(fallback);
        if (route === 'studio') {
          window.history.replaceState(null, '', `#project=${encodeURIComponent(fallback)}`);
        }
      }
    }
  }, [studioProjects, activeStudioProjectId, route]);

  useEffect(() => {
    if (route !== 'studio') return undefined;
    const applyHash = () => {
      const m = window.location.hash.match(/^#project=(.+)$/);
      if (!m) return;
      const id = decodeURIComponent(m[1]);
      if (studioProjectsRef.current.some((p) => p.id === id)) setActiveStudioProjectId(id);
    };
    applyHash();
    window.addEventListener('hashchange', applyHash);
    return () => window.removeEventListener('hashchange', applyHash);
  }, [route]);

  useEffect(() => {
    if (route !== 'studio') return;
    const want = `#project=${encodeURIComponent(activeStudioProjectId)}`;
    if (window.location.hash !== want) window.history.replaceState(null, '', want);
  }, [route, activeStudioProjectId]);

  /** Block trackpad swipe-back / browser Back from leaving Studio; only topbar Home may exit. */
  useEffect(() => {
    if (route !== 'studio') {
      document.body.classList.remove('cx-route-studio');
      return undefined;
    }
    document.body.classList.add('cx-route-studio');
    const pushTrap = () => {
      try {
        window.history.pushState({ custexStudioTrap: 1 }, '', window.location.href);
      } catch {
        /* ignore */
      }
    };
    pushTrap();
    const onPopState = () => {
      if (routeRef.current === 'studio') pushTrap();
    };
    window.addEventListener('popstate', onPopState);
    return () => {
      window.removeEventListener('popstate', onPopState);
      document.body.classList.remove('cx-route-studio');
    };
  }, [route]);

  const setRouteApp = useCallback((next) => {
    if (next === 'home') {
      setRoute('home');
      try {
        const base = window.location.pathname + window.location.search;
        window.history.replaceState(null, '', base);
      } catch {
        /* ignore */
      }
      return;
    }
    setRoute(next);
  }, []);

  const selectStudioProject = useCallback((id) => {
    setActiveStudioProjectId(id);
    if (typeof window !== 'undefined') {
      window.history.replaceState(null, '', `#project=${encodeURIComponent(id)}`);
    }
  }, []);

  const addStudioProject = useCallback(() => {
    setStudioProjects((ps) => {
      const id = `proj-${Date.now()}`;
      const dot = NEW_PROJECT_DOT_COLORS[ps.length % NEW_PROJECT_DOT_COLORS.length];
      const next = [...ps, { id, name: `Untitled · ${ps.length + 1}`, dot }];
      queueMicrotask(() => {
        setActiveStudioProjectId(id);
        window.history.replaceState(null, '', `#project=${encodeURIComponent(id)}`);
      });
      return next;
    });
  }, []);

  const deleteStudioProject = useCallback((id) => {
    setStudioProjects((ps) => (ps.length <= 1 ? ps : ps.filter((p) => p.id !== id)));
  }, []);

  useEffect(() => {
    if (previewCollapsed || previewPaneWidthPx == null) return undefined;
    const fix = () => {
      setPreviewPaneWidthPx((prev) => {
        if (prev == null) return null;
        const next = clampPreviewPaneWidth(prev);
        return next === prev ? prev : next;
      });
    };
    window.addEventListener('resize', fix);
    requestAnimationFrame(fix);
    return () => window.removeEventListener('resize', fix);
  }, [previewCollapsed, previewPaneWidthPx]);

  useEffect(() => {
    if (route !== 'studio' || previewCollapsed) {
      document.documentElement.style.removeProperty('--cx-preview-w');
      return undefined;
    }
    const w = previewPaneWidthPx ?? 392;
    document.documentElement.style.setProperty('--cx-preview-w', `${Math.round(w)}px`);
    return () => document.documentElement.style.removeProperty('--cx-preview-w');
  }, [route, previewCollapsed, previewPaneWidthPx]);

  const onPreviewPaneResizeMouseDown = useCallback((e) => {
    if (e.button !== 0 || previewCollapsed) return;
    e.preventDefault();
    e.stopPropagation();
    const aside = e.currentTarget.closest('.cx-preview');
    const startX = e.clientX;
    const startW = aside?.getBoundingClientRect().width ?? 380;
    const move = (ev) => {
      setPreviewPaneWidthPx(clampPreviewPaneWidth(startW - (ev.clientX - startX)));
    };
    const up = () => {
      window.removeEventListener('mousemove', move);
      window.removeEventListener('mouseup', up);
      setPreviewPaneWidthPx((w) => {
        if (w == null) return null;
        const c = clampPreviewPaneWidth(w);
        try {
          localStorage.setItem(PREVIEW_PANE_STORAGE_KEY, String(Math.round(c)));
        } catch {
          /* ignore */
        }
        return c;
      });
    };
    window.addEventListener('mousemove', move);
    window.addEventListener('mouseup', up);
  }, [previewCollapsed]);

  const onPreviewPaneResizeReset = useCallback(() => {
    setPreviewPaneWidthPx(null);
    try {
      localStorage.removeItem(PREVIEW_PANE_STORAGE_KEY);
    } catch {
      /* ignore */
    }
  }, []);

  const studioMainGridStyle = useMemo(() => {
    if (previewCollapsed || previewPaneWidthPx == null) return undefined;
    return { gridTemplateColumns: `240px 1fr ${Math.round(previewPaneWidthPx)}px` };
  }, [previewCollapsed, previewPaneWidthPx]);

  // re-validate whenever graph changes
  useEffect(() => {
    setValidation(window.CxSchema.validateGraph(graph));
  }, [graph]);

  // Home landing (iframe) → parent shell: Open Studio / nav links use postMessage
  useEffect(() => {
    const allowed = new Set(['studio', 'library', 'manufacture', 'community']);
    const onMsg = (e) => {
      const d = e.data;
      if (!d || d.type !== 'custex-route' || typeof d.route !== 'string') return;
      if (!allowed.has(d.route)) return;
      setRouteApp(d.route);
    };
    window.addEventListener('message', onMsg);
    return () => window.removeEventListener('message', onMsg);
  }, [setRouteApp]);


  const {
    wrapRef, scissorStroke, connect, panning, wireTool,
    onCanvasMouseDown, onWheel, onCanvasDragOver, onCanvasDrop,
    onHeaderMouseDown, onPortMouseDown, onPortMouseUp,
    updateNode, setNodeCollapsed, updateCraftParam, changeCraftType, changeProduct,
    deleteConnection, setWireToolSafe, duplicateNode, deleteNode, resetView,
    updateMoodboard, onMoodboardMoveStart, onMoodboardResizeStart,
  } = useCxEditorCanvas({
    graph, setGraph, pan, setPan, scale, setScale, selected, setSelected, visibleGraph, route,
  });

  if (!CxTopbar || !CxNode) {
    return (
      <div className="cx-app" data-route={route}>
        <p style={{ padding: 24, fontFamily: 'system-ui,sans-serif' }}>
          Editor UI failed to load. Hard-refresh (Cmd+Shift+R). If it persists, check the browser console.
        </p>
      </div>
    );
  }

  return (
    <div className="cx-app" data-theme={theme} data-density={density} data-tone={tone} data-shell={route === 'home' ? 'home' : 'app'} data-route={route}>
      {route !== 'home' && (
        <CxTopbar
          onTechpack={() => setShowTechpack(true)}
          onReset={resetView}
          route={route}
          setRoute={setRouteApp}
          appearanceValue={appearanceSelectValue(theme, canvasPattern)}
          onAppearanceChange={(v) => applyAppearanceSelect(v, setTheme, setCanvasPattern)}
        />
      )}
      {route !== 'studio' && (
        <window.CxPageRouter route={route} setRoute={setRouteApp} graph={graph} setGraph={setGraph} validation={validation} theme={theme} />
      )}
      {route === 'studio' && <div className={`cx-main${previewCollapsed ? ' cx-main-prev-collapsed' : ''}`} style={studioMainGridStyle}>
        <CxSidebar
          projectName={sidebarProjectTitle}
          studioProjects={studioProjects}
          activeStudioProjectId={activeStudioProjectId}
          onSelectStudioProject={selectStudioProject}
          onAddStudioProject={addStudioProject}
          onDeleteStudioProject={deleteStudioProject}
          selected={selected}
          graph={visibleGraph}
          setSelected={setSelected}
          validation={validation}
          workflowNodeIds={workflowNodeIds}
        />
        <div className="cx-canvas-col">
        <div
          ref={wrapRef}
          className={['cx-canvas-wrap', connect && 'cx-connecting', panning && 'cx-panning', wireTool === 'needle' && 'cx-wiretool-needle', wireTool === 'scissors' && 'cx-wiretool-scissors', scissorStroke && 'cx-scissor-slicing'].filter(Boolean).join(' ')}
          onMouseDown={onCanvasMouseDown}
          onWheel={onWheel}
          onDragOverCapture={onCanvasDragOver}
          onDrop={onCanvasDrop}
        >
          <CxCanvasGrid theme={theme} pan={pan} scale={scale} canvasPattern={canvasPattern} />
          {CX_TEXTILE_CHROME_THEMES.has(theme) && <CxFabricTag />}
          <svg className="cx-wires" style={{ transform:`translate(${pan.x}px,${pan.y}px) scale(${scale})`, transformOrigin:'0 0' }}>
            {graph.connections.map((c, i) => {
              const fromNode = visibleGraph.nodes[c.from], toNode = visibleGraph.nodes[c.to];
              if (!fromNode || !toNode) return null;
              const a = portPos(fromNode, c.fromPort, 'out');
              const b = portPos(toNode, c.toPort, 'in');
              const fromPortMeta = fromNode.outputs.find(p => p.name === c.fromPort);
              const pType = fromPortMeta?.type || '';
              const wireTint = pType ? `cx-wire-t-${pType}` : '';
              const isFab = c.fromPort === 'fab' || pType === 'fab';
              const isYarn = pType === 'yarn';
              const cutMode = wireTool === 'scissors';
              return (
                <g key={i}>
                  <path d={bezier(a,b)} className={`cx-wire ${wireTint} ${isFab?'fab':''} ${isYarn?'yarn':''} ${cutMode ? 'cx-wire-cuttable' : ''}`}
                        onContextMenu={(e)=>{e.preventDefault();deleteConnection(i);}} />
                </g>
              );
            })}
            {scissorStroke && scissorStroke.points.length >= 2 && (
              <polyline
                className="cx-scissor-slash"
                fill="none"
                vectorEffect="nonScalingStroke"
                points={scissorStroke.points.map(([x, y]) => `${x},${y}`).join(' ')}
              />
            )}
            {connect && (() => {
              const fromNode = visibleGraph.nodes[connect.fromId];
              if (!fromNode) return null;
              const a = portPos(fromNode, connect.fromPort, 'out');
              const cls = connect.hover ? 'cx-wire-temp valid' : (connect.hoverInvalid ? 'cx-wire-temp invalid' : 'cx-wire-temp');
              return <path d={bezier(a, {x: connect.x, y: connect.y})} className={cls} />;
            })()}
          </svg>
          <div className="cx-canvas" style={{ transform:`translate(${pan.x}px,${pan.y}px) scale(${scale})`, transformOrigin:'0 0' }}>
            {CxMoodboard ? (
              <CxMoodboard
                moodboard={graph.moodboard ?? C.defaultMoodboardFromGraph(graph)}
                scale={scale}
                updateMoodboard={updateMoodboard}
                onMoodboardMoveStart={onMoodboardMoveStart}
                onMoodboardResizeStart={onMoodboardResizeStart}
              />
            ) : null}
            {Object.values(visibleGraph.nodes).map(n => (
              <CxNode
                key={n.id}
                node={n}
                selected={selected === n.id}
                graph={graph}
                validation={validation}
                onHeaderMouseDown={onHeaderMouseDown}
                onPortMouseDown={onPortMouseDown}
                onPortMouseUp={onPortMouseUp}
                wireTool={wireTool}
                updateNode={updateNode}
                updateCraftParam={updateCraftParam}
                changeCraftType={changeCraftType}
                changeProduct={changeProduct}
                onDuplicate={() => duplicateNode(n.id)}
                onDelete={() => deleteNode(n.id)}
                onSelect={() => setSelected(n.id)}
                onSetCollapsed={(c) => setNodeCollapsed(n.id, c)}
                connect={connect}
                theme={theme}
              />
            ))}
          </div>
          <CxValidation validation={validation} />
          <CxHint theme={theme} />
        </div>
        <div className="cx-canvas-minimap-dock">
          <CxMinimap graph={visibleGraph} pan={pan} scale={scale} setPan={setPan} wrapRef={wrapRef} />
        </div>
        <div className="cx-canvas-br-dock">
          <CxStudioAiChat />
          <CxWireTools tool={wireTool} onChange={setWireToolSafe} />
          <CxZoomCtrl scale={scale} setScale={setScale} resetView={resetView} />
        </div>
        </div>
        <CxPreview graph={graph} product={product} craft={craft} material={material} yarns={yarns} motif={motif} totalPrice={priceQuote.totalPrice} priceQuote={priceQuote} validation={validation} theme={theme} previewCollapsed={previewCollapsed} onPreviewCollapsedChange={setPreviewCollapsed} onPreviewPaneResizeMouseDown={onPreviewPaneResizeMouseDown} onPreviewPaneResizeReset={onPreviewPaneResizeReset} />
      </div>}
      {showTechpack && <CxTechpackModal onClose={() => setShowTechpack(false)} graph={graph} product={product} craft={craft} material={material} yarns={yarns} motif={motif} totalPrice={priceQuote.totalPrice} priceQuote={priceQuote} validation={validation} theme={theme} />}
    </div>
  );
}
window.NodeEditor = NodeEditor;
