import type { PartyRole, RpgRoguelikeAction, RpgRoguelikeRunState } from "./types";
import { canRemovePartyMember } from "./run";
import {
  MAX_ACTIVE_ROSTER,
  MAX_EQUIPPED_SPELLS,
  PARTY_DRAFT_WAVE_COUNT,
  PARTY_RECRUITS_PER_WAVE,
  SPELL_DRAFT_WAVE_COUNT,
  SPELL_PICKS_PER_WAVE,
} from "./types";

export type RpgUiCommand =
  | { readonly type: "run-action"; readonly action: RpgRoguelikeAction }
  | { readonly type: "restart-run" }
  | { readonly type: "exit-run" };

export interface RpgFocusItem {
  readonly id: string;
  readonly label: string;
  readonly command: RpgUiCommand;
}

export type RpgFocusDirection = "left" | "right" | "up" | "down";

export interface PartyRolePresentation {
  readonly label: "Tank" | "DPS";
  readonly icon: "⬡" | "⚔";
  readonly className: "tank" | "damage";
}

const PARTY_ROLE_PRESENTATIONS: Record<PartyRole, PartyRolePresentation> = {
  Tank: { label: "Tank", icon: "⬡", className: "tank" },
  Damage: { label: "DPS", icon: "⚔", className: "damage" },
};

/** Player-facing role copy used by party cards, selected-party lists, and controller labels. */
export function partyRolePresentation(role: PartyRole): PartyRolePresentation {
  return PARTY_ROLE_PRESENTATIONS[role];
}

/** Compact composition summary for draft surfaces where duplicate tanks must be obvious. */
export function partyCompositionLabel(members: readonly { readonly role: PartyRole }[]): string {
  const tankCount = members.reduce((count, member) => count + (member.role === "Tank" ? 1 : 0), 0);
  const damageCount = members.length - tankCount;
  return `${tankCount} ${tankCount === 1 ? "Tank" : "Tanks"} · ${damageCount} DPS`;
}

export const rpgFocusId = {
  partyOffer: (candidateId: string) => `party-offer:${candidateId}`,
  partyMember: (memberId: string) => `party-member:${memberId}`,
  partyContinue: "party-continue",
  spellOffer: (spellId: string) => `spell-offer:${spellId}`,
  spellSelected: (spellId: string) => `spell-selected:${spellId}`,
  spellContinue: "spell-continue",
  challengeStart: "challenge-start",
  bossStart: "boss-start",
  rewardOpen: "reward-open",
  rewardChoice: (choiceId: string) => `reward:${choiceId}`,
  shopOffer: (offerId: string) => `shop-offer:${offerId}`,
  shopSell: (itemId: string) => `shop-sell:${itemId}`,
  shopRest: "shop-rest",
  shopRevive: (memberId: string) => `shop-revive:${memberId}`,
  shopLeave: "shop-leave",
  restartRun: "restart-run",
  exitRun: "exit-run",
} as const;

function action(id: string, label: string, runAction: RpgRoguelikeAction): RpgFocusItem {
  return { id, label, command: { type: "run-action", action: runAction } };
}

function canAdvancePartyDraft(state: RpgRoguelikeRunState): boolean {
  const draft = state.partyDraft;
  if (!draft) return false;
  if (draft.waveIndex === PARTY_DRAFT_WAVE_COUNT - 1) {
    return state.roster.length === MAX_ACTIVE_ROSTER && state.roster.some((member) => member.role === "Tank");
  }
  const remainingWaves = PARTY_DRAFT_WAVE_COUNT - draft.waveIndex - 1;
  return state.roster.length + remainingWaves * PARTY_RECRUITS_PER_WAVE >= MAX_ACTIVE_ROSTER;
}

function canAdvanceSpellDraft(state: RpgRoguelikeRunState): boolean {
  const draft = state.spellDraft;
  return Boolean(draft && (draft.waveIndex < SPELL_DRAFT_WAVE_COUNT - 1 || state.selectedSpellIds.length > 0));
}

/** Canonical ordered semantic targets shared by controller and both displays. */
export function rpgFocusItems(state: RpgRoguelikeRunState): RpgFocusItem[] {
  if (state.phase === "party-draft" && state.partyDraft) {
    const draft = state.partyDraft;
    const canRemove = draft.waveIndex > 0;
    const canRecruit = state.roster.length < MAX_ACTIVE_ROSTER
      && draft.recruitedThisWaveIds.length < PARTY_RECRUITS_PER_WAVE;
    const offerItems = draft.offers.flatMap((candidate) => {
      const recruited = state.roster.some((member) => member.instanceId === candidate.candidateId);
      if ((recruited && !canRemovePartyMember(state, candidate.candidateId)) || (!recruited && !canRecruit)) return [];
      return [action(
        rpgFocusId.partyOffer(candidate.candidateId),
        `${recruited ? "Remove" : "Recruit"} ${candidate.name}, ${partyRolePresentation(candidate.role).label}`,
        recruited
          ? { type: "party-remove", memberId: candidate.candidateId }
          : { type: "party-recruit", candidateId: candidate.candidateId },
      )];
    });
    const rosterItems = canRemove ? state.roster.filter((member) => canRemovePartyMember(state, member.instanceId)).map((member) => action(
      rpgFocusId.partyMember(member.instanceId),
      `Remove ${member.name}, ${partyRolePresentation(member.role).label}`,
      { type: "party-remove", memberId: member.instanceId },
    )) : [];
    return [
      ...offerItems,
      ...rosterItems,
      ...(canAdvancePartyDraft(state)
        ? [action(rpgFocusId.partyContinue, "Next party draft", { type: "party-next-wave" })]
        : []),
    ];
  }

  if (state.phase === "spell-draft" && state.spellDraft) {
    const draft = state.spellDraft;
    const canRemove = draft.waveIndex > 0;
    const canPick = state.selectedSpellIds.length < MAX_EQUIPPED_SPELLS
      && draft.pickedThisWaveIds.length < SPELL_PICKS_PER_WAVE;
    const offerItems = draft.offers.flatMap((spellId) => {
      const selected = state.selectedSpellIds.includes(spellId);
      if ((selected && !canRemove) || (!selected && !canPick)) return [];
      return [action(
        rpgFocusId.spellOffer(spellId),
        `${selected ? "Remove" : "Learn"} ${spellId}`,
        selected ? { type: "spell-remove", spellId } : { type: "spell-pick", spellId },
      )];
    });
    const selectedItems = canRemove ? state.selectedSpellIds.map((spellId) => action(
      rpgFocusId.spellSelected(spellId),
      `Remove ${spellId}`,
      { type: "spell-remove", spellId },
    )) : [];
    return [
      ...offerItems,
      ...selectedItems,
      ...(canAdvanceSpellDraft(state)
        ? [action(rpgFocusId.spellContinue, "Next spell draft", { type: "spell-next-wave" })]
        : []),
    ];
  }

  if (state.phase === "challenge-briefing" && state.currentChallenge) {
    return [action(rpgFocusId.challengeStart, "Start challenge", { type: "challenge-start" })];
  }
  if (state.phase === "boss-briefing") {
    return [action(rpgFocusId.bossStart, "Enter boss room", { type: "boss-start" })];
  }
  if (state.phase === "boss-cleared") {
    return [action(rpgFocusId.rewardOpen, "Open reward chest", { type: "reward-open" })];
  }
  if (state.phase === "reward" && state.pendingReward) {
    return state.pendingReward.choices.map((choice) => action(
      rpgFocusId.rewardChoice(choice.id),
      `Choose ${choice.label}`,
      { type: "reward-choose", choiceId: choice.id },
    ));
  }
  if (state.phase === "shop" && state.shop) {
    const items: RpgFocusItem[] = state.shop.offers.flatMap((offer) => (
      !offer.sold && state.currency >= offer.price
        ? [action(rpgFocusId.shopOffer(offer.id), `Buy ${offer.item.name}`, { type: "shop-buy", offerId: offer.id })]
        : []
    ));
    items.push(...state.bag.map((item) => action(
      rpgFocusId.shopSell(item.id),
      `Sell ${item.name}`,
      { type: "shop-sell", itemId: item.id },
    )));
    const needsRest = state.playerHp > 0 && state.playerHp < 100
      || state.roster.some((member) => member.hp > 0 && member.hp < member.stats.maxHp);
    if (needsRest && state.currency >= state.shop.restCost) {
      items.push(action(rpgFocusId.shopRest, "Rest party", { type: "shop-rest" }));
    }
    if (state.currency >= state.shop.reviveCost) {
      items.push(...state.roster.filter((member) => member.hp <= 0).map((member) => action(
        rpgFocusId.shopRevive(member.instanceId),
        `Revive ${member.name}`,
        { type: "shop-revive", memberId: member.instanceId },
      )));
    }
    items.push(action(rpgFocusId.shopLeave, "Leave shop", { type: "shop-leave" }));
    return items;
  }
  if (state.phase === "victory" || state.phase === "defeat") {
    return [
      { id: rpgFocusId.restartRun, label: "Start a new run", command: { type: "restart-run" } },
      { id: rpgFocusId.exitRun, label: "Return to mode select", command: { type: "exit-run" } },
    ];
  }
  return [];
}

export function resolveRpgFocusCommand(
  state: RpgRoguelikeRunState,
  focusId: string | null | undefined,
): RpgUiCommand | null {
  const items = rpgFocusItems(state);
  return items.find((item) => item.id === focusId)?.command ?? items[0]?.command ?? null;
}

export function normalizeRpgFocusId(
  state: RpgRoguelikeRunState,
  focusId: string | null | undefined,
): string | null {
  const items = rpgFocusItems(state);
  return items.some((item) => item.id === focusId) ? focusId ?? null : items[0]?.id ?? null;
}

function rpgFocusRows(state: RpgRoguelikeRunState, items: readonly RpgFocusItem[]): string[][] {
  const enabled = new Set(items.map((item) => item.id));
  const keep = (ids: readonly string[]) => ids.filter((id) => enabled.has(id));
  if (state.phase === "party-draft" && state.partyDraft) {
    return [
      keep(state.partyDraft.offers.map((offer) => rpgFocusId.partyOffer(offer.candidateId))),
      keep(state.roster.map((member) => rpgFocusId.partyMember(member.instanceId))),
      keep([rpgFocusId.partyContinue]),
    ].filter((row) => row.length > 0);
  }
  if (state.phase === "spell-draft" && state.spellDraft) {
    return [
      keep(state.spellDraft.offers.map((spellId) => rpgFocusId.spellOffer(spellId))),
      keep(state.selectedSpellIds.map((spellId) => rpgFocusId.spellSelected(spellId))),
      keep([rpgFocusId.spellContinue]),
    ].filter((row) => row.length > 0);
  }
  if (state.phase === "shop" && state.shop) {
    return [
      keep(state.shop.offers.map((offer) => rpgFocusId.shopOffer(offer.id))),
      keep([
        ...state.bag.map((item) => rpgFocusId.shopSell(item.id)),
        rpgFocusId.shopRest,
        ...state.roster.map((member) => rpgFocusId.shopRevive(member.instanceId)),
      ]),
      keep([rpgFocusId.shopLeave]),
    ].filter((row) => row.length > 0);
  }
  return items.length ? [items.map((item) => item.id)] : [];
}

/** Four-direction semantic navigation for card grids and stacked action groups. */
export function moveRpgFocus(
  state: RpgRoguelikeRunState,
  focusId: string | null | undefined,
  direction: RpgFocusDirection,
): string | null {
  const items = rpgFocusItems(state);
  if (!items.length) return null;
  const current = normalizeRpgFocusId(state, focusId) ?? items[0].id;
  const rows = rpgFocusRows(state, items);
  const rowIndex = rows.findIndex((row) => row.includes(current));
  if (rowIndex < 0) return items[0].id;
  const row = rows[rowIndex];
  const columnIndex = row.indexOf(current);
  if (direction === "left" || direction === "right") {
    const offset = direction === "left" ? -1 : 1;
    return row[(columnIndex + offset + row.length) % row.length];
  }
  const targetRowIndex = rowIndex + (direction === "up" ? -1 : 1);
  if (targetRowIndex < 0 || targetRowIndex >= rows.length) return current;
  const targetRow = rows[targetRowIndex];
  const proportionalColumn = row.length <= 1
    ? 0
    : Math.round(columnIndex / (row.length - 1) * Math.max(0, targetRow.length - 1));
  return targetRow[Math.min(targetRow.length - 1, proportionalColumn)];
}
