Skip to content
import { type CardDef, CARDS, upgradeEffect } from "../../lib/game.ts";
import { useState } from "preact/hooks";
import { MAX_DECK_SIZE, MIN_DECK_SIZE, PACK_COST } from "../../lib/cards.ts";
import {
  type HeroProgression,
  progressionPoints,
  SKILL_IDS,
  type SkillId,
  skillRankCost,
  SKILLS,
  VIGOR_PER_RANK,
} from "../../lib/progression.ts";

export const NODE_GLYPH: Record<string, string> = {
  fight: "⚔",
  elite: "✷",
  treasure: "❖",
  rest: "☾",
  boss: "♛",
};

export const NODE_LABEL: Record<string, string> = {
  fight: "Skirmish",
  elite: "Elite",
  treasure: "Treasury",
  rest: "Sanctum",
  boss: "Sovereign",
};

export function cardDef(id: string): CardDef {
  const def = CARDS[id];
  if (def) return def;
  const baseId = id.startsWith("+") ? id.slice(1) : id;
  const base = CARDS[baseId];
  return {
    ...base,
    id,
    name: `${base.name} ✦`,
    effects: upgradeEffect(base.effects),
  };
}

export function cardSummary(
  id: string,
): { label: string; color: string }[] {
  const def = cardDef(id);
  const eff = def.effects;
  const parts: { label: string; color: string }[] = [];
  if (eff.damage) {
    parts.push({
      label: `${eff.damage}${eff.hits ? `×${eff.hits}` : ""} dmg`,
      color: "badge-error",
    });
  }
  if (eff.block) {
    parts.push({ label: `${eff.block} block`, color: "badge-info" });
  }
  if (eff.poison) {
    parts.push({ label: `${eff.poison} poison`, color: "badge-success" });
  }
  if (eff.weak) {
    parts.push({ label: `${eff.weak} weak`, color: "badge-warning" });
  }
  if (eff.heal) {
    parts.push({ label: `heal ${eff.heal}`, color: "badge-success" });
  }
  if (eff.strength) {
    parts.push({ label: `+${eff.strength} strength`, color: "badge-warning" });
  }
  if (eff.evade) {
    parts.push({ label: `${eff.evade} evade`, color: "badge-secondary" });
  }
  if (eff.retaliate) {
    parts.push({ label: `retaliate ${eff.retaliate}`, color: "badge-error" });
  }
  if (eff.draw) parts.push({ label: `draw ${eff.draw}`, color: "badge-info" });
  if (eff.energyGain) {
    parts.push({ label: `+${eff.energyGain} energy`, color: "badge-primary" });
  }
  if (eff.anima) {
    parts.push({ label: `+${eff.anima} anima`, color: "badge-accent" });
  }
  if (eff.aoe) parts.push({ label: "all foes", color: "badge-neutral" });
  return parts;
}

export function CardFace(
  { id, onClick, extraClass = "" }: {
    id: string;
    onClick?: () => void;
    extraClass?: string;
  },
) {
  const def = cardDef(id);
  const Tag = onClick ? "button" : "div";
  return (
    <Tag
      onClick={onClick}
      class={`card-fan-card game-card game-card-${def.type} card card-border bg-base-100 ${
        onClick ? "cursor-pointer" : ""
      } ${extraClass}`}
    >
      <figure
        class={`card-art-frame arch-frame card-art-${def.type} ${
          def.rare ? "card-art-rare" : ""
        } relative overflow-hidden`}
      >
        <img
          src={def.art}
          alt=""
          class="aspect-video w-full object-contain p-3"
          draggable={false}
        />
        {def.rare && <span class="sr-only">Rare card</span>}
      </figure>
      <div class="corner-fleurons card-body p-2.5 gap-1">
        <div class="flex items-center justify-between gap-2">
          <span
            class={`badge badge-sm ${
              def.cost === 0 ? "badge-secondary" : "badge-primary"
            } font-bold`}
          >
            {def.cost}
          </span>
          <span class="font-heading text-[0.7rem] uppercase tracking-widest text-right">
            {def.name}
          </span>
        </div>
        <div class="flex flex-wrap gap-1 pt-0.5">
          {cardSummary(id).map((detail) => (
            <span
              key={detail.label}
              class={`badge badge-xs badge-soft ${detail.color} whitespace-nowrap`}
            >
              {detail.label}
            </span>
          ))}
        </div>
      </div>
    </Tag>
  );
}

export function DeckScreen(
  { deck, onClose }: { deck: string[]; onClose: () => void },
) {
  return (
    <main class="room-screen room-archive flex-1 min-h-0 overflow-y-auto p-4 sm:p-8">
      <div class="mx-auto max-w-4xl">
        <div class="arch-panel vellum mx-auto w-fit max-w-full px-8 pb-3">
          <h3 class="illuminated font-gothic text-2xl tracking-[0.14em] text-accent text-center">
            Your deck · {deck.length} cards
          </h3>
          <div class="fleuron-rule mt-1">
            <span aria-hidden="true">☾ ✧ ☽</span>
          </div>
        </div>
        <div class="mt-5 grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
          {deck.map((id, i) => <CardFace key={`${id}-${i}`} id={id} />)}
        </div>
        <div class="mt-6 flex justify-center">
          <button
            type="button"
            class="btn btn-primary iron-band"
            onClick={onClose}
          >
            Close
          </button>
        </div>
      </div>
    </main>
  );
}

export function ArcanaHallScreen(
  {
    deckSize,
    collectionSize,
    onViewDeck,
    onBuildDeck,
    onBuyPack,
    onClose,
  }: {
    deckSize: number;
    collectionSize: number;
    onViewDeck: () => void;
    onBuildDeck: () => void;
    onBuyPack: () => void;
    onClose: () => void;
  },
) {
  return (
    <main class="room-screen room-archive flex flex-1 min-h-0 items-center overflow-y-auto p-4 sm:p-8">
      <div class="mx-auto w-full max-w-5xl">
        <p class="text-center text-xs uppercase tracking-[0.4em] text-base-content/60">
          Cards & collection
        </p>
        <h3 class="illuminated font-gothic text-3xl tracking-[0.14em] text-accent text-center mt-2">
          Arcana Hall
        </h3>
        <div class="fleuron-rule my-4">
          <span aria-hidden="true">☾ ✦ ☽</span>
        </div>
        <div class="grid gap-4 sm:grid-cols-3">
          <button
            type="button"
            class="arch-panel vellum card text-left"
            onClick={onViewDeck}
          >
            <div class="corner-fleurons card-body">
              <span class="text-3xl text-info" aria-hidden="true">▤</span>
              <h4 class="card-title font-heading">Active Deck</h4>
              <p class="text-sm text-base-content/70">
                Review the {deckSize} cards you carry.
              </p>
            </div>
          </button>
          <button
            type="button"
            class="arch-panel vellum card text-left"
            onClick={onBuildDeck}
          >
            <div class="corner-fleurons card-body">
              <span class="text-3xl text-secondary" aria-hidden="true">✥</span>
              <h4 class="card-title font-heading">Collection</h4>
              <p class="text-sm text-base-content/70">
                Build from {collectionSize} owned cards.
              </p>
            </div>
          </button>
          <button
            type="button"
            class="arch-panel vellum card text-left"
            onClick={onBuyPack}
          >
            <div class="corner-fleurons card-body">
              <span class="text-3xl text-accent" aria-hidden="true">✦</span>
              <h4 class="card-title font-heading">Card Packs</h4>
              <p class="text-sm text-base-content/70">
                Acquire new arcana for your collection.
              </p>
            </div>
          </button>
        </div>
        <div class="mt-6 flex justify-center">
          <button type="button" class="btn btn-ghost" onClick={onClose}>
            Return to the city
          </button>
        </div>
      </div>
    </main>
  );
}

export function ProgressionScreen(
  {
    level,
    levelCost,
    anima,
    progression,
    onLevelUp,
    onImproveVigor,
    onLearnSkill,
    onClose,
  }: {
    level: number;
    levelCost: number;
    anima: number;
    progression: HeroProgression;
    onLevelUp: () => void;
    onImproveVigor: () => void;
    onLearnSkill: (skill: SkillId) => void;
    onClose: () => void;
  },
) {
  const points = progressionPoints(level, progression);
  return (
    <main class="room-screen room-keep flex-1 min-h-0 overflow-y-auto p-4 sm:p-8">
      <div class="mx-auto max-w-6xl">
        <p class="text-center text-xs uppercase tracking-[0.4em] text-base-content/60">
          Ascendant constellation
        </p>
        <h3 class="illuminated font-gothic text-3xl tracking-[0.14em] text-accent text-center mt-2">
          Shape Your Power
        </h3>
        <div class="fleuron-rule my-4">
          <span aria-hidden="true">☾ ✦ ☽</span>
        </div>

        <div class="passive-board">
          <div class="passive-level">
            <span class="passive-level-number">{level}</span>
            <div>
              <p>Hero Level</p>
              <small>Ascend to earn an attribute point</small>
            </div>
            <button
              type="button"
              class="btn btn-primary iron-band btn-sm"
              disabled={anima < levelCost || level >= 999}
              onClick={onLevelUp}
            >
              {level >= 999 ? "Mastered" : `Ascend · ${levelCost} Anima`}
            </button>
          </div>

          <div class="passive-grid">
            <button
              type="button"
              class="passive-tile passive-vigor"
              disabled={points.attribute === 0}
              onClick={onImproveVigor}
            >
              <span class="passive-icon" aria-hidden="true">♥</span>
              <div class="passive-copy">
                <strong>Vigor</strong>
                <small>+{VIGOR_PER_RANK} maximum vitality per rank</small>
              </div>
              <span class="passive-rank">Rank {progression.vigor}</span>
              <em>
                {points.attribute > 0
                  ? `${points.attribute} point available`
                  : "Ascend for points"}
              </em>
            </button>

            {SKILL_IDS.map((id) => {
              const skill = SKILLS[id];
              const current = progression.skills[id];
              const cost = skillRankCost(current);
              const mastered = current >= skill.maxRank;
              return (
                <button
                  type="button"
                  key={id}
                  class={`passive-tile passive-${id}`}
                  disabled={anima < cost || mastered}
                  onClick={() => onLearnSkill(id)}
                >
                  <span class="passive-icon" aria-hidden="true">
                    {skill.glyph}
                  </span>
                  <div class="passive-copy">
                    <strong>{skill.name}</strong>
                    <small>{skill.effect}</small>
                  </div>
                  <span
                    class="passive-pips"
                    aria-label={`${current} of ${skill.maxRank} ranks`}
                  >
                    {Array.from(
                      { length: skill.maxRank },
                      (_, rank) => (
                        <i
                          class={rank < current ? "is-filled" : ""}
                          key={rank}
                        />
                      ),
                    )}
                  </span>
                  <em>{mastered ? "Mastered" : `${cost} Anima`}</em>
                </button>
              );
            })}
          </div>
        </div>

        <div class="mt-6 flex justify-center">
          <button
            type="button"
            class="btn btn-primary iron-band"
            onClick={onClose}
          >
            Return to the city
          </button>
        </div>
      </div>
    </main>
  );
}

export function PackScreen(
  { anima, opened, buying, error, onBuy, onClose }: {
    anima: number;
    opened: string[] | null;
    buying: boolean;
    error: string | null;
    onBuy: () => void;
    onClose: () => void;
  },
) {
  return (
    <main class="room-screen room-atelier flex flex-1 min-h-0 items-center overflow-y-auto p-4 sm:p-8">
      <div class="mx-auto w-full max-w-5xl">
        <p class="text-center text-xs uppercase tracking-[0.4em] text-base-content/60">
          The Gilded Atelier
        </p>
        <h3 class="illuminated font-gothic text-3xl tracking-[0.14em] text-accent text-center mt-2">
          Arcana Pack
        </h3>
        <div class="fleuron-rule my-4">
          <span aria-hidden="true">☾ ✦ ☽</span>
        </div>
        <button
          type="button"
          class="arcana-pack card mx-auto"
          disabled={buying || anima < PACK_COST}
          onClick={onBuy}
        >
          <span class="arcana-pack-seal" aria-hidden="true" />
          <div class="arcana-pack-face">
            <p class="arcana-pack-maker font-heading">The Gilded Atelier</p>
            <div class="arcana-pack-sigil wax-seal" aria-hidden="true">
              <span>☾</span>
              <strong>✦</strong>
              <span>☽</span>
            </div>
            <div>
              <h4 class="font-display">Arcana</h4>
              <p class="font-heading">Five-card pack</p>
            </div>
            <span class="arcana-pack-rarity">
              One rare card guaranteed
            </span>
            <span class="badge badge-accent badge-lg arcana-pack-price">
              {PACK_COST} Anima
            </span>
            <small>{buying ? "Opening..." : "Click to open"}</small>
          </div>
          <span class="arcana-pack-seal" aria-hidden="true" />
        </button>
        {opened && (
          <div class="mt-6">
            <p class="text-center text-sm text-base-content/70 mb-5">
              Five cards joined your collection. Click the pack again to open
              another.
            </p>
            <div class="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-3">
              {opened.map((id, index) => (
                <CardFace
                  key={`${id}-${index}`}
                  id={id}
                  extraClass="w-full"
                />
              ))}
            </div>
          </div>
        )}
        {error && (
          <div role="alert" class="alert alert-error alert-soft mt-4">
            <span>{error}</span>
          </div>
        )}
        <div class="mt-6 flex justify-center gap-2">
          <button type="button" class="btn btn-ghost" onClick={onClose}>
            {opened ? "Return to Arcana Hall" : "Leave"}
          </button>
        </div>
        <p class="text-center text-[0.65rem] text-base-content/45 mt-4">
          Card icons by Lorc and Delapouite via{" "}
          <a
            class="link"
            href="https://game-icons.net/"
            target="_blank"
            rel="noreferrer"
          >
            Game-icons.net
          </a>{" "}
          · CC BY 3.0
        </p>
      </div>
    </main>
  );
}

export function DeckBuilderScreen(
  { deck, collection, saving, error, onSave, onClose }: {
    deck: string[];
    collection: Record<string, number>;
    saving: boolean;
    error: string | null;
    onSave: (deck: string[]) => void;
    onClose: () => void;
  },
) {
  const [draft, setDraft] = useState(deck);
  const used = draft.reduce<Record<string, number>>((counts, id) => {
    counts[id] = (counts[id] ?? 0) + 1;
    return counts;
  }, {});
  const owned = Object.entries(collection)
    .filter(([id, quantity]) =>
      quantity > 0 && (CARDS[id] || CARDS[id.slice(1)])
    )
    .sort(([a], [b]) => cardDef(a).name.localeCompare(cardDef(b).name));
  const valid = draft.length >= MIN_DECK_SIZE && draft.length <= MAX_DECK_SIZE;

  return (
    <main class="room-screen room-archive flex-1 min-h-0 overflow-y-auto p-4 sm:p-6">
      <div class="mx-auto max-w-7xl">
        <div class="flex flex-col sm:flex-row items-center justify-between gap-3">
          <div>
            <p class="text-xs uppercase tracking-[0.35em] text-base-content/60">
              The Archive
            </p>
            <h3 class="illuminated font-gothic text-3xl tracking-[0.12em] text-accent mt-1">
              Deck Builder
            </h3>
          </div>
          <span
            class={`badge badge-lg pennant mr-2 ${
              valid ? "badge-success" : "badge-warning"
            }`}
          >
            {draft.length} / {MAX_DECK_SIZE} cards
          </span>
        </div>
        <div class="fleuron-rule my-4">
          <span aria-hidden="true">☾ ✧ ☽</span>
        </div>
        <div class="grid lg:grid-cols-2 gap-6">
          <section class="frame-panel vellum px-3 pb-4 pt-4 sm:px-4">
            <h4 class="font-heading text-sm uppercase tracking-[0.2em] text-center mb-3">
              Collection · select to add
            </h4>
            <div class="grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-[58vh] overflow-y-auto pr-1">
              {owned.map(([id, quantity]) => {
                const remaining = quantity - (used[id] ?? 0);
                return (
                  <div class="relative" key={id}>
                    <CardFace
                      id={id}
                      extraClass={`w-full ${
                        remaining <= 0 ? "opacity-45" : ""
                      }`}
                      onClick={remaining > 0 && draft.length < MAX_DECK_SIZE
                        ? () => setDraft([...draft, id])
                        : undefined}
                    />
                    <span class="badge badge-neutral badge-sm pennant absolute left-1 top-1">
                      {remaining} left
                    </span>
                  </div>
                );
              })}
            </div>
          </section>
          <section class="frame-panel vellum px-3 pb-4 pt-4 sm:px-4">
            <h4 class="font-heading text-sm uppercase tracking-[0.2em] text-center mb-3">
              Active deck · select to remove
            </h4>
            <div class="grid grid-cols-2 sm:grid-cols-3 gap-2 max-h-[58vh] overflow-y-auto pr-1">
              {draft.map((id, index) => (
                <CardFace
                  key={`${id}-${index}`}
                  id={id}
                  extraClass="w-full"
                  onClick={() => setDraft(draft.filter((_, i) => i !== index))}
                />
              ))}
            </div>
          </section>
        </div>
        {!valid && (
          <div role="alert" class="alert alert-warning alert-soft mt-4">
            <span>An active deck needs at least {MIN_DECK_SIZE} cards.</span>
          </div>
        )}
        {error && (
          <div role="alert" class="alert alert-error alert-soft mt-4">
            <span>{error}</span>
          </div>
        )}
        <div class="mt-5 flex justify-end gap-2">
          <button type="button" class="btn btn-ghost" onClick={onClose}>
            Cancel
          </button>
          <button
            type="button"
            class="btn btn-primary iron-band uppercase tracking-[0.18em] font-heading"
            disabled={!valid || saving}
            onClick={() => onSave(draft)}
          >
            {saving ? "Saving..." : "Set active deck"}
          </button>
        </div>
      </div>
    </main>
  );
}

export function SanctumScreen(
  { deck, cost, affordable, onUpgrade, onClose }: {
    deck: string[];
    cost: number;
    affordable: boolean;
    onUpgrade: (index: number) => void;
    onClose: () => void;
  },
) {
  return (
    <main class="room-screen room-sanctum flex min-h-0 flex-1 overflow-hidden p-4 sm:p-6">
      <div class="mx-auto flex h-full min-h-0 w-full max-w-5xl flex-col">
        <div class="shrink-0">
          <div class="arch-panel vellum mx-auto w-fit max-w-full px-8 pb-3">
            <h3 class="illuminated font-gothic text-2xl tracking-[0.14em] text-accent text-center">
              The Sanctum
            </h3>
            <p class="text-center text-xs uppercase tracking-[0.3em] text-base-content/65">
              Refine a card · {cost} anima
            </p>
            <div class="fleuron-rule mt-1">
              <span aria-hidden="true">☾ ✧ ☽</span>
            </div>
          </div>
        </div>
        <div class="mt-4 grid min-h-0 flex-1 content-start grid-cols-2 gap-3 overflow-y-auto sm:grid-cols-3 lg:grid-cols-5">
          {deck.map((id, i) => (
            <CardFace
              key={`${id}-${i}`}
              id={id}
              onClick={affordable && !id.startsWith("+")
                ? () => onUpgrade(i)
                : undefined}
            />
          ))}
        </div>
        {!affordable && (
          <p class="text-center text-error text-sm mt-3">
            Not enough Anima.
          </p>
        )}
        <div class="mt-4 flex shrink-0 justify-center">
          <button
            type="button"
            class="btn btn-primary iron-band"
            onClick={onClose}
          >
            Leave
          </button>
        </div>
      </div>
    </main>
  );
}