import { type NameOptions, type RNGState, SeededRandom } from "@common/random";
import { Component, World } from "../core/world";
import { component } from "../utils/decorators";

@component
export class Random extends Component<{ random: RNGState }> implements Omit<SeededRandom, 'jump' | 'longJump' | 'fork' | 'toJSON' | 'getState' | 'setState'> {
    private rng?: SeededRandom;

    constructor(random: SeededRandom | string | number | RNGState = Date.now()) {
        super({
            random: (() => {
                const rng = random instanceof SeededRandom ? random : new SeededRandom(random);
                return rng.getState();
            })()
        });
    }
    nextFloat(): number {
        return this.use((rng) => rng.nextFloat());
    }
    nextInt(min: number, max: number): number;
    nextInt(max: number): number;
    nextInt(minOrMax: number, max?: number): number {
        return this.use((rng) => rng.nextInt(minOrMax, max as number));
    }
    nextBool(): boolean {
        return this.use((rng) => rng.nextBool());
    }
    nextName(options?: NameOptions): string {
        return this.use((rng) => rng.nextName(options));
    }
    choice<T>(iterable: Iterable<T>): T;
    choice<T>(iterable: Iterable<T>, k: number): T[];
    choice<T>(iterable: Iterable<T>, k?: number): T | T[] {
        return this.use((rng) => rng.choice(iterable, k as number));
    }
    weightedChoice<T>(items: readonly T[], weights: readonly number[]): T;
    weightedChoice<T>(items: readonly T[], weights: readonly number[], k: number): T[];
    weightedChoice<T>(items: readonly T[], weights: readonly number[], k?: number): T | T[] {
        return this.use((rng) => rng.weightedChoice(items, weights, k as number));
    }
    shuffle<T>(arr: T[]): T[] {
        return this.use((rng) => rng.shuffle(arr));
    }
    toShuffled<T>(arr: readonly T[]): T[] {
        return this.use((rng) => rng.toShuffled(arr));
    }

    use<T>(fn: (rng: SeededRandom) => T): T {
        if (this.rng) {
            this.rng.setState(this.state.random);
        } else {
            this.rng = new SeededRandom(this.state.random);
        }
        const result = fn(this.rng);
        this.state.random = this.rng.getState();
        return result;
    }
}

export const getWorldRandom = (world: World): Random => {
    const random = world.findComponent(Random);
    if (random) {
        return random;
    }

    const entity = world.createEntity('random_*');
    return entity.add(new Random());
}
