import mitt from "mitt";
import { gameRegistry } from "@shared/games/registry";
import type { Room, RoomSummary } from "./types";

const ROOM_CONFIG = {
    WAITING_CLEANUP_MS: 60_000,
    FINISHED_CLEANUP_MS: 60_000,
} as const;

type ActionResult =
    | { ok: true; state: unknown; gameOver: { winner: string | null; reason: string } | null }
    | { ok: false; error: string };

type CreateResult = { ok: true; roomId: string } | { ok: false; error: string };
type JoinResult =
    | { ok: true; joinedAs: "player" | "spectator"; started: boolean }
    | { ok: false; error: string };

type RoomEvents = {
    "room:created": { roomId: string; gameSlug: string; hostId: string };
    "player:joined": { roomId: string; playerId: string; username: string; joinedAs: "player" | "spectator" };
    "game:started": { roomId: string; spectatorView: unknown; playerViews: Map<string, unknown> };
    "game:updated": { roomId: string; spectatorView: unknown; playerViews: Map<string, unknown> };
    "game:ended": { roomId: string; winner: string | null; reason: string };
    "player:left": { roomId: string; playerId: string };
    "room:list:changed": void;
};

export class RoomManager {
    private rooms = new Map<string, Room>();
    private cleanupTimers = new Map<string, Timer>();
    readonly emitter = mitt<RoomEvents>();

    createRoom(gameSlug: string, hostId: string): CreateResult {
        const plugin = gameRegistry.get(gameSlug);
        if (!plugin) return { ok: false, error: `Unknown game type: ${gameSlug}` };

        const id = crypto.randomUUID();
        const room: Room = {
            id,
            gameSlug,
            host: hostId,
            players: [hostId],
            spectators: new Set(),
            state: null,
            status: "waiting",
            createdAt: Date.now(),
        };

        this.rooms.set(id, room);
        this.scheduleCleanup(id, ROOM_CONFIG.WAITING_CLEANUP_MS);

        this.emitter.emit("room:created", { roomId: id, gameSlug, hostId });
        this.emitter.emit("room:list:changed");

        return { ok: true, roomId: id };
    }

    joinRoom(roomId: string, playerId: string, preferAs: "player" | "spectator", role?: string): JoinResult {
        const room = this.rooms.get(roomId);
        if (!room) return { ok: false, error: "Room not found" };

        if (preferAs === "spectator" || room.status !== "waiting") {
            room.spectators.add(playerId);
            return { ok: true, joinedAs: "spectator", started: room.status === "playing" };
        }

        if (room.players.includes(playerId)) {
            return { ok: true, joinedAs: "player", started: false };
        }

        const plugin = gameRegistry.get(room.gameSlug)!;
        const isAdmin = role === "admin";

        if (room.players.length >= plugin.maxPlayers && !isAdmin) {
            // Downgrade to spectator — room is full but still waiting, so game hasn't started
            room.spectators.add(playerId);
            return { ok: true, joinedAs: "spectator", started: false };
        }

        room.players.push(playerId);

        if (room.players.length >= plugin.maxPlayers) {
            room.state = plugin.createInitialState(room.players);
            room.status = "playing";
            this.clearCleanup(roomId);

            const spectatorView = plugin.getSpectatorView(room.state);
            const playerViews = new Map<string, unknown>();
            for (const pid of room.players) {
                playerViews.set(pid, plugin.getPlayerView(room.state, pid));
            }
            this.emitter.emit("game:started", { roomId, spectatorView, playerViews });
            this.emitter.emit("room:list:changed");
            return { ok: true, joinedAs: "player", started: true };
        }

        this.emitter.emit("room:list:changed");
        return { ok: true, joinedAs: "player", started: false };
    }

    handleAction(roomId: string, playerId: string, action: unknown): ActionResult {
        const room = this.rooms.get(roomId);
        if (!room) return { ok: false, error: "Room not found" };
        if (room.status !== "playing") return { ok: false, error: "Game is not in progress" };
        if (!room.players.includes(playerId)) return { ok: false, error: "You are not a player in this game" };

        const plugin = gameRegistry.get(room.gameSlug)!;
        const result = plugin.handleAction(room.state, action, playerId);
        if (!result.ok) return result;

        room.state = result.state;
        const gameOver = plugin.isGameOver?.(room.state) ?? null;
        if (gameOver) {
            room.status = "finished";
            this.scheduleCleanup(roomId, ROOM_CONFIG.FINISHED_CLEANUP_MS);
        }

        const spectatorView = plugin.getSpectatorView(room.state);
        const playerViews = new Map<string, unknown>();
        for (const pid of room.players) {
            playerViews.set(pid, plugin.getPlayerView(room.state, pid));
        }
        this.emitter.emit("game:updated", { roomId, spectatorView, playerViews });

        if (gameOver) {
            this.emitter.emit("game:ended", { roomId, winner: gameOver.winner, reason: gameOver.reason });
            this.emitter.emit("room:list:changed");
        }

        return { ok: true, state: room.state, gameOver };
    }

    leaveRoom(roomId: string, playerId: string): void {
        const room = this.rooms.get(roomId);
        if (!room) return;

        room.spectators.delete(playerId);

        const playerIdx = room.players.indexOf(playerId);
        if (playerIdx !== -1) {
            room.players.splice(playerIdx, 1);

            if (room.status === "playing") {
                const plugin = gameRegistry.get(room.gameSlug)!;
                if (plugin.onPlayerDisconnect) {
                    room.state = plugin.onPlayerDisconnect(room.state, playerId);
                    const gameOver = plugin.isGameOver?.(room.state) ?? null;
                    if (gameOver) {
                        room.status = "finished";
                        this.scheduleCleanup(roomId, ROOM_CONFIG.FINISHED_CLEANUP_MS);
                        this.emitter.emit("game:ended", { roomId, winner: gameOver.winner, reason: gameOver.reason });
                    }
                }
            }

            if (room.players.length === 0 && room.status === "waiting") {
                this.deleteRoom(roomId);
                this.emitter.emit("room:list:changed");
                return;
            }
        }

        this.emitter.emit("player:left", { roomId, playerId });
        this.emitter.emit("room:list:changed");
    }

    fillRoom(roomId: string, adminId: string): { ok: true } | { ok: false; error: string } {
        const room = this.rooms.get(roomId);
        if (!room) return { ok: false, error: "Room not found" };
        if (room.status !== "waiting") return { ok: false, error: "Game is not in waiting state" };
        if (!room.players.includes(adminId)) return { ok: false, error: "You are not in this room" };

        const plugin = gameRegistry.get(room.gameSlug)!;
        while (room.players.length < plugin.maxPlayers) {
            room.players.push(adminId);
        }

        room.state = plugin.createInitialState(room.players);
        room.status = "playing";
        this.clearCleanup(roomId);

        const spectatorView = plugin.getSpectatorView(room.state);
        const playerViews = new Map<string, unknown>();
        for (const pid of new Set(room.players)) {
            playerViews.set(pid, plugin.getPlayerView(room.state, pid));
        }
        this.emitter.emit("game:started", { roomId, spectatorView, playerViews });
        this.emitter.emit("room:list:changed");
        return { ok: true };
    }

    getRoom(roomId: string): Room | undefined {
        return this.rooms.get(roomId);
    }

    listRooms(gameSlug?: string): RoomSummary[] {
        const summaries: RoomSummary[] = [];
        for (const room of this.rooms.values()) {
            if (gameSlug && room.gameSlug !== gameSlug) continue;
            const plugin = gameRegistry.get(room.gameSlug);
            summaries.push({
                id: room.id,
                gameSlug: room.gameSlug,
                gameName: plugin?.name ?? room.gameSlug,
                host: room.host,
                playerCount: room.players.length,
                maxPlayers: plugin?.maxPlayers ?? 0,
                spectatorCount: room.spectators.size,
                status: room.status,
            });
        }
        return summaries;
    }

    getPlayerView(roomId: string, playerId: string): unknown {
        const room = this.rooms.get(roomId);
        if (!room || !room.state) return null;
        const plugin = gameRegistry.get(room.gameSlug)!;
        return plugin.getPlayerView(room.state, playerId);
    }

    getSpectatorView(roomId: string): unknown {
        const room = this.rooms.get(roomId);
        if (!room || !room.state) return null;
        const plugin = gameRegistry.get(room.gameSlug)!;
        return plugin.getSpectatorView(room.state);
    }

    private scheduleCleanup(roomId: string, ms: number): void {
        this.clearCleanup(roomId);
        const timer = setTimeout(() => this.deleteRoom(roomId), ms);
        this.cleanupTimers.set(roomId, timer);
    }

    private clearCleanup(roomId: string): void {
        const existing = this.cleanupTimers.get(roomId);
        if (existing) {
            clearTimeout(existing);
            this.cleanupTimers.delete(roomId);
        }
    }

    private deleteRoom(roomId: string): void {
        this.clearCleanup(roomId);
        this.rooms.delete(roomId);
    }
}
