import type { BattleMap, Calendar, Campaign, Character, Encounter, Quest } from '@/lib/schemas';
import { getSystem } from '@/lib/rules';
import { toPlayerEncounter } from '@/lib/combat/playerProjection';
import { toPlayerProjection } from '@/lib/map';
import type { Snapshot } from './messages';

/** Stable id for a character's portrait in the session image cache. */
const charImageId = (id: string) => `char:${id}`;
/** Stable id for a token's own icon image. */
const tokenImageId = (id: string) => `tok:${id}`;

export interface SnapshotResult {
  snapshot: Snapshot;
  /** icon/portrait/map images keyed by id, to stream over the image channel */
  images: Record<string, string>;
}

/** Build the player-safe session snapshot + the images it references. */
export function buildSnapshot(input: {
  campaign: Campaign;
  characters: Character[];
  encounters: Encounter[];
  maps: BattleMap[];
  calendar: Calendar | null;
  activeEncounterId: string | null;
  activeMapId: string | null;
  handout?: { title: string; body: string; image?: string } | null;
  quests?: Quest[];
}): SnapshotResult {
  const { campaign, characters, encounters, maps, calendar, activeEncounterId, activeMapId, handout, quests } = input;
  const sys = getSystem(campaign.system);
  const images: Record<string, string> = {};
  const byId = new Map(characters.map((c) => [c.id, c]));

  const party = characters.filter((c) => c.kind === 'pc').map((c) => {
    if (c.portrait) images[charImageId(c.id)] = c.portrait;
    return {
      id: c.id,
      name: c.name,
      level: c.level,
      ac: sys.baseArmorClass({ level: c.level, abilities: c.abilities, armorBonus: c.armorBonus, ...(c.equippedArmor ? { equippedArmor: c.equippedArmor } : {}) }),
      ...(c.portrait ? { imageId: charImageId(c.id) } : {}),
      hp: c.hp,
      conditions: c.conditions.map((x) => ({ name: x.name, ...(x.value !== undefined ? { value: x.value } : {}) })),
      ...(c.spellcasting.slots.length ? { slots: c.spellcasting.slots.map((s) => ({ level: s.level, current: s.current, max: s.max })) } : {}),
      ...(c.concentration ? { concentration: c.concentration.spellName } : {}),
      ...(c.resources.length ? { resources: c.resources.map((r) => ({ name: r.name, current: r.current, max: r.max })) } : {}),
    };
  });

  const activeEnc = encounters.find((e) => e.id === activeEncounterId && e.status === 'active')
    ?? encounters.find((e) => e.status === 'active') ?? null;
  const activeMap = maps.find((m) => m.id === activeMapId) ?? null;
  const projection = activeMap ? toPlayerProjection(activeMap) : null;

  // Resolve each visible token's icon (own image, else linked character portrait).
  if (projection && activeMap) {
    for (const pt of projection.tokens) {
      const orig = activeMap.tokens.find((t) => t.id === pt.id);
      if (!orig) continue;
      if (orig.image) {
        const id = tokenImageId(orig.id);
        images[id] = orig.image;
        pt.imageId = id;
      } else if (orig.characterId) {
        const portrait = byId.get(orig.characterId)?.portrait;
        if (portrait) {
          const id = charImageId(orig.characterId);
          images[id] = portrait;
          pt.imageId = id;
        }
      }
    }
  }

  if (activeMap?.image) images[activeMap.id] = activeMap.image;

  let handoutOut: { title: string; body: string; imageId: string | null } | null = null;
  if (handout) {
    if (handout.image) images['handout'] = handout.image;
    handoutOut = { title: handout.title, body: handout.body, imageId: handout.image ? 'handout' : null };
  }

  return {
    snapshot: {
      campaignName: campaign.name,
      system: campaign.system,
      calendarDay: calendar ? calendar.currentDay : null,
      party,
      encounter: activeEnc ? toPlayerEncounter(activeEnc) : null,
      map: projection,
      mapImageId: activeMap && activeMap.image ? activeMap.id : null,
      handout: handoutOut,
      quests: (quests ?? [])
        .filter((q) => q.status !== 'failed')
        .map((q) => ({
          title: q.title,
          completed: q.status === 'completed',
          objectives: q.objectives.map((o) => ({ text: o.text, done: o.done })),
        })),
    },
    images,
  };
}
