// Sprite registry. To add a new animation:
//   1. Add its name to `AnimName` in types.ts.
//   2. Add an entry under SPRITES[stage] here pointing at a 16x16-frame PNG sheet in /mascot/.
//   3. (Optional) Reference it from a behavior in behavior.ts or a reaction in stimuli.ts.
// `resolveAnim()` falls back to the stage's `idle` and finally a 1-frame
// placeholder, so a missing animation never crashes the renderer.
//
// Sheets are bundled at web/public/mascot/*.png (CC0, see
// web/public/mascot/LICENSE.txt). Each sheet is a horizontal strip of
// 16x16 px frames; the renderer slices frame `i` at x = i*16.
//
// Egg-stage animations come from the Onocentaur egg pack (single-frame
// 16x16 PNGs): an idle egg and shell halves (shown briefly at the hatch
// moment). The egg → chick transition fires on first naming (see
// state.svelte.ts), not on a timed incubation, so there's no progressive
// crack animation — the egg sits on egg-idle until the name dialog is
// submitted, then swaps to the chick. The egg-crack sheet is kept in the
// registry for future use but isn't selected by any behavior today.

import type { AnimDef, AnimName, MascotStage } from './types'

const EGG_IDLE: AnimDef = { src: '/mascot/egg-idle.png', frames: 1, fps: 1, loop: true }
const EGG_SHELL: AnimDef = { src: '/mascot/egg-shell.png', frames: 1, fps: 1, loop: true }

export const SPRITES: Record<MascotStage, Partial<Record<AnimName, AnimDef>>> = {
  egg: {
    'egg-idle': EGG_IDLE,
    'egg-wiggle': EGG_IDLE, // wiggle is applied as a render-time transform; no separate frame
    hatch: EGG_SHELL,
    dragged: EGG_IDLE,
    'fall-flutter': EGG_IDLE,
    land: EGG_IDLE
  },
  // Chick and adult share sheets; only the render scale differs.
  chick: {
    idle: { src: '/mascot/idle.png', frames: 4, fps: 6, loop: true },
    blink: { src: '/mascot/blink.png', frames: 4, fps: 6, loop: true },
    walk: { src: '/mascot/walk.png', frames: 4, fps: 8, loop: true },
    peck: { src: '/mascot/peck.png', frames: 4, fps: 6, loop: true },
    flap: { src: '/mascot/jump.png', frames: 4, fps: 8, loop: true },
    sleep: { src: '/mascot/sleep.png', frames: 4, fps: 4, loop: true },
    talk: { src: '/mascot/peep.png', frames: 2, fps: 6, loop: true },
    dragged: { src: '/mascot/jump.png', frames: 4, fps: 8, loop: true },
    'fall-flutter': { src: '/mascot/jump.png', frames: 4, fps: 10, loop: true },
    land: { src: '/mascot/hurt.png', frames: 4, fps: 8, loop: false },
    'react-think': { src: '/mascot/react-sigh.png', frames: 4, fps: 4, loop: true },
    'react-eureka': { src: '/mascot/react-joy.png', frames: 4, fps: 6, loop: true },
    'react-alarm': { src: '/mascot/react-yell.png', frames: 4, fps: 8, loop: true },
    'react-happy': { src: '/mascot/react-joy.png', frames: 4, fps: 6, loop: true }
  },
  adult: {
    idle: { src: '/mascot/idle.png', frames: 4, fps: 6, loop: true },
    blink: { src: '/mascot/blink.png', frames: 4, fps: 6, loop: true },
    walk: { src: '/mascot/walk.png', frames: 4, fps: 8, loop: true },
    peck: { src: '/mascot/peck.png', frames: 4, fps: 6, loop: true },
    flap: { src: '/mascot/jump.png', frames: 4, fps: 8, loop: true },
    sleep: { src: '/mascot/sleep.png', frames: 4, fps: 4, loop: true },
    talk: { src: '/mascot/peep.png', frames: 2, fps: 6, loop: true },
    dragged: { src: '/mascot/jump.png', frames: 4, fps: 8, loop: true },
    'fall-flutter': { src: '/mascot/jump.png', frames: 4, fps: 10, loop: true },
    land: { src: '/mascot/hurt.png', frames: 4, fps: 8, loop: false },
    'react-think': { src: '/mascot/react-sigh.png', frames: 4, fps: 4, loop: true },
    'react-eureka': { src: '/mascot/react-joy.png', frames: 4, fps: 6, loop: true },
    'react-alarm': { src: '/mascot/react-yell.png', frames: 4, fps: 8, loop: true },
    'react-happy': { src: '/mascot/react-joy.png', frames: 4, fps: 6, loop: true }
  }
}

// Render scale per stage. The asset pack has one chicken size; the chick
// and adult both render at full scale (1.0) — downscaling to 0.75 for the
// chick looked blurry on high-DPI displays. The stage is conveyed by the
// tamagotchi model + behavior, not by sprite size.
export const STAGE_SCALE: Record<MascotStage, number> = {
  egg: 1,
  chick: 1,
  adult: 1
}

const PLACEHOLDER: AnimDef = { src: '/mascot/idle.png', frames: 4, fps: 6, loop: true }

/** Resolve an animation for a stage, falling back to the stage's idle, then a placeholder. */
export function resolveAnim(stage: MascotStage, name: AnimName): AnimDef {
  const set = SPRITES[stage]
  const direct = set[name]
  if (direct) return direct
  if (name !== 'idle') {
    const idle = set.idle
    if (idle) return idle
  }
  return PLACEHOLDER
}

/** Pick which frame of an AnimDef to draw at time `now` (ms). */
export function frameIndex(anim: AnimDef, now: number, animStart: number): number {
  const elapsed = now - animStart
  if (anim.frames <= 1) return 0
  const idx = Math.floor((elapsed / 1000) * anim.fps)
  if (anim.loop) return ((idx % anim.frames) + anim.frames) % anim.frames
  return Math.min(idx, anim.frames - 1)
}

// ─── Image cache / loader ────────────────────────────────────────────────
// PNG sheets are loaded once into HTMLImageElement instances and reused.
// `loadSprites()` is called from Mascot.svelte on mount; `getImage()`
// returns the cached element (or null if not yet loaded, in which case
// the renderer just skips that frame — the loop will pick it up next
// tick once the image arrives).

const imageCache = new Map<string, HTMLImageElement>()

function loadOne(src: string): Promise<HTMLImageElement> {
  const existing = imageCache.get(src)
  if (existing && existing.complete) return Promise.resolve(existing)
  return new Promise((resolve, reject) => {
    const img = new Image()
    img.src = src
    img.onload = () => {
      imageCache.set(src, img)
      resolve(img)
    }
    img.onerror = () => reject(new Error(`mascot: failed to load ${src}`))
  })
}

/** Preload every sheet referenced by SPRITES for the given stages (default: all). */
export async function loadSprites(
  stages: MascotStage[] = ['egg', 'chick', 'adult']
): Promise<void> {
  const srcs = new Set<string>()
  for (const stage of stages) {
    for (const anim of Object.values(SPRITES[stage])) {
      if (anim && anim.src) srcs.add(anim.src)
    }
  }
  await Promise.all([...srcs].map(loadOne))
}

/** Get a cached sheet image, or null if not yet loaded. */
export function getImage(src: string): HTMLImageElement | null {
  const img = imageCache.get(src)
  if (!img || !img.complete) return null
  return img
}
