import { useMemo, useState } from 'react';
import { useUIStore } from '../../store/uiStore';
import { useGraphStore } from '../../store/graphStore';
import { CinemaStudioToolbar } from './CinemaStudioToolbar';
import { CinemaSharedControls } from './CinemaSharedControls';
import { CinemaShotsRail } from './CinemaShotsRail';
import { CinemaShotPanel } from './CinemaShotPanel';
import type { CinemaSceneSpec, CinemaShot } from '../../types';
import '../../styles/cinema-studio.css';

/** A minimal valid scene for a node that has none yet (e.g. dragged from the
 *  library before the Studio seeded it). License guard (spec §10): the default
 *  base must be commercial-OK — never FLUX.1-dev. Mirrors graphStore's
 *  createDefaultScene so the editor and store agree on the empty shape. */
function emptyScene(): CinemaSceneSpec {
  return {
    version: 1,
    base: { model: 'seedream-4-5' },
    aspectRatio: '16:9',
    shots: [],
  };
}

/** Full-screen Cinema Studio host. Mounted by App.tsx when uiStore
 *  .cinemaEditorNodeId is set — mirrors RemotionEditorView's mount pattern.
 *  Reads the target node's data.params.scene; every edit routes back through
 *  graphStore.updateScene (optimistic store + cli_graph round-trip). */
export function CinemaStudioView() {
  const cinemaNodeId = useUIStore((s) => s.cinemaEditorNodeId);
  const exitCinemaEditor = useUIStore((s) => s.exitCinemaEditor);
  const node = useGraphStore((s) =>
    cinemaNodeId ? s.nodes.find((n) => n.id === cinemaNodeId) : null,
  );
  const updateScene = useGraphStore((s) => s.updateScene);
  const addShot = useGraphStore((s) => s.addShot);
  const removeShot = useGraphStore((s) => s.removeShot);
  const edges = useGraphStore((s) => s.edges);
  const nodes = useGraphStore((s) => s.nodes);

  const [selectedShotId, setSelectedShotId] = useState<string | null>(null);

  const scene: CinemaSceneSpec =
    (node?.data as { params?: { scene?: CinemaSceneSpec } } | undefined)?.params?.scene ?? emptyScene();

  // Derive a valid effective selection during render: keep the stored id when it
  // still refers to a real shot, otherwise fall back to the first shot (or null).
  // Avoids a setState-in-effect cycle — selection validity is purely derived from
  // scene.shots, so it can be computed without a side-effect.
  const shotIds = scene.shots.map((s) => s.id);
  const effectiveShotId = selectedShotId && shotIds.includes(selectedShotId)
    ? selectedShotId
    : (shotIds[0] ?? null);

  // Canvas ↔ Studio parity: character refs wired into the node's `character_refs`
  // input port on the canvas must show here too (the backend already conditions
  // on them — handlers/cinema_scene.py reads inputs['character_refs']). Resolve
  // each connected edge to its source node's output image URL.
  const connectedCharacterRefs = useMemo(() => {
    if (!cinemaNodeId) return [];
    const urls: string[] = [];
    const asStr = (v: unknown): string | undefined => (typeof v === 'string' && v ? v : undefined);
    for (const e of edges) {
      if (e.target !== cinemaNodeId || e.targetHandle !== 'character_refs') continue;
      const sdata = nodes.find((n) => n.id === e.source)?.data as
        | { outputs?: Record<string, { value?: unknown }>; params?: Record<string, unknown> }
        | undefined;
      const outputs = sdata?.outputs;
      const params = sdata?.params;
      // A generated source (e.g. gpt-image-2) keeps its result in data.outputs;
      // a static image-input keeps it in params (_previewUrl / filePath) and may
      // have empty outputs until run. Cover both.
      const val =
        (e.sourceHandle ? asStr(outputs?.[e.sourceHandle]?.value) : undefined) ??
        asStr(outputs?.image?.value) ??
        (outputs ? asStr(Object.values(outputs).map((o) => o?.value).find((v) => asStr(v))) : undefined) ??
        asStr(params?._previewUrl) ??
        asStr(params?.filePath);
      if (val) urls.push(val);
    }
    return urls;
  }, [cinemaNodeId, edges, nodes]);

  if (!cinemaNodeId || !node) {
    return (
      <div className="cinema-studio-view">
        <div className="cinema-studio-view__empty">
          No cinema-scene node selected.{' '}
          <button type="button" onClick={exitCinemaEditor}>
            Back to canvas
          </button>
        </div>
      </div>
    );
  }

  const selectedShot = scene.shots.find((s) => s.id === effectiveShotId) ?? null;

  const handleAddShot = () => {
    const id = addShot(cinemaNodeId);
    if (id) setSelectedShotId(id);
  };

  const handleRemoveShot = (shotId: string) => {
    removeShot(cinemaNodeId, shotId);
    // Clear stored id; effectiveShotId will auto-fall-back to the next shot.
    if (selectedShotId === shotId) setSelectedShotId(null);
  };

  const handleReorder = (shots: CinemaShot[]) => {
    updateScene(cinemaNodeId, { ...scene, shots });
  };

  const handleChangeShot = (next: CinemaShot) => {
    updateScene(cinemaNodeId, {
      ...scene,
      shots: scene.shots.map((s) => (s.id === next.id ? next : s)),
    });
  };

  return (
    <div className="cinema-studio-view">
      <header className="cinema-studio-view__header">
        <CinemaStudioToolbar cinemaNodeId={cinemaNodeId} />
      </header>

      <div className="cinema-studio-view__shared">
        <CinemaSharedControls
          scene={scene}
          connectedRefs={connectedCharacterRefs}
          onChange={(next) => updateScene(cinemaNodeId, next)}
        />
      </div>

      <div className="cinema-studio-view__rail">
        <CinemaShotsRail
          scene={scene}
          selectedShotId={effectiveShotId}
          onSelect={setSelectedShotId}
          onAddShot={handleAddShot}
          onRemoveShot={handleRemoveShot}
          onReorder={handleReorder}
        />
      </div>

      <div className="cinema-studio-view__panel">
        {selectedShot ? (
          <CinemaShotPanel
            cinemaNodeId={cinemaNodeId}
            scene={scene}
            shot={selectedShot}
            onChangeShot={handleChangeShot}
          />
        ) : (
          <div className="cinema-studio-view__panel-empty">
            Add a shot to begin storyboarding.
          </div>
        )}
      </div>
    </div>
  );
}
