import { useEffect, useRef, useState } from "react";
import { useParams, Link } from "react-router-dom";
import socket from "../socket";
import {
  getCampaigns,
  getCharacters,
  createCharacter,
  updateCharacter,
  deleteCharacter,
  updateStat,
  addGear,
  removeGear,
  addTalent,
  removeTalent,
  getRolls,
  getMyCampaignRole,
  generateInvite,
  undoRoll,
} from "../api";
import { useAuth } from "../context/AuthContext";
import type { Character, Gear, Talent, GameItem, RollResult, CombatState } from "../types.js";
import InitiativeTracker from "../components/InitiativeTracker.js";
import CombatStartModal from "../components/CombatStartModal.js";
import CharacterCard from "../components/CharacterCard";
import CharacterDetail from "../components/CharacterDetail";
import RollLog from "../components/RollLog";
import DiceTray from "../components/DiceTray";
import FogOverlay from "../components/FogOverlay";
import AtmospherePanel from "../components/AtmospherePanel";
import ParticleOverlay from "../components/ParticleOverlay";
import ThreeFireOverlay from "../components/ThreeFireOverlay";
import type { AtmosphereState } from "../lib/atmosphereTypes";
import { defaultAtmosphere } from "../lib/atmosphereTypes";
import CharacterWizard from "../components/CharacterWizard";
import type { CreateCharacterData } from "../api";
import styles from "./CampaignView.module.css";

export default function CampaignView() {
  const { id } = useParams<{ id: string }>();
  const campaignId = Number(id);
  const { user } = useAuth();
  const [role, setRole] = useState<"dm" | "player" | null>(null);
  const [characters, setCharacters] = useState<Character[]>([]);
  const [selectedId, setSelectedId] = useState<number | null>(null);
  const [showCreate, setShowCreate] = useState(false);
  const [campaignName, setCampaignName] = useState("");
  const [rolls, setRolls] = useState<RollResult[]>([]);
  const [freshIds, setFreshIds] = useState<Set<number>>(new Set());
  const [critKeys, setCritKeys] = useState<Set<string>>(new Set());
  const [diceRoll, setDiceRoll] = useState<{
    expression: string;
    rolls: number[];
    characterColor?: string;
    id: number;
  } | null>(null);
  const pendingRollRef = useRef<RollResult | null>(null);
  const [atmosphere, setAtmosphere] = useState<AtmosphereState>(defaultAtmosphere);
  const [focusSpells, setFocusSpells] = useState<Map<number, string>>(new Map());
  const [combats, setCombats] = useState<CombatState[]>([]);
  const [showCombatStart, setShowCombatStart] = useState(false);

  function handleAtmosphereChange(next: AtmosphereState) {
    setAtmosphere(next);
    socket.emit("atmosphere:update", { campaignId, ...next });
  }

  // Fetch characters and join socket room
  useEffect(() => {
    getCharacters(campaignId).then(setCharacters);
    getRolls(campaignId).then(setRolls);
    getMyCampaignRole(campaignId).then((r) => setRole(r.role)).catch(() => {});
    getCampaigns().then((camps) => {
      const c = camps.find((x) => x.id === campaignId);
      if (c) setCampaignName(c.name);
    });
    socket.emit("join-campaign", String(campaignId));
    socket.emit("initiative:request-state", campaignId);
    return () => {
      socket.emit("leave-campaign", String(campaignId));
    };
  }, [campaignId]);

  function showRollInLog(roll: RollResult) {
    setRolls((prev) => [roll, ...prev].slice(0, 50));
    setFreshIds((prev) => new Set(prev).add(roll.id));
    setTimeout(() => {
      setFreshIds((prev) => {
        const next = new Set(prev);
        next.delete(roll.id);
        return next;
      });
    }, 2000);

    // Track crits by character+weapon key
    if (roll.nat20 && roll.character_id && roll.label.includes("attack")) {
      const weaponName = roll.label.replace(" attack", "");
      const key = `${roll.character_id}:${weaponName}`;
      setCritKeys((prev) => new Set(prev).add(key));
    }
    if (roll.character_id && roll.label.includes("damage")) {
      const weaponName = roll.label
        .replace(" damage", "")
        .replace(" (CRIT)", "");
      const key = `${roll.character_id}:${weaponName}`;
      setCritKeys((prev) => {
        const next = new Set(prev);
        next.delete(key);
        return next;
      });
    }
  }

  function handleDiceComplete() {
    if (pendingRollRef.current) {
      showRollInLog(pendingRollRef.current);
      pendingRollRef.current = null;
    }
  }

  // Socket event listeners
  useEffect(() => {
    function onCharacterCreated(char: Character) {
      setCharacters((prev) => {
        if (prev.some((c) => c.id === char.id)) return prev;
        return [...prev, char];
      });
    }

    function onCharacterUpdated(data: Partial<Character> & { id: number }) {
      setCharacters((prev) =>
        prev.map((c) => (c.id === data.id ? { ...c, ...data } : c)),
      );
    }

    function onCharacterDeleted({ id }: { id: number }) {
      setCharacters((prev) => prev.filter((c) => c.id !== id));
      setSelectedId((prev) => (prev === id ? null : prev));
    }

    function onStatUpdated({
      characterId,
      statName,
      value,
    }: {
      characterId: number;
      statName: string;
      value: number;
    }) {
      setCharacters((prev) =>
        prev.map((c) =>
          c.id === characterId
            ? {
                ...c,
                stats: c.stats.map((s) =>
                  s.stat_name === statName ? { ...s, value } : s,
                ),
              }
            : c,
        ),
      );
    }

    function onGearAdded({
      characterId,
      gear,
    }: {
      characterId: number;
      gear: Gear;
    }) {
      setCharacters((prev) =>
        prev.map((c) =>
          c.id === characterId
            ? { ...c, gear: [...c.gear.filter((g) => g.id !== gear.id), gear] }
            : c,
        ),
      );
    }

    function onGearRemoved({
      characterId,
      gearId,
    }: {
      characterId: number;
      gearId: number;
    }) {
      setCharacters((prev) =>
        prev.map((c) =>
          c.id === characterId
            ? { ...c, gear: c.gear.filter((g) => g.id !== gearId) }
            : c,
        ),
      );
    }

    function onTalentAdded({
      characterId,
      talent,
    }: {
      characterId: number;
      talent: Talent;
    }) {
      setCharacters((prev) =>
        prev.map((c) =>
          c.id === characterId
            ? {
                ...c,
                talents: [
                  ...c.talents.filter((t) => t.id !== talent.id),
                  talent,
                ],
              }
            : c,
        ),
      );
    }

    function onTalentRemoved({
      characterId,
      talentId,
    }: {
      characterId: number;
      talentId: number;
    }) {
      setCharacters((prev) =>
        prev.map((c) =>
          c.id === characterId
            ? { ...c, talents: c.talents.filter((t) => t.id !== talentId) }
            : c,
        ),
      );
    }

    function onRollResult(roll: RollResult) {
      // Check if this roll can trigger 3D dice
      const hasDice = /\d*d\d+/i.test(roll.dice_expression);
      if (hasDice) {
        // Queue roll — it will appear in log when dice animation finishes
        pendingRollRef.current = roll;
        setDiceRoll({
          expression: roll.dice_expression,
          rolls: roll.rolls,
          characterColor: roll.character_color,
          id: roll.id,
        });
      } else {
        // No 3D dice — show immediately
        showRollInLog(roll);
      }
    }

    function onAtmosphereUpdate(data: AtmosphereState) {
      setAtmosphere(data);
    }

    function onRollUndone({ rollId }: { rollId: number }) {
      setRolls((prev) => prev.map((r) => r.id === rollId ? { ...r, undone: true } : r));
    }

    function onCharacterRested({ characterId }: { characterId: number }) {
      // SpellList local state already updated via handleRest; suppress unused warning
      void characterId;
    }

    function onInitiativeState(data: CombatState[]) {
      setCombats(data);
    }

    function onInitiativeUpdated(data: CombatState[]) {
      setCombats(data);
    }

    socket.on("character:created", onCharacterCreated);
    socket.on("character:updated", onCharacterUpdated);
    socket.on("character:deleted", onCharacterDeleted);
    socket.on("stat:updated", onStatUpdated);
    socket.on("gear:added", onGearAdded);
    socket.on("gear:removed", onGearRemoved);
    socket.on("talent:added", onTalentAdded);
    socket.on("talent:removed", onTalentRemoved);
    socket.on("roll:result", onRollResult);
    socket.on("atmosphere:update", onAtmosphereUpdate);
    socket.on("roll:undone", onRollUndone);
    socket.on("character:rested", onCharacterRested);
    socket.on("initiative:state", onInitiativeState);
    socket.on("initiative:updated", onInitiativeUpdated);

    return () => {
      socket.off("character:created", onCharacterCreated);
      socket.off("character:updated", onCharacterUpdated);
      socket.off("character:deleted", onCharacterDeleted);
      socket.off("stat:updated", onStatUpdated);
      socket.off("gear:added", onGearAdded);
      socket.off("gear:removed", onGearRemoved);
      socket.off("talent:added", onTalentAdded);
      socket.off("talent:removed", onTalentRemoved);
      socket.off("roll:result", onRollResult);
      socket.off("atmosphere:update", onAtmosphereUpdate);
      socket.off("roll:undone", onRollUndone);
      socket.off("character:rested", onCharacterRested);
      socket.off("initiative:state", onInitiativeState);
      socket.off("initiative:updated", onInitiativeUpdated);
    };
  }, []);

  async function handleCreate(data: CreateCharacterData) {
    try {
      await createCharacter(campaignId, data);
      setShowCreate(false);
    } catch (err) {
      console.error("Failed to create character:", err);
    }
  }

  async function handleHpChange(characterId: number, hp: number) {
    const clampedHp = Math.max(0, hp);
    await updateCharacter(characterId, { hp_current: clampedHp });
  }

  async function handleStatChange(
    characterId: number,
    statName: string,
    value: number,
  ) {
    await updateStat(characterId, statName, value);
  }

  async function handleUpdate(characterId: number, data: Partial<Character>) {
    await updateCharacter(characterId, data);
  }

  async function handleDelete(characterId: number) {
    await deleteCharacter(characterId);
    setSelectedId(null);
  }

  async function handleAddGearFromItem(characterId: number, item: GameItem) {
    await addGear(characterId, {
      name: item.name,
      type: item.type,
      slot_count: item.slot_count,
      properties: item.properties,
      effects: item.effects,
      game_item_id: item.id,
    });
  }

  async function handleAddGearCustom(
    characterId: number,
    data: { name: string; type: string; slot_count: number },
  ) {
    await addGear(characterId, data);
  }

  async function handleRemoveGear(characterId: number, gearId: number) {
    await removeGear(characterId, gearId);
  }

  async function handleAddTalent(
    characterId: number,
    data: {
      name: string;
      description: string;
      effect?: Record<string, unknown>;
      game_talent_id?: number | null;
    },
  ) {
    await addTalent(characterId, data);
  }

  async function handleRemoveTalent(characterId: number, talentId: number) {
    await removeTalent(characterId, talentId);
  }

  async function handleUndoRoll(rollId: number) {
    await undoRoll(campaignId, rollId);
    setRolls((prev) => prev.map((r) => r.id === rollId ? { ...r, undone: true } : r));
  }

  async function handleInvite() {
    try {
      const { url } = await generateInvite(campaignId);
      await navigator.clipboard.writeText(url);
      alert("Invite link copied to clipboard!");
    } catch {
      alert("Failed to generate invite link");
    }
  }

  const selectedCharacter = characters.find((c) => c.id === selectedId) ?? null;

  return (
    <div className={styles.layout}>
      <div className={styles.main}>
        <div className={styles.header}>
          <Link to="/" className={styles.backLink}>
            ← Campaigns
          </Link>
          <span className={styles.campaignName}>
            {campaignName || "Campaign"}
          </span>
          <div className={styles.headerBtns}>
            {role === "dm" && (
              <AtmospherePanel
                atmosphere={atmosphere}
                onAtmosphereChange={handleAtmosphereChange}
              />
            )}
            {role === "dm" && (
              <button
                className={`${styles.addBtn} ${combats.length > 0 ? styles.addBtnActive : ""}`}
                onClick={() => setShowCombatStart(true)}
              >
                ⚔ Combat
              </button>
            )}
            {role === "dm" && (
              <button className={styles.addBtn} onClick={handleInvite}>
                Invite Player
              </button>
            )}
            <button
              className={styles.addBtn}
              onClick={() => setShowCreate(true)}
            >
              + Add Character
            </button>
          </div>
        </div>

        <div className={`${styles.content} ${combats.length > 0 ? styles.withCombat : ""}`}>
          {combats.length > 0 && (
            <div className={styles.combatSidebar}>
              <InitiativeTracker
                combat={combats[0]}
                characters={characters}
                isDM={role === "dm"}
                currentUserId={user?.userId ?? null}
                campaignId={campaignId}
              />
            </div>
          )}
          <div className={styles.grid}>
            {characters.length === 0 && (
              <p className={styles.empty}>
                No characters yet. Add one to get started!
              </p>
            )}
            {characters.map((char) => (
              <CharacterCard
                key={char.id}
                character={char}
                onHpChange={handleHpChange}
                onUpdate={handleUpdate}
                onClick={setSelectedId}
                canEdit={role === "dm" || char.user_id === user?.userId}
                focusSpell={focusSpells.get(char.id)}
                isDM={role === "dm"}
              />
            ))}
          </div>
        </div>

        {selectedCharacter && (
          <CharacterDetail
            character={selectedCharacter}
            campaignId={campaignId}
            critKeys={critKeys}
            onUpdate={handleUpdate}
            onStatChange={handleStatChange}
            onAddGearFromItem={handleAddGearFromItem}
            onAddGearCustom={handleAddGearCustom}
            onRemoveGear={handleRemoveGear}
            onAddTalent={handleAddTalent}
            onRemoveTalent={handleRemoveTalent}
            onDelete={handleDelete}
            onClose={() => setSelectedId(null)}
            canEdit={role === "dm" || selectedCharacter.user_id === user?.userId}
          />
        )}

        {showCreate && (
          <CharacterWizard
            campaignId={campaignId}
            onSubmit={handleCreate}
            onClose={() => setShowCreate(false)}
          />
        )}
        {showCombatStart && (
          <CombatStartModal
            characters={characters}
            campaignId={campaignId}
            onClose={() => setShowCombatStart(false)}
          />
        )}
      </div>
      <RollLog campaignId={campaignId} rolls={rolls} freshIds={freshIds} onUndoRoll={handleUndoRoll} />
      <DiceTray roll={diceRoll} onAnimationComplete={handleDiceComplete} />
      <FogOverlay active={atmosphere.fog.active} intensity={atmosphere.fog.intensity} />
      <ThreeFireOverlay active={atmosphere.fire.active} intensity={atmosphere.fire.intensity} />
      <ParticleOverlay atmosphere={atmosphere} />
    </div>
  );
}
