"use client";

import { useEffect, useMemo, useState } from "react";
import ChapterSuccess from "./ChapterSuccess";
import Completion from "./Completion";
import MissionRail from "./MissionRail";
import Puzzle from "./Puzzle";
import StartScreen from "./StartScreen";
import SystemBlueprint from "./SystemBlueprint";
import StrikeReset from "./StrikeReset";
import {
  calculateLeaderboardScore,
  calculateMissionScore,
  chapterResults,
  createRunId,
  createRunQuestionIds,
  EMPTY_STATE,
  nextOpenIndexInChapter,
  registerWrongAnswer,
  RUN_MODES,
  sanitizeProgress,
  solutionText,
  STORAGE_KEY,
  STRIKE_LIMIT
} from "../lib/game";
import {
  allQuestions,
  chapters,
  questionsById
} from "../lib/question-bank";
import { buildStudySheet } from "../lib/study-sheet";

const questionIds = allQuestions.map((question) => question.id);
const chapterIds = chapters.map((chapter) => chapter.id);

function freshState(mode) {
  return {
    ...EMPTY_STATE,
    started: true,
    runId: createRunId(),
    startedAt: new Date().toISOString(),
    mode,
    runQuestionIds: createRunQuestionIds(chapters, mode),
    completed: [],
    attempts: {},
    hints: []
  };
}

export default function Game() {
  const [state, setState] = useState({ ...EMPTY_STATE });
  const [currentIndex, setCurrentIndex] = useState(0);
  const [hydrated, setHydrated] = useState(false);
  const [showCompletion, setShowCompletion] = useState(false);
  const [chapterCelebrationId, setChapterCelebrationId] = useState(null);
  const [strikeResetChapterId, setStrikeResetChapterId] = useState(null);
  // Erzwingt ein Remount von Puzzle auch dann, wenn die Navigation auf
  // derselben Frage landet — sonst bliebe die Fehlerbox stehen und die Frage
  // nähme keine Antwort mehr an.
  const [visit, setVisit] = useState(0);

  const runQuestions = useMemo(
    () =>
      state.runQuestionIds
        .map((id) => questionsById[id])
        .filter(Boolean),
    [state.runQuestionIds]
  );

  useEffect(() => {
    try {
      const saved = window.localStorage.getItem(STORAGE_KEY);
      if (saved) {
        const parsed = sanitizeProgress(
          JSON.parse(saved),
          questionIds,
          chapterIds
        );
        setState(parsed);
        const savedQuestions = parsed.runQuestionIds
          .map((id) => questionsById[id])
          .filter(Boolean);
        const firstOpen = savedQuestions.findIndex(
          (question) => !parsed.completed.includes(question.id)
        );
        setCurrentIndex(
          firstOpen === -1 ? Math.max(0, savedQuestions.length - 1) : firstOpen
        );
        if (
          savedQuestions.length > 0 &&
          parsed.completed.length === savedQuestions.length
        ) {
          setShowCompletion(true);
        }
        // Ein ausstehender Strike-Reset darf sich nicht durch Neuladen
        // erledigen lassen.
        if (parsed.strikes >= STRIKE_LIMIT && parsed.strikeChapterId) {
          const openIndex = nextOpenIndexInChapter(
            savedQuestions,
            parsed.completed,
            parsed.strikeChapterId
          );
          if (openIndex !== -1) setCurrentIndex(openIndex);
          setState({ ...parsed, strikes: 0 });
        }
      }
    } catch {
      window.localStorage.removeItem(STORAGE_KEY);
    } finally {
      setHydrated(true);
    }
  }, []);

  useEffect(() => {
    if (!hydrated) return;
    window.localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
  }, [hydrated, state]);

  const currentQuestion = runQuestions[currentIndex];
  const currentChapterQuestions = currentQuestion
    ? runQuestions.filter(
        (question) => question.chapterId === currentQuestion.chapterId
      )
    : [];
  const currentChapterPosition = currentQuestion
    ? currentChapterQuestions.findIndex(
        (question) => question.id === currentQuestion.id
      ) + 1
    : 0;

  const completedModules = useMemo(
    () =>
      chapters
        .filter((chapter) => {
          if (chapter.module === "online") return false;
          const selected = runQuestions.filter(
            (question) => question.chapterId === chapter.id
          );
          return (
            selected.length > 0 &&
            selected.every((question) => state.completed.includes(question.id))
          );
        })
        .map((chapter) => chapter.module),
    [runQuestions, state.completed]
  );

  const progress =
    runQuestions.length === 0
      ? 0
      : Math.round((state.completed.length / runQuestions.length) * 100);
  const isCompleted = currentQuestion
    ? state.completed.includes(currentQuestion.id)
    : false;
  const hintUsed = currentQuestion
    ? state.hints.includes(currentQuestion.id)
    : false;
  // Letzte Position des aktuellen Kapitels. Nach einer Wiedervorlage steht der
  // Cursor mitten im Kapitel, deshalb taugt currentIndex nicht als Sprungbrett
  // zum nächsten Kapitel.
  const lastIndexOfChapter = currentQuestion
    ? runQuestions.reduce(
        (last, question, index) =>
          question.chapterId === currentQuestion.chapterId ? index : last,
        currentIndex
      )
    : currentIndex;
  const nextQuestion = runQuestions[currentIndex + 1];
  const chapterEnds =
    !nextQuestion || nextQuestion.chapterId !== currentQuestion?.chapterId;
  const nextLabel = chapterEnds
    ? "Kapitel abschließen →"
    : "Nächste Frage →";
  const results = useMemo(
    () => chapterResults(chapters, runQuestions, state),
    [runQuestions, state]
  );
  const maxXp = useMemo(
    () => runQuestions.reduce((total, question) => total + question.xp, 0),
    [runQuestions]
  );
  const leaderboardScore = calculateLeaderboardScore({
    xp: state.xp,
    maxXp,
    integrity: state.integrity
  });

  function startGame(mode) {
    setState(freshState(mode));
    setCurrentIndex(0);
    setShowCompletion(false);
    setChapterCelebrationId(null);
  }

  function useHint() {
    setState((current) => ({
      ...current,
      hints: current.hints.includes(currentQuestion.id)
        ? current.hints
        : [...current.hints, currentQuestion.id]
    }));
  }

  function wrongAttempt() {
    const outcome = registerWrongAnswer(state, {
      mission: currentQuestion,
      chapterId: currentQuestion.chapterId
    });
    setState(outcome.state);
    return outcome;
  }

  // Bewusst getrennt von wrongAttempt: beim dritten Fehler soll die Fehlerbox
  // mit der Lösung erst gelesen werden können. Würde das Overlay sofort
  // gesetzt, ersetzte es Puzzle im selben Render und die Lösung der dritten
  // Frage bliebe unsichtbar.
  function triggerStrikeReset() {
    setStrikeResetChapterId(currentQuestion.chapterId);
  }

  function solveQuestion() {
    setState((current) => {
      if (current.completed.includes(currentQuestion.id)) return current;
      const gainedXp = calculateMissionScore({
        baseXp: currentQuestion.xp,
        wrongAttempts: Number(current.attempts[currentQuestion.id] ?? 0),
        hintUsed: current.hints.includes(currentQuestion.id)
      });
      return {
        ...current,
        completed: [...current.completed, currentQuestion.id],
        xp: current.xp + gainedXp
      };
    });
  }

  function goToIndex(index) {
    setCurrentIndex(index);
    setVisit((count) => count + 1);
    window.scrollTo({ top: 0, behavior: "auto" });
  }

  function nextMission() {
    // Offene Fragen des Kapitels zuerst — der Erfolgsmoment kommt erst, wenn
    // wirklich alle gelöst sind. Ohne diese Suche liefe der Cursor nach einer
    // Wiedervorlage erneut durch längst gelöste Fragen.
    const openIndex = nextOpenIndexInChapter(
      runQuestions,
      state.completed,
      currentQuestion.chapterId,
      currentIndex
    );
    if (openIndex !== -1) {
      goToIndex(openIndex);
      return;
    }

    // Nichts mehr offen im Kapitel. Im Nachlesemodus eines fertigen Laufs
    // blättert man weiter, sonst ist das Kapitel gerade fertig geworden —
    // auch dann, wenn positionell noch gelöste Fragen folgen.
    const runComplete = state.completed.length === runQuestions.length;
    if (runComplete && !chapterEnds) {
      goToIndex(currentIndex + 1);
      return;
    }
    setChapterCelebrationId(currentQuestion.chapterId);
  }

  function applyStrikeReset() {
    const openIndex = nextOpenIndexInChapter(
      runQuestions,
      state.completed,
      strikeResetChapterId
    );
    setState((current) => ({ ...current, strikes: 0 }));
    setStrikeResetChapterId(null);
    goToIndex(openIndex === -1 ? currentIndex : openIndex);
  }

  function continueAfterChapter() {
    setChapterCelebrationId(null);
    if (lastIndexOfChapter >= runQuestions.length - 1) {
      setShowCompletion(true);
      return;
    }
    goToIndex(lastIndexOfChapter + 1);
  }

  function selectChapter(chapterId) {
    const chapterIndexes = runQuestions
      .map((question, index) => ({ question, index }))
      .filter(({ question }) => question.chapterId === chapterId);
    const firstOpen = chapterIndexes.find(
      ({ question }) => !state.completed.includes(question.id)
    );
    goToIndex(firstOpen?.index ?? chapterIndexes[0]?.index ?? 0);
    setShowCompletion(false);
    setChapterCelebrationId(null);
  }

  function resetGame() {
    const confirmed = window.confirm(
      "Fortschritt wirklich löschen und einen neuen Lauf starten?"
    );
    if (!confirmed) return;
    window.localStorage.removeItem(STORAGE_KEY);
    setState({ ...EMPTY_STATE });
    setCurrentIndex(0);
    setShowCompletion(false);
    setChapterCelebrationId(null);
  }

  if (!hydrated) {
    return (
      <main className="loading-screen">
        <span>LOADING QUESTION BANK …</span>
      </main>
    );
  }

  if (!state.started || !currentQuestion) {
    return <StartScreen onStart={startGame} />;
  }

  if (strikeResetChapterId) {
    const chapter = chapters.find((item) => item.id === strikeResetChapterId);
    const result = results.find((item) => item.id === strikeResetChapterId);

    if (chapter && result) {
      // result.mistakes zählt nur gelöste Fragen. Beim Strike-Reset ist meist
      // keine gelöst, deshalb hier die Fehlversuche aller Kapitelfragen.
      const chapterMistakes = runQuestions
        .filter((question) => question.chapterId === strikeResetChapterId)
        .reduce(
          (total, question) =>
            total + Number(state.attempts[question.id] ?? 0),
          0
        );

      return (
        <StrikeReset
          chapter={chapter}
          result={result}
          mistakes={chapterMistakes}
          onRestart={applyStrikeReset}
        />
      );
    }
  }

  if (chapterCelebrationId) {
    const chapter = chapters.find(
      (item) => item.id === chapterCelebrationId
    );
    const result = results.find((item) => item.id === chapterCelebrationId);

    if (chapter && result) {
      return (
        <ChapterSuccess
          chapter={chapter}
          result={result}
          isFinal={lastIndexOfChapter >= runQuestions.length - 1}
          onContinue={continueAfterChapter}
        />
      );
    }
  }

  if (showCompletion) {
    return (
      <Completion
        xp={state.xp}
        integrity={state.integrity}
        score={leaderboardScore}
        runId={state.runId}
        runQuestionIds={state.runQuestionIds}
        mode={state.mode}
        startedAt={state.startedAt}
        outcomes={runQuestions.map((question) => [
          question.id,
          Number(state.attempts[question.id] ?? 0),
          state.hints.includes(question.id)
        ])}
        modeLabel={RUN_MODES[state.mode]?.label ?? "Kurslauf"}
        questionCount={runQuestions.length}
        results={results}
        sheet={buildStudySheet({
          modeLabel: RUN_MODES[state.mode]?.label ?? "Kurslauf",
          runQuestions,
          state,
          results,
          score: leaderboardScore,
          createdAt: new Date().toISOString().slice(0, 10)
        })}
        onReview={() => {
          setShowCompletion(false);
          setCurrentIndex(0);
        }}
        onReset={resetGame}
      />
    );
  }

  return (
    <main className="game-shell">
      <header className="game-header">
        <button
          type="button"
          className="brand-button"
          onClick={() => setCurrentIndex(0)}
          aria-label="Zur ersten Frage"
        >
          <span className="brand-mark">$&gt;_</span>
          <span>
            <strong>KERNEL PANIC</strong>
            <small>{RUN_MODES[state.mode]?.label.toUpperCase()}</small>
          </span>
        </button>

        <div className="system-stats" aria-label="Spielstatus">
          <div>
            <span>BUILD</span>
            <strong>{progress}%</strong>
          </div>
          <div>
            <span>FRAGEN</span>
            <strong>
              {state.completed.length}/{runQuestions.length}
            </strong>
          </div>
          <div>
            <span>XP</span>
            <strong>{state.xp}</strong>
          </div>
          <div>
            <span>INTEGRITÄT</span>
            <strong>{state.integrity}%</strong>
          </div>
          <button type="button" onClick={resetGame}>
            Reset
          </button>
        </div>
        <div className="progress-track" aria-hidden="true">
          <span style={{ width: `${progress}%` }} />
        </div>
      </header>

      <div className="game-layout">
        <MissionRail
          chapters={chapters}
          runQuestions={runQuestions}
          completed={state.completed}
          currentChapterId={currentQuestion.chapterId}
          onSelect={selectChapter}
        />

        <Puzzle
          key={`${currentQuestion.id}-${visit}`}
          mission={currentQuestion}
          solution={solutionText(currentQuestion)}
          strikeLimit={STRIKE_LIMIT}
          completed={isCompleted}
          hintUsed={hintUsed}
          positionLabel={`Frage ${currentChapterPosition}/${currentChapterQuestions.length} · Gesamt ${currentIndex + 1}/${runQuestions.length}`}
          onUseHint={useHint}
          onWrong={wrongAttempt}
          onStrikeReset={triggerStrikeReset}
          onSuccess={solveQuestion}
          onNext={nextMission}
          nextLabel={nextLabel}
        />

        <SystemBlueprint
          completedModules={completedModules}
          activeModule={currentQuestion.module}
          online={state.completed.length === runQuestions.length}
        />
      </div>

      <footer className="game-footer">
        <span>Projektarbeit · André Stiebitz · 2026</span>
        <span>
          <a
            href="/kernel-panic/kernel-panic-projektarbeit.pdf"
            target="_blank"
          >
            Projekt-PDF
          </a>
          <a href="/kernel-panic/kernel-panic-lpic-projekt.zip" download>
            Projekt-ZIP
          </a>
          <a
            href="https://codeberg.org/andremadstop/lpic-kernel-panic"
            target="_blank"
            rel="noreferrer"
          >
            Codeberg · MIT
          </a>
          <a
            href="https://www.lpi.org/de/exam-101-102-objectives/"
            target="_blank"
            rel="noreferrer"
          >
            LPI-Lernziele
          </a>
        </span>
      </footer>
    </main>
  );
}
