import { runChoreo } from './animationHelper.js';

/**
 * Builder-pattern DSL for choreography sequences.
 *
 * @example
 * ```ts
 * seq()
 *   .enterFromLeft(0.6)
 *   .happy(300)
 *   .waveHand(600)
 *   .celebrate(500)
 * ```
 */
class ChoreoBuilder {
    constructor() {
        this.steps = [];
        this.labels = {};
    }
    pose(pose, holdMs = 0, options) {
        this.steps.push({ type: "pose", pose, holdMs, options });
        return this;
    }
    wait(ms) {
        this.steps.push({ type: "wait", ms });
        return this;
    }
    tempo(multiplier) {
        const m = Math.max(0, multiplier);
        this.steps = this.steps.map((s) => {
            if (s.type === "pose") {
                return {
                    ...s,
                    holdMs: typeof s.holdMs === "number" ? Math.round(s.holdMs * m) : s.holdMs,
                    options: s.options
                        ? {
                            ...s.options,
                            duration: typeof s.options.duration === "number"
                                ? s.options.duration * m
                                : s.options.duration,
                        }
                        : s.options,
                };
            }
            if (s.type === "wait")
                return { ...s, ms: Math.round(s.ms * m) };
            return s;
        });
        return this;
    }
    repeat(times) {
        const t = Math.max(0, Math.floor(times));
        const base = this.steps.slice();
        for (let i = 1; i < t; i++)
            this.steps.push(...base);
        return this;
    }
    loop(times) {
        this.loopCount = times;
        return this;
    }
    label(name) {
        this.labels[name] = this.steps.length;
        this.steps.push({ type: "label", name });
        return this;
    }
    goto(name) {
        this.steps.push({ type: "goto", name });
        return this;
    }
    gotoIf(name, when) {
        this.steps.push({ type: "goto", name, when });
        return this;
    }
    // Convenience shortcuts
    idle(ms = 0) { return this.pose("idle", ms); }
    enterFromLeft(duration = 0.6) { return this.pose("enterFromLeft", 0, { duration }); }
    enterFromRight(duration = 0.6) { return this.pose("enterFromRight", 0, { duration }); }
    exitLeft(duration = 0.6) { return this.pose("exitLeft", 0, { duration }); }
    exitRight(duration = 0.6) { return this.pose("exitRight", 0, { duration }); }
    hideDown(duration = 0.6) { return this.pose("hideDown", 0, { duration }); }
    happy(ms = 350) { return this.pose("happy", ms); }
    angry(ms = 350) { return this.pose("angry", ms); }
    disappointed(ms = 350) { return this.pose("disappointed", ms); }
    swearing(ms = 350) { return this.pose("swearing", ms); }
    shouting(ms = 350) { return this.pose("shouting", ms); }
    rock(ms = 500) { return this.pose("rock", ms); }
    danceA(ms = 350) { return this.pose("danceA", ms); }
    danceB(ms = 350) { return this.pose("danceB", ms); }
    danceC(ms = 350) { return this.pose("danceC", ms); }
    waveHand(ms = 600) { return this.pose("waveHand", ms); }
    facepalm(ms = 500) { return this.pose("facepalm", ms); }
    shrug(ms = 450) { return this.pose("shrug", ms); }
    nodYes(ms = 450) { return this.pose("nodYes", ms); }
    shakeNo(ms = 450) { return this.pose("shakeNo", ms); }
    pointRight(ms = 400) { return this.pose("pointRight", ms); }
    leanBack(ms = 400) { return this.pose("leanBack", ms); }
    jump(ms = 450) { return this.pose("jump", ms); }
    spin(ms = 650) { return this.pose("spin", ms); }
    celebrate(ms = 600) { return this.pose("celebrate", ms); }
    raiseCard(score = 10, holdMs = 800, duration = 0.6) {
        return this.pose("raiseCard", holdMs, { duration, score });
    }
    build() { return this.steps.slice(); }
    getLoop() { return this.loopCount; }
}
/** Create a new ChoreoBuilder. */
const seq = () => new ChoreoBuilder();
/** Compile builder or steps into flat ChoreoStep[] (resolving gotos). */
function compile(builderOrSteps, ctx = {}) {
    const internal = builderOrSteps instanceof ChoreoBuilder
        ? builderOrSteps.build()
        : builderOrSteps;
    const labelIndex = {};
    internal.forEach((s, i) => { if (s.type === "label")
        labelIndex[s.name] = i; });
    const out = [];
    for (let pc = 0; pc < internal.length; pc++) {
        const s = internal[pc];
        if (s.type === "pose")
            out.push({ pose: s.pose, holdMs: s.holdMs, options: s.options });
        else if (s.type === "wait")
            out.push({ pose: "idle", holdMs: s.ms });
        else if (s.type === "goto") {
            const should = s.when ? !!s.when(ctx) : true;
            if (should) {
                const idx = labelIndex[s.name];
                if (idx !== undefined)
                    pc = idx;
            }
        }
    }
    return out;
}
/** Run compiled choreography on a single actor (with optional looping). */
async function runSeq(fx, builderOrSteps, ctx = {}) {
    if (builderOrSteps instanceof ChoreoBuilder) {
        const loop = builderOrSteps.getLoop();
        if (loop === Infinity) {
            // Intentional infinite loop for unbounded choreography playback
            // eslint-disable-next-line no-constant-condition
            while (true) {
                const steps = compile(builderOrSteps, ctx);
                await runChoreo(fx, steps);
            }
        }
        else {
            const times = Math.max(1, loop !== null && loop !== void 0 ? loop : 1);
            for (let i = 0; i < times; i++) {
                const steps = compile(builderOrSteps, ctx);
                await runChoreo(fx, steps);
            }
        }
    }
    else {
        await runChoreo(fx, compile(builderOrSteps, ctx));
    }
}
async function runChoreoWithDelay(fx, steps, delayMs) {
    if (delayMs > 0)
        await new Promise((r) => setTimeout(r, delayMs));
    await runChoreo(fx, steps);
}
/** Wave pattern: stagger actors left→right. */
async function runWave(actors, choreo, staggerMs = 220, ctx = {}) {
    const steps = compile(choreo, ctx);
    await Promise.all(actors.map((fx, i) => runChoreoWithDelay(fx, steps, i * Math.max(0, staggerMs))));
}
/** Cannon pattern: wave left→right, then right→left. */
async function runCannon(actors, choreo, staggerMs = 220, ctx = {}) {
    await runWave(actors, choreo, staggerMs, ctx);
    const reversed = [...actors].reverse();
    await runWave(reversed, choreo, staggerMs, ctx);
}
/** Assign programs to actors round-robin. */
async function runRounds(actors, programs, staggerMs = 0, ctx = {}) {
    const jobs = actors.map((fx, i) => runChoreoWithDelay(fx, compile(programs[i % programs.length], ctx), staggerMs * i));
    await Promise.all(jobs);
}

export { ChoreoBuilder, compile, runCannon, runRounds, runSeq, runWave, seq };
//# sourceMappingURL=choreoDSL.js.map
