// Type definitions for the desktop mascot sprite/behavior/action/reaction
// system. Every registry below (sprites.ts, behavior.ts, actions.ts,
// stimuli.ts) is plain data over these types, so each can be extended
// independently without touching the engine code in Mascot.svelte.

import type { Component } from 'svelte'

/** Slow-moving lifecycle stage of the tamagotchi. Drives which sprite set is drawn and the render scale. */
export type MascotStage = 'egg' | 'chick' | 'adult'

/** A named animation. Add a name here, then add an entry under SPRITES[stage] in sprites.ts. */
export type AnimName =
  | 'egg-idle'
  | 'egg-wiggle'
  | 'egg-crack'
  | 'hatch'
  | 'idle'
  | 'blink'
  | 'walk'
  | 'peck'
  | 'flap'
  | 'sleep'
  | 'talk'
  | 'dragged'
  | 'fall-flutter'
  | 'land'
  | 'react-eureka'
  | 'react-alarm'
  | 'react-think'
  | 'react-happy'

/**
 * A sprite-sheet animation. The sheet is a horizontal strip of 16x16 px
 * frames (PNG, RGBA) served from /mascot/*. The renderer slices frame
 * `i` from x = i*16, y = 0, w = 16, h = 16. The whole mascot sprite
 * canvas is 20x20 logical px (so feet land on a consistent ground line
 * across stages); the 16x16 frame is bottom-anchored and horizontally
 * centered inside it.
 *
 * Egg-stage animations are vector-drawn by render.ts (no PNG); their
 * AnimDef entries still exist for the FSM to reference but their `src`
 * is ignored.
 */
export interface AnimDef {
  /** Sheet URL (resolved from /mascot/<src>). Ignored for egg-vector anims. */
  src: string
  /** Frame count in the sheet (sheet width = frames * 16). */
  frames: number
  /** Frames per second. */
  fps: number
  /** Whether to wrap the frame index once it reaches `frames`. */
  loop: boolean
}

/** Autonomous FSM state. Add an id here, then add a BehaviorDef entry to BEHAVIORS in behavior.ts. */
export type BehaviorId =
  | 'egg'
  | 'idle'
  | 'wander'
  | 'peck'
  | 'hop'
  | 'sleep'
  | 'dragged'
  | 'falling'
  | 'land'
  | 'react'
  | 'busy'

/** Opaque identifier for an environment stimulus reaction. See stimuli.ts. */
export type Stimulus = string

/** Shape of a radial-menu action node. See actions.ts. */
export interface RadialAction {
  id: string
  label: string
  icon?: Component
  /**
   * Shown as a small muted second line under the label — mainly used by
   * Debug entries to say what normally triggers the thing being forced
   * (e.g. "Normally fires when Nomos starts streaming a reply"), so a
   * manual test doesn't need to be cross-referenced against the code to
   * know what it's simulating.
   */
  description?: string
  /** Visibility predicate (e.g. "Rename" only once hatched). Defaults to always visible. */
  visible?: (model: import('./state.svelte').MascotModel) => boolean
  /** Sub-actions — selecting this node swaps the ring to its children + a back button. */
  children?: RadialAction[]
  /** Leaf handler. Mutates model/runtime via the passed context. */
  action?: (ctx: MascotActionCtx) => void
}

/** Argument passed to a RadialAction leaf handler. */
export interface MascotActionCtx {
  model: import('./state.svelte').MascotModel
  runtime: MascotRuntime
  /** Force a behavior (e.g. sleep). See behavior.ts. */
  force: (id: BehaviorId, opts?: { anim?: AnimName; durationMs?: number }) => void
  /** Request the name dialog to open. */
  requestRename: () => void
  /** Advance to the next lifecycle stage immediately (debug). */
  forceHatch: () => void
  /** Force a specific lifecycle stage (debug). */
  forceStage: (stage: MascotStage) => void
  /** Reset the tamagotchi model to defaults. */
  reset: () => void
  /** Redraw the menu (after a visibility-affecting mutation). */
  refresh: () => void
}

/** Frame-to-frame state of the mascot on the desktop surface. Owned by Mascot.svelte. */
export interface MascotRuntime {
  /** Sprite bottom-center, surface (not viewport) coords. */
  x: number
  y: number
  vx: number
  vy: number
  facing: 1 | -1
  behavior: BehaviorId
  /** performance.now() ms after which the current behavior should transition (its next() is consulted). */
  behaviorUntil: number
  /** Currently-playing animation. */
  anim: AnimName
  /** performance.now() ms when the current animation started. */
  animStart: number
  /** When behavior === 'react', the animation to play (overrides the behavior's default anim). */
  reactAnim: AnimName | null
  /** Surface bounds (width/height in CSS pixels). Updated on resize. */
  bounds: { w: number; h: number }
  /**
   * Current ground line at the mascot's x: the top edge of the highest
   * non-minimized window beneath it, or bounds.h (surface bottom) when
   * no window is beneath. Updated each tick by Mascot.svelte from
   * wmState; the FSM uses this as the ground for falling/landing.
   */
  groundY: number
  /**
   * Optional text/emoji shown above the mascot in a real HTML speech
   * bubble (Mascot.svelte's template), not a sprite — e.g. '❤️', '💡', or
   * a short phrase.
   */
  bubbleText: string | null
  /** performance.now() ms until which bubbleText stays visible; the game loop (tick()) clears it once this passes. */
  bubbleUntil: number
  /** performance.now() ms until which the idle behavior should play 'blink' instead of 'idle'. */
  blinkUntil: number
  /**
   * performance.now() ms marking the start of the current flap/glide
   * sub-phase within a `falling` behavior — see flapCycleMs below and
   * scheduleFlap() in behavior.ts. Reset each time `falling` is entered.
   */
  fallPhaseAt: number
  /**
   * Current flap-cycle length (ms) while `falling`: the interval between
   * wing-beat impulses. Dynamically shortened by descent speed (panic
   * flapping) plus random jitter so the rhythm never reads metronomic.
   */
  flapCycleMs: number
  /**
   * performance.now() ms of the last ground impact (a landing or a
   * bounce contact). Drives the squash-and-stretch impact spring in
   * Mascot.svelte and the feather-poof particle spawn. 0 = never landed.
   */
  squashAt: number
  /**
   * Descent speed (px/s) at the moment of the last ground impact —
   * scales the squash-spring depth and the feather-poof size.
   * 0 = a gentle set-down (no squash, no poof).
   */
  impactVy: number
  /**
   * Bounces so far in the current fall. Reset to 0 by `falling.enter()`;
   * capped by BOUNCE_MAX in behavior.ts.
   */
  bounceCount: number
  /**
   * Set (by Mascot.svelte's onPointerUp) when a drag is released on top of
   * a desktop icon. Consumed once by the `land` BehaviorDef's `next()` —
   * routes the next landing to `peck` instead of `idle`, whether that
   * landing happens immediately (already on the ground) or after a fall.
   * Always false outside that one moment.
   */
  investigateOnLand: boolean
  /**
   * When behavior === 'busy': which phase of the focused task's active
   * turn to render — true plays the 'talk' sprite (text is streaming
   * out), false plays 'react-think' (computing, nothing written yet).
   * Set directly by MascotLayer's busy-state callback (see stimuli.ts);
   * read each tick by the `busy` BehaviorDef's anim() in behavior.ts.
   */
  busyTalking: boolean
}
