/* === JitterFree — UI primitives === */
const { useState, useEffect, useRef, useMemo, useCallback, useReducer, useLayoutEffect } = React;

const Icon = ({ name, size = 14, stroke = 1.6 }) => {
  const c = { width: size, height: size, viewBox: "0 0 24 24", fill: "none",
    stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (name) {
    case "play":      return <svg {...c}><polygon points="6 4 20 12 6 20 6 4"/></svg>;
    case "pause":     return <svg {...c}><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>;
    case "loop":      return <svg {...c}><path d="M17 2l4 4-4 4"/><path d="M3 11v-1a4 4 0 0 1 4-4h14"/><path d="M7 22l-4-4 4-4"/><path d="M21 13v1a4 4 0 0 1-4 4H3"/></svg>;
    case "skip-back": return <svg {...c}><polygon points="19 20 9 12 19 4 19 20"/><line x1="5" y1="19" x2="5" y2="5"/></svg>;
    case "chevron-down": return <svg {...c}><polyline points="6 9 12 15 18 9"/></svg>;
    case "chevron-right":return <svg {...c}><polyline points="9 18 15 12 9 6"/></svg>;
    case "upload":    return <svg {...c}><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>;
    case "image":     return <svg {...c}><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="9" cy="9" r="2"/><path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/></svg>;
    case "sparkles":  return <svg {...c}><path d="M12 3l1.6 4.6L18 9l-4.4 1.4L12 15l-1.6-4.6L6 9l4.4-1.4z"/><path d="M19 15l.8 2.2L22 18l-2.2.8L19 21l-.8-2.2L16 18l2.2-.8z"/></svg>;
    case "plus":      return <svg {...c}><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>;
    case "minus":     return <svg {...c}><line x1="5" y1="12" x2="19" y2="12"/></svg>;
    case "trash":     return <svg {...c}><polyline points="3 6 5 6 21 6"/><path d="M19 6l-2 14a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"/></svg>;
    case "eye":       return <svg {...c}><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>;
    case "eye-off":   return <svg {...c}><path d="M17.94 17.94A10.07 10.07 0 0 1 12 19c-7 0-10-7-10-7a18.45 18.45 0 0 1 5.06-5.94"/><path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 10 7 10 7a18.5 18.5 0 0 1-2.16 3.19"/><path d="m1 1 22 22"/><path d="M9 9a3 3 0 0 0 5 5"/></svg>;
    case "grip":      return <svg {...c}><circle cx="9" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="15" cy="18" r="1"/></svg>;
    case "x":         return <svg {...c}><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>;
    case "download":  return <svg {...c}><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>;
    case "keyboard":  return <svg {...c}><rect x="2" y="6" width="20" height="12" rx="2"/><path d="M6 10h0M10 10h0M14 10h0M18 10h0M7 14h10"/></svg>;
    default: return null;
  }
};

/* ===== Stage / canvas =====
   Renders the composition into a <canvas>. When a layer is selected, draws
   transform handles as DOM overlay so they remain crisp regardless of zoom.
*/
const Stage = ({ comp, currentTime, onUpload, selectedId, onSelect, onUpdateLayer }) => {
  const canvasRef = useRef(null);
  const frameRef = useRef(null);
  const [dragOver, setDragOver] = useState(false);
  const fileInputRef = useRef(null);

  // Render canvas whenever inputs change
  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const dpr = window.devicePixelRatio || 1;
    const rect = canvas.getBoundingClientRect();
    const W = rect.width, H = rect.height;
    if (canvas.width !== Math.round(W * dpr) || canvas.height !== Math.round(H * dpr)) {
      canvas.width = Math.round(W * dpr);
      canvas.height = Math.round(H * dpr);
    }
    const ctx = canvas.getContext("2d");
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    window.renderComposition(ctx, comp.layers, currentTime, { width: W, height: H, bg: "#ffffff" });
  });

  const handleFile = (file) => {
    if (!file || !file.type.startsWith("image/")) return;
    const reader = new FileReader();
    reader.onload = (e) => {
      const src = e.target.result;
      const img = new Image();
      img.onload = () => onUpload({ src, image: img, name: file.name });
      img.src = src;
    };
    reader.readAsDataURL(file);
  };

  const onDrop = (e) => {
    e.preventDefault(); setDragOver(false);
    const files = Array.from(e.dataTransfer.files || []);
    files.forEach(handleFile);
  };

  // Layer hit-testing on click in stage canvas
  const handleStageClick = (e) => {
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    const sx = (e.clientX - rect.left) * (window.CANVAS_W / rect.width);
    const sy = (e.clientY - rect.top)  * (window.CANVAS_H / rect.height);
    // Topmost first
    for (let i = comp.layers.length - 1; i >= 0; i--) {
      const L = comp.layers[i];
      if (sx >= L.x && sx <= L.x + L.w && sy >= L.y && sy <= L.y + L.h) {
        onSelect(L.id); return;
      }
    }
    onSelect(null);
  };

  // Drag/resize for selected layer
  const dragRef = useRef(null);
  const onPointerDownLayer = (e, mode) => {
    e.preventDefault(); e.stopPropagation();
    const canvas = canvasRef.current;
    const rect = canvas.getBoundingClientRect();
    const layer = comp.layers.find((l) => l.id === selectedId);
    if (!layer) return;
    dragRef.current = {
      mode, startX: e.clientX, startY: e.clientY,
      sx0: layer.x, sy0: layer.y, sw0: layer.w, sh0: layer.h,
      scaleX: window.CANVAS_W / rect.width, scaleY: window.CANVAS_H / rect.height,
      id: layer.id,
    };
    const move = (ev) => {
      const d = dragRef.current; if (!d) return;
      const dx = (ev.clientX - d.startX) * d.scaleX;
      const dy = (ev.clientY - d.startY) * d.scaleY;
      let { sx0, sy0, sw0, sh0 } = d;
      let x = sx0, y = sy0, w = sw0, h = sh0;
      const aspect = sw0 / sh0;
      if (d.mode === "move") { x = sx0 + dx; y = sy0 + dy; }
      else if (d.mode === "tl") {
        // resize keeping bottom-right anchored, preserve aspect with shift...
        w = Math.max(40, sw0 - dx); h = w / aspect;
        x = sx0 + (sw0 - w); y = sy0 + (sh0 - h);
      } else if (d.mode === "tr") {
        w = Math.max(40, sw0 + dx); h = w / aspect;
        y = sy0 + (sh0 - h);
      } else if (d.mode === "bl") {
        w = Math.max(40, sw0 - dx); h = w / aspect;
        x = sx0 + (sw0 - w);
      } else if (d.mode === "br") {
        w = Math.max(40, sw0 + dx); h = w / aspect;
      }
      onUpdateLayer(d.id, { x, y, w, h });
    };
    const up = () => {
      window.removeEventListener("pointermove", move);
      window.removeEventListener("pointerup", up);
      dragRef.current = null;
    };
    window.addEventListener("pointermove", move);
    window.addEventListener("pointerup", up);
  };

  const selectedLayer = comp.layers.find((l) => l.id === selectedId);
  // Handle box position in screen coords
  const renderHandles = () => {
    if (!selectedLayer || !frameRef.current) return null;
    const fr = frameRef.current.getBoundingClientRect();
    const sx = fr.width  / window.CANVAS_W;
    const sy = fr.height / window.CANVAS_H;
    const x = selectedLayer.x * sx;
    const y = selectedLayer.y * sy;
    const w = selectedLayer.w * sx;
    const h = selectedLayer.h * sy;
    return (
      <div className="handle-box" style={{ left: x, top: y, width: w, height: h }}
           onPointerDown={(e) => onPointerDownLayer(e, "move")}>
        <div className="handle tl" onPointerDown={(e) => onPointerDownLayer(e, "tl")}/>
        <div className="handle tr" onPointerDown={(e) => onPointerDownLayer(e, "tr")}/>
        <div className="handle bl" onPointerDown={(e) => onPointerDownLayer(e, "bl")}/>
        <div className="handle br" onPointerDown={(e) => onPointerDownLayer(e, "br")}/>
      </div>
    );
  };

  return (
    <div className="stage"
         onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
         onDragLeave={() => setDragOver(false)}
         onDrop={onDrop}>
      <div className="stage-frame" ref={frameRef} onClick={handleStageClick}>
        <canvas className="stage-canvas" ref={canvasRef}/>
        <div className="stage-overlay">{renderHandles()}</div>
        {comp.layers.length === 0 && (
          <div className="stage-empty">
            <div className={`stage-drop${dragOver ? " dragover" : ""}`}
                 onClick={(e) => { e.stopPropagation(); fileInputRef.current?.click(); }}>
              <Icon name="image" size={26} stroke={1.4}/>
              <div>
                <div className="stage-drop-title">Drop images to animate</div>
                <div className="stage-drop-sub">PNG, JPG, SVG · multiple files OK</div>
              </div>
            </div>
          </div>
        )}
      </div>
      <input ref={fileInputRef} className="file-input" type="file" accept="image/*" multiple
             onChange={(e) => { Array.from(e.target.files || []).forEach(handleFile); e.target.value = ""; }}/>
      <div className="stage-watermark">
        <span>1280 × 800</span><span className="dot"/><span>Preview · 60 fps</span>
      </div>
    </div>
  );
};

/* ===== Layers panel ===== */
const LayersPanel = ({ comp, selectedId, onSelect, onAdd, onDelete, onToggleVisibility, onReorder }) => {
  const [dragId, setDragId] = useState(null);
  const [dropTarget, setDropTarget] = useState(null); // {id, where}
  const fileInputRef = useRef(null);

  return (
    <aside className="layers">
      <div className="layers-header">
        <div className="section-title">Layers</div>
        <button className="icon-btn" onClick={() => fileInputRef.current?.click()} title="Add image">
          <Icon name="plus" size={14}/>
        </button>
      </div>
      <div className="layers-list">
        {comp.layers.length === 0 && (
          <div className="layers-empty">No layers yet.<br/>Add an image to start.</div>
        )}
        {/* Reverse so top-most rendered layer appears at top of list. */}
        {[...comp.layers].slice().reverse().map((layer) => (
          <div
            key={layer.id}
            className={
              "layer-row" +
              (selectedId === layer.id ? " selected" : "") +
              (layer.visible ? " visible" : "") +
              (dragId === layer.id ? " dragging" : "") +
              (dropTarget?.id === layer.id && dropTarget?.where === "above" ? " drop-above" : "") +
              (dropTarget?.id === layer.id && dropTarget?.where === "below" ? " drop-below" : "")
            }
            draggable
            onDragStart={(e) => { setDragId(layer.id); e.dataTransfer.effectAllowed = "move"; }}
            onDragOver={(e) => {
              e.preventDefault();
              const rect = e.currentTarget.getBoundingClientRect();
              const where = e.clientY < rect.top + rect.height / 2 ? "above" : "below";
              setDropTarget({ id: layer.id, where });
            }}
            onDragLeave={() => setDropTarget(null)}
            onDrop={(e) => {
              e.preventDefault();
              if (dragId && dropTarget) onReorder(dragId, dropTarget.id, dropTarget.where);
              setDragId(null); setDropTarget(null);
            }}
            onDragEnd={() => { setDragId(null); setDropTarget(null); }}
            onClick={() => onSelect(layer.id)}
          >
            <span className="layer-grip"><Icon name="grip" size={12}/></span>
            <div className="layer-thumb" style={{ backgroundImage: `url(${layer.src})` }}/>
            <div className="layer-name">{layer.name}</div>
            <div className="layer-actions">
              <button className="icon-btn" onClick={(e) => { e.stopPropagation(); onToggleVisibility(layer.id); }}
                      title={layer.visible ? "Hide" : "Show"}>
                <Icon name={layer.visible ? "eye" : "eye-off"} size={12}/>
              </button>
              <button className="icon-btn danger" onClick={(e) => { e.stopPropagation(); onDelete(layer.id); }} title="Delete">
                <Icon name="trash" size={12}/>
              </button>
            </div>
          </div>
        ))}
      </div>
      <button className="layers-add" onClick={() => fileInputRef.current?.click()}>
        <Icon name="plus" size={13}/> Add image
      </button>
      <input ref={fileInputRef} className="file-input" type="file" accept="image/*" multiple
             onChange={(e) => {
               Array.from(e.target.files || []).forEach((f) => onAdd(f));
               e.target.value = "";
             }}/>
    </aside>
  );
};

/* ===== Sidebar (per-selected-layer animation config) ===== */
const PresetGrid = ({ presets, selectedId, onSelect, onHover, onHoverEnd }) => (
  <div className="preset-grid">
    {presets.map((preset) => {
      const spec = preset.render(0.55);
      const css = window.specToCss(spec);
      return (
        <button key={preset.id}
                className={`preset-card${selectedId === preset.id ? " selected" : ""}`}
                onClick={() => onSelect(preset.id)}
                onMouseEnter={() => onHover(preset.id)}
                onMouseLeave={onHoverEnd}>
          <div className="preset-thumb">
            <div className="preset-thumb-shape" style={css}/>
          </div>
          <div className="preset-name">{preset.name}</div>
        </button>
      );
    })}
  </div>
);

const SidebarSection = ({ title, summary, defaultOpen = true, children }) => {
  const [open, setOpen] = useState(defaultOpen);
  return (
    <div className="side-section">
      <div className="side-section-header" onClick={() => setOpen(!open)}>
        <div className="side-section-title">
          <span className="side-section-title-dot"/><span>{title}</span>
        </div>
        <div className="side-section-summary">
          <span>{summary}</span>
          <Icon name={open ? "chevron-down" : "chevron-right"} size={11}/>
        </div>
      </div>
      {open && <div className="side-section-body">{children}</div>}
    </div>
  );
};

/* Combined Intro/Outro editor — tabs swap which side you're editing, while the
   timing essentials (duration + easing) stay pinned at the top so they don't
   disappear behind a scroll or a tab switch. */
const AnimTabsEditor = ({ layer, onChange, onHoverPreset, onHoverEnd }) => {
  const [tab, setTab] = useState("intro");
  const state = tab === "intro" ? layer.intro : layer.outro;
  const update = (patch) => onChange(tab, { ...state, ...patch });
  const presetName = window.PRESET_BY_ID[state.preset].name;

  // Forward hover events tagged with the active tab.
  const hover = (id) => onHoverPreset({ kind: tab, id });

  return (
    <div className="side-section anim-section">
      <div className="anim-sticky">
        <div className="anim-tabs" role="tablist">
          {["intro", "outro"].map((k) => {
            const s = k === "intro" ? layer.intro : layer.outro;
            const name = window.PRESET_BY_ID[s.preset].name;
            return (
              <button key={k} role="tab" aria-selected={tab === k}
                      className={`anim-tab${tab === k ? " active" : ""}`}
                      onClick={() => setTab(k)}>
                <span className="anim-tab-label">{k === "intro" ? "Intro" : "Outro"}</span>
                <span className="anim-tab-meta">{name} · {s.duration.toFixed(1)}s</span>
              </button>
            );
          })}
        </div>

        {/* Pinned essentials — timing + easing always visible at the top. */}
        <div className="anim-pinned">
          <div className="anim-pinned-row">
            <div className="anim-pinned-cell">
              <div className="field-label">
                <span>Duration</span>
                <span className="field-label-value">{state.duration.toFixed(2)}s</span>
              </div>
              <input className="slider" type="range" min={0} max={3} step={0.05}
                     value={state.duration}
                     onChange={(e) => update({ duration: parseFloat(e.target.value) })}/>
            </div>
            <div className="anim-pinned-cell">
              <div className="field-label"><span>Easing</span></div>
              <div className="select-wrap">
                <select className="select" value={state.easing}
                        onChange={(e) => update({ easing: e.target.value })}>
                  {window.EASING_OPTIONS.map((o) => <option key={o.id} value={o.id}>{o.label}</option>)}
                </select>
                <span className="select-chevron"><Icon name="chevron-down" size={11}/></span>
              </div>
            </div>
          </div>
        </div>
      </div>

      <div className="side-section-body">
        <div>
          <div className="preset-group-label">Mask reveals</div>
          <PresetGrid presets={window.PRESETS_MASK} selectedId={state.preset}
            onSelect={(id) => update({ preset: id })}
            onHover={hover} onHoverEnd={onHoverEnd}/>
        </div>
        <div>
          <div className="preset-group-label">Standard</div>
          <PresetGrid presets={window.PRESETS_STANDARD} selectedId={state.preset}
            onSelect={(id) => update({ preset: id })}
            onHover={hover} onHoverEnd={onHoverEnd}/>
        </div>
      </div>
    </div>
  );
};

const Sidebar = ({ layer, onUpdateLayer, onHoverPreset, onHoverEnd }) => {
  if (!layer) {
    return (
      <aside className="side">
        <div className="side-empty">
          <strong>No layer selected</strong>
          Click a layer in the panel — or on the canvas — to edit its intro & outro.
        </div>
      </aside>
    );
  }
  return (
    <aside className="side">
      <div className="side-scroll">
        <SidebarSection title="Layer" summary={layer.name} defaultOpen={true}>
          <div>
            <div className="field-label"><span>Name</span></div>
            <input className="numeric-input" style={{ width: "100%", textAlign: "left" }} value={layer.name}
                   onChange={(e) => onUpdateLayer(layer.id, { name: e.target.value })}/>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8 }}>
            <div>
              <div className="field-label"><span>Start</span><span className="field-label-value">{layer.startTime.toFixed(1)}s</span></div>
              <input className="slider" type="range" min={0} max={10} step={0.05} value={layer.startTime}
                     onChange={(e) => {
                       const v = parseFloat(e.target.value);
                       const delta = v - layer.startTime;
                       const curOutro = window.layerOutroStart(layer);
                       onUpdateLayer(layer.id, { startTime: v, outroStart: Math.max(v + layer.intro.duration, curOutro + delta) });
                     }}/>
            </div>
            <div>
              <div className="field-label"><span>Outro start</span><span className="field-label-value">{window.layerOutroStart(layer).toFixed(1)}s</span></div>
              <input className="slider" type="range"
                     min={(layer.startTime + layer.intro.duration).toFixed(2)}
                     max={Math.max(layer.startTime + layer.intro.duration + 10, window.layerOutroStart(layer) + 2).toFixed(2)}
                     step={0.05}
                     value={window.layerOutroStart(layer)}
                     onChange={(e) => onUpdateLayer(layer.id, { outroStart: parseFloat(e.target.value) })}/>
            </div>
          </div>
        </SidebarSection>
        <AnimTabsEditor layer={layer}
                        onChange={(kind, val) => onUpdateLayer(layer.id, { [kind]: val })}
                        onHoverPreset={onHoverPreset}
                        onHoverEnd={onHoverEnd}/>
      </div>
    </aside>
  );
};

window.Icon = Icon;
window.Stage = Stage;
window.LayersPanel = LayersPanel;
window.Sidebar = Sidebar;
