import { createContext, useContext, useState, useEffect, useCallback, useRef } from "react";
import { BrowserRouter, Routes, Route, Outlet, useMatch, useNavigate, useParams, Link } from "react-router-dom";
import { Menu, Sparkles, UserCircle } from "lucide-react";
import { StudioView } from "./components/GalleryView";
import { CharacterProfileView } from "./components/CharacterProfileView";
import { ProjectsView } from "./components/ProjectsView";
import { LoraTrainingPage } from "./components/LoraTrainingPage";
import { ProjectDetailView } from "./components/ProjectDetailView";
import { ProjectFilmsView } from "./components/ProjectFilmsView";
import { AppSidebar } from "./components/sidebar/AppSidebar";
import LandingPage from "./LandingPage";
import type { SidebarTab } from "./components/sidebar/AppSidebar";
import type { JobItem, LoraCheckpoint, LoraTrainingStatus, MediaItem, Project, Scene, Shot, ProjectPhase, ProjectModeData } from "./types";

async function fetchJson<T>(url: string, timeoutMs = 5000): Promise<T> {
  const controller = new AbortController();
  const id = window.setTimeout(() => controller.abort(), timeoutMs);
  try {
    const response = await fetch(url, { signal: controller.signal });
    if (!response.ok) throw new Error(`${url} returned ${response.status}`);
    return await response.json();
  } finally {
    window.clearTimeout(id);
  }
}

/* ── App Context ── */
interface AppContextType {
  items: MediaItem[];
  jobs: JobItem[];
  loading: boolean;
  hasLoadedOnce: boolean;
  error: string | null;
  selected: string | null;
  setSelected: (url: string | null) => void;
  sidebarOpen: boolean;
  setSidebarOpen: (v: boolean) => void;
  activeSidebarTab: SidebarTab;
  setActiveSidebarTab: (tab: SidebarTab) => void;
  training: LoraTrainingStatus | null;
  checkpoints: LoraCheckpoint[];
  trainingJobs: any[];
  deleteItem: (item: MediaItem) => Promise<void>;
  load: () => Promise<void>;
  projectData: { project: Project; scenes: Scene[]; shots: Shot[] } | null;
  selectedSceneId: string | null;
  selectedShotId: string | null;
  setSelectedSceneId: (id: string | null) => void;
  setSelectedShotId: (id: string | null) => void;
  loadProject: (id: string) => Promise<void>;
  addScene: () => Promise<void>;
  deleteScene: (sceneId: string) => Promise<void>;
  deleteShot: (shotId: string) => Promise<void>;
}

const AppContext = createContext<AppContextType>(null!);
export function useApp() {
  return useContext(AppContext);
}

/* ── Layout Shell: header + sidebar + <Outlet /> ── */
function Shell() {
  const ctx = useApp();
  const navigate = useNavigate();
  const projectMatch = useMatch("/studio/projects/:projectId");
  const loraMatch = useMatch("/studio/lora-training");

  // Load project when entering a project-detail route
  const lastProjectId = useRef<string | undefined>(undefined);
  useEffect(() => {
    const pid = projectMatch?.params.projectId;
    if (pid && pid !== lastProjectId.current) {
      lastProjectId.current = pid;
      ctx.loadProject(pid);
      ctx.setActiveSidebarTab("projects");
    }
  }, [projectMatch?.params.projectId]);

  // Keep "Characters & LoRA Training" sidebar tab highlighted when on /lora-training
  useEffect(() => {
    if (loraMatch) ctx.setActiveSidebarTab("characters");
  }, [loraMatch]);

  const videoCount = ctx.items.filter(
    (item) => item.type === "video" || item.url.endsWith(".mp4") || item.url.endsWith(".webm")
  ).length;
  const imageCount = ctx.items.length - videoCount;

  const projectMode: ProjectModeData | undefined =
    projectMatch && ctx.projectData
      ? {
          project: ctx.projectData.project,
          scenes: ctx.projectData.scenes,
          shots: ctx.projectData.shots,
          selectedSceneId: ctx.selectedSceneId,
          selectedShotId: ctx.selectedShotId,
          phase: "outline",
          onSelectScene: (id) => {
            ctx.setSelectedSceneId(id);
            ctx.setSelectedShotId(null);
          },
          onSelectShot: ctx.setSelectedShotId,
          onBack: () => {
            ctx.setSelectedShotId(null);
            navigate("/studio/projects");
          },
          onRefresh: () => {
            const pid = projectMatch?.params.projectId;
            return pid ? ctx.loadProject(pid) : Promise.resolve();
          },
          onAddScene: ctx.addScene,
          onDeleteScene: ctx.deleteScene,
          onDeleteShot: ctx.deleteShot,
        }
      : undefined;

  return (
    <div className="min-h-screen bg-black text-white flex flex-col">
      <header className="h-14 border-b border-gray-800/60 bg-black/90 backdrop-blur-xl flex items-center justify-between px-5 sticky top-0 z-40">
        <div className="flex items-center gap-3 min-w-0">
          {!ctx.sidebarOpen && (
            <button
              onClick={() => ctx.setSidebarOpen(true)}
              className="w-9 h-9 rounded-xl border border-gray-800 text-gray-500 hover:text-gray-200 hover:border-gray-600 transition"
              title="Open tools"
            >
              <Menu className="w-4 h-4 mx-auto" />
            </button>
          )}
          <button
            onClick={() => {
              navigate("/studio");
              ctx.setActiveSidebarTab("generate");
            }}
            className="flex items-center gap-3 min-w-0 hover:opacity-80 transition"
            title="Studio home"
          >
            <div className="w-9 h-9 rounded-xl bg-gradient-to-br from-rose-500 via-fuchsia-500 to-amber-400 flex items-center justify-center shadow-lg shadow-rose-500/20 ring-1 ring-white/10">
              <Sparkles className="w-4 h-4 text-white" />
            </div>
            <div className="min-w-0 hidden sm:block">
              <h1 className="text-sm font-bold tracking-tight">Nemoflix Studio</h1>
              <p className="text-[10px] text-rose-400/60 tracking-wide">AMD MI300X</p>
            </div>
          </button>
          <Link to="/" className="hidden sm:inline-flex items-center text-[11px] text-gray-600 hover:text-gray-400 transition ml-1" title="Back to home">
            ← Home
          </Link>
        </div>

        <div className="flex items-center gap-2 text-[11px]">
          <div className="hidden md:flex items-center gap-1.5">
            {ctx.jobs.length > 0 && (
              <span className="inline-flex items-center gap-1.5 rounded-full border border-amber-800/40 bg-amber-950/30 px-2.5 py-1 text-amber-400 font-medium">
                <span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
                {ctx.jobs.length} generating
              </span>
            )}
            <span className="rounded-full border border-gray-800 bg-gray-900/50 px-2.5 py-1 text-gray-500">
              {ctx.items.length} media
            </span>
            <span className="hidden lg:inline rounded-full border border-gray-800 bg-gray-900/50 px-2.5 py-1 text-gray-500">
              {imageCount} images
            </span>
            <span className="hidden lg:inline rounded-full border border-gray-800 bg-gray-900/50 px-2.5 py-1 text-gray-500">
              {videoCount} videos
            </span>
          </div>

          <div className="group relative">
            <button className="flex items-center gap-2 rounded-xl border border-rose-500/30 bg-rose-950/20 px-2.5 py-1.5 text-rose-100 hover:border-rose-400/50 transition">
              <UserCircle className="w-4 h-4" />
              <span className="hidden sm:inline font-medium">Demo Account</span>
              <span className="rounded-full bg-amber-500/15 border border-amber-500/30 px-1.5 py-0.5 text-[9px] uppercase tracking-wider text-amber-300">
                Hackathon
              </span>
            </button>
            <div className="pointer-events-none absolute right-0 top-full z-50 mt-2 w-72 rounded-2xl border border-gray-800 bg-gray-950/95 p-4 shadow-2xl shadow-black/60 opacity-0 translate-y-1 transition group-hover:opacity-100 group-hover:translate-y-0">
              <p className="text-xs font-semibold text-gray-200">Demo workspace</p>
              <p className="text-[11px] text-gray-500 mt-1 leading-relaxed">
                This hackathon build uses a sample owner dataset to demonstrate character LoRA training,
                generation, and media management. Authentication is intentionally mocked for the demo.
              </p>
            </div>
          </div>
        </div>
      </header>

      <div className="flex flex-1 min-h-0">
        {ctx.sidebarOpen && (
          <AppSidebar
            activeTab={ctx.activeSidebarTab}
            onTabChange={(tab) => {
              ctx.setActiveSidebarTab(tab);
              if (tab === "generate") navigate("/studio");
              if (tab === "projects") navigate("/studio/projects");
              if (tab === "characters") navigate("/studio/lora-training");
            }}
            onClose={() => ctx.setSidebarOpen(false)}
            checkpoints={ctx.checkpoints}
            onQueued={ctx.load}
            onSelectCharacter={(id) => {
              ctx.setActiveSidebarTab("characters");
              navigate(`/studio/characters/${id}`);
            }}
            projectMode={projectMode}
          />
        )}

        <main className="flex-1 min-w-0 overflow-y-auto bg-gradient-to-b from-transparent via-transparent to-gray-950/30">
          <Outlet />
        </main>
      </div>

      {/* Lightbox */}
      {ctx.selected && (
        <div
          className="fixed inset-0 z-50 bg-black/95 flex items-center justify-center p-4"
          onClick={() => ctx.setSelected(null)}
        >
          <div className="max-w-full max-h-full" onClick={(e) => e.stopPropagation()}>
            {ctx.selected.endsWith(".mp4") || ctx.selected.endsWith(".webm") ? (
              <video src={ctx.selected} controls autoPlay className="max-w-full max-h-[90vh] rounded" />
            ) : (
              <img src={ctx.selected} alt="" className="max-w-full max-h-[90vh] rounded" />
            )}
          </div>
        </div>
      )}
    </div>
  );
}

/* ── Route wrappers that connect URL params to existing components ── */
function StudioRoute() {
  const ctx = useApp();
  return (
    <StudioView
      items={ctx.items}
      jobs={ctx.jobs}
      loading={ctx.loading && !ctx.hasLoadedOnce && !(ctx.jobs.length > 0 || ctx.items.length > 0)}
      error={ctx.error}
      onOpen={ctx.setSelected}
      onDelete={ctx.deleteItem}
      onOpenProjects={() => window.location.href = "/studio/projects"}
    />
  );
}

function ProjectsRoute() {
  const navigate = useNavigate();
  return <ProjectsView onOpenProject={(id) => navigate(`/studio/projects/${id}`)} />;
}

function ProjectRoute() {
  const { projectId } = useParams<{ projectId: string }>();
  const ctx = useApp();

  useEffect(() => {
    if (projectId) ctx.loadProject(projectId);
  }, [projectId]);

  const navigate = useNavigate();

  if (!ctx.projectData) {
    return <div className="p-8 text-center text-gray-500">Loading project…</div>;
  }

  return (
    <ProjectDetailView
      project={ctx.projectData.project}
      scenes={ctx.projectData.scenes}
      shots={ctx.projectData.shots}
      jobs={ctx.jobs}
      selectedSceneId={ctx.selectedSceneId}
      selectedShotId={ctx.selectedShotId}
      onSelectScene={(id) => {
        ctx.setSelectedSceneId(id);
        ctx.setSelectedShotId(null);
      }}
      onSelectShot={ctx.setSelectedShotId}
      onRefresh={() => (projectId ? ctx.loadProject(projectId) : Promise.resolve())}
      onBack={() => {
        ctx.setSelectedShotId(null);
        navigate("/studio/projects");
      }}
      onDeleteScene={ctx.deleteScene}
      onDeleteShot={ctx.deleteShot}
    />
  );
}

function ProjectFilmsRoute() {
  const { projectId } = useParams<{ projectId: string }>();
  const navigate = useNavigate();
  if (!projectId) return null;
  return (
    <ProjectFilmsView
      projectId={projectId}
      onBack={() => navigate(`/studio/projects/${projectId}`)}
    />
  );
}

function CharacterRoute() {
  const { characterId } = useParams<{ characterId: string }>();
  const { items, setSelected, deleteItem, setActiveSidebarTab, setSidebarOpen } = useApp();
  const navigate = useNavigate();
  if (!characterId) return null;
  return (
    <CharacterProfileView
      characterId={characterId}
      items={items}
      onOpen={setSelected}
      onDelete={deleteItem}
      onGenerate={() => {
        setSidebarOpen(true);
        setActiveSidebarTab("generate");
        navigate(`/studio?character=${characterId}`);
      }}
    />
  );
}

/* ── App Root ── */
export default function App() {
  const [items, setItems] = useState<MediaItem[]>([]);
  const [jobs, setJobs] = useState<JobItem[]>([]);
  const [training, setTraining] = useState<LoraTrainingStatus | null>(null);
  const [checkpoints, setCheckpoints] = useState<LoraCheckpoint[]>([]);
  const [trainingJobs, setTrainingJobs] = useState<any[]>([]);
  const [loading, setLoading] = useState(true);
  const [hasLoadedOnce, setHasLoadedOnce] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [selected, setSelected] = useState<string | null>(null);
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const [activeSidebarTab, setActiveSidebarTab] = useState<SidebarTab>("generate");
  const [projectData, setProjectData] = useState<{
    project: Project;
    scenes: Scene[];
    shots: Shot[];
  } | null>(null);
  const [selectedSceneId, setSelectedSceneId] = useState<string | null>(null);
  const [selectedShotId, setSelectedShotId] = useState<string | null>(null);
  const [projectId, setProjectId] = useState<string | null>(null);

  const load = useCallback(async () => {
    try {
      setError(null);
      const listing = await fetchJson<{ images?: MediaItem[] }>("/api/listing", 8000);
      setItems(listing.images || []);
      if (!hasLoadedOnce) setHasLoadedOnce(true);
    } catch (e) {
      console.error("Failed to load gallery", e);
      setError(e instanceof Error ? e.message : "Failed to load gallery");
    } finally {
      setLoading(false);
    }

    const [jobsResult, trainingResult, checkpointsResult, trainingJobsResult] = await Promise.allSettled([
      fetchJson<{ jobs?: JobItem[] }>("/api/jobs", 3500),
      fetchJson<LoraTrainingStatus & { ok?: boolean }>("/api/lora-training/status", 3500),
      fetchJson<{ checkpoints?: LoraCheckpoint[] }>("/api/lora-training/checkpoints", 3500),
      fetchJson<{ jobs?: any[] }>("/api/lora-training/jobs", 3500),
    ]);

    if (jobsResult.status === "fulfilled") setJobs(jobsResult.value.jobs || []);
    if (trainingResult.status === "fulfilled")
      setTraining(trainingResult.value.ok ? trainingResult.value : null);
    if (checkpointsResult.status === "fulfilled")
      setCheckpoints(checkpointsResult.value.checkpoints || []);
    if (trainingJobsResult.status === "fulfilled")
      setTrainingJobs(trainingJobsResult.value.jobs || []);
  }, []);

  // Stable polling — uses refs to avoid dependency churn
  const loadRef = useRef(load);
  loadRef.current = load;

  useEffect(() => {
    loadRef.current();
    const id = window.setInterval(() => loadRef.current(), 5000);

    const es = new EventSource("/api/events");
    es.addEventListener("job_update", () => loadRef.current());
    es.onerror = () => {};

    return () => {
      window.clearInterval(id);
      es.close();
    };
  }, []);

  const loadProject = useCallback(async (id: string) => {
    setProjectId(id);
    try {
      const response = await fetch(`/api/projects/${id}`);
      if (!response.ok) throw new Error(`Project fetch failed: ${response.status}`);
      const data = await response.json();
      const scenes: Scene[] = (data.scenes || []).slice().sort(
        (a: Scene, b: Scene) => a.scene_number - b.scene_number
      );
      const shots: Shot[] = (data.shots || []).slice().sort(
        (a: Shot, b: Shot) => a.shot_number - b.shot_number
      );
      setProjectData({ project: data.project, scenes, shots });
      setSelectedSceneId((current) => {
        if (current && scenes.some((scene) => scene.id === current)) return current;
        return scenes.length > 0 ? scenes[0].id : null;
      });
    } catch (e) {
      console.error("Failed to load project", e);
    }
  }, []);

  const addScene = useCallback(async () => {
    if (!projectId || !projectData) return;
    const next =
      projectData.scenes.length > 0
        ? Math.max(...projectData.scenes.map((scene) => scene.scene_number)) + 1
        : 1;
    try {
      const response = await fetch(`/api/projects/${projectId}/scenes`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ scene_number: next, heading: `SCENE ${next}` }),
      });
      if (!response.ok) throw new Error(`Add scene failed: ${response.status}`);
      const created = await response.json();
      await loadProject(projectId);
      setSelectedSceneId(created.id);
      setSelectedShotId(null);
    } catch (e) {
      console.error("Failed to add scene", e);
    }
  }, [projectId, projectData, loadProject]);

  const deleteScene = useCallback(async (sceneId: string) => {
    if (!projectId) return;
    if (!confirm("Delete this scene and all its shots?")) return;
    await fetch(`/api/projects/${projectId}/scenes/${sceneId}`, { method: "DELETE" });
    if (selectedSceneId === sceneId) {
      setSelectedSceneId("");
      setSelectedShotId(null);
    }
    loadProject(projectId);
  }, [projectId, selectedSceneId, loadProject]);

  const deleteShot = useCallback(async (shotId: string) => {
    if (!projectId || !projectData) return;
    if (!confirm("Delete this shot?")) return;
    const shot = projectData.shots.find((s) => s.id === shotId);
    if (!shot) return;
    await fetch(`/api/projects/${projectId}/scenes/${shot.scene_id}/shots/${shotId}`, { method: "DELETE" });
    if (selectedShotId === shotId) setSelectedShotId(null);
    loadProject(projectId);
  }, [projectId, projectData, selectedShotId, loadProject]);

  const deleteItem = useCallback(
    async (item: MediaItem) => {
      const filename = item.filename || item.url.replace(/^\/media\//, "");
      const response = await fetch("/api/delete", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ files: [filename] }),
      });
      if (!response.ok) {
        const data = await response.json().catch(() => ({}));
        throw new Error(data?.detail || `Failed to delete ${filename}`);
      }
      setItems((current) =>
        current.filter(
          (candidate) =>
            (candidate.filename || candidate.url) !== (item.filename || item.url)
        )
      );
      if (selected === item.url) setSelected(null);
    },
    [selected]
  );

  const ctxValue: AppContextType = {
    items,
    jobs,
    loading,
    hasLoadedOnce,
    error,
    selected,
    setSelected,
    sidebarOpen,
    setSidebarOpen,
    activeSidebarTab,
    setActiveSidebarTab,
    training,
    checkpoints,
    trainingJobs,
    deleteItem,
    load,
    projectData,
    selectedSceneId,
    selectedShotId,
    setSelectedSceneId,
    setSelectedShotId,
    loadProject,
    addScene,
    deleteScene,
    deleteShot,
  };

  return (
    <BrowserRouter>
      <AppContext.Provider value={ctxValue}>
        <Routes>
          <Route path="/" element={<LandingPage />} />
          <Route path="/studio" element={<Shell />}>
            <Route index element={<StudioRoute />} />
            <Route path="projects" element={<ProjectsRoute />} />
            <Route path="projects/:projectId" element={<ProjectRoute />} />
            <Route path="projects/:projectId/films" element={<ProjectFilmsRoute />} />
            <Route path="characters/:characterId" element={<CharacterRoute />} />
            <Route path="lora-training" element={<LoraTrainingPage />} />
          </Route>
        </Routes>
      </AppContext.Provider>
    </BrowserRouter>
  );
}
