export type Mark = 'X' | 'O';
export type Result = Mark | 'draw' | null;
const wins = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [0, 3, 6], [1, 4, 7], [2, 5, 8], [0, 4, 8], [2, 4, 6]];

/** Synchronous rules: no network, clocks, browser state, or asynchronous turn races. */
export class Game {
    private readonly players: string[] = [];
    private readonly board: (Mark | null)[] = Array(9).fill(null);
    private revision = 0;
    private result: Result = null;

    join(account: string) {
        if (this.players.includes(account)) return;
        if (this.players.length === 2) throw new Error('This room is full.');
        this.players.push(account);
    }

    snapshot(account: string) {
        const seat = this.players.indexOf(account);
        if (seat < 0) throw new Error('Join this room first.');
        return { board: [...this.board], players: [...this.players], revision: this.revision,
            result: this.result, turn: (this.revision % 2 === 0 ? 'X' : 'O') as Mark,
            you: (seat === 0 ? 'X' : 'O') as Mark };
    }

    move(account: string, cell: number, revision: number) {
        const { you, turn } = this.snapshot(account);
        if (this.players.length !== 2) throw new Error('Wait for a second player.');
        if (this.result) throw new Error('The game is finished. Choose a new room.');
        if (revision !== this.revision) throw new Error('The board changed. Try again.');
        if (you !== turn) throw new Error('Wait for your turn.');
        if (!Number.isInteger(cell) || cell < 0 || cell > 8 || this.board[cell] !== null) throw new Error('Choose an empty square.');
        this.board[cell] = you;
        this.revision++;
        if (wins.some(line => line.every(index => this.board[index] === you))) this.result = you;
        else if (this.revision === 9) this.result = 'draw';
    }
}

/** Rooms are public to signed-in players. Seats stay reserved for reconnects. */
export class Games {
    private readonly rooms = new Map<string, Game>();
    join(room: string, account: string) {
        let game = this.rooms.get(room);
        if (!game) {
            if (this.rooms.size >= 100) throw new Error('Room capacity reached. Restart this tutorial server.');
            game = new Game();
            this.rooms.set(room, game);
        }
        game.join(account);
        return game;
    }
    resume(room: string, account: string) {
        const game = this.rooms.get(room);
        if (!game) throw new Error('Room not found. Join a new room.');
        game.snapshot(account);
        return game;
    }
}
