import { z } from 'zod';
import {
  abilityScoresSchema,
  conditionSchema,
  currencySchema,
  hpSchema,
  int,
  proficiencyRankSchema,
  systemIdSchema,
} from './common';

export const characterKindSchema = z.enum(['pc', 'npc']);

export const inventoryItemSchema = z.object({
  id: z.string(),
  name: z.string().min(1).max(120),
  quantity: int.min(0).default(1),
  /** weight per unit, in lbs (5e) / Bulk*10 abstracted (pf2e) */
  weight: z.number().finite().nonnegative().default(0),
  equipped: z.boolean().default(false),
  attuned: z.boolean().default(false),
  description: z.string().max(4000).default(''),
  /** link back to a compendium item slug */
  compendiumRef: z.string().optional(),
});
export type InventoryItem = z.infer<typeof inventoryItemSchema>;

/** When a tracked resource refreshes. */
export const recoverySchema = z.enum(['short', 'long', 'daily', 'none']);

export const resourceSchema = z.object({
  id: z.string(),
  name: z.string().min(1).max(80),
  current: int.min(0).default(0),
  max: int.min(0).default(0),
  recovery: recoverySchema.default('long'),
});
export type CharacterResource = z.infer<typeof resourceSchema>;

export const abilityKeySchema = z.enum(['str', 'dex', 'con', 'int', 'wis', 'cha']);

/** Worn body armor, snapshotted from the compendium so AC is computed correctly. */
export const equippedArmorSchema = z.object({
  name: z.string().max(120),
  category: z.enum(['light', 'medium', 'heavy', 'shield', 'unarmored']),
  baseAc: int,
  /** null = no Dex cap (light); 0 = Dex ignored (heavy) */
  dexCap: int.nullable(),
  stealthDisadvantage: z.boolean().default(false),
});
export type EquippedArmor = z.infer<typeof equippedArmorSchema>;

/** A spell's saving throw, snapshotted from the compendium at link-time. */
export const spellSaveSchema = z.object({
  ability: abilityKeySchema,
  basis: z.enum(['negates', 'half']),
});

export const spellEntrySchema = z.object({
  id: z.string(),
  name: z.string().min(1).max(120),
  /** 0 = cantrip */
  level: int.min(0).max(10).default(0),
  prepared: z.boolean().default(false),
  compendiumRef: z.string().optional(),
  notes: z.string().max(2000).default(''),
  // --- mechanics, snapshotted from the compendium so combat needs no async load ---
  concentration: z.boolean().default(false),
  save: spellSaveSchema.nullable().default(null),
  damage: z.array(z.object({ dice: z.string(), type: z.string() })).default([]),
});
export type SpellEntry = z.infer<typeof spellEntrySchema>;

/** Build a spell entry with all mechanics fields defaulted. Callers override what they know. */
export function newSpellEntry(
  partial: Pick<SpellEntry, 'id' | 'name'> & Partial<SpellEntry>,
): SpellEntry {
  return {
    level: 0, prepared: false, notes: '', concentration: false, save: null, damage: [],
    ...partial,
  };
}

export const spellSlotSchema = z.object({
  level: int.min(1).max(10),
  max: int.min(0).default(0),
  current: int.min(0).default(0),
});

/** A feat (5e) or class/ancestry feat (pf2e), tracked with its effect text. */
export const featSchema = z.object({
  id: z.string(),
  name: z.string().min(1).max(120),
  source: z.string().max(120).default(''),
  description: z.string().max(4000).default(''),
  compendiumRef: z.string().optional(),
});
export type Feat = z.infer<typeof featSchema>;

export const spellcastingSchema = z.object({
  slots: z.array(spellSlotSchema).default([]),
  /** warlock-style pact magic, refreshes on short rest */
  pact: spellSlotSchema.optional(),
  spells: z.array(spellEntrySchema).default([]),
});
export type Spellcasting = z.infer<typeof spellcastingSchema>;

export const attackSchema = z.object({
  id: z.string(),
  name: z.string().min(1).max(120),
  ability: abilityKeySchema.default('str'),
  rank: proficiencyRankSchema.default('trained'),
  damageDice: z.string().max(40).default('1d6'),
  damageType: z.string().max(40).default(''),
  itemBonus: int.default(0),
  addAbilityToDamage: z.boolean().default(true),
});
export type Attack = z.infer<typeof attackSchema>;

export const defensesSchema = z.object({
  // 5e
  deathSaves: z
    .object({ successes: int.min(0).max(3).default(0), failures: int.min(0).max(3).default(0) })
    .default({ successes: 0, failures: 0 }),
  inspiration: z.boolean().default(false),
  exhaustion: int.min(0).max(6).default(0),
  // pf2e
  dying: int.min(0).max(4).default(0),
  wounded: int.min(0).default(0),
  doomed: int.min(0).default(0),
  heroPoints: int.min(0).default(0),
});
export type Defenses = z.infer<typeof defensesSchema>;

export const characterSchema = z.object({
  id: z.string(),
  campaignId: z.string(),
  system: systemIdSchema,
  kind: characterKindSchema.default('pc'),

  name: z.string().min(1).max(120),
  /** square portrait as a data URL (downscaled); doubles as the map-token icon */
  portrait: z.string().optional(),
  /** ancestry (PF2e) / race (5e), free text for MVP */
  ancestry: z.string().max(80).default(''),
  /** class, free text for MVP */
  className: z.string().max(80).default(''),
  level: int.min(1).max(20).default(1),

  abilities: abilityScoresSchema,
  hp: hpSchema,
  speed: int.nonnegative().default(30),
  /** shield / misc AC bonus on top of worn armor */
  armorBonus: int.default(0),
  /** worn body armor (sets AC base + Dex cap); null = unarmored */
  equippedArmor: equippedArmorSchema.nullable().default(null),

  skillRanks: z.record(z.string(), proficiencyRankSchema).default({}),
  saveRanks: z.record(z.string(), proficiencyRankSchema).default({}),
  perceptionRank: proficiencyRankSchema.default('trained'),
  spellcastingRank: proficiencyRankSchema.optional(),
  spellcastingAbility: z.enum(['int', 'wis', 'cha']).optional(),

  // --- Phase 1: character depth ---
  currency: currencySchema.default({ cp: 0, sp: 0, ep: 0, gp: 0, pp: 0 }),
  inventory: z.array(inventoryItemSchema).default([]),
  feats: z.array(featSchema).default([]),
  attacks: z.array(attackSchema).default([]),
  resources: z.array(resourceSchema).default([]),
  spellcasting: spellcastingSchema.default({ slots: [], spells: [] }),
  /** the single spell this character is concentrating on, if any (enforced in combat) */
  concentration: z
    .object({ spellId: z.string(), spellName: z.string(), startedRound: int.optional() })
    .nullable()
    .default(null),
  defenses: defensesSchema.default({
    deathSaves: { successes: 0, failures: 0 },
    inspiration: false,
    exhaustion: 0,
    dying: 0,
    wounded: 0,
    doomed: 0,
    heroPoints: 0,
  }),
  conditions: z.array(conditionSchema).default([]),

  notes: z.string().max(20000).default(''),

  createdAt: z.string(),
  updatedAt: z.string(),
});

export type Character = z.infer<typeof characterSchema>;

export const characterDraftSchema = characterSchema.pick({
  name: true,
  kind: true,
  ancestry: true,
  className: true,
  level: true,
});
export type CharacterDraft = z.infer<typeof characterDraftSchema>;

/** Default values for the Phase-1 depth fields. Used by create + DB migration. */
export function characterDefaults(): Pick<
  Character,
  'currency' | 'inventory' | 'feats' | 'attacks' | 'resources' | 'spellcasting' | 'concentration' | 'equippedArmor' | 'defenses' | 'conditions'
> {
  return {
    currency: { cp: 0, sp: 0, ep: 0, gp: 0, pp: 0 },
    inventory: [],
    feats: [],
    attacks: [],
    resources: [],
    spellcasting: { slots: [], spells: [] },
    concentration: null,
    equippedArmor: null,
    defenses: {
      deathSaves: { successes: 0, failures: 0 },
      inspiration: false,
      exhaustion: 0,
      dying: 0,
      wounded: 0,
      doomed: 0,
      heroPoints: 0,
    },
    conditions: [],
  };
}
