import { useCallback, useEffect, useMemo, useState, type CSSProperties } from "react";
import { useProgress } from "@react-three/drei";
import { classById, raceById } from "../app/characterCatalog";
import { useShellStore } from "../app/shellStore";
import { abilityAtLevel, resourceProfileForClass } from "../game/abilityCatalog";
import { ACTION_CONTROLS, actionBindingId } from "../game/actionBindings";
import { useCombatStore } from "../game/combatStore";
import { requireDungeonDefinition } from "../game/dungeonRegistry";
import { resolveStagePresentation } from "../game/manastormStagePresentation";
import { useManastormStore } from "../game/manastormStore";
import { usePartyStore } from "../game/partyStore";
import { PLAYER_AGGRO_ID } from "../game/aggro";
import { threatMeterRows } from "../game/threatMeter";
import { useGameStore } from "../game/store";
import { ActionBar, type ActionBarItem, type ActionBarItems } from "./ActionBar";
import { ExperienceBar } from "./ExperienceBar";
import { Minimap } from "./Minimap";
import { PlayerFrame } from "./PlayerFrame";
import { PartyFrames } from "./PartyFrames";
import { TimedEffectStrip } from "./TimedEffectStrip";
import { AuraStrip } from "./AuraStrip";
import { ManastormStatus } from "./ManastormStatus";
import { ThreatMeterWidget } from "./ThreatMeterWidget";
import { BossLootWindow } from "./BossLootWindow";

function LoadingOverlay() {
  const activeDungeonId = useGameStore((state) => state.activeDungeonId);
  const gameMode = useGameStore((state) => state.gameMode);
  const encounter = useManastormStore((state) => state.currentEncounter);
  const dungeon = requireDungeonDefinition(activeDungeonId);
  const assetStatus = useGameStore((state) => state.assetStatus);
  const { active, progress, item } = useProgress();

  if (assetStatus !== "checking" && assetStatus !== "loading") return null;
  const networkComplete = !active && progress >= 100;
  const value = active || networkComplete ? Math.max(3, Math.round(progress)) : 3;
  const statusText = active && item
    ? "Streaming dungeon geometry"
    : networkComplete
      ? "Finalizing collision and lighting"
      : "Reading expedition assets";
  const stagePresentation = resolveStagePresentation(gameMode, encounter, {
    title: dungeon.title,
    mapId: dungeon.mapId,
    areaName: dungeon.title,
  });
  return (
    <div className="loading-screen" role="status" aria-live="polite">
      <div className="loading-screen__mark" aria-hidden="true">HM</div>
      <p className="eyebrow">{stagePresentation.title} · Map {stagePresentation.mapId}</p>
      <h1>Healer Man</h1>
      <div className="loading-screen__track"><span style={{ width: `${value}%` }} /></div>
      <p className="loading-screen__item">{statusText} - {value}%</p>
    </div>
  );
}

export function Hud() {
  const character = useShellStore((state) => state.activeCharacter);
  const gameMode = useGameStore((state) => state.gameMode);
  const areaName = useGameStore((state) => state.areaName);
  const distance = useGameStore((state) => state.distanceTravelled);
  const playerPosition = useGameStore((state) => state.playerPosition);
  const cameraYaw = useGameStore((state) => state.cameraYaw);
  const hasMoved = useGameStore((state) => state.hasMoved);
  const cameraLookActive = useGameStore((state) => state.cameraLookActive);
  const inputMode = useGameStore((state) => state.inputMode);
  const actionLayer = useGameStore((state) => state.actionLayer);
  const assetStatus = useGameStore((state) => state.assetStatus);
  const assetMessage = useGameStore((state) => state.assetMessage);
  const activeDungeonId = useGameStore((state) => state.activeDungeonId);
  const dungeon = requireDungeonDefinition(activeDungeonId);
  const encounter = useManastormStore((state) => state.currentEncounter);
  const toggleMap = useGameStore((state) => state.toggleMap);
  const classId = useCombatStore((state) => state.classId);
  const level = useCombatStore((state) => state.level);
  const xp = useCombatStore((state) => state.xp);
  const xpToNext = useCombatStore((state) => state.xpToNext);
  const health = useCombatStore((state) => state.health);
  const maxHealth = useCombatStore((state) => state.maxHealth);
  const shield = useCombatStore((state) => state.shield);
  const resource = useCombatStore((state) => state.resource);
  const maxResource = useCombatStore((state) => state.maxResource);
  const resourcePools = useCombatStore((state) => state.resourcePools);
  const resourceName = useCombatStore((state) => state.resourceName);
  const abilities = useCombatStore((state) => state.abilities);
  const talentRanks = useCombatStore((state) => state.talentRanks);
  const actionBindings = useCombatStore((state) => state.actionBindings);
  const cooldowns = useCombatStore((state) => state.cooldowns);
  const globalCooldownEndsAt = useCombatStore((state) => state.globalCooldownEndsAt);
  const activeCast = useCombatStore((state) => state.activeCast);
  const feedback = useCombatStore((state) => state.feedback);
  const target = useCombatStore((state) => state.selectedTargetId ? state.mobs[state.selectedTargetId] ?? null : null);
  const friendlyTarget = usePartyStore((state) => state.selectedMemberId
    ? state.members.find((member) => member.id === state.selectedMemberId) ?? null
    : null);
  const selfSelected = usePartyStore((state) => state.selfSelected);
  const partyMembers = usePartyStore((state) => state.members);
  const playerRole = usePartyStore((state) => state.playerRole);
  const settings = useCombatStore((state) => state.settings);
  const [clock, setClock] = useState(() => Date.now());

  useEffect(() => {
    const timer = window.setInterval(() => setClock(Date.now()), 100);
    return () => window.clearInterval(timer);
  }, []);

  const updateThreatMeterPosition = useCallback((threatMeterPosition: typeof settings.threatMeterPosition) => {
    useCombatStore.getState().updateSettings({ threatMeterPosition });
  }, []);

  const classColor = classById(classId).color;
  const resourceTone = resourceProfileForClass(classId).type;
  const actionItems = useMemo<ActionBarItems>(() => Object.fromEntries(ACTION_CONTROLS.map((control) => {
    const abilityId = actionBindings[actionBindingId(actionLayer, control)];
    const catalogAbility = abilityId ? abilities.find((candidate) => candidate.id === abilityId) ?? null : null;
    if (!catalogAbility) return [control, null];
    const learnedTalentRank = catalogAbility.talentEntryId === undefined
      ? undefined
      : talentRanks[`coa-entry-${catalogAbility.talentEntryId}`] ?? 0;
    const ability = abilityAtLevel(catalogAbility, level, learnedTalentRank);
    const talentLocked = ability.talentEntryId !== undefined && !learnedTalentRank;
    const personalRemaining = Math.max(0, (cooldowns[ability.id] ?? 0) - clock);
    const globalRemaining = Math.max(0, globalCooldownEndsAt - clock);
    const remaining = Math.max(personalRemaining, globalRemaining);
    const abilityCosts = ability.costs?.length ? ability.costs : [ability.cost];
    const cannotPay = abilityCosts.some((cost) => cost.resource === "health"
      ? health <= cost.amount
      : (resourcePools[cost.resource] ?? (cost.resource === resourceTone ? resource : 0)) < cost.amount);
    const displayCost = abilityCosts[0] ?? ability.cost;
    const item: ActionBarItem = {
      id: ability.id,
      name: ability.name,
      shortLabel: ability.sigil,
      iconUrl: ability.icon,
      description: ability.description,
      color: classColor,
      resourceCost: displayCost.amount,
      resourceLabel: displayCost.resource === resourceTone ? resourceName : displayCost.resource.replace("-", " "),
      cooldownRemaining: remaining / 1_000,
      cooldownDuration: (personalRemaining >= globalRemaining ? ability.cooldownMs : ability.gcdMs) / 1_000,
      disabled: talentLocked || level < ability.unlockLevel || health <= 0 || remaining > 0 || Boolean(activeCast) || cannotPay,
      lockedUntilLevel: level < ability.unlockLevel ? ability.unlockLevel : undefined,
      target: ability.target,
    };
    return [control, item];
  })) as ActionBarItems, [abilities, actionBindings, actionLayer, activeCast, classColor, clock, cooldowns, globalCooldownEndsAt, health, level, resource, resourceName, resourcePools, resourceTone, talentRanks]);

  const castBinding = (control: (typeof ACTION_CONTROLS)[number]) => useCombatStore.getState().castActionBinding(
    useGameStore.getState().actionLayer,
    control,
    useGameStore.getState().playerPosition,
    Date.now(),
  );
  const targetPercent = target ? Math.max(0, Math.min(100, target.health / target.maxHealth * 100)) : 0;
  const threatRows = target ? threatMeterRows(
    target.threatByActor,
    [
      {
        id: PLAYER_AGGRO_ID,
        name: character?.name ?? "You",
        role: playerRole,
        alive: health > 0,
      },
      ...partyMembers.map((member) => ({
        id: member.id,
        name: member.name,
        role: member.role,
        alive: member.health > 0,
      })),
    ],
    target.targetActorId,
    target.forcedTarget,
    clock,
  ) : [];
  const selectedFriendly = selfSelected
    ? {
        name: character?.name ?? "Adventurer",
        detail: `You · Level ${level}`,
        health,
        maxHealth,
        self: true,
      }
    : friendlyTarget
      ? {
          name: friendlyTarget.name,
          detail: `${raceById(friendlyTarget.raceId).name} · Level ${friendlyTarget.level}`,
          health: friendlyTarget.health,
          maxHealth: friendlyTarget.maxHealth,
          self: false,
        }
      : null;
  const friendlyTargetPercent = selectedFriendly
    ? Math.max(0, Math.min(100, selectedFriendly.health / selectedFriendly.maxHealth * 100))
    : 0;
  const castingAbility = activeCast ? abilities.find((ability) => ability.id === activeCast.abilityId) : null;
  const castProgress = activeCast
    ? Math.max(0, Math.min(100, (clock - activeCast.startedAt) / (activeCast.completesAt - activeCast.startedAt) * 100))
    : 0;
  const castRemaining = activeCast ? Math.max(0, activeCast.completesAt - clock) / 1_000 : 0;
  const targetCastProgress = target?.activeCast
    ? Math.max(0, Math.min(100, (clock - target.activeCast.startedAt)
      / Math.max(1, target.activeCast.completesAt - target.activeCast.startedAt) * 100))
    : 0;
  const targetCastRemaining = target?.activeCast
    ? Math.max(0, target.activeCast.completesAt - clock) / 1_000
    : 0;
  const stagePresentation = resolveStagePresentation(gameMode, encounter, {
    title: dungeon.title,
    mapId: dungeon.mapId,
    areaName,
  });

  return (
    <div className="hud" style={{ "--game-ui-scale": settings.uiScale } as CSSProperties}>
      <ManastormStatus />
      <BossLootWindow />
      {feedback?.kind === "loot" ? (
        <section className="loot-reward" role="status" aria-live="polite">
          <span aria-hidden="true">◆</span>
          <p><strong>Loot acquired</strong><small>{feedback.message}</small><em>Press I to inspect and equip</em></p>
        </section>
      ) : null}
      <PlayerFrame
        character={character ? { ...character, level } : null}
        health={{ current: health, maximum: maxHealth, label: "Health", tone: "health" }}
        resource={{ current: resource, maximum: maxResource, label: resourceName, tone: resourceTone }}
        shield={shield}
        dead={health <= 0}
      />
      <PartyFrames />

      <Minimap
        position={playerPosition}
        yaw={cameraYaw}
        areaName={stagePresentation.areaName}
        distanceTravelled={distance}
        rotationMode={settings.minimapRotation}
        onOpenMap={toggleMap}
      />

      {target && settings.showTargetFrame && (
        <section className={`target-frame ${target.boss ? "target-frame--boss" : ""}`} aria-label={`Target ${target.name}`}>
          <header><strong>{target.name}</strong><span>{target.boss ? "Boss" : `Level ${target.level}`}</span></header>
          <div className="target-frame__track"><span style={{ width: `${targetPercent}%` }} /></div>
          {target.activeCast ? (
            <div
              className={`target-frame__cast ${target.activeCast.interruptible ? "" : "target-frame__cast--locked"}`}
              aria-label={`${target.activeCast.name}, ${targetCastRemaining.toFixed(1)} seconds remaining`}
            >
              <header><strong>{target.activeCast.name}</strong><span>{targetCastRemaining.toFixed(1)}s</span></header>
              <div className="target-frame__cast-track"><span style={{ width: `${targetCastProgress}%` }} /></div>
            </div>
          ) : null}
          <TimedEffectStrip targetId={target.id} kinds={["dot"]} className="timed-effect-strip--target" />
          <AuraStrip targetId={target.id} className="aura-strip--target" />
          <small>{Math.ceil(target.health)} / {target.maxHealth}</small>
        </section>
      )}

      {target && settings.showThreatMeter && threatRows.length > 0 && (
        <ThreatMeterWidget
          targetName={target.name}
          rows={threatRows}
          position={settings.threatMeterPosition}
          onPositionChange={updateThreatMeterPosition}
        />
      )}

      {selectedFriendly && settings.showTargetFrame && (
        <section className="target-frame target-frame--friendly" aria-label={`Friendly target ${selectedFriendly.name}${selectedFriendly.self ? ", self" : ""}`}>
          <header>
            <strong>{selectedFriendly.name}</strong>
            <span>{selectedFriendly.detail}</span>
          </header>
          <div className="target-frame__track"><span style={{ width: `${friendlyTargetPercent}%` }} /></div>
          <TimedEffectStrip
            targetId={selectedFriendly.self ? null : friendlyTarget?.id ?? null}
            className="timed-effect-strip--target"
          />
          <AuraStrip
            targetId={selectedFriendly.self ? PLAYER_AGGRO_ID : friendlyTarget?.id ?? PLAYER_AGGRO_ID}
            className="aura-strip--target"
          />
          <small>{Math.ceil(selectedFriendly.health)} / {selectedFriendly.maxHealth}</small>
        </section>
      )}

      <div className="hud-bottom-stack">
        {activeCast && castingAbility && (
          <section className={`cast-bar cast-bar--${activeCast.mode}`} aria-label={`${activeCast.mode === "channel" ? "Channeling" : "Casting"} ${castingAbility.name}`}>
            <header><strong>{castingAbility.name}</strong><span>{castRemaining.toFixed(1)}s</span></header>
            <div className="cast-bar__track">
              <span style={{ width: `${activeCast.mode === "channel" ? 100 - castProgress : castProgress}%` }} />
            </div>
            <small>{activeCast.mode === "channel" ? "Channeling" : "Casting"}</small>
          </section>
        )}

        {!activeCast && (
          <div className={`control-hint ${hasMoved && cameraLookActive ? "control-hint--settled" : ""}`}>
            {inputMode === "gamepad"
              ? "Left stick move  |  L1 / L2 layers  |  Face / R1 / R2 / D-pad abilities  |  R3 target  |  Start pause"
              : cameraLookActive
                ? "Release right mouse for cursor  |  WASD move  |  Arrow keys look  |  1-8 abilities  |  Shift / Alt layers"
                : "Right mouse / Arrow keys look  |  Click party frames to target  |  WASD move  |  Tab enemy"}
          </div>
        )}

        <ActionBar
          items={actionItems}
          activeLayer={actionLayer}
          onActivate={castBinding}
          inputMode={inputMode}
          feedback={settings.showFloatingCombatText ? feedback?.message ?? null : null}
        />
      </div>
      <ExperienceBar level={level} current={xp} required={xpToNext} />

      {cameraLookActive && <span className="reticle" aria-hidden="true" />}

      {assetStatus === "proxy" && (
        <div className="asset-notice" role="status">
          <strong>Cave proxy active</strong>
          <span>{assetMessage ?? `Add ${[
            ...dungeon.assets.visual,
            ...dungeon.assets.collision,
          ].map((asset) => asset.fileName).join(" and ")}.`}</span>
        </div>
      )}
      <LoadingOverlay />
    </div>
  );
}
