import { db } from './db';
import { newId } from '@/lib/ids';
import {
  campaignSchema,
  characterSchema,
  encounterSchema,
  diceRollSchema,
  noteSchema,
  npcSchema,
  questSchema,
  calendarSchema,
  battleMapSchema,
  homebrewSchema,
  sessionLogEntrySchema,
  type SessionLogEntry,
  type HomebrewKind,
  type Homebrew,
  type Campaign,
  type CampaignDraft,
  type Character,
  type Encounter,
  type DiceRoll,
  type Note,
  type Npc,
  type Quest,
  type Calendar,
  type BattleMap,
} from '@/lib/schemas';
import { getSystem } from '@/lib/rules';
import type { ParsedUvtt } from '@/lib/vtt/uvtt';

function now(): string {
  return new Date().toISOString();
}

// ---------------- Campaigns ----------------
export const campaignsRepo = {
  async list(): Promise<Campaign[]> {
    const all = await db.campaigns.toArray();
    return all.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
  },

  get(id: string): Promise<Campaign | undefined> {
    return db.campaigns.get(id);
  },

  async create(draft: CampaignDraft): Promise<Campaign> {
    const ts = now();
    const campaign = campaignSchema.parse({
      ...draft,
      id: newId(),
      description: draft.description ?? '',
      createdAt: ts,
      updatedAt: ts,
    });
    await db.campaigns.add(campaign);
    return campaign;
  },

  async update(id: string, patch: Partial<Omit<Campaign, 'id' | 'createdAt'>>): Promise<void> {
    await db.campaigns.update(id, { ...patch, updatedAt: now() });
  },

  /** Delete a campaign and ALL its children atomically (real cascade). */
  async remove(id: string): Promise<void> {
    await db.transaction(
      'rw',
      [db.campaigns, db.characters, db.encounters, db.diceRolls, db.notes, db.npcs, db.quests, db.calendars, db.maps, db.homebrew],
      async () => {
        await db.characters.where('campaignId').equals(id).delete();
        await db.encounters.where('campaignId').equals(id).delete();
        await db.diceRolls.where('campaignId').equals(id).delete();
        await db.notes.where('campaignId').equals(id).delete();
        await db.npcs.where('campaignId').equals(id).delete();
        await db.quests.where('campaignId').equals(id).delete();
        await db.maps.where('campaignId').equals(id).delete();
        await db.homebrew.where('campaignId').equals(id).delete();
        await db.calendars.delete(id);
        await db.campaigns.delete(id);
      },
    );
  },
};

// ---------------- Characters ----------------
export const charactersRepo = {
  listByCampaign(campaignId: string): Promise<Character[]> {
    return db.characters.where('campaignId').equals(campaignId).toArray();
  },

  /** All player characters across every campaign (PCs are campaign-agnostic). */
  listAllPcs(): Promise<Character[]> {
    return db.characters.where('kind').equals('pc').toArray();
  },

  get(id: string): Promise<Character | undefined> {
    return db.characters.get(id);
  },

  /** Fetch several characters by id (missing ids are dropped). */
  async getMany(ids: string[]): Promise<Character[]> {
    if (ids.length === 0) return [];
    const rows = await db.characters.bulkGet(ids);
    return rows.filter((c): c is Character => !!c);
  },

  async create(
    campaignId: string,
    draft: Pick<Character, 'name' | 'kind' | 'ancestry' | 'className' | 'level'> & {
      system: Character['system'];
    },
  ): Promise<Character> {
    const ts = now();
    const sys = getSystem(draft.system);
    const character = characterSchema.parse({
      id: newId(),
      campaignId,
      system: draft.system,
      kind: draft.kind,
      name: draft.name,
      ancestry: draft.ancestry,
      className: draft.className,
      level: draft.level,
      abilities: sys.defaultAbilityScores(),
      hp: { current: 1, max: 1, temp: 0 },
      speed: 30,
      armorBonus: 0,
      skillRanks: {},
      saveRanks: {},
      perceptionRank: 'trained',
      notes: '',
      createdAt: ts,
      updatedAt: ts,
    });
    await db.characters.add(character);
    return character;
  },

  async update(id: string, patch: Partial<Omit<Character, 'id' | 'campaignId' | 'createdAt'>>): Promise<void> {
    await db.characters.update(id, { ...patch, updatedAt: now() });
  },

  /** Insert a fully-formed character (e.g. from an import). Validates on write. */
  async insert(character: Character): Promise<void> {
    await db.characters.add(characterSchema.parse(character));
  },

  async remove(id: string): Promise<void> {
    await db.characters.delete(id);
  },
};

// ---------------- Encounters ----------------
export const encountersRepo = {
  listByCampaign(campaignId: string): Promise<Encounter[]> {
    return db.encounters.where('campaignId').equals(campaignId).toArray();
  },

  get(id: string): Promise<Encounter | undefined> {
    return db.encounters.get(id);
  },

  async create(campaignId: string, name: string): Promise<Encounter> {
    const ts = now();
    const encounter = encounterSchema.parse({
      id: newId(),
      campaignId,
      name,
      status: 'planning',
      round: 0,
      turnIndex: 0,
      combatants: [],
      createdAt: ts,
      updatedAt: ts,
    });
    await db.encounters.add(encounter);
    return encounter;
  },

  /** Persist a full encounter (combat state lives as one document). */
  async save(encounter: Encounter): Promise<void> {
    const validated = encounterSchema.parse({ ...encounter, updatedAt: now() });
    await db.encounters.put(validated);
  },

  /**
   * Apply a transition by reading the freshest committed state inside a
   * transaction, so two rapid mutations can't overwrite each other (the
   * read-modify-write race the old app shipped — bug class C19).
   */
  async mutate(id: string, fn: (e: Encounter) => Encounter): Promise<void> {
    await db.transaction('rw', db.encounters, async () => {
      const current = await db.encounters.get(id);
      if (!current) return;
      await db.encounters.put(encounterSchema.parse({ ...fn(current), updatedAt: now() }));
    });
  },

  async remove(id: string): Promise<void> {
    await db.encounters.delete(id);
  },
};

// ---------------- Dice rolls ----------------
export const diceRepo = {
  async recent(campaignId: string | undefined, limit = 50): Promise<DiceRoll[]> {
    const coll = campaignId
      ? db.diceRolls.where('campaignId').equals(campaignId)
      : db.diceRolls.toCollection();
    const all = await coll.toArray();
    return all.sort((a, b) => b.createdAt.localeCompare(a.createdAt)).slice(0, limit);
  },

  async add(roll: Omit<DiceRoll, 'id' | 'createdAt'>): Promise<DiceRoll> {
    const record = diceRollSchema.parse({ ...roll, id: newId(), createdAt: now() });
    await db.diceRolls.add(record);
    return record;
  },

  async clear(campaignId?: string): Promise<void> {
    if (campaignId) await db.diceRolls.where('campaignId').equals(campaignId).delete();
    else await db.diceRolls.clear();
  },
};

// ---------------- Session recap (rolls + chat, GM-persisted) ----------------
export const sessionLogRepo = {
  async recent(campaignId: string, limit = 200): Promise<SessionLogEntry[]> {
    const all = await db.sessionLog.where('campaignId').equals(campaignId).toArray();
    return all.sort((a, b) => a.ts.localeCompare(b.ts)).slice(-limit);
  },
  async add(entry: Omit<SessionLogEntry, 'id' | 'ts'> & { ts?: string }): Promise<void> {
    const record = sessionLogEntrySchema.parse({ ...entry, id: newId(), ts: entry.ts ?? now() });
    await db.sessionLog.add(record);
  },
  async clear(campaignId?: string): Promise<void> {
    if (campaignId) await db.sessionLog.where('campaignId').equals(campaignId).delete();
    else await db.sessionLog.clear();
  },
};

// ---------------- Notes ----------------
export const notesRepo = {
  listByCampaign(campaignId: string): Promise<Note[]> {
    return db.notes.where('campaignId').equals(campaignId).toArray();
  },
  get(id: string): Promise<Note | undefined> {
    return db.notes.get(id);
  },
  async create(campaignId: string, title: string): Promise<Note> {
    const ts = now();
    const note = noteSchema.parse({ id: newId(), campaignId, title, body: '', tags: [], createdAt: ts, updatedAt: ts });
    await db.notes.add(note);
    return note;
  },
  async update(id: string, patch: Partial<Omit<Note, 'id' | 'campaignId' | 'createdAt'>>): Promise<void> {
    await db.notes.update(id, { ...patch, updatedAt: now() });
  },
  async remove(id: string): Promise<void> {
    await db.notes.delete(id);
  },
};

// ---------------- NPCs ----------------
export const npcsRepo = {
  listByCampaign(campaignId: string): Promise<Npc[]> {
    return db.npcs.where('campaignId').equals(campaignId).toArray();
  },
  async create(campaignId: string, name: string): Promise<Npc> {
    const ts = now();
    const npc = npcSchema.parse({
      id: newId(), campaignId, name, role: '', location: '', faction: '',
      status: 'alive', description: '', createdAt: ts, updatedAt: ts,
    });
    await db.npcs.add(npc);
    return npc;
  },
  async update(id: string, patch: Partial<Omit<Npc, 'id' | 'campaignId' | 'createdAt'>>): Promise<void> {
    await db.npcs.update(id, { ...patch, updatedAt: now() });
  },
  async remove(id: string): Promise<void> {
    await db.npcs.delete(id);
  },
};

// ---------------- Quests ----------------
export const questsRepo = {
  listByCampaign(campaignId: string): Promise<Quest[]> {
    return db.quests.where('campaignId').equals(campaignId).toArray();
  },
  async create(campaignId: string, title: string): Promise<Quest> {
    const ts = now();
    const quest = questSchema.parse({
      id: newId(), campaignId, title, status: 'active', description: '', reward: '',
      objectives: [], createdAt: ts, updatedAt: ts,
    });
    await db.quests.add(quest);
    return quest;
  },
  async update(id: string, patch: Partial<Omit<Quest, 'id' | 'campaignId' | 'createdAt'>>): Promise<void> {
    await db.quests.update(id, { ...patch, updatedAt: now() });
  },
  async remove(id: string): Promise<void> {
    await db.quests.delete(id);
  },
};

// ---------------- Calendar (one per campaign) ----------------
export const calendarRepo = {
  /** Read-only — safe to call inside a Dexie liveQuery. Missing = undefined. */
  get(campaignId: string): Promise<Calendar | undefined> {
    return db.calendars.get(campaignId);
  },
  async save(calendar: Calendar): Promise<void> {
    await db.calendars.put(calendarSchema.parse(calendar));
  },
};

// ---------------- Battle maps ----------------
export const mapsRepo = {
  listByCampaign(campaignId: string): Promise<BattleMap[]> {
    return db.maps.where('campaignId').equals(campaignId).toArray();
  },
  async create(campaignId: string, name: string, image: string): Promise<BattleMap> {
    const ts = now();
    const map = battleMapSchema.parse({
      id: newId(), campaignId, name, image, gridSize: 50, showGrid: true,
      fogEnabled: false, revealed: [], tokens: [], createdAt: ts, updatedAt: ts,
    });
    await db.maps.add(map);
    return map;
  },
  /** Create a map from a parsed Universal VTT (image + grid + walls/doors/lights). */
  async createFromVtt(campaignId: string, name: string, vtt: ParsedUvtt): Promise<BattleMap> {
    const ts = now();
    const map = battleMapSchema.parse({
      id: newId(), campaignId, name, image: vtt.image, gridSize: vtt.gridSize,
      showGrid: true, fogEnabled: false, revealed: [], tokens: [],
      walls: vtt.walls.map((w) => ({ id: newId(), points: w.points })),
      doors: vtt.doors.map((d) => ({ id: newId(), a: d.a, b: d.b, open: d.open })),
      lights: vtt.lights.map((l) => ({ id: newId(), x: l.x, y: l.y, range: l.range, color: l.color, intensity: l.intensity })),
      createdAt: ts, updatedAt: ts,
    });
    await db.maps.add(map);
    return map;
  },
  get(id: string): Promise<BattleMap | undefined> {
    return db.maps.get(id);
  },
  async save(map: BattleMap): Promise<void> {
    await db.maps.put(battleMapSchema.parse({ ...map, updatedAt: now() }));
  },
  /** Transactional read-modify-write (mirrors encountersRepo.mutate). */
  async mutate(id: string, fn: (m: BattleMap) => BattleMap): Promise<void> {
    await db.transaction('rw', db.maps, async () => {
      const current = await db.maps.get(id);
      if (!current) return;
      await db.maps.put(battleMapSchema.parse({ ...fn(current), updatedAt: now() }));
    });
  },
  async remove(id: string): Promise<void> {
    await db.maps.delete(id);
  },
};

// ---------------- Homebrew ----------------
export const homebrewRepo = {
  listByCampaign(campaignId: string): Promise<Homebrew[]> {
    return db.homebrew.where('campaignId').equals(campaignId).toArray();
  },
  byKind(campaignId: string, kind: HomebrewKind): Promise<Homebrew[]> {
    return db.homebrew.where('[campaignId+kind]').equals([campaignId, kind]).toArray();
  },
  async create(campaignId: string, system: Homebrew['system'], kind: HomebrewKind, name: string): Promise<Homebrew> {
    const ts = now();
    const hb = homebrewSchema.parse({ id: newId(), campaignId, system, kind, name, fields: {}, description: '', createdAt: ts, updatedAt: ts });
    await db.homebrew.add(hb);
    return hb;
  },
  async update(id: string, patch: Partial<Omit<Homebrew, 'id' | 'campaignId' | 'createdAt'>>): Promise<void> {
    await db.homebrew.update(id, { ...patch, updatedAt: now() });
  },
  async remove(id: string): Promise<void> {
    await db.homebrew.delete(id);
  },
  /** Insert a batch (from a content pack import), reassigning ids + campaign. */
  async importMany(campaignId: string, entries: Homebrew[]): Promise<number> {
    const ts = now();
    const records = entries.map((e) =>
      homebrewSchema.parse({ ...e, id: newId(), campaignId, createdAt: ts, updatedAt: ts }),
    );
    await db.homebrew.bulkAdd(records);
    return records.length;
  },
};
