
import React, { useCallback, useEffect, useMemo, useState } from "react";
import GuidelinePage from "./pages/GuidelinePage";
import TrainingPage from "./pages/TrainingPage";
import EvaluationPage from "./pages/EvaluationPage";
import RoundCompletePage from "./pages/RoundCompletePage";
import rawPrompts from "./data/prompts.json";
import { normalizePromptList } from "./lib/promptSchema";
import "./styles/guideline.css";

const API_BASE_URL = (
  import.meta.env.VITE_API_BASE_URL ||
  (import.meta.env.DEV ? "http://localhost:8000" : "")
).replace(/\/$/, "");

const STORAGE_KEYS = {
  stage: "t2av_stage",
  annotatorName: "t2av_annotator_name",
  trainingPassed: "t2av_training_passed"
};

function readSession(key, fallback) {
  try {
    const value = sessionStorage.getItem(key);
    return value === null ? fallback : JSON.parse(value);
  } catch {
    return fallback;
  }
}

function writeSession(key, value) {
  try {
    sessionStorage.setItem(key, JSON.stringify(value));
  } catch {
    // sessionStorage unavailable (e.g. private browsing) - state just won't survive a refresh.
  }
}

const normalizedPrompts = normalizePromptList(rawPrompts);

export default function App() {
  // Gating note: there is no router and no URL for 'training'/'evaluation' -
  // the only way to reach them is through the state transitions below, so
  // there is nothing to type into an address bar to skip ahead.
  const [stage, setStage] = useState(() => readSession(STORAGE_KEYS.stage, "guideline"));
  const [annotatorName, setAnnotatorName] = useState(() => readSession(STORAGE_KEYS.annotatorName, ""));
  const [trainingPassed, setTrainingPassed] = useState(() =>
    readSession(STORAGE_KEYS.trainingPassed, false)
  );

  const [videoManifest, setVideoManifest] = useState(null);
  const [videoManifestError, setVideoManifestError] = useState(null);

  const [assignmentData, setAssignmentData] = useState(null);
  const [assignmentError, setAssignmentError] = useState(null);

  useEffect(() => {
    let cancelled = false;
    fetch(`${API_BASE_URL}/videos`)
      .then((res) => {
        if (!res.ok) throw new Error(`Video catalog request failed (status ${res.status})`);
        return res.json();
      })
      .then((data) => {
        if (!cancelled) setVideoManifest(data);
      })
      .catch((err) => {
        if (!cancelled) setVideoManifestError(err.message || "Failed to load videos");
      });
    return () => {
      cancelled = true;
    };
  }, []);

  useEffect(() => writeSession(STORAGE_KEYS.stage, stage), [stage]);
  useEffect(() => writeSession(STORAGE_KEYS.annotatorName, annotatorName), [annotatorName]);
  useEffect(() => writeSession(STORAGE_KEYS.trainingPassed, trainingPassed), [trainingPassed]);

  // Defensive fallback: the UI never lets you reach 'evaluation' without
  // trainingPassed, but if sessionStorage were hand-edited, fall back to
  // training rather than trusting the stored stage.
  useEffect(() => {
    if (stage === "evaluation" && !trainingPassed) {
      setStage("training");
    }
  }, [stage, trainingPassed]);
  const effectiveStage = stage === "evaluation" && !trainingPassed ? "training" : stage;

  // Each annotator gets a balanced 20-video round, not the full catalog.
  // The backend decides everything about round state (in progress / just
  // completed / time to auto-advance to the next round) - this call is
  // always the same "give me my current status" request, made automatically
  // on every evaluation-stage visit and again once EvaluationPage reports
  // the last video was saved, so the completed/code state is picked up
  // without any manual "start new round" action.
  const fetchAssignment = useCallback(() => {
    if (!annotatorName.trim()) return;
    fetch(`${API_BASE_URL}/assignment`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ annotator: annotatorName })
    })
      .then((res) => {
        if (!res.ok) throw new Error(`Assignment request failed (status ${res.status})`);
        return res.json();
      })
      .then((data) => setAssignmentData(data))
      .catch((err) => setAssignmentError(err.message || "Failed to load your assignment"));
  }, [annotatorName]);

  useEffect(() => {
    if (effectiveStage !== "evaluation") return;
    fetchAssignment();
  }, [effectiveStage, fetchAssignment]);

  // All resolved (itemId, model) videos, keyed by taskId - the full 200,
  // before per-annotator filtering.
  const videoEntriesByTaskId = useMemo(() => {
    if (!videoManifest) return null;
    return new Map(videoManifest.items.map((entry) => [`${entry.itemId}__${entry.model}`, entry]));
  }, [videoManifest]);

  const itemsById = useMemo(
    () => new Map(normalizedPrompts.map((item) => [item.itemId, item])),
    []
  );

  // The annotator's actual queue: their current round's video_ids (already
  // excludes anything they've completed before, in any round), resolved
  // against the live video catalog. An assigned id that no longer resolves
  // (e.g. a video went missing after assignment) is skipped gracefully,
  // matching the existing missing-video handling elsewhere.
  const tasks = useMemo(() => {
    if (!videoEntriesByTaskId || !assignmentData) return [];

    const built = [];
    assignmentData.video_ids.forEach((taskId) => {
      const entry = videoEntriesByTaskId.get(taskId);
      if (!entry) return;
      const item = itemsById.get(entry.itemId);
      if (!item) return;
      built.push({
        taskId,
        itemId: entry.itemId,
        model: entry.model,
        videoUrl: entry.videoUrl,
        variant: entry.variant,
        item
      });
    });
    return built;
  }, [videoEntriesByTaskId, itemsById, assignmentData]);

  const handleContinueToTraining = () => {
    if (!annotatorName.trim()) return;
    setStage("training");
  };

  const handlePassTraining = () => {
    setTrainingPassed(true);
    setStage("evaluation");
  };

  if (effectiveStage === "training") {
    return <TrainingPage onPass={handlePassTraining} trainingPassed={trainingPassed} />;
  }

  if (effectiveStage === "evaluation") {
    if (videoManifestError) {
      return (
        <div className="t2av-shell">
          <div className="g-main">
            <div className="callout missing-videos-banner">
              <span className="k">Could not load videos</span>
              {videoManifestError}. Check that the backend is running and reachable at{" "}
              {API_BASE_URL || "(same origin)"}.
            </div>
          </div>
        </div>
      );
    }
    if (!videoManifest) {
      return (
        <div className="t2av-shell">
          <div className="g-main">
            <p>Loading videos…</p>
          </div>
        </div>
      );
    }
    if (assignmentError) {
      return (
        <div className="t2av-shell">
          <div className="g-main">
            <div className="callout missing-videos-banner">
              <span className="k">Could not load your assignment</span>
              {assignmentError}. Check that the backend is running and reachable at{" "}
              {API_BASE_URL || "(same origin)"}.
            </div>
          </div>
        </div>
      );
    }
    if (!assignmentData) {
      return (
        <div className="t2av-shell">
          <div className="g-main">
            <p>Loading your assignment…</p>
          </div>
        </div>
      );
    }
    if (assignmentData.status === "completed") {
      return (
        <RoundCompletePage
          annotatorName={annotatorName}
          roundNumber={assignmentData.round_number}
          completionCode={assignmentData.completion_code}
          videoCount={assignmentData.actual_size}
        />
      );
    }
    if (tasks.length === 0) {
      return (
        <div className="t2av-shell">
          <div className="g-main">
            <div className="callout missing-videos-banner">
              <span className="k">No videos available</span>
              The video catalog loaded, but none of your {assignmentData.video_ids.length} assigned
              videos matched a video in {normalizedPrompts.length} prompt items. Check
              HF_VIDEO_DATASET_REPO and the dataset's folder structure.
            </div>
          </div>
        </div>
      );
    }
    return (
      <EvaluationPage
        tasks={tasks}
        missingVideos={videoManifest.missing}
        annotatorName={annotatorName}
        apiBaseUrl={API_BASE_URL}
        onRoundFinished={fetchAssignment}
      />
    );
  }

  return (
    <GuidelinePage
      annotatorName={annotatorName}
      onChangeAnnotatorName={setAnnotatorName}
      onContinue={handleContinueToTraining}
      apiBaseUrl={API_BASE_URL}
    />
  );
}
