import type {
  SetSpriteOptions,
  SpriteName,
  SpriteRecipe,
  TransitionDetail,
  TransitionName,
  TransitionToOptions,
} from "./index.js";

export const STATE_SEQUENCE_SCHEMA_VERSION: 1;

export type StateSequenceSprite = SpriteName | SpriteRecipe;
export type StateSequenceTransition =
  | false
  | TransitionName
  | (SetSpriteOptions & { name?: TransitionName });

export interface StateSequenceStep {
  sprite: StateSequenceSprite;
  /** Time to keep the entered state before advancing, in milliseconds. */
  holdMs?: number;
  /** Compatibility alias for holdMs; normalized definitions use holdMs. */
  durationMs?: number;
  transition?: StateSequenceTransition;
  options?: SetSpriteOptions;
  label?: string;
  metadata?: Readonly<Record<string, unknown>>;
}

export interface DefinedStateSequenceStep
  extends Omit<StateSequenceStep, "durationMs" | "holdMs"> {
  readonly holdMs: number;
}

export interface StateSequenceDefinition {
  schemaVersion?: number;
  name: string;
  steps: readonly (StateSequenceStep | SpriteName)[];
  transition?: StateSequenceTransition;
  transitionFirst?: boolean;
  metadata?: Readonly<Record<string, unknown>>;
}

export interface DefinedStateSequence {
  readonly schemaVersion: 1;
  readonly name: string;
  readonly steps: readonly Readonly<DefinedStateSequenceStep>[];
  readonly transition?: StateSequenceTransition;
  readonly transitionFirst: boolean;
  readonly metadata?: Readonly<Record<string, unknown>>;
}

export interface StateSequenceTarget {
  currentRecipe?: { id?: string } | null;
  setSprite(sprite: StateSequenceSprite, options?: SetSpriteOptions): unknown;
  transitionTo?(
    sprite: StateSequenceSprite,
    options?: TransitionToOptions,
  ): PromiseLike<TransitionDetail | unknown> | TransitionDetail | unknown;
}

export interface StateSequenceClock {
  now(): number;
  setTimeout(callback: () => void, delayMs: number): unknown;
  clearTimeout(timer: unknown): void;
}

export interface StateSequencePlayerOptions {
  transition?: StateSequenceTransition;
  transitionFirst?: boolean;
  preservePhase?: boolean;
  clock?: StateSequenceClock;
  sequences?:
    | readonly (DefinedStateSequence | StateSequenceDefinition)[]
    | Readonly<
        Record<
          string,
          | readonly (StateSequenceStep | SpriteName)[]
          | StateSequenceDefinition
        >
      >;
}

export interface StateSequencePlayOptions {
  name?: string;
  signal?: AbortSignal;
  transition?: StateSequenceTransition;
  transitionFirst?: boolean;
  preservePhase?: boolean;
}

export interface StateSequenceResult {
  readonly runId: number;
  readonly name: string;
  readonly sequence: DefinedStateSequence;
  readonly status: "completed" | "stopped";
  readonly reason: unknown;
  readonly completedSteps: number;
}

export interface StateSequenceEventDetail {
  readonly runId: number;
  readonly name: string;
  readonly sequence: DefinedStateSequence;
  readonly index: number;
  readonly step?: Readonly<DefinedStateSequenceStep>;
  readonly sprite?: string;
  readonly from?: string | null;
  readonly to?: string;
  readonly state?: string;
  readonly reason?: unknown;
  readonly error?: unknown;
  readonly totalSteps?: number;
  readonly completedSteps?: number;
}

export type StateSequenceEventName =
  | "sequencestart"
  | "stepstart"
  | "statechange"
  | "stepenter"
  | "stepcomplete"
  | "sequencepause"
  | "sequenceresume"
  | "sequencestop"
  | "sequencecancel"
  | "sequencecomplete"
  | "sequenceerror";

export function defineStateSequence(
  name: string,
  steps: readonly (StateSequenceStep | SpriteName)[],
  options?: Omit<StateSequenceDefinition, "name" | "steps" | "schemaVersion">,
): DefinedStateSequence;
export function defineStateSequence(
  definition: StateSequenceDefinition,
): DefinedStateSequence;

export class StateSequencePlayer extends EventTarget {
  constructor(target: StateSequenceTarget, options?: StateSequencePlayerOptions);
  readonly target: StateSequenceTarget;
  readonly sequences: Map<string, DefinedStateSequence>;
  readonly currentSequence: DefinedStateSequence | null;
  readonly currentStepIndex: number;
  readonly isPlaying: boolean;
  readonly isPaused: boolean;
  status: "idle" | "running" | "paused" | "destroyed";
  state: string | null;
  destroyed: boolean;
  finished: Promise<StateSequenceResult | null>;

  addEventListener(
    type: StateSequenceEventName,
    listener: (
      this: StateSequencePlayer,
      event: CustomEvent<StateSequenceEventDetail>,
    ) => unknown,
    options?: boolean | AddEventListenerOptions,
  ): void;
  addEventListener(
    type: string,
    listener: EventListenerOrEventListenerObject | null,
    options?: boolean | AddEventListenerOptions,
  ): void;

  register(
    name: string,
    steps: readonly (StateSequenceStep | SpriteName)[],
    options?: Omit<StateSequenceDefinition, "name" | "steps" | "schemaVersion">,
  ): DefinedStateSequence;
  register(
    definition: DefinedStateSequence | StateSequenceDefinition,
  ): DefinedStateSequence;
  unregister(name: string): boolean;
  getSequence(name: string): DefinedStateSequence | null;
  listSequences(): readonly DefinedStateSequence[];
  play(
    sequence:
      | string
      | DefinedStateSequence
      | StateSequenceDefinition
      | readonly (StateSequenceStep | SpriteName)[],
    options?: StateSequencePlayOptions,
  ): Promise<StateSequenceResult>;
  run(
    sequence:
      | string
      | DefinedStateSequence
      | StateSequenceDefinition
      | readonly (StateSequenceStep | SpriteName)[],
    options?: StateSequencePlayOptions,
  ): Promise<StateSequenceResult>;
  /**
   * Pauses the hold clock and step progression. It does not rewind or suspend
   * a visual transition already running inside the target engine.
   */
  pause(): this;
  resume(): this;
  stop(reason?: string): this;
  destroy(): void;
}

export interface PlayStateSequenceOptions extends StateSequencePlayOptions {
  clock?: StateSequenceClock;
  metadata?: Readonly<Record<string, unknown>>;
}

export function playStateSequence(
  target: StateSequenceTarget,
  steps: readonly (StateSequenceStep | SpriteName)[],
  options?: PlayStateSequenceOptions,
): StateSequencePlayer;

export default StateSequencePlayer;
