import { z } from "zod";
import { GameConfig } from "../types";
import { ImportedGameDefinition, RESERVED_GAME_IDS } from "../types/gameImport";

const scoringCategorySchema = z.object({
  id: z.string(),
  label: z.string(),
  icon: z.string().max(40).optional(),
  color: z.string().max(30).optional(),
});

const gameOptionSchema = z.object({
  id: z.string(),
  label: z.string(),
  type: z.enum(["boolean", "number", "select"]),
  defaultValue: z.any(),
  options: z
    .array(z.object({ label: z.string(), value: z.union([z.string(), z.number()]) }))
    .optional(),
});

const scoreFormulaSchema = z.discriminatedUnion("strategy", [
  z.object({ strategy: z.literal("sum_rounds") }),
  z.object({
    strategy: z.literal("sum_rounds_with_reset"),
    resetThreshold: z.number(),
    resetTo: z.number(),
    oncePerPlayer: z.boolean().optional(),
  }),
]);

const targetScoreSchema = z.discriminatedUnion("type", [
  z.object({ type: z.literal("fixed"), value: z.number() }),
  z.object({
    type: z.literal("fromOption"),
    optionId: z.string(),
    customOptionId: z.string().optional(),
    customSentinel: z.string().optional(),
  }),
]);

export const importedGameSchema = z.object({
  schemaVersion: z.literal(1),
  id: z
    .string()
    .min(1)
    .regex(/^[a-z0-9_-]+$/i, "id: lettres, chiffres, _ ou - uniquement")
    .refine((id) => !RESERVED_GAME_IDS.includes(id), {
      message: "cet identifiant est réservé à un jeu natif",
    }),
  name: z.string().min(1).max(60),
  minPlayers: z.number().int().min(1).max(20),
  maxPlayers: z.number().int().min(1).max(20),
  scoreType: z.enum([
    "positive",
    "negative",
    "target",
    "fixed_rounds",
    "sudden_death",
  ]),
  scoreFormula: scoreFormulaSchema,
  targetScore: targetScoreSchema.optional(),
  targetScoreCondition: z.enum(["reaches", "exceeds"]).optional(),
  fixedRounds: z.number().int().positive().optional(),
  scoringCategories: z.array(scoringCategorySchema).optional(),
  options: z.array(gameOptionSchema).optional(),
  description: z.string().max(400).optional(),
  colors: z.object({
    primary: z.string(),
    secondary: z.string().optional(),
  }),
  icon: z.string().max(40).optional(),
  iconImage: z.string().optional(),
  bannerImage: z.string().optional(),
});

// Parse + validate a raw JSON string into a game definition (throws on error).
export function parseImportedGame(raw: string): ImportedGameDefinition {
  let data: unknown;
  try {
    data = JSON.parse(raw);
  } catch {
    throw new Error("Fichier JSON invalide.");
  }
  const result = importedGameSchema.safeParse(data);
  if (!result.success) {
    const first = result.error.issues[0];
    throw new Error(
      `Définition invalide : ${first.path.join(".") || "?"} — ${first.message}`,
    );
  }
  return result.data as ImportedGameDefinition;
}

// Translate a declarative definition into a runtime GameConfig. The generated
// closures come from a fixed, reviewed set of strategies — never user code.
export function toGameConfig(def: ImportedGameDefinition): GameConfig {
  const config: GameConfig = {
    id: def.id,
    name: def.name,
    minPlayers: def.minPlayers,
    maxPlayers: def.maxPlayers,
    scoreType: def.scoreType,
    targetScoreCondition: def.targetScoreCondition,
    fixedRounds: def.fixedRounds,
    scoringCategories: def.scoringCategories,
    options: def.options,
    description: def.description,
    colors: def.colors,
    icon: def.icon,
    imagePath: def.iconImage,
    bannerPath: def.bannerImage,
  };

  // Target score
  if (def.targetScore) {
    const spec = def.targetScore;
    if (spec.type === "fixed") {
      config.targetScore = spec.value;
    } else {
      config.getTargetScore = (options) => {
        const raw = options?.[spec.optionId];
        if (
          spec.customSentinel !== undefined &&
          raw === spec.customSentinel &&
          spec.customOptionId
        ) {
          return parseInt(options?.[spec.customOptionId] ?? "0", 10);
        }
        return parseInt(raw ?? "0", 10);
      };
    }
  }

  // Score aggregation
  if (def.scoreFormula.strategy === "sum_rounds_with_reset") {
    const { resetThreshold, resetTo, oncePerPlayer } = def.scoreFormula;
    config.calculatePlayerTotal = (rounds, playerId) => {
      let total = 0;
      let used = false;
      for (const round of rounds) {
        const pScore = round.scores.find((s) => s.playerId === playerId);
        total += pScore?.score || 0;
        if (total === resetThreshold && (!oncePerPlayer || !used)) {
          total = resetTo;
          used = true;
        }
      }
      return total;
    };
  }
  // "sum_rounds" -> leave calculatePlayerTotal undefined (default sum)

  return config;
}
