// Playback — the handle `play()` returns (docs/animator-plan.md). Thenable: `await anim.play('slash')`
// resolves at the clip's HAND-OVER — where its fade-back starts (`end − fadeOut`; the end when there
// is no fadeOut) — with `true`; `false` when it was cut short (replaced / stopped). What
// you call right after the await is what the clip fades into: `anim.play(next)` (a chained one-shot,
// crossfaded), `anim.playLoop(next)` — nothing = back to the loop (or hold, on a layer without one).
// Returning one from an `async` function flattens it into `Promise<boolean>` — return `p.done` (or
// make the function sync) when the caller needs the handle.

import type { AnimationClip } from "./AnimationClip"
import type { Core, PlaybackRec, StopOptions } from "./core"
import type { Layer } from "./Layer"

export class Playback implements PromiseLike<boolean> {
  private readonly _r: PlaybackRec
  private readonly _c: Core
  /** @internal — created by play(). */
  constructor(rec: PlaybackRec, core: Core) { this._r = rec; this._c = core }

  /** The clip that plays. */
  get clip(): AnimationClip { return this._r.slot.clip }
  /** The layer it plays on. */
  get layer(): Layer { return this._r.slot.layer.view }
  /** Clock (s). */
  get time(): number { return this._r.slot.last === this._r ? this._c.slotTime(this._r.slot) : 0 }
  /** 0–1 through the clip (the current cycle, when looping). */
  get progress(): number { const d = this.clip.duration; return d > 0 ? this.time / d : 0 }
  /** Not handed over yet (the clip still owns the layer). */
  get playing(): boolean { return this._r.playing }
  /** Current contribution to the pose, 0–1 — moves during crossfades, still falling after the
   *  hand-over while the clip fades beneath what took over. */
  get weight(): number { return this._r.slot.last === this._r ? this._c.slotWeight(this._r.slot) : 0 }
  /** Resolves at the hand-over (`true`) or when cut short (`false`) — the promise `await playback`
   *  uses; the one to return from async functions or put in `Promise.all`. */
  get done(): Promise<boolean> { return this._r.done }

  then<A = boolean, B = never>(onFulfilled?: ((v: boolean) => A | PromiseLike<A>) | null, onRejected?: ((e: unknown) => B | PromiseLike<B>) | null): Promise<A | B> {
    return this._r.done.then(onFulfilled, onRejected)
  }

  /** Move the clock (s). */
  seek(time: number): this { this._c.seek(this._r.slot, time); return this }
  /** Cut this clip short: fade it out (the layer's loop returns if nothing else is coming in). */
  stop(options: StopOptions = {}): this { this._c.stopSlot(this._r.slot, options.fade ?? 0); return this }
}
