import { z } from 'zod';
import type { Character } from '@/lib/schemas';
import type { CampaignContext, CreatureCandidate } from './context';

// Counts are coerced (models routinely return numbers as JSON strings, e.g. "2")
// and allow a wider upper bound than the prompt asks for, so a slightly
// over-eager suggestion is accepted rather than rejecting the whole response.
const monsterCount = z.coerce.number().int().min(1).max(12);

/** An array of at least one `{ name, … }` object — the shape of an additions list. */
function looksLikeCreatureList(v: unknown): v is Record<string, unknown>[] {
  return Array.isArray(v) && v.length > 0 &&
    v.every((x) => !!x && typeof x === 'object' && typeof (x as { name?: unknown }).name === 'string');
}

/**
 * Models keep inventing their own name for the additions array — DeepSeek has
 * returned "creatures", "addCreatures", … instead of the expected "add". Rather
 * than chase a fixed alias list, find the additions array structurally: a known
 * synonym first, else any other top-level array of `{ name, … }` objects that
 * isn't the "remove" list. Keeps a correct suggestion from being discarded over a
 * key-name mismatch.
 */
function normalizeBalanceShape(raw: unknown): unknown {
  if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return raw;
  const o = { ...(raw as Record<string, unknown>) };
  if (Array.isArray(o.add)) return o;

  for (const alias of ['creatures', 'monsters', 'additions', 'addCreatures', 'add_creatures', 'toAdd']) {
    if (looksLikeCreatureList(o[alias])) { o.add = o[alias]; return o; }
  }
  for (const [key, value] of Object.entries(o)) {
    if (key === 'add' || key === 'remove') continue;
    if (looksLikeCreatureList(value)) { o.add = value; return o; }
  }
  return o;
}

export const balanceSuggestionSchema = z.preprocess(normalizeBalanceShape, z.object({
  reasoning: z.string(),
  add: z.array(z.object({
    name: z.string(),
    count: monsterCount,
    ref: z.string().optional(),
  })),
  /** Monsters to take out when the fight is over-tuned (already at/above target). */
  remove: z.array(z.object({
    name: z.string(),
    count: monsterCount,
  })).optional(),
  targetDifficulty: z.string(),
}));
export type BalanceSuggestion = z.infer<typeof balanceSuggestionSchema>;

function partyLine(ctx: CampaignContext): string {
  return ctx.party.map((p) => `${p.name} (level ${p.level} ${p.className})`).join(', ') || 'no PCs recorded';
}

/**
 * Prompt for rebalancing the current encounter. The system message leads with the
 * grounding constraint and restricts choices to the provided candidate list.
 */
export function buildBalancePrompt(
  ctx: CampaignContext,
  current: { difficulty: string; targetDifficulty: string; candidates: CreatureCandidate[] },
): { system: string; user: string } {
  const system =
    `${ctx.systemConstraint}\n\n` +
    `You are an encounter-balancing assistant for a ${ctx.systemLabel} game master. ` +
    `Recommend which creatures to add to the current encounter to bring it to roughly "${current.targetDifficulty}" difficulty. ` +
    `You MUST choose only from the provided candidate creatures (by exact name). Do not invent creatures or use any from another system. ` +
    `Prefer 1–3 distinct creatures with sensible counts.\n\n` +
    `Respond with a JSON object with EXACTLY these keys:\n` +
    `  "add": array of objects, each { "name": <exact candidate name>, "count": <integer ≥ 1> }\n` +
    `  "reasoning": a one or two sentence string\n` +
    `  "targetDifficulty": the target difficulty as a string\n` +
    `Name the array "add" — not "creatures" or "monsters". Counts are plain integers (2, not "2").`;

  const candidateList = current.candidates
    .map((c) => `- ${c.name} (rating ${c.rating}${c.hp ? `, hp ${c.hp}` : ''}${c.ac ? `, ac ${c.ac}` : ''})`)
    .join('\n');

  const user =
    `Party: ${partyLine(ctx)}.\n` +
    `Current encounter difficulty: ${current.difficulty}.\n` +
    `Target difficulty: ${current.targetDifficulty}.\n\n` +
    `Candidate creatures (choose only from these):\n${candidateList}\n\n` +
    `Return the JSON object described above (keys: "add", "reasoning", "targetDifficulty").`;

  return { system, user };
}

// ---------------- Level-up advisor ----------------

export const buildRoutesSchema = z.object({
  routes: z.array(z.object({
    title: z.string(),
    summary: z.string(),
    playstyle: z.string(),
  })).min(3).max(4),
});
export type BuildRoute = z.infer<typeof buildRoutesSchema>['routes'][number];

export const buildStepsSchema = z.object({
  steps: z.array(z.object({ label: z.string(), detail: z.string() })).min(1),
});
export type BuildStep = z.infer<typeof buildStepsSchema>['steps'][number];

function charLine(c: Character): string {
  const abilities = Object.entries(c.abilities).map(([k, v]) => `${k.toUpperCase()} ${v}`).join(', ');
  return `${c.name}, a level ${c.level} ${c.className || 'adventurer'} (${abilities})`;
}

export function buildLevelUpRoutesPrompt(ctx: CampaignContext, c: Character, nextLevel: number): { system: string; user: string } {
  const system =
    `${ctx.systemConstraint}\n\n` +
    `You are a character-building advisor for ${ctx.systemLabel}. Propose 4 distinct build routes for the next level. ` +
    `Each route needs a short title, a one-sentence summary, and a one-phrase playstyle. ` +
    `Use only ${ctx.systemLabel} mechanics, feats, and class options — never another system's.`;
  const user = `Character: ${charLine(c)}. Leveling up to ${nextLevel}. Suggest 4 build routes.`;
  return { system, user };
}

export function buildLevelUpStepsPrompt(ctx: CampaignContext, c: Character, nextLevel: number, chosenRoute: string): { system: string; user: string } {
  const system =
    `${ctx.systemConstraint}\n\n` +
    `You are a character-building advisor for ${ctx.systemLabel}. Give concrete, ordered steps to pursue the chosen build route at this level. ` +
    `Each step has a short label and a one-sentence detail. Reference only ${ctx.systemLabel} options (feats, ability boosts, spells, skill increases as appropriate). Keep to 3–5 steps.`;
  const user = `Character: ${charLine(c)}. Leveling up to ${nextLevel}. Chosen route: "${chosenRoute}". How should they achieve it?`;
  return { system, user };
}
