import { Component } from "../core/world";
import { action, component, variable } from "../utils/decorators";

/**
 * How much XP is required to advance from level N to N+1.
 *
 * - `number[]`  — explicit table; `values[0]` = XP for level 1→2.
 *   Max level = `values.length + 1`. Past the end the entity is at max level.
 * - `{ base, factor }` — geometric: XP[N] = floor(base * factor^(N-1)).
 *   No max level.
 */
export type ThresholdSpec = number[] | { base: number; factor: number };

function xpForStep(spec: ThresholdSpec, level: number): number | null {
    if (Array.isArray(spec)) {
        const idx = level - 1;
        return idx < spec.length ? spec[idx] : null;
    }
    return Math.floor(spec.base * Math.pow(spec.factor, level - 1));
}

@component
export class Experience extends Component<{
    xp: number;
    level: number;
    xpAtLevel: number;  // total XP accumulated when current level was reached
    spec: ThresholdSpec;
}> {
    constructor(spec: ThresholdSpec) {
        super({ xp: 0, level: 1, xpAtLevel: 0, spec });
    }

    /** Current level, starting at 1. Primary variable for conditions: `experience >= 5`. */
    @variable get level(): number { return this.state.level; }

    /** Total accumulated XP (never resets). */
    @variable('.') get xp(): number { return this.state.xp; }

    /** XP accumulated within the current level. */
    @variable get xpInLevel(): number { return this.state.xp - this.state.xpAtLevel; }

    /** XP remaining until the next level, or `null` at max level. */
    get xpToNext(): number | null {
        const needed = xpForStep(this.state.spec, this.state.level);
        return needed === null ? null : needed - this.xpInLevel;
    }

    /** Progress toward the next level as a 0–1 fraction. `1` at max level. */
    @variable get progress(): number {
        const needed = xpForStep(this.state.spec, this.state.level);
        if (needed === null) return 1;
        return Math.min(this.xpInLevel / needed, 1);
    }

    /** Add XP and emit `'levelup'` with `{ level, prev }` for each level gained. */
    @action
    award(xp: number): void {
        this.state.xp += xp;
        while (true) {
            const needed = xpForStep(this.state.spec, this.state.level);
            if (needed === null) break;
            if (this.xpInLevel < needed) break;
            this.state.xpAtLevel += needed;
            const prev = this.state.level++;
            this.emit('levelup', { level: this.state.level, prev });
        }
    }
}
