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,
} from "../api";
import type { Character, Gear, Talent, GameItem, RollResult } from "../types";
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 SelectDropdown from "../components/SelectDropdown";
import styles from "./CampaignView.module.css";

const CLASSES = ["Fighter", "Priest", "Thief", "Wizard"];
const ANCESTRIES = ["Human", "Elf", "Dwarf", "Halfling", "Goblin", "Half-Orc"];

export default function CampaignView() {
  const { id } = useParams<{ id: string }>();
  const campaignId = Number(id);
  const [characters, setCharacters] = useState<Character[]>([]);
  const [selectedId, setSelectedId] = useState<number | null>(null);
  const [showCreate, setShowCreate] = useState(false);
  const [newChar, setNewChar] = useState({
    name: "",
    class: "Fighter",
    ancestry: "Human",
    hp_max: 1,
  });
  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);

  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);
    getCampaigns().then((camps) => {
      const c = camps.find((x) => x.id === campaignId);
      if (c) setCampaignName(c.name);
    });
    socket.emit("join-campaign", String(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);
      }
    }

    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);
    function onAtmosphereUpdate(data: AtmosphereState) {
      setAtmosphere(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);

    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);
    };
  }, []);

  async function handleCreate(e: React.FormEvent) {
    e.preventDefault();
    if (!newChar.name.trim()) return;
    try {
      await createCharacter(campaignId, newChar);
      setNewChar({ name: "", class: "Fighter", ancestry: "Human", hp_max: 1 });
      setShowCreate(false);
    } catch (err) {
      console.error("Failed to create character:", err);
    }
  }

  async function handleHpChange(characterId: number, hp: number) {
    await updateCharacter(characterId, { hp_current: hp });
  }

  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);
  }

  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}>
            <AtmospherePanel
              atmosphere={atmosphere}
              onAtmosphereChange={handleAtmosphereChange}
            />
            <button
              className={styles.addBtn}
              onClick={() => setShowCreate(true)}
            >
              + Add Character
            </button>
          </div>
        </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}
            />
          ))}
        </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)}
          />
        )}

        {showCreate && (
          <div
            className={styles.createModal}
            onClick={() => setShowCreate(false)}
          >
            <form
              className={styles.createForm}
              onClick={(e) => e.stopPropagation()}
              onSubmit={handleCreate}
            >
              <div className={styles.createTitle}>New Character</div>
              <div className={styles.formField}>
                <label className={styles.formLabel}>Name</label>
                <input
                  className={styles.formInput}
                  type="text"
                  value={newChar.name}
                  onChange={(e) =>
                    setNewChar({ ...newChar, name: e.target.value })
                  }
                  autoFocus
                />
              </div>
              <div className={styles.formField}>
                <label className={styles.formLabel}>Class</label>
                <SelectDropdown
                  value={newChar.class}
                  options={CLASSES}
                  onChange={(v) => setNewChar({ ...newChar, class: v })}
                />
              </div>
              <div className={styles.formField}>
                <label className={styles.formLabel}>Ancestry</label>
                <SelectDropdown
                  value={newChar.ancestry}
                  options={ANCESTRIES}
                  onChange={(v) => setNewChar({ ...newChar, ancestry: v })}
                />
              </div>
              <div className={styles.formField}>
                <label className={styles.formLabel}>Max HP</label>
                <input
                  className={styles.formInput}
                  type="number"
                  min={1}
                  value={newChar.hp_max}
                  onChange={(e) =>
                    setNewChar({ ...newChar, hp_max: Number(e.target.value) })
                  }
                />
              </div>
              <div className={styles.formActions}>
                <button
                  type="button"
                  className={`${styles.formBtn} ${styles.formBtnSecondary}`}
                  onClick={() => setShowCreate(false)}
                >
                  Cancel
                </button>
                <button
                  type="submit"
                  className={`${styles.formBtn} ${styles.formBtnPrimary}`}
                >
                  Create
                </button>
              </div>
            </form>
          </div>
        )}
      </div>
      <RollLog campaignId={campaignId} rolls={rolls} freshIds={freshIds} />
      <DiceTray roll={diceRoll} onAnimationComplete={handleDiceComplete} />
      <FogOverlay active={atmosphere.fog.active} />
      <ThreeFireOverlay active={atmosphere.fire.active} intensity={atmosphere.fire.intensity} />
      <ParticleOverlay atmosphere={atmosphere} />
    </div>
  );
}
