/* === JitterFree — App entry === */
const { useState: useStateA, useEffect: useEffectA, useRef: useRefA, useMemo: useMemoA, useCallback: useCallbackA, useReducer: useReducerA } = React;

/* ----- Reducer for composition state ----- */
function compReducer(state, action) {
  switch (action.type) {
    case "addLayer": {
      return { ...state, layers: [...state.layers, action.layer], selectedId: action.layer.id };
    }
    case "deleteLayer": {
      const layers = state.layers.filter((l) => l.id !== action.id);
      const selectedId = state.selectedId === action.id ? (layers.length ? layers[layers.length - 1].id : null) : state.selectedId;
      return { ...state, layers, selectedId };
    }
    case "updateLayer": {
      return {
        ...state,
        layers: state.layers.map((l) => l.id === action.id ? { ...l, ...action.patch } : l),
      };
    }
    case "toggleVisibility": {
      return {
        ...state,
        layers: state.layers.map((l) => l.id === action.id ? { ...l, visible: !l.visible } : l),
      };
    }
    case "select": return { ...state, selectedId: action.id };
    case "reorder": {
      const { dragId, targetId, where } = action;
      const layers = state.layers.slice();
      const di = layers.findIndex((l) => l.id === dragId);
      if (di === -1) return state;
      const [drag] = layers.splice(di, 1);
      let ti = layers.findIndex((l) => l.id === targetId);
      if (ti === -1) ti = layers.length;
      const insertAt = where === "above" ? ti + 1 : ti;
      layers.splice(insertAt, 0, drag);
      return { ...state, layers };
    }
    case "setManualDuration": return { ...state, manualDuration: action.value };
    default: return state;
  }
}

const initialComp = { layers: [], selectedId: null, manualDuration: null };

function App() {
  const [comp, dispatch] = useReducerA(compReducer, initialComp);
  const [playing, setPlaying] = useStateA(true);
  const [looping, setLooping] = useStateA(true);
  const [currentTime, setCurrentTime] = useStateA(0);
  const [zoom, setZoom] = useStateA(1);
  const [hoverPreset, setHoverPreset] = useStateA(null);
  const [exportOpen, setExportOpen] = useStateA(false);
  const [toast, setToast] = useStateA(null);

  const totalDur = window.compositionDuration(comp);

  useEffectA(() => {
    let cancelled = false;
    (async () => {
      const { img, src } = await window.makePlaceholderImage();
      if (cancelled) return;
      const layer = window.makeLayer({ name: "Card", src, image: img });
      dispatch({ type: "addLayer", layer });
    })();
    return () => { cancelled = true; };
  }, []);

  const rafRef = useRefA(null);
  const lastRef = useRefA(null);
  useEffectA(() => {
    if (!playing) {
      cancelAnimationFrame(rafRef.current);
      lastRef.current = null;
      return;
    }
    const tick = (ts) => {
      if (lastRef.current == null) lastRef.current = ts;
      const dt = (ts - lastRef.current) / 1000;
      lastRef.current = ts;
      setCurrentTime((t) => {
        let next = t + dt;
        if (next >= totalDur) {
          if (looping) next = next % totalDur;
          else { next = totalDur; setPlaying(false); }
        }
        return next;
      });
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(rafRef.current);
  }, [playing, looping, totalDur]);

  useEffectA(() => {
    const handler = (e) => {
      if (["INPUT", "SELECT", "TEXTAREA"].includes(document.activeElement?.tagName)) return;
      if (e.code === "Space") {
        e.preventDefault();
        setPlaying((p) => !p);
      } else if (e.code === "Equal" || e.code === "NumpadAdd") {
        if (e.metaKey || e.ctrlKey) { e.preventDefault(); setZoom((z) => Math.min(8, z * 1.25)); }
      } else if (e.code === "Minus" || e.code === "NumpadSubtract") {
        if (e.metaKey || e.ctrlKey) { e.preventDefault(); setZoom((z) => Math.max(0.25, z / 1.25)); }
      } else if (e.code === "Backspace" || e.code === "Delete") {
        if (comp.selectedId) {
          e.preventDefault();
          dispatch({ type: "deleteLayer", id: comp.selectedId });
        }
      }
    };
    window.addEventListener("keydown", handler);
    return () => window.removeEventListener("keydown", handler);
  }, [comp.selectedId]);

  const hoverTimeRef = useRefA(0);
  const [hoverTime, setHoverTime] = useStateA(0);
  useEffectA(() => {
    if (!hoverPreset) { hoverTimeRef.current = 0; setHoverTime(0); return; }
    let raf, last = null;
    const tick = (ts) => {
      if (last == null) last = ts;
      const dt = (ts - last) / 1000; last = ts;
      hoverTimeRef.current += dt;
      setHoverTime(hoverTimeRef.current);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(raf);
  }, [hoverPreset]);

  const renderedLayers = useMemoA(() => {
    if (!hoverPreset || !comp.selectedId) return comp.layers;
    const sel = comp.layers.find((l) => l.id === comp.selectedId);
    if (!sel) return comp.layers;
    return comp.layers.map((L) => {
      if (L.id !== sel.id) return L;
      if (hoverPreset.kind === "intro") {
        return { ...L, intro: { ...L.intro, preset: hoverPreset.id } };
      }
      return { ...L, outro: { ...L.outro, preset: hoverPreset.id } };
    });
  }, [comp.layers, comp.selectedId, hoverPreset]);

  const renderTime = useMemoA(() => {
    if (!hoverPreset || !comp.selectedId) return currentTime;
    const sel = comp.layers.find((l) => l.id === comp.selectedId);
    if (!sel) return currentTime;
    const cycle = sel.intro.duration + Math.max(0, window.layerOutroStart(sel) - (sel.startTime + sel.intro.duration)) + sel.outro.duration;
    return sel.startTime + (hoverTime % cycle);
  }, [hoverPreset, hoverTime, currentTime, comp.layers, comp.selectedId]);

  const addLayerFromFile = useCallbackA((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 = () => {
        const layer = window.makeLayer({ name: file.name.replace(/\.[^.]+$/, ""), src, image: img });
        dispatch({ type: "addLayer", layer });
      };
      img.src = src;
    };
    reader.readAsDataURL(file);
  }, []);

  const setManualDuration = useCallbackA((value) => {
    dispatch({ type: "setManualDuration", value });
  }, []);

  const selectedLayer = comp.layers.find((l) => l.id === comp.selectedId) || null;

  return (
    <div className="app">
      <header className="header">
        <div className="brand">
          <span className="brand-mark"><window.Icon name="sparkles" size={12}/></span>
          <span>JitterFree</span>
          <span className="sep">·</span>
          <span className="doc">Untitled</span>
        </div>
        <div className="header-actions">
          <span className="btn ghost" style={{ cursor: "default" }}>
            <window.Icon name="keyboard" size={12}/>
            <span className="kbd">Space</span>
            <span style={{ color: "var(--fg-3)", fontWeight: 400 }}>play / pause</span>
          </span>
          <button className="btn primary" onClick={() => setExportOpen(true)} disabled={!comp.layers.length}>
            <window.Icon name="download" size={12}/> Export
          </button>
        </div>
      </header>

      <window.LayersPanel
        comp={comp}
        selectedId={comp.selectedId}
        onSelect={(id) => dispatch({ type: "select", id })}
        onAdd={addLayerFromFile}
        onDelete={(id) => dispatch({ type: "deleteLayer", id })}
        onToggleVisibility={(id) => dispatch({ type: "toggleVisibility", id })}
        onReorder={(dragId, targetId, where) => dispatch({ type: "reorder", dragId, targetId, where })}
      />

      <window.Stage
        comp={{ layers: renderedLayers }}
        currentTime={renderTime}
        selectedId={comp.selectedId}
        onSelect={(id) => dispatch({ type: "select", id })}
        onUpload={({ src, image, name }) => {
          const layer = window.makeLayer({ name: name?.replace(/\.[^.]+$/, "") || "Layer", src, image });
          dispatch({ type: "addLayer", layer });
        }}
        onUpdateLayer={(id, patch) => dispatch({ type: "updateLayer", id, patch })}
      />

      <window.Sidebar
        layer={selectedLayer}
        onUpdateLayer={(id, patch) => dispatch({ type: "updateLayer", id, patch })}
        onHoverPreset={setHoverPreset}
        onHoverEnd={() => setHoverPreset(null)}
      />

      <window.Timeline
        comp={comp}
        currentTime={currentTime}
        playing={playing}
        looping={looping}
        selectedId={comp.selectedId}
        onSelect={(id) => dispatch({ type: "select", id })}
        onPlay={() => { if (currentTime >= totalDur - 0.01) setCurrentTime(0); setPlaying(true); }}
        onPause={() => setPlaying(false)}
        onLoop={() => setLooping((l) => !l)}
        onRestart={() => { setCurrentTime(0); setPlaying(true); }}
        onSeek={setCurrentTime}
        onUpdateLayer={(id, patch) => dispatch({ type: "updateLayer", id, patch })}
        zoom={zoom} setZoom={setZoom}
        manualDuration={comp.manualDuration} setManualDuration={setManualDuration}
      />

      <window.ExportModal open={exportOpen} onClose={() => setExportOpen(false)}
                          comp={comp} totalDur={totalDur}/>

      {toast && <div className="toast">{toast}</div>}
    </div>
  );
}

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);
