import { useEffect, useRef, type CSSProperties, type ReactNode } from "react";
import { BOSS_DEFINITIONS } from "../../game/bossCatalog";
import { HEALER_ABILITIES } from "../../game/healers";
import type { AbilitySlotId, ActiveCast, HealerAbilityId, MemberId, PartyMember } from "../../game/types";
import type {
  PartyDraftCandidate,
  PartyRosterMember,
  RewardChoice,
  RpgRoguelikeAction,
  RpgRoguelikeRunState,
  RunGearItem,
} from "../../game/rpgRoguelike";
import { BOSSES_PER_ACT, TOTAL_BOSS_COUNT, compareRunGear } from "../../game/rpgRoguelike";
import type { RpgUiCommand } from "../../game/rpgRoguelike";
import { normalizeRpgFocusId } from "../../game/rpgRoguelike";
import { PartyRoleBadge } from "./PartyRoleBadge";

export interface RpgRunUiProps {
  readonly run: RpgRoguelikeRunState;
  readonly focusedId?: string | null;
  readonly onFocusChange?: (focusId: string) => void;
  readonly onAction: (action: RpgRoguelikeAction) => void;
  readonly onRestartRun?: () => void;
  readonly onExitRun?: () => void;
  readonly className?: string;
  readonly liveCombat?: RpgLiveCombatUi;
}

export interface RpgLiveCombatUi {
  readonly party: readonly PartyMember[];
  readonly selectedMemberId: MemberId;
  readonly mana: number;
  readonly maxMana: number;
  readonly cooldowns: Readonly<Record<AbilitySlotId, number>>;
  readonly globalCooldownUntil: number;
  readonly time: number;
  readonly activeCast: ActiveCast | null;
  readonly spellResources: {
    readonly verdancy: number;
    readonly tidalSurge: number;
    readonly conviction: number;
    readonly chronoshards: number;
  };
  readonly onSelectMember?: (memberId: MemberId) => void;
  readonly onCastAbility?: (abilitySlotId: AbilitySlotId) => void;
}

export interface RpgRunUiContext extends RpgRunUiProps {
  readonly activeFocusId: string | null;
}

export function createUiContext(props: RpgRunUiProps): RpgRunUiContext {
  return { ...props, activeFocusId: normalizeRpgFocusId(props.run, props.focusedId) };
}

export function runAccentStyle(accent: string): CSSProperties {
  return { "--rpg-accent": accent } as CSSProperties;
}

export function executeUiCommand(context: RpgRunUiContext, command: RpgUiCommand): void {
  if (command.type === "run-action") context.onAction(command.action);
  else if (command.type === "restart-run") context.onRestartRun?.();
  else context.onExitRun?.();
}

interface FocusButtonProps {
  readonly context: RpgRunUiContext;
  readonly focusId: string;
  readonly command: RpgUiCommand;
  readonly className?: string;
  readonly disabled?: boolean;
  readonly pressed?: boolean;
  readonly style?: CSSProperties;
  readonly children: ReactNode;
  readonly label?: string;
}

export function FocusButton({
  context,
  focusId,
  command,
  className = "",
  disabled = false,
  pressed,
  style,
  children,
  label,
}: FocusButtonProps) {
  const selected = context.activeFocusId === focusId;
  const buttonRef = useRef<HTMLButtonElement>(null);
  useEffect(() => {
    const button = buttonRef.current;
    if (!selected || !button) return;
    let parent = button.parentElement;
    while (parent && (parent.closest(".rpg-run-overlay") || parent.closest(".rpg-run-tactical"))) {
      const childRect = button.getBoundingClientRect();
      const parentRect = parent.getBoundingClientRect();
      const overflow = getComputedStyle(parent);
      const canScrollY = /^(auto|scroll|overlay)$/.test(overflow.overflowY);
      const canScrollX = /^(auto|scroll|overlay)$/.test(overflow.overflowX);
      if (canScrollY && parent.scrollHeight > parent.clientHeight) {
        if (childRect.top < parentRect.top) parent.scrollTop -= parentRect.top - childRect.top;
        else if (childRect.bottom > parentRect.bottom) parent.scrollTop += childRect.bottom - parentRect.bottom;
      }
      if (canScrollX && parent.scrollWidth > parent.clientWidth) {
        if (childRect.left < parentRect.left) parent.scrollLeft -= parentRect.left - childRect.left;
        else if (childRect.right > parentRect.right) parent.scrollLeft += childRect.right - parentRect.right;
      }
      parent = parent.parentElement;
    }
  }, [selected]);
  return (
    <button
      ref={buttonRef}
      type="button"
      className={`${className} ${selected ? "is-controller-selected" : ""}`.trim()}
      data-rpg-focus={focusId}
      aria-current={selected ? "true" : undefined}
      aria-pressed={pressed}
      aria-label={label}
      disabled={disabled}
      style={style}
      onFocus={() => context.onFocusChange?.(focusId)}
      onPointerEnter={() => !disabled && context.onFocusChange?.(focusId)}
      onClick={() => executeUiCommand(context, command)}
    >
      {children}
    </button>
  );
}

export function phaseLabel(run: RpgRoguelikeRunState): string {
  switch (run.phase) {
    case "party-draft": return "Party Draft";
    case "spell-draft": return "Spell Draft";
    case "challenge-briefing": return "Hallway Challenge";
    case "challenge-active": return "Challenge Active";
    case "boss-briefing": return run.bossIndex === TOTAL_BOSS_COUNT - 1 ? "Final Boss" : "Boss Door";
    case "boss-combat": return run.bossIndex === TOTAL_BOSS_COUNT - 1 ? "Final Battle" : "Boss Battle";
    case "boss-cleared": return "Room Cleared";
    case "reward": return "Reward Chest";
    case "shop": return `Act ${run.shop?.act ?? 1} Intermission`;
    case "victory": return "Run Complete";
    case "defeat": return "Party Fallen";
  }
}

export function routeLabel(run: RpgRoguelikeRunState): string {
  if (run.bossIndex >= TOTAL_BOSS_COUNT - 1) return "Finale";
  return `Act ${Math.floor(run.bossIndex / BOSSES_PER_ACT) + 1} · Room ${(run.bossIndex % BOSSES_PER_ACT) + 1}`;
}

export function currentBoss(run: RpgRoguelikeRunState) {
  const bossId = run.bossRoute[run.bossIndex];
  return bossId ? BOSS_DEFINITIONS[bossId] : null;
}

export function RunHeader({ run, compact = false }: { run: RpgRoguelikeRunState; compact?: boolean }) {
  return (
    <header className={`rpg-run-header ${compact ? "is-compact" : ""}`.trim()}>
      <div>
        <small>{routeLabel(run)}</small>
        <strong>{phaseLabel(run)}</strong>
      </div>
      <div className="rpg-run-resources" aria-label={`${run.currency} gold, ${Math.ceil(run.playerHp)} healer health, ${run.bossesDefeated} bosses defeated`}>
        <span><i>◆</i>{run.currency}</span>
        <span><i>♥</i>{Math.ceil(run.playerHp)}</span>
        <span><i>♛</i>{run.bossesDefeated}/{TOTAL_BOSS_COUNT}</span>
      </div>
    </header>
  );
}

export function RoutePips({ run }: { run: RpgRoguelikeRunState }) {
  return (
    <div className="rpg-route-pips" aria-label={`${run.bossesDefeated} of ${TOTAL_BOSS_COUNT} bosses defeated`}>
      {run.bossRoute.map((bossId, index) => (
        <i
          key={`${bossId}-${index}`}
          className={`${index < run.bossesDefeated ? "is-cleared" : ""} ${index === run.bossIndex ? "is-current" : ""} ${index === TOTAL_BOSS_COUNT - 1 ? "is-finale" : ""}`.trim()}
          title={BOSS_DEFINITIONS[bossId].name}
        />
      ))}
    </div>
  );
}

function titleCase(value: string): string {
  return value.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
}

export function PartyCard({
  member,
  candidate,
  selected = false,
  compact = false,
  action,
}: {
  readonly member?: PartyRosterMember;
  readonly candidate?: PartyDraftCandidate;
  readonly selected?: boolean;
  readonly compact?: boolean;
  readonly action?: ReactNode;
}) {
  const entry = member ?? candidate;
  if (!entry) return null;
  const hp = member?.hp ?? entry.stats.maxHp;
  const hpPercent = Math.max(0, Math.min(100, (hp / entry.stats.maxHp) * 100));
  return (
    <article
      className={`rpg-party-card rarity-${entry.rarity} ${selected ? "is-picked" : ""} ${compact ? "is-compact" : ""}`.trim()}
      style={runAccentStyle(entry.color)}
    >
      <div className="rpg-card-kicker"><span>{entry.rarity}</span><PartyRoleBadge role={entry.role} /></div>
      <h3>{entry.name}</h3>
      <p>{entry.className}</p>
      {!compact && (
        <div className="rpg-stat-row" aria-label="Combat stats">
          <span><small>HP</small>{entry.stats.maxHp}</span>
          <span><small>ST</small>{entry.stats.singleTarget.toFixed(2)}</span>
          <span><small>AOE</small>{entry.stats.areaDamage.toFixed(2)}</span>
          <span><small>DEF</small>{entry.stats.defense.toFixed(2)}</span>
        </div>
      )}
      {member && (
        <div className="rpg-vital-bar" aria-label={`${Math.round(hp)} of ${entry.stats.maxHp} health`}>
          <i style={{ width: `${hpPercent}%` }} />
          <small>{hp <= 0 ? "Fallen" : `${Math.ceil(hp)} / ${entry.stats.maxHp}`}</small>
        </div>
      )}
      {!compact && entry.traitIds.length > 0 && <footer>{entry.traitIds.map(titleCase).join(" · ")}</footer>}
      {action}
    </article>
  );
}

export function SpellCard({
  spellId,
  rank = 0,
  selected = false,
  compact = false,
  action,
}: {
  readonly spellId: HealerAbilityId;
  readonly rank?: number;
  readonly selected?: boolean;
  readonly compact?: boolean;
  readonly action?: ReactNode;
}) {
  const spell = HEALER_ABILITIES[spellId];
  return (
    <article
      className={`rpg-spell-card ${selected ? "is-picked" : ""} ${compact ? "is-compact" : ""}`.trim()}
      style={runAccentStyle(spell.color)}
    >
      <i className="rpg-spell-icon">{spell.icon}</i>
      <div>
        <small>{spell.targeting} · {spell.mana} mana{rank > 0 ? ` · Rank ${rank}` : ""}</small>
        <h3>{spell.name}</h3>
        {!compact && <p>{spell.description}</p>}
      </div>
      {action}
    </article>
  );
}

export function gearOwnerName(run: RpgRoguelikeRunState, item: RunGearItem): string {
  if (item.ownerId === "player") return "Healer";
  return run.roster.find((member) => member.instanceId === item.ownerId)?.name ?? "Companion";
}

export function gearComparisonLabel(run: RpgRoguelikeRunState, item: RunGearItem): string {
  const comparison = compareRunGear(run.equipment, item);
  const ownerName = gearOwnerName(run, item);
  const currentRank = comparison.currentItem ? `plus ${comparison.currentItem.enhancement}` : "none";
  const change = comparison.delta >= 0
    ? `Gain ${comparison.delta} percentage points`
    : `Lose ${Math.abs(comparison.delta)} percentage points`;
  return `${ownerName} ${comparison.effectLabel}. Current gear: ${currentRank}, ${comparison.currentValue} percent. Replacement: plus ${item.enhancement}, ${comparison.replacementValue} percent. ${change}.`;
}

export function GearStatComparison({ run, item }: {
  readonly run: RpgRoguelikeRunState;
  readonly item: RunGearItem;
}) {
  const ownerName = gearOwnerName(run, item);
  const comparison = compareRunGear(run.equipment, item);
  const currentRank = comparison.currentItem ? `+${comparison.currentItem.enhancement}` : "none";
  const delta = `${comparison.delta >= 0 ? "+" : ""}${comparison.delta} pts`;
  return (
    <span
      className="rpg-gear-comparison"
      aria-label={gearComparisonLabel(run, item)}
    >
      <strong>{ownerName} · {comparison.effectLabel}<em>{delta}</em></strong>
      <span>
        <span><small>Current · {currentRank}</small><b>+{comparison.currentValue}%</b></span>
        <i aria-hidden="true">→</i>
        <span><small>Replacement · +{item.enhancement}</small><b>+{comparison.replacementValue}%</b></span>
      </span>
    </span>
  );
}

export function GearCard({ run, item, price, action }: {
  readonly run: RpgRoguelikeRunState;
  readonly item: RunGearItem;
  readonly price?: number;
  readonly action?: ReactNode;
}) {
  return (
    <article className="rpg-gear-card">
      <i>{item.slotId === "weapon" ? "⚔" : item.slotId === "armor" ? "⬡" : "◇"}</i>
      <div>
        <small>{gearOwnerName(run, item)} · {item.slotId}</small>
        <h3>{item.name}</h3>
        {price !== undefined && <p className="rpg-gear-price">◆ {price}</p>}
        <GearStatComparison run={run} item={item} />
      </div>
      {action}
    </article>
  );
}

export function rewardSummary(choice: RewardChoice): { icon: string; eyebrow: string; detail: string; accent: string } {
  if (choice.kind === "spell-rank") {
    const spell = HEALER_ABILITIES[choice.spellId];
    return { icon: spell.icon, eyebrow: "Spell upgrade", detail: `${spell.name} reaches rank ${choice.nextRank}.`, accent: spell.color };
  }
  if (choice.kind === "member-rarity") {
    return { icon: "♟", eyebrow: "Party upgrade", detail: `Promote companion to ${choice.nextRarity}.`, accent: "#b47aec" };
  }
  if (choice.kind === "run-gear") {
    return { icon: "◇", eyebrow: `Gear +${choice.item.enhancement}`, detail: `Equip ${choice.item.name}; displaced gear moves to bag.`, accent: "#65aef2" };
  }
  return { icon: "◆", eyebrow: "Run currency", detail: `Gain ${choice.amount} gold for this run.`, accent: "#efc858" };
}

export function ChallengeObjective({ run, compact = false }: { run: RpgRoguelikeRunState; compact?: boolean }) {
  const challenge = run.currentChallenge;
  const previous = run.lastChallengeResult;
  if (!challenge && !previous) return null;
  const objective = challenge?.objective ?? previous!.objective;
  const metrics = challenge?.metrics ?? previous!.metrics;
  const current = metrics[objective.metric];
  const progress = Math.max(0, Math.min(100, (current / objective.target) * 100));
  const copy = objective.challengeId === "blockbreaker"
    ? `Break ${objective.target} bricks before the board falls.`
    : objective.challengeId === "hockey"
      ? `Defeat ${objective.target} ${objective.target === 1 ? "boss" : "bosses"} without conceding.`
      : `Defeat ${objective.target} enemies before the assault ends.`;
  return (
    <section className={`rpg-challenge-objective ${compact ? "is-compact" : ""}`.trim()}>
      <div><small>Repeat tier {objective.repeatIndex + 1}</small><strong>{objective.name}</strong></div>
      {!compact && <p>{copy}</p>}
      <div className="rpg-objective-progress">
        <i style={{ width: `${progress}%` }} />
        <span>{current} / {objective.target}</span>
      </div>
      {!compact && <footer>Success: ◆ {objective.rewardCurrency} · chest quality +{objective.chestQualityBonus}</footer>}
    </section>
  );
}
