Skip to content
import { define } from "../../../utils.ts";
import { CARDS } from "../../../lib/game.ts";
import { getHero, saveHeroProgress } from "../../../lib/heroes.ts";
import {
  isValidProgression,
  levelUpCost,
  parseProgression,
  progressionChange,
  SKILL_IDS,
  skillRankCost,
} from "../../../lib/progression.ts";

export const handler = define.handlers({
  async POST(ctx) {
    const user = ctx.state.user;
    if (!user) return new Response("Unauthorized", { status: 401 });
    const hero = await getHero(user.id);
    if (!hero) return Response.json({ error: "No hero." }, { status: 404 });

    let body: unknown;
    try {
      body = await ctx.req.json();
    } catch {
      return Response.json({ error: "Invalid JSON." }, { status: 400 });
    }
    const {
      anima,
      level,
      progression,
      deck,
      expectedAnima,
      expectedLevel,
      expectedProgression,
      expectedDeck,
    } = (body ?? {}) as {
      anima?: unknown;
      level?: unknown;
      progression?: unknown;
      deck?: unknown;
      expectedAnima?: unknown;
      expectedLevel?: unknown;
      expectedProgression?: unknown;
      expectedDeck?: unknown;
    };
    if (
      typeof anima !== "number" || typeof level !== "number" ||
      !Array.isArray(deck) || typeof expectedAnima !== "number" ||
      typeof expectedLevel !== "number" || !Array.isArray(expectedDeck) ||
      !progression || typeof progression !== "object" ||
      !expectedProgression || typeof expectedProgression !== "object"
    ) {
      return Response.json({ error: "Invalid payload." }, { status: 400 });
    }
    const validCard = (id: string) =>
      CARDS[id.startsWith("+") ? id.slice(1) : id];
    if (
      [...deck, ...expectedDeck].some((c) =>
        typeof c !== "string" || !validCard(c)
      )
    ) {
      return Response.json({ error: "Unknown card in deck." }, { status: 400 });
    }

    const nextDeck = deck as string[];
    const previousDeck = expectedDeck as string[];
    if (
      expectedAnima !== hero.anima || expectedLevel !== hero.level ||
      JSON.stringify(previousDeck) !== JSON.stringify(hero.deck)
    ) {
      return Response.json(
        { error: "Game state changed. Reload before saving again." },
        { status: 409 },
      );
    }
    const previousProgression = parseProgression(
      expectedProgression,
      expectedLevel,
    );
    const nextProgression = parseProgression(progression, level);
    if (
      JSON.stringify(previousProgression) !==
        JSON.stringify(hero.progression) ||
      !isValidProgression(level, nextProgression)
    ) {
      return Response.json({ error: "Invalid progression." }, { status: 400 });
    }
    const progressionTransition = progressionChange(
      previousProgression,
      nextProgression,
    );
    const changed = nextDeck.flatMap((id, index) =>
      id === previousDeck[index] ? [] : [index]
    );
    const appended = nextDeck.length === previousDeck.length + 1 &&
      nextDeck.slice(0, -1).every((id, index) => id === previousDeck[index]);
    const upgraded = nextDeck.length === previousDeck.length &&
      changed.length === 1 &&
      nextDeck[changed[0]] === `+${previousDeck[changed[0]]}`;
    const unchanged = nextDeck.length === previousDeck.length &&
      changed.length === 0;
    const addedId = appended ? nextDeck.at(-1)! : null;
    const addedCard = addedId && !addedId.startsWith("+")
      ? CARDS[addedId]
      : null;
    const classAllowed = Boolean(
      addedCard &&
        (addedCard.klass === "any" || addedCard.klass === hero.class),
    );
    const sameProgression = progressionTransition === "unchanged";
    const learnedSkill = progressionTransition === "skill"
      ? SKILL_IDS.find((id) =>
        nextProgression.skills[id] !== previousProgression.skills[id]
      )
      : undefined;
    const levelUp = expectedLevel < 999 && level === expectedLevel + 1 &&
      anima === expectedAnima - levelUpCost(expectedLevel) && unchanged &&
      sameProgression;
    const cardUpgrade = level === expectedLevel && upgraded &&
      sameProgression &&
      anima === expectedAnima - (20 + 10 * expectedLevel);
    const reward = level === expectedLevel && appended && classAllowed &&
      sameProgression &&
      anima === expectedAnima;
    const progressOnly = level === expectedLevel && unchanged &&
      sameProgression &&
      anima >= expectedAnima;
    const attributeSpent = level === expectedLevel && unchanged &&
      anima === expectedAnima && progressionTransition === "attribute";
    const learnedSkillCost = learnedSkill === undefined
      ? undefined
      : skillRankCost(previousProgression.skills[learnedSkill]);
    const skillPurchased = level === expectedLevel && unchanged &&
      learnedSkillCost !== undefined && expectedAnima >= learnedSkillCost &&
      anima === expectedAnima - learnedSkillCost;
    if (
      !levelUp && !cardUpgrade && !reward && !progressOnly &&
      !attributeSpent && !skillPurchased
    ) {
      return Response.json({ error: "Invalid progress transition." }, {
        status: 400,
      });
    }

    const saved = await saveHeroProgress(user.id, {
      anima,
      level,
      progression: nextProgression,
      deck: nextDeck,
      expectedAnima,
      expectedLevel,
      expectedProgression: previousProgression,
      expectedDeck: expectedDeck as string[],
    });
    if (!saved) {
      return Response.json(
        { error: "Game state changed. Reload before saving again." },
        { status: 409 },
      );
    }
    return Response.json({ ok: true });
  },
});