export type BuiltInSpriteId =
  | "ai.idle"
  | "ai.ambient-idle"
  | "ai.ambient-thinking"
  | "ai.ambient-thinking-symmetric"
  | "ai.ambient-speaking"
  | "ai.listening"
  | "ai.thinking"
  | "ai.thinking-deep"
  | "ai.still-working"
  | "ai.loading"
  | "ai.progress"
  | "ai.generating"
  | "ai.searching"
  | "ai.tool-use"
  | "ai.speaking"
  | "ai.awaiting-input"
  | "status.success"
  | "status.warning"
  | "status.error"
  | "status.paused"
  | "status.cancelled"
  | "status.offline"
  | "transfer.active"
  | "workflow.handoff"
  | "status.celebration";

export type SpriteAlias =
  | "idle"
  | "ready"
  | "ambient"
  | "ambient-idle"
  | "field-idle"
  | "ambient-thinking"
  | "field-thinking"
  | "ambient-thinking-symmetric"
  | "field-thinking-symmetric"
  | "symmetric-thinking"
  | "ambient-speaking"
  | "field-speaking"
  | "listening"
  | "thinking"
  | "thinking-deep"
  | "deep-thinking"
  | "still-working"
  | "working"
  | "loading"
  | "progress"
  | "generating"
  | "searching"
  | "tool-use"
  | "tool"
  | "speaking"
  | "awaiting-input"
  | "prompt"
  | "success"
  | "warning"
  | "error"
  | "paused"
  | "cancelled"
  | "offline"
  | "transfer"
  | "upload"
  | "download"
  | "sync"
  | "handoff"
  | "celebration";

export type SpriteName = BuiltInSpriteId | SpriteAlias;

export type FieldId =
  | "fbm"
  | "ridged"
  | "domain-warp"
  | "curl"
  | "flow"
  | "worley"
  | "voronoi"
  | "plasma"
  | "interference"
  | "vortex"
  | "metaballs"
  | "caustics"
  | "strata"
  | "radar"
  | "constellation"
  | "liquid"
  | "electric"
  | "ripple"
  | "kaleidoscope";

export type FieldAlias =
  | "noise"
  | "turbulence"
  | "ridge"
  | "domainWarp"
  | "domain_warp"
  | "warp"
  | "curl-flow"
  | "curlFlow"
  | "cellular"
  | "cells"
  | "cell"
  | "water"
  | "lightning"
  | "waves"
  | "mandala";

export type FieldName = FieldId | FieldAlias;

export type PixelShape =
  | "disc"
  | "square"
  | "diamond"
  | "capsule"
  | "line"
  | "ring"
  | "cross"
  | "square-cross"
  | "square-cross-ring";
export type PixelShapeAlias = "circle" | "rounded-square" | "dot";
export type PixelShapeName = PixelShape | PixelShapeAlias;

export type PixelSwitch =
  | "ordered-dither"
  | "temporal-blue-noise"
  | "threshold-hysteresis"
  | "sdf-wavefront"
  | "contour-trace"
  | "curl-advect"
  | "neighbor-propagation"
  | "radial-cascade"
  | "path-draw"
  | "axis-flip"
  | "seeded-dissolve"
  | "field-morph";
export type PixelSwitchAlias = "cluster-dissolve" | "neighbor-ignite";
export type PixelSwitchName = PixelSwitch | PixelSwitchAlias;

export type Transition =
  | "field-morph"
  | "seeded-dissolve"
  | "radial-cascade"
  | "angular-sweep"
  | "scanline"
  | "contour-trace"
  | "path-draw"
  | "axis-flip"
  | "cluster-dissolve"
  | "neighbor-ignite"
  | "glitch-bands"
  | "instant";
export type TransitionAlias =
  | "dissolve"
  | "radial"
  | "bloom"
  | "spiral"
  | "wave"
  | "shutter"
  | "glitch"
  | "sdf-wavefront"
  | "neighbor-propagation";
export type TransitionName = Transition | TransitionAlias;

export type Quality = "low" | "balanced" | "high" | "auto";
export type EffectiveQuality = Exclude<Quality, "auto">;
export type ReducedMotion = boolean | "system";
export type PreviewMode = "fit" | "actual";
export type OrbBoundary = "defined" | "gestalt";
export type OrbBackgroundMode = "none" | "solid" | "pixelated";
export type CSSColor = string;

export interface Palette {
  /** Optional authored palette identifier preserved by exported configs. */
  name?: string;
  background?: CSSColor;
  /** Authored inactive color alias; effective palettes also expose `off`. */
  shadow?: CSSColor;
  off?: CSSColor;
  ink?: CSSColor;
  accent?: CSSColor;
  /** Use `transparent` to disable glow. */
  glow?: CSSColor;
  /** Optional overrides selected by a recipe's authored palette name. */
  variants?: Readonly<Record<string, Palette>>;
}

export interface FieldLayer {
  field: FieldName;
  weight?: number;
  scale?: number;
  blend?: string;
  phase?: number;
  speed?: number;
  contrast?: number;
  [key: string]: unknown;
}

export interface TransitionRecipe {
  name?: TransitionName;
  type?: TransitionName;
  enter?: TransitionName;
  exit?: TransitionName;
  duration?: number;
  durationMs?: number;
  exitDurationMs?: number;
  preservePhase?: boolean;
  preserveBuffer?: boolean;
  interruptible?: boolean;
  [key: string]: unknown;
}

export interface PixelSwitchRecipe {
  mode: PixelSwitchName;
  dither?: string;
  hysteresis?: number;
  temporalRate?: number;
  neighborhood?: number;
  direction?: string | null;
  [key: string]: unknown;
}

export interface SpriteComposition {
  mode?: "glyph" | "field-orb" | string;
  /** Opts a circular recipe into the global Gestalt boundary preference. */
  gestaltBoundary?: boolean;
  /** Controls how much open space remains between implied boundary arcs. */
  gestaltOpenness?: number;
  [key: string]: unknown;
}

export interface SpriteRecipe {
  schemaVersion?: number;
  id: string;
  glyph: string;
  /** A recipe can use one primary field, a field mix, or both. */
  field?: FieldName;
  fieldMix?: readonly FieldLayer[];
  palette?: Readonly<Palette>;
  semantic?: Readonly<Record<string, unknown>>;
  composition?: Readonly<SpriteComposition>;
  pixel?: Readonly<{
    shape?: PixelShapeName;
    scale?: number;
    gap?: number;
    [key: string]: unknown;
  }>;
  pixelShape?: PixelShapeName;
  pixelSwitch?: PixelSwitchName | Readonly<PixelSwitchRecipe>;
  transition?: TransitionName | Readonly<TransitionRecipe>;
  interactions?: Readonly<Record<string, unknown>> | false;
  timeline?: Readonly<Record<string, unknown>>;
  reducedMotion?: Readonly<Record<string, unknown>>;
  labels?: Readonly<Record<string, string>>;
  threshold?: number;
  density?: number;
  speed?: number;
  [key: string]: unknown;
}

export interface JoanEngineOptions {
  sprite?: SpriteName | SpriteRecipe;
  seed?: string | number;
  gridSize?: number;
  speed?: number;
  density?: number;
  contrast?: number;
  fps?: number;
  pixelShape?: PixelShapeName | null;
  pixelSwitch?: PixelSwitchName | PixelSwitchRecipe | null;
  transition?: TransitionName | TransitionRecipe | null;
  field?: FieldName | null;
  fieldMode?: "recipe" | "override";
  palette?: Palette | null;
  /** Compatibility alias for the global palette override. */
  paletteOverride?: Palette | null;
  nonErrorPalette?: Palette | null;
  previewMode?: PreviewMode;
  /** Use an implied, discontinuous edge for supported circular presence states. */
  orbBoundary?: OrbBoundary;
  /** Optional color used by the background layer inside supported presence orbs. */
  orbBackgroundColor?: CSSColor | null;
  /** Selects no disk, a smooth disk, or a grid-aligned pixel disk. */
  orbBackgroundMode?: OrbBackgroundMode;
  background?: boolean;
  offPixels?: boolean;
  interactive?: boolean;
  autoplay?: boolean;
  autoResize?: boolean;
  dprMax?: number;
  reducedMotion?: ReducedMotion;
  quality?: Quality;
  ariaLive?: HTMLElement | null;
  direction?: string;
  targetGlyph?: string | null;
  progress?: number;
}

export interface CreateProceduralGlyphOptions extends JoanEngineOptions {
  canvas: HTMLCanvasElement;
}

export interface SetSpriteOptions {
  transition?: TransitionName | TransitionRecipe;
  duration?: number;
  preservePhase?: boolean;
  preserveBuffer?: boolean;
  immediate?: boolean;
  forceTransition?: boolean;
  useRecipeFields?: boolean;
}

export interface TransitionToOptions extends SetSpriteOptions {
  signal?: AbortSignal;
}

export interface ActivateOptions {
  x?: number;
  y?: number;
  energy?: number;
  life?: number;
  source?: string;
  key?: string | null;
}

export interface ResumeOptions extends SetSpriteOptions {
  sprite?: SpriteName | SpriteRecipe;
  source?: string;
}

export interface SignalPayload {
  value?: unknown;
  energy?: number;
  life?: number;
  x?: number;
  y?: number;
  direction?: string;
  source?: string;
  [key: string]: unknown;
}

export interface EngineStats {
  fps: number;
  activePixels: number;
  frameMs: number;
  resolution: number;
  simulationSteps: number;
  sampledPixels: number;
  quality: EffectiveQuality;
}

export interface EngineInspection {
  sprite: string | null;
  recipeSource: string | null;
  previousSprite: string | null;
  fieldMode: "recipe" | "override";
  field: string | null;
  running: boolean;
  visible: boolean;
  destroyed: boolean;
  reducedMotion: boolean;
  transition: {
    active: boolean;
    name: Transition;
    progress: number;
    elapsed: number;
    duration: number;
    pending: string | null;
  };
  signals: Array<{ type: string; value: number; age: number; life: number }>;
  semanticSignals: Record<string, unknown>;
  palette: Palette;
  orbBoundary: OrbBoundary;
  orbBackgroundColor: CSSColor | null;
  orbBackgroundMode: OrbBackgroundMode;
  quality: { requested: Quality; effective: EffectiveQuality };
  stats: EngineStats;
}

export interface TransitionDetail {
  from: string | null;
  to: string | null;
  duration: number;
  elapsed: number;
  transition: Transition;
  status: "completed" | "interrupted";
  reason: string;
  serial: number;
}

export interface JoanGlyphEventDetailMap {
  activate: {
    sprite: string;
    x: number;
    y: number;
    source: string;
    key: string | null;
  };
  configchange: Partial<JoanEngineOptions> & Record<string, unknown>;
  destroy: Record<string, never>;
  pause: { sprite: string };
  play: { sprite: string };
  qualitychange: {
    from: EffectiveQuality;
    to: EffectiveQuality;
    frameMs: number;
    budgetMs: number;
  };
  resume: {
    from: string;
    to: string;
    restoredBuffer: boolean;
    source: string;
  };
  signal: { type: string; payload: SignalPayload };
  spritechange: { from?: string; to: string; label: string };
  stats: EngineStats;
  timelinecomplete: { from: string; to: string };
  transitioncomplete: TransitionDetail;
  transitionqueued: { from?: string; to?: string };
}

export type JoanGlyphEventName = keyof JoanGlyphEventDetailMap;
export type JoanGlyphEvent<K extends JoanGlyphEventName> = CustomEvent<
  JoanGlyphEventDetailMap[K]
>;

export interface ExportedJoanConfig {
  package: "@joan/procedural-glyph-engine";
  version: string;
  sprite: string;
  seed: string;
  gridSize: number;
  speed: number;
  density: number;
  contrast: number;
  fps: number;
  field?: FieldName;
  fieldMode: "recipe" | "override";
  pixelShape: PixelShape | null;
  pixelSwitch: PixelSwitch | PixelSwitchRecipe | null;
  transition: Transition | TransitionRecipe | null;
  palette: Palette &
    Required<Pick<Palette, "background" | "off" | "ink" | "accent" | "glow">>;
  paletteOverride: Palette | null;
  nonErrorPalette: Palette | null;
  orbBoundary: OrbBoundary;
  orbBackgroundColor: CSSColor | null;
  orbBackgroundMode: OrbBackgroundMode;
  progress: number;
  background: boolean;
  offPixels: boolean;
  interactive: boolean;
  autoplay: boolean;
  autoResize: boolean;
  dprMax: number;
  quality: Quality;
  reducedMotion: ReducedMotion;
  direction: string | null;
  targetGlyph: string | null;
}

export interface SvgExportOptions {
  size?: number;
  background?: boolean;
  offPixels?: boolean;
}

export interface AnimatedSvgExportOptions extends SvgExportOptions {
  duration?: number;
  fps?: number;
  maxGridSize?: number;
}

export interface GlyphSampleContext extends Record<string, unknown> {
  progress?: number;
  audioLevel?: number;
  energy?: number;
  elapsed?: number;
  resolution?: number;
  reducedMotion?: boolean;
  orbBoundary?: OrbBoundary;
  gestaltOpenness?: number;
}

export type GlyphSampler = (
  x: number,
  y: number,
  time: number,
  context: GlyphSampleContext,
) => number;
export type GlyphBitmap =
  | ArrayLike<number>
  | readonly (readonly number[])[];

export interface RegisterGlyphDimensions {
  width?: number;
  height?: number;
}

export interface LoadGlyphFileOptions {
  id?: string;
  label?: string;
  baseSprite?: SpriteName;
  resolution?: number;
  transition?: TransitionName | TransitionRecipe;
}

export const TRANSITIONS: readonly Transition[];
export const PIXEL_SWITCHES: readonly PixelSwitch[];
export const PIXEL_SHAPES: readonly PixelShape[];
export const ORB_BACKGROUND_MODES: readonly OrbBackgroundMode[];
export const SPRITES: Readonly<Record<BuiltInSpriteId, Readonly<SpriteRecipe>>>;

export function getSprite(id: SpriteName): Readonly<SpriteRecipe> | undefined;
export function listSprites(): readonly Readonly<SpriteRecipe>[];
export function transitionDelay(
  transition: TransitionName,
  x: number,
  y: number,
  random?: number,
  origin?: { x: number; y: number },
): number;

export class JoanGlyphEngine extends EventTarget {
  static readonly sprites: typeof SPRITES;
  static listSprites(): readonly Readonly<SpriteRecipe>[];

  constructor(canvas: HTMLCanvasElement, options?: JoanEngineOptions);

  readonly canvas: HTMLCanvasElement;
  readonly context: CanvasRenderingContext2D;
  options: JoanEngineOptions;
  currentRecipe: SpriteRecipe;
  recipeSource: SpriteRecipe;
  previousRecipe: SpriteRecipe;
  fieldOverride: FieldName | null;
  gridSize: number;
  seed: string;
  running: boolean;
  destroyed: boolean;
  visible: boolean;
  reducedMotion: boolean;
  progress: number;
  audioLevel: number;
  clock: number;
  stats: EngineStats;
  readonly values: Float32Array;
  readonly gates: Uint8Array;
  readonly luminance: Float32Array;

  on<K extends JoanGlyphEventName>(
    type: K,
    listener: (event: JoanGlyphEvent<K>) => void,
    options?: boolean | AddEventListenerOptions,
  ): () => void;
  addEventListener<K extends JoanGlyphEventName>(
    type: K,
    listener: (this: JoanGlyphEngine, event: JoanGlyphEvent<K>) => unknown,
    options?: boolean | AddEventListenerOptions,
  ): void;
  addEventListener(
    type: string,
    listener: EventListenerOrEventListenerObject | null,
    options?: boolean | AddEventListenerOptions,
  ): void;

  activate(options?: ActivateOptions): this;
  resume(options?: ResumeOptions): this;
  setSprite(sprite: SpriteName | SpriteRecipe, options?: SetSpriteOptions): this;
  transitionTo(
    sprite: SpriteName | SpriteRecipe,
    options?: TransitionToOptions,
  ): Promise<TransitionDetail>;
  whenTransitionComplete(options?: { signal?: AbortSignal }): Promise<TransitionDetail>;
  setField(field: FieldName): this;
  useRecipeFields(): this;
  setPixelSwitch(pixelSwitch: PixelSwitchName | PixelSwitchRecipe): this;
  setPixelShape(pixelShape: PixelShapeName): this;
  setResolution(size: number): this;
  setSeed(seed: string | number): this;
  setProgress(value: number): this;
  setAudioLevel(value: number): this;
  setNonErrorPalette(
    palette: Palette | null,
    options?: { render?: boolean },
  ): this;
  configure(
    options?: Partial<JoanEngineOptions>,
    configureOptions?: { strict?: boolean },
  ): this;
  setOptions(options?: Partial<JoanEngineOptions>): this;
  signal(type: string, payload?: SignalPayload): this;
  registerGlyph(
    id: string,
    source: GlyphSampler | GlyphBitmap,
    dimensions?: RegisterGlyphDimensions,
  ): this;
  loadGlyphFile(file: Blob, options?: LoadGlyphFileOptions): Promise<string>;
  play(): this;
  pause(): this;
  toggle(): this;
  renderFrame(time?: number, dt?: number, force?: boolean): EngineStats;
  renderOnce(time?: number): EngineStats;
  inspect(): EngineInspection;
  exportConfig(): ExportedJoanConfig;
  toSVG(options?: SvgExportOptions): string;
  toAnimatedSVG(options?: AnimatedSvgExportOptions): Promise<string>;
  downloadPNG(filename?: string): Promise<Blob>;
  downloadSVG(filename?: string, options?: SvgExportOptions): Promise<string>;
  downloadAnimatedSVG(
    filename?: string,
    options?: AnimatedSvgExportOptions,
  ): Promise<string>;
  toDataURL(type?: string, quality?: number): string;
  toBlob(type?: string, quality?: number): Promise<Blob>;
  destroy(): void;
}

export function createProceduralGlyph(
  options: CreateProceduralGlyphOptions,
): JoanGlyphEngine;
export function createProceduralGlyph(
  target: string | HTMLCanvasElement,
  options?: JoanEngineOptions,
): JoanGlyphEngine;
export function createGlyph(
  target: string | HTMLCanvasElement,
  options?: JoanEngineOptions,
): JoanGlyphEngine;
export function mountProceduralGlyph(
  target: string | HTMLCanvasElement,
  options?: JoanEngineOptions,
): JoanGlyphEngine;

export default JoanGlyphEngine;
