// 五子棋主面板 Alpine 组件，负责 PvP/PvE 对局、Canvas 棋盘绘制和实时同步。

const DEFAULT_GOMOKU_BASE_URL = "/gomoku";
const DEFAULT_GOMOKU_CONFIG_URL = "/gomoku/config";
const DEFAULT_GOMOKU_ACTIVE_URL = "/gomoku/active";
const DEFAULT_GOMOKU_CREATE_URL = "/gomoku/create";
const BOARD_SIZE = 15;
const DEFAULT_CELL_SIZE = 30;
const DEFAULT_BOARD_PADDING = 22;
const DEFAULT_INVITE_TIMEOUT = 60;
const DEFAULT_PVP_REWARD = 80;
const DEFAULT_AI_ICONS = ["🟢", "🟡", "🔴", "⚡"];
const DEFAULT_AI_COLORS = ["#16a34a", "#ca8a04", "#dc2626", "#7c3aed"];
const DEFAULT_AI_LEVELS = [
    { id: 1, name: "简单", icon: "🟢", reward: 20, fee: 0, color: "#16a34a" },
    { id: 2, name: "普通", icon: "🟡", reward: 50, fee: 10, color: "#ca8a04" },
    { id: 3, name: "困难", icon: "🔴", reward: 120, fee: 30, color: "#dc2626" },
    { id: 4, name: "专家", icon: "⚡", reward: 300, fee: 80, color: "#7c3aed" },
];

/**
 * 读取五子棋接口地址，优先使用 Blade 写入的命名路由。
 *
 * @returns {{base: string, config: string, active: string, create: string}}
 */
function gomokuUrls() {
    const panel = document.getElementById("gomoku-panel");

    return {
        base: panel?.dataset.gomokuBaseUrl || DEFAULT_GOMOKU_BASE_URL,
        config: panel?.dataset.gomokuConfigUrl || DEFAULT_GOMOKU_CONFIG_URL,
        active: panel?.dataset.gomokuActiveUrl || DEFAULT_GOMOKU_ACTIVE_URL,
        create: panel?.dataset.gomokuCreateUrl || DEFAULT_GOMOKU_CREATE_URL,
    };
}

/**
 * 拼接五子棋对局动态接口路径。
 *
 * @param {number|string} gameId 对局 ID
 * @param {string} action 动作名称
 * @returns {string}
 */
function gomokuGameUrl(gameId, action) {
    return `${gomokuUrls().base}/${gameId}/${action}`;
}

/**
 * 读取 CSRF Token。
 *
 * @returns {string}
 */
function csrfToken() {
    return document.querySelector('meta[name="csrf-token"]')?.content ?? "";
}

/**
 * 生成空棋盘矩阵。
 *
 * @returns {number[][]}
 */
function emptyBoard() {
    return Array.from({ length: BOARD_SIZE }, () => Array(BOARD_SIZE).fill(0));
}

/**
 * 请求 JSON，失败时返回 null，保持原组件的静默容错行为。
 *
 * @param {string} url 请求地址
 * @returns {Promise<Record<string, any>|null>}
 */
async function fetchJson(url) {
    return fetch(url).then((response) => response.json()).catch(() => null);
}

/**
 * 创建五子棋面板 Alpine 组件。
 *
 * @returns {Record<string, any>}
 */
export function gomokuPanel() {
    return {
        show: false,
        mode: "pvp",
        gameStatus: "idle",
        gameId: null,
        myColor: null,
        blackName: "",
        whiteName: "",
        currentTurn: 1,
        stepCount: 0,
        aiLevels: DEFAULT_AI_LEVELS.map((level) => ({ ...level })),
        board: [],
        hoverPos: null,
        lastMove: null,
        CELL: DEFAULT_CELL_SIZE,
        PAD: DEFAULT_BOARD_PADDING,
        rewardGold: 0,
        resultEmoji: "",
        resultText: "",
        resultGold: 0,
        inviteTimeout: DEFAULT_INVITE_TIMEOUT,
        moveTimeout: 0,
        _inviteTimer: null,
        _moveTimer: null,
        _opponentPollTimer: null,
        _echoChannel: null,
        _pvpReward: DEFAULT_PVP_REWARD,

        /**
         * 面板标题。
         *
         * @returns {string}
         */
        get title() {
            if (this.gameStatus === "idle") {
                return "五子棋";
            }
            if (this.gameStatus === "waiting") {
                return "等待对手…";
            }
            if (this.gameStatus === "finished") {
                return "对局结束";
            }

            return this.mode === "pvp" ? "五子棋 PvP 对战" : "五子棋 AI 对战";
        },

        /**
         * 面板副标题。
         *
         * @returns {string}
         */
        get subtitle() {
            if (this.gameStatus === "idle") {
                return "选择游戏模式开始";
            }
            if (this.gameStatus === "waiting") {
                return "已发出对战邀请";
            }
            if (this.gameStatus === "finished") {
                return "对局已结束";
            }

            return this.isMyTurn ? "● 轮到您落子" : "○ 等待对方落子…";
        },

        /**
         * 判断是否轮到当前用户落子。
         *
         * @returns {boolean}
         */
        get isMyTurn() {
            if (this.mode === "pve") {
                return this.currentTurn === 1;
            }

            return this.currentTurn === this.myColor;
        },

        /**
         * 当前回合文案。
         *
         * @returns {string}
         */
        get turnText() {
            if (this.gameStatus === "finished") {
                return "对局结束";
            }

            return this.currentTurn === 1 ? "● 黑棋回合" : "○ 白棋回合";
        },

        /**
         * 从外部打开面板，并尝试恢复未完成对局。
         *
         * @returns {Promise<void>}
         */
        async open() {
            this.show = true;
            this.resetToIdle();
            this.$nextTick(() => this.initCanvas());

            const [config, active] = await Promise.all([
                fetchJson(gomokuUrls().config),
                fetchJson(gomokuUrls().active),
            ]);

            this.applyConfig(config);

            if (active?.has_active) {
                await this.confirmActiveGame(active);
            }
        },

        /**
         * 初始化面板并直接加入对局。
         *
         * @param {number|string} gameId 对局 ID
         * @returns {Promise<void>}
         */
        async openAndJoin(gameId) {
            this.show = true;
            this.$nextTick(async () => {
                this.initCanvas();
                await this.joinGame(gameId);
            });
        },

        /**
         * 关闭面板，对局中需要二次确认以避免误关。
         *
         * @returns {void}
         */
        closePanel() {
            if (this.gameStatus === "playing") {
                window.chatDialog?.confirm("对局进行中，关闭面板将不会认输，确定关闭？").then((ok) => {
                    if (ok) {
                        this.cleanUp();
                        this.show = false;
                    }
                });
                return;
            }

            this.cleanUp();
            this.show = false;
        },

        /**
         * 重置到选择模式界面。
         *
         * @returns {void}
         */
        resetToIdle() {
            this.cleanUp();
            this.gameStatus = "idle";
            this.gameId = null;
            this.myColor = null;
            this.board = emptyBoard();
            this.lastMove = null;
            this.stepCount = 0;
            this.rewardGold = 0;
            this.resultEmoji = "";
            this.resultText = "";
            this.resultGold = 0;
            this.$nextTick(() => this.redrawBoard());
        },

        /**
         * 清理定时器和 WebSocket 监听。
         *
         * @returns {void}
         */
        cleanUp() {
            clearInterval(this._inviteTimer);
            clearInterval(this._moveTimer);
            clearInterval(this._opponentPollTimer);
            this._inviteTimer = null;
            this._moveTimer = null;
            this._opponentPollTimer = null;

            if (this._echoChannel && window.Echo) {
                window.Echo.leave(`gomoku.${this.gameId}`);
                this._echoChannel = null;
            }
        },

        /**
         * 发起 PvP 随机对战。
         *
         * @returns {Promise<void>}
         */
        async startPvP() {
            const roomId = window.chatContext?.roomId;
            if (!roomId) {
                return;
            }

            const response = await this.post(gomokuUrls().create, {
                mode: "pvp",
                room_id: roomId,
            });

            if (!response?.ok) {
                window.chatDialog?.alert(response?.message || "发起失败，请稍后重试");
                return;
            }

            this.gameId = response.game_id;
            this.mode = "pvp";
            this.myColor = 1;
            this.blackName = window.chatContext?.username || "我";
            this.whiteName = "等待中…";
            this.gameStatus = "waiting";
            this.inviteTimeout = DEFAULT_INVITE_TIMEOUT;
            this.startInviteCountdown();
            this.waitForOpponent();
        },

        /**
         * 发起 PvE 人机对战。
         *
         * @param {Record<string, any>} level AI 难度配置
         * @returns {Promise<void>}
         */
        async startPvE(level) {
            const roomId = window.chatContext?.roomId;
            if (!roomId) {
                return;
            }

            const response = await this.post(gomokuUrls().create, {
                mode: "pve",
                room_id: roomId,
                ai_level: level.id,
            });

            if (!response?.ok) {
                window.chatDialog?.alert(response?.message || "创建失败，请稍后重试");
                return;
            }

            this.gameId = response.game_id;
            this.mode = "pve";
            this.myColor = 1;
            this.blackName = window.chatContext?.username || "我";
            this.whiteName = `AI（${level.name}）`;
            this.rewardGold = level.reward;
            this.board = emptyBoard();
            this.currentTurn = 1;
            this.gameStatus = "playing";
            this.$nextTick(() => this.redrawBoard());
        },

        /**
         * 加入 PvP 对战。
         *
         * @param {number|string} gameId 对局 ID
         * @returns {Promise<void>}
         */
        async joinGame(gameId) {
            const response = await this.post(gomokuGameUrl(gameId, "join"), {});

            if (!response?.ok) {
                window.chatDialog?.alert(response?.message || "加入失败");
                this.show = false;
                return;
            }

            await this.syncState(gameId);
            this.subscribeToGame(gameId);
        },

        /**
         * 等待对手加入，保留轮询兜底。
         *
         * @returns {void}
         */
        waitForOpponent() {
            const maxAttempts = 20;
            let attempts = 0;

            clearInterval(this._opponentPollTimer);
            this._opponentPollTimer = setInterval(async () => {
                if (this.gameStatus !== "waiting") {
                    clearInterval(this._opponentPollTimer);
                    this._opponentPollTimer = null;
                    return;
                }

                attempts += 1;
                if (attempts > maxAttempts) {
                    clearInterval(this._opponentPollTimer);
                    this._opponentPollTimer = null;
                    return;
                }

                const response = await fetchJson(gomokuGameUrl(this.gameId, "state"));
                if (response?.status === "playing") {
                    clearInterval(this._opponentPollTimer);
                    this._opponentPollTimer = null;
                    this.whiteName = response.opponent_name || "对手";
                    this.currentTurn = response.current_turn;
                    this.gameStatus = "playing";
                    this.subscribeToGame(this.gameId);
                    this.$nextTick(() => this.redrawBoard());
                }
            }, 3000);
        },

        /**
         * 同步对局状态。
         *
         * @param {number|string} gameId 对局 ID
         * @returns {Promise<void>}
         */
        async syncState(gameId) {
            const response = await fetchJson(gomokuGameUrl(gameId, "state"));
            if (!response?.ok) {
                return;
            }

            this.gameId = gameId;
            this.mode = response.mode;
            this.myColor = response.your_color;
            this.currentTurn = response.current_turn;
            this.board = response.board;
            this.gameStatus = response.status;
            this.blackName = response.black_name || "黑棋";
            this.whiteName = response.white_name || "白棋";
            this.rewardGold = response.mode === "pvp" ? DEFAULT_PVP_REWARD : response.reward_gold ?? 0;
            this.$nextTick(() => this.redrawBoard());
        },

        /**
         * 订阅对局私有频道。
         *
         * @param {number|string} gameId 对局 ID
         * @returns {void}
         */
        subscribeToGame(gameId) {
            if (!window.Echo) {
                return;
            }

            this._echoChannel = window.Echo.private(`gomoku.${gameId}`)
                .listen(".gomoku.moved", (event) => {
                    this.onRemoteMove(event);
                })
                .listen(".gomoku.finished", (event) => {
                    this.onGameFinished(event);
                });
        },

        /**
         * 收到远端落子。
         *
         * @param {Record<string, any>} event 广播事件
         * @returns {void}
         */
        onRemoteMove(event) {
            if (this.mode !== "pvp") {
                return;
            }

            this.board[event.row][event.col] = event.color;
            this.lastMove = {
                row: event.row,
                col: event.col,
            };
            this.currentTurn = event.current_turn;
            this.stepCount += 1;
            this.redrawBoard();
        },

        /**
         * 收到对局结束广播。
         *
         * @param {Record<string, any>} event 广播事件
         * @returns {void}
         */
        onGameFinished(event) {
            if (event.game_id !== this.gameId) {
                return;
            }

            this.gameStatus = "finished";
            this.showResult(event.winner, event.winner_name, event.reason, event.reward_gold);
        },

        /**
         * Canvas 点击事件：计算坐标并落子。
         *
         * @param {MouseEvent} event 鼠标事件
         * @returns {Promise<void>}
         */
        async handleCanvasClick(event) {
            if (this.gameStatus !== "playing" || !this.isMyTurn) {
                return;
            }

            const position = this.resolveBoardPosition(event);
            if (!position || this.board[position.row][position.col] !== 0) {
                return;
            }

            this.placeLocalStone(position.row, position.col, this.myColor);

            if (this.mode === "pvp") {
                this.currentTurn = this.myColor === 1 ? 2 : 1;
            } else {
                this.currentTurn = 2;
            }

            const response = await this.post(gomokuGameUrl(this.gameId, "move"), {
                row: position.row,
                col: position.col,
            });

            if (!response?.ok) {
                this.rollbackLocalStone(position.row, position.col);
                return;
            }

            if (response.finished) {
                this.gameStatus = "finished";
                this.showResult(response.winner, "", response.reason, response.reward_gold);
                return;
            }

            if (this.mode === "pve" && response.ai_moved) {
                this.applyDelayedAiMove(response.ai_moved);
            }
        },

        /**
         * Canvas 鼠标移动：更新悬停预览。
         *
         * @param {MouseEvent} event 鼠标事件
         * @returns {void}
         */
        handleCanvasHover(event) {
            if (this.gameStatus !== "playing" || !this.isMyTurn) {
                return;
            }

            const position = this.resolveBoardPosition(event);
            if (position && this.board[position.row][position.col] === 0) {
                this.hoverPos = position;
            } else {
                this.hoverPos = null;
            }

            this.redrawBoard();
        },

        /**
         * 认输。
         *
         * @returns {Promise<void>}
         */
        async resign() {
            const ok = await window.chatDialog?.confirm("确定认输？").catch(() => false) ?? false;
            if (!ok) {
                return;
            }

            const response = await this.post(gomokuGameUrl(this.gameId, "resign"), {});
            if (response?.finished) {
                this.gameStatus = "finished";
                this.showResult(response.winner, "", "resign", 0);
            }
        },

        /**
         * 取消邀请。
         *
         * @returns {Promise<void>}
         */
        async cancelInvite() {
            await this.post(gomokuGameUrl(this.gameId, "cancel"), {});
            clearInterval(this._inviteTimer);
            this._inviteTimer = null;
            this.gameStatus = "idle";
        },

        /**
         * 显示对局结果。
         *
         * @param {number} winner 获胜颜色，0 表示平局
         * @param {string} winnerName 获胜玩家名称
         * @param {string} reason 结束原因
         * @param {number} gold 奖励金币
         * @returns {void}
         */
        showResult(winner, winnerName, reason, gold) {
            const isWinner = winner === this.myColor;
            const isDraw = winner === 0;
            this.resultGold = gold ?? 0;

            if (isDraw) {
                this.resultEmoji = "🤝";
                this.resultText = "平局！势均力敌";
            } else if (reason === "resign") {
                this.resultEmoji = isWinner ? "🏆" : "😔";
                this.resultText = isWinner ? "对手认输，您获胜！" : "您认输了";
            } else {
                this.resultEmoji = isWinner ? "🏆" : "😔";
                this.resultText = isWinner
                    ? (this.mode === "pve" ? "恭喜！您击败了 AI！" : "恭喜！您获胜了！")
                    : (this.mode === "pve" ? "AI 获胜，再接再厉！" : "惜败，再来一局！");
            }

            if (isWinner && gold > 0 && window.chatContext) {
                window.chatContext.userJjb = (window.chatContext.userJjb ?? 0) + gold;
            }
        },

        /**
         * 初始化 Canvas 尺寸。
         *
         * @returns {void}
         */
        initCanvas() {
            const canvas = document.getElementById("gomoku-canvas");
            if (!canvas) {
                return;
            }

            const size = this.CELL * (BOARD_SIZE - 1) + this.PAD * 2;
            canvas.width = size;
            canvas.height = size;
            this.redrawBoard();
        },

        /**
         * 重绘棋盘、棋子、最后落子和悬停预览。
         *
         * @returns {void}
         */
        redrawBoard() {
            const canvas = document.getElementById("gomoku-canvas");
            if (!canvas) {
                return;
            }

            const context = canvas.getContext("2d");
            const { CELL, PAD } = this;
            const size = CELL * (BOARD_SIZE - 1) + PAD * 2;

            this.drawBoardBase(context, size, CELL, PAD);
            this.drawStones(context, CELL, PAD);
            this.drawHoverStone(context, CELL, PAD);
        },

        /**
         * 封装 POST 请求。
         *
         * @param {string} url 请求地址
         * @param {Record<string, any>} data 请求数据
         * @returns {Promise<Record<string, any>|null>}
         */
        async post(url, data) {
            try {
                const response = await fetch(url, {
                    method: "POST",
                    headers: {
                        "X-CSRF-TOKEN": csrfToken(),
                        "Content-Type": "application/json",
                        Accept: "application/json",
                    },
                    body: JSON.stringify(data),
                });

                return await response.json();
            } catch (error) {
                return null;
            }
        },

        /**
         * 应用后台五子棋配置。
         *
         * @param {Record<string, any>|null} config 后台配置
         * @returns {void}
         */
        applyConfig(config) {
            if (config?.ok && Array.isArray(config.pve_levels)) {
                this.aiLevels = config.pve_levels.map((level, index) => ({
                    id: level.level,
                    name: level.name,
                    icon: DEFAULT_AI_ICONS[index] ?? "♟️",
                    reward: level.reward,
                    fee: level.fee,
                    color: DEFAULT_AI_COLORS[index] ?? "#336699",
                }));
                this._pvpReward = config.pvp_reward ?? DEFAULT_PVP_REWARD;
            }
        },

        /**
         * 有未完成对局时询问用户是否恢复。
         *
         * @param {Record<string, any>} active 活跃对局数据
         * @returns {Promise<void>}
         */
        async confirmActiveGame(active) {
            const modeLabel = active.mode === "pve"
                ? `人机对战（${active.white_name}）`
                : `PvP 对战（${active.black_name} vs ${active.white_name}）`;
            const resume = await window.chatDialog.confirm(
                `您有一局未完成的${modeLabel}，确定继续吗？\n取消则直接认输并结束本局。`,
                "♟️ 恢复对局",
                "#1e3a5f",
            );

            if (resume) {
                this.restoreActiveGame(active);
                return;
            }

            await fetch(gomokuGameUrl(active.game_id, "resign"), {
                method: "POST",
                headers: {
                    "X-CSRF-TOKEN": csrfToken(),
                    Accept: "application/json",
                },
            }).catch(() => {});
            await window.chatDialog.alert("已认输，对局结束。", "♟️ 对局结束", "#336699");
        },

        /**
         * 恢复未完成对局状态。
         *
         * @param {Record<string, any>} active 活跃对局数据
         * @returns {void}
         */
        restoreActiveGame(active) {
            this.gameId = active.game_id;
            this.mode = active.mode;
            this.myColor = active.your_color;
            this.currentTurn = active.current_turn;
            this.board = active.board;
            this.blackName = active.black_name;
            this.whiteName = active.white_name;
            this.gameStatus = active.status === "waiting" ? "waiting" : "playing";
            this.rewardGold = active.mode === "pvp" ? (this._pvpReward ?? DEFAULT_PVP_REWARD) : 0;
            this.$nextTick(() => this.redrawBoard());

            if (active.mode === "pvp" && active.status === "playing") {
                this.subscribeToGame(active.game_id);
            }
        },

        /**
         * 启动邀请倒计时。
         *
         * @returns {void}
         */
        startInviteCountdown() {
            clearInterval(this._inviteTimer);
            this._inviteTimer = setInterval(() => {
                this.inviteTimeout -= 1;
                if (this.inviteTimeout <= 0) {
                    clearInterval(this._inviteTimer);
                    this._inviteTimer = null;
                    if (this.gameStatus === "waiting") {
                        this.gameStatus = "idle";
                        window.chatDialog?.alert("邀请已超时，无人接受挑战。");
                    }
                }
            }, 1000);
        },

        /**
         * 计算鼠标在棋盘上的行列。
         *
         * @param {MouseEvent} event 鼠标事件
         * @returns {{row: number, col: number}|null}
         */
        resolveBoardPosition(event) {
            const canvas = document.getElementById("gomoku-canvas");
            if (!canvas) {
                return null;
            }

            const rect = canvas.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;
            const col = Math.round((x - this.PAD) / this.CELL);
            const row = Math.round((y - this.PAD) / this.CELL);

            if (row < 0 || row >= BOARD_SIZE || col < 0 || col >= BOARD_SIZE) {
                return null;
            }

            return { row, col };
        },

        /**
         * 本地先行落子，等待服务端确认。
         *
         * @param {number} row 行
         * @param {number} col 列
         * @param {number} color 棋子颜色
         * @returns {void}
         */
        placeLocalStone(row, col, color) {
            this.board[row][col] = color;
            this.lastMove = { row, col };
            this.stepCount += 1;
            this.redrawBoard();
        },

        /**
         * 服务端拒绝落子时回滚本地乐观更新。
         *
         * @param {number} row 行
         * @param {number} col 列
         * @returns {void}
         */
        rollbackLocalStone(row, col) {
            this.board[row][col] = 0;
            this.stepCount -= 1;
            this.currentTurn = this.myColor;
            this.redrawBoard();
        },

        /**
         * 延迟展示 AI 落子，保留“思考”节奏。
         *
         * @param {{row: number, col: number}} aiMove AI 落子
         * @returns {void}
         */
        applyDelayedAiMove(aiMove) {
            setTimeout(() => {
                this.board[aiMove.row][aiMove.col] = 2;
                this.lastMove = {
                    row: aiMove.row,
                    col: aiMove.col,
                };
                this.stepCount += 1;
                this.currentTurn = 1;
                this.redrawBoard();
            }, 600);
        },

        /**
         * 绘制棋盘背景、网格、星位和坐标。
         *
         * @param {CanvasRenderingContext2D} context Canvas 上下文
         * @param {number} size 棋盘尺寸
         * @param {number} cell 格子大小
         * @param {number} pad 内边距
         * @returns {void}
         */
        drawBoardBase(context, size, cell, pad) {
            context.fillStyle = "#dcb866";
            context.fillRect(0, 0, size, size);
            context.strokeStyle = "#a0783a";
            context.lineWidth = 0.8;

            for (let index = 0; index < BOARD_SIZE; index += 1) {
                const position = pad + index * cell;
                context.beginPath();
                context.moveTo(position, pad);
                context.lineTo(position, pad + (BOARD_SIZE - 1) * cell);
                context.stroke();
                context.beginPath();
                context.moveTo(pad, position);
                context.lineTo(pad + (BOARD_SIZE - 1) * cell, position);
                context.stroke();
            }

            this.drawStarPoints(context, cell, pad);
            this.drawCoordinates(context, cell, pad);
        },

        /**
         * 绘制棋盘星位。
         *
         * @param {CanvasRenderingContext2D} context Canvas 上下文
         * @param {number} cell 格子大小
         * @param {number} pad 内边距
         * @returns {void}
         */
        drawStarPoints(context, cell, pad) {
            context.fillStyle = "#7a5c28";
            [[3, 3], [3, 11], [11, 3], [11, 11], [7, 7]].forEach(([row, col]) => {
                context.beginPath();
                context.arc(pad + col * cell, pad + row * cell, 3.5, 0, Math.PI * 2);
                context.fill();
            });
        },

        /**
         * 绘制棋盘坐标。
         *
         * @param {CanvasRenderingContext2D} context Canvas 上下文
         * @param {number} cell 格子大小
         * @param {number} pad 内边距
         * @returns {void}
         */
        drawCoordinates(context, cell, pad) {
            const columns = "ABCDEFGHJKLMNOP";
            context.fillStyle = "#7a5c28";
            context.font = "9px monospace";
            context.textAlign = "center";
            context.textBaseline = "middle";

            for (let index = 0; index < BOARD_SIZE; index += 1) {
                context.fillText(columns[index], pad + index * cell, pad - 12);
                context.fillText(BOARD_SIZE - index, pad - 14, pad + index * cell);
            }
        },

        /**
         * 绘制所有棋子和最后落子标记。
         *
         * @param {CanvasRenderingContext2D} context Canvas 上下文
         * @param {number} cell 格子大小
         * @param {number} pad 内边距
         * @returns {void}
         */
        drawStones(context, cell, pad) {
            this.board.forEach((row, rowIndex) => {
                row.forEach((stone, colIndex) => {
                    if (stone === 0) {
                        return;
                    }

                    this.drawStone(context, rowIndex, colIndex, stone, cell, pad);
                });
            });
        },

        /**
         * 绘制单枚棋子。
         *
         * @param {CanvasRenderingContext2D} context Canvas 上下文
         * @param {number} row 行
         * @param {number} col 列
         * @param {number} stone 棋子颜色
         * @param {number} cell 格子大小
         * @param {number} pad 内边距
         * @returns {void}
         */
        drawStone(context, row, col, stone, cell, pad) {
            const x = pad + col * cell;
            const y = pad + row * cell;
            const isLast = this.lastMove?.row === row && this.lastMove?.col === col;
            const gradient = context.createRadialGradient(x - 3, y - 3, 1, x, y, cell * 0.45);

            if (stone === 1) {
                gradient.addColorStop(0, "#888");
                gradient.addColorStop(1, "#111");
            } else {
                gradient.addColorStop(0, "#fff");
                gradient.addColorStop(1, "#ccc");
            }

            context.beginPath();
            context.arc(x, y, cell * 0.44, 0, Math.PI * 2);
            context.fillStyle = gradient;
            context.fill();
            context.strokeStyle = stone === 1 ? "#000" : "#aaa";
            context.lineWidth = 0.5;
            context.stroke();

            if (isLast) {
                context.beginPath();
                context.arc(x, y, 4, 0, Math.PI * 2);
                context.fillStyle = stone === 1 ? "#fff" : "#666";
                context.fill();
            }
        },

        /**
         * 绘制悬停预览棋子。
         *
         * @param {CanvasRenderingContext2D} context Canvas 上下文
         * @param {number} cell 格子大小
         * @param {number} pad 内边距
         * @returns {void}
         */
        drawHoverStone(context, cell, pad) {
            if (!this.hoverPos || !this.isMyTurn || this.gameStatus !== "playing") {
                return;
            }

            const { row, col } = this.hoverPos;
            if (this.board[row][col] !== 0) {
                return;
            }

            const x = pad + col * cell;
            const y = pad + row * cell;
            context.beginPath();
            context.arc(x, y, cell * 0.44, 0, Math.PI * 2);
            context.fillStyle = this.myColor === 1 ? "rgba(0,0,0,0.35)" : "rgba(255,255,255,0.55)";
            context.fill();
            context.strokeStyle = this.myColor === 1 ? "rgba(0,0,0,.5)" : "rgba(150,150,150,.5)";
            context.lineWidth = 1;
            context.stroke();
        },
    };
}

/**
 * 挂载五子棋主面板全局组件名，兼容 Blade 的 x-data。
 *
 * @returns {void}
 */
export function bindGomokuPanelControls() {
    if (typeof window === "undefined") {
        return;
    }

    window.gomokuPanel = gomokuPanel;
}
