/** Configures BetModel indexes. */
export interface BetModelConfig
{
    bets: number[];
    currentBetIndex?: number;
    defaultBetIndex?: number;
    locked?: boolean;
}

/** Controls bet selection and lock state. */
export default class BetModel
{
    private readonly availableBets: number[];

    private activeBetIndex: number;

    private readonly defaultIndex: number;

    private locked: boolean;

    constructor(bets: number[], currentBetIndex?: number, defaultBetIndex?: number);
    constructor(config: BetModelConfig);
    constructor(
        betsOrConfig: number[] | BetModelConfig,
        currentBetIndex = 0,
        defaultBetIndex = 0,
    )
    {
        const config = BetModel.normalizeConfig(
            betsOrConfig,
            currentBetIndex,
            defaultBetIndex,
        );

        this.availableBets = BetModel.validateBets(config.bets);

        const resolvedDefaultIndex = BetModel.validateIndex(
            config.defaultBetIndex ?? 0,
            this.availableBets,
        );

        const resolvedCurrentIndex = BetModel.validateIndex(
            ((config.currentBetIndex ?? 0) || resolvedDefaultIndex),
            this.availableBets,
        );

        this.defaultIndex = resolvedDefaultIndex;
        this.activeBetIndex = resolvedCurrentIndex;
        this.locked = Boolean(config.locked);
    }

    public get bet(): number
    {
        return this.availableBets[this.activeBetIndex];
    }

    public get bets(): readonly number[]
    {
        return this.availableBets;
    }

    public get currentBetIndex(): number
    {
        return this.activeBetIndex;
    }

    public get defaultBetIndex(): number
    {
        return this.defaultIndex;
    }

    public resetToDefault(): void
    {
        if (this.isLocked())
        {
            return;
        }

        this.activeBetIndex = this.defaultIndex;
    }

    public decreaseBet(): void
    {
        if (this.canDecreaseBet())
        {
            this.activeBetIndex -= 1;
        }
        else
        {
            console.warn('Cannot decrease bet.');
        }
    }

    public increaseBet(): void
    {
        if (this.canIncreaseBet())
        {
            this.activeBetIndex += 1;
        }
        else
        {
            console.warn('Cannot increase bet.');
        }
    }

    public setToMaxBet(): void
    {
        if (this.isLocked())
        {
            return;
        }

        this.activeBetIndex = this.availableBets.length - 1;
    }

    public setBetByValue(value: number): void
    {
        this.ensureUnlocked();

        const index = this.availableBets.indexOf(value);

        if (index === -1)
        {
            throw new Error(`Bet value ${value} is not valid.`);
        }

        this.activeBetIndex = index;
    }

    public canDecreaseBet(): boolean
    {
        return !this.isLocked() && this.activeBetIndex > 0;
    }

    public canIncreaseBet(): boolean
    {
        return !this.isLocked() && this.activeBetIndex < this.availableBets.length - 1;
    }

    public lock(): void
    {
        this.locked = true;
    }

    public unlock(): void
    {
        this.locked = false;
    }

    public isLocked(): boolean
    {
        return this.locked;
    }

    private ensureUnlocked(): void
    {
        if (this.locked)
        {
            throw new Error('Cannot set bet value: bets are locked.');
        }
    }

    private static validateBets(bets: number[]): number[]
    {
        if (!Array.isArray(bets) || bets.length === 0)
        {
            throw new Error('Bets must be a non-empty array of numbers.');
        }

        return bets;
    }

    private static validateIndex(index: number, bets: number[]): number
    {
        if (index < 0 || index >= bets.length)
        {
            throw new Error(`Index ${index} is out of bounds for bets array.`);
        }

        return index;
    }

    private static normalizeConfig(
        betsOrConfig: number[] | BetModelConfig,
        currentBetIndex: number,
        defaultBetIndex: number,
    ): BetModelConfig
    {
        if (Array.isArray(betsOrConfig))
        {
            return {
                bets: betsOrConfig,
                currentBetIndex,
                defaultBetIndex,
            };
        }

        return betsOrConfig;
    }
}
