local MonkeyGames = LibStub("AceAddon-3.0"):GetAddon("MonkeyGames")
local L = MonkeyGames.L

local Chess = {}
MonkeyGames.Chess = Chess

-- Board layout: board[row][col]
-- Row 1 = rank 8 (black's back rank), Row 8 = rank 1 (white's back rank)
-- Col 1 = file a, Col 8 = file h
-- Pieces: "wK","wQ","wR","wB","wN","wP","bK","bQ","bR","bB","bN","bP","" (empty)

local function CreateInitialBoard()
    return {
        { "bR", "bN", "bB", "bQ", "bK", "bB", "bN", "bR" }, -- rank 8
        { "bP", "bP", "bP", "bP", "bP", "bP", "bP", "bP" }, -- rank 7
        { "",   "",   "",   "",   "",   "",   "",   ""   },   -- rank 6
        { "",   "",   "",   "",   "",   "",   "",   ""   },   -- rank 5
        { "",   "",   "",   "",   "",   "",   "",   ""   },   -- rank 4
        { "",   "",   "",   "",   "",   "",   "",   ""   },   -- rank 3
        { "wP", "wP", "wP", "wP", "wP", "wP", "wP", "wP" }, -- rank 2
        { "wR", "wN", "wB", "wQ", "wK", "wB", "wN", "wR" }, -- rank 1
    }
end

function Chess:CreateGameState()
    return {
        board = CreateInitialBoard(),
        turn = "w",
        castleRights = { wK = true, wQ = true, bK = true, bQ = true },
        enPassantTarget = nil,   -- { row, col } or nil
        halfMoveClock = 0,
        fullMoveNumber = 1,
        inCheck = false,
        kingPositions = {
            w = { 8, 5 },
            b = { 1, 5 },
        },
    }
end

-- ========================================================================
-- Deep copy
-- ========================================================================

function Chess:CopyState(state)
    local copy = {
        turn = state.turn,
        halfMoveClock = state.halfMoveClock,
        fullMoveNumber = state.fullMoveNumber,
        inCheck = state.inCheck,
        enPassantTarget = state.enPassantTarget and { state.enPassantTarget[1], state.enPassantTarget[2] } or nil,
        castleRights = {
            wK = state.castleRights.wK,
            wQ = state.castleRights.wQ,
            bK = state.castleRights.bK,
            bQ = state.castleRights.bQ,
        },
        kingPositions = {
            w = { state.kingPositions.w[1], state.kingPositions.w[2] },
            b = { state.kingPositions.b[1], state.kingPositions.b[2] },
        },
        board = {},
    }
    for r = 1, 8 do
        copy.board[r] = {}
        for c = 1, 8 do
            copy.board[r][c] = state.board[r][c]
        end
    end
    return copy
end

-- ========================================================================
-- Utility helpers
-- ========================================================================

local function InBounds(row, col)
    return row >= 1 and row <= 8 and col >= 1 and col <= 8
end

local function PieceColor(piece)
    if piece == "" then return nil end
    return piece:sub(1, 1)
end

local function PieceType(piece)
    if piece == "" then return nil end
    return piece:sub(2, 2)
end

local function EnemyColor(color)
    return color == "w" and "b" or "w"
end

-- ========================================================================
-- Square attack detection
-- ========================================================================

-- Knight offsets
local KNIGHT_OFFSETS = {
    { -2, -1 }, { -2, 1 }, { -1, -2 }, { -1, 2 },
    {  1, -2 }, {  1, 2 }, {  2, -1 }, {  2, 1 },
}

-- Diagonal offsets (bishop/queen)
local DIAGONAL_OFFSETS = {
    { -1, -1 }, { -1, 1 }, { 1, -1 }, { 1, 1 },
}

-- Orthogonal offsets (rook/queen)
local ORTHOGONAL_OFFSETS = {
    { -1, 0 }, { 1, 0 }, { 0, -1 }, { 0, 1 },
}

-- King offsets
local KING_OFFSETS = {
    { -1, -1 }, { -1, 0 }, { -1, 1 },
    {  0, -1 },            {  0, 1 },
    {  1, -1 }, {  1, 0 }, {  1, 1 },
}

--- Check if a square is attacked by the given color.
function Chess:IsSquareAttackedBy(state, row, col, attackerColor)
    local board = state.board

    -- Check knight attacks
    for _, off in ipairs(KNIGHT_OFFSETS) do
        local r, c = row + off[1], col + off[2]
        if InBounds(r, c) then
            local p = board[r][c]
            if PieceColor(p) == attackerColor and PieceType(p) == "N" then
                return true
            end
        end
    end

    -- Check diagonal attacks (bishop, queen)
    for _, dir in ipairs(DIAGONAL_OFFSETS) do
        local r, c = row + dir[1], col + dir[2]
        while InBounds(r, c) do
            local p = board[r][c]
            if p ~= "" then
                if PieceColor(p) == attackerColor then
                    local t = PieceType(p)
                    if t == "B" or t == "Q" then return true end
                end
                break
            end
            r, c = r + dir[1], c + dir[2]
        end
    end

    -- Check orthogonal attacks (rook, queen)
    for _, dir in ipairs(ORTHOGONAL_OFFSETS) do
        local r, c = row + dir[1], col + dir[2]
        while InBounds(r, c) do
            local p = board[r][c]
            if p ~= "" then
                if PieceColor(p) == attackerColor then
                    local t = PieceType(p)
                    if t == "R" or t == "Q" then return true end
                end
                break
            end
            r, c = r + dir[1], c + dir[2]
        end
    end

    -- Check king attacks
    for _, off in ipairs(KING_OFFSETS) do
        local r, c = row + off[1], col + off[2]
        if InBounds(r, c) then
            local p = board[r][c]
            if PieceColor(p) == attackerColor and PieceType(p) == "K" then
                return true
            end
        end
    end

    -- Check pawn attacks
    local pawnDir = (attackerColor == "w") and 1 or -1 -- white pawns attack upward (row decreases)
    for _, dc in ipairs({ -1, 1 }) do
        local r, c = row + pawnDir, col + dc
        if InBounds(r, c) then
            local p = board[r][c]
            if PieceColor(p) == attackerColor and PieceType(p) == "P" then
                return true
            end
        end
    end

    return false
end

function Chess:IsKingInCheck(state, color)
    local kp = state.kingPositions[color]
    return self:IsSquareAttackedBy(state, kp[1], kp[2], EnemyColor(color))
end

-- ========================================================================
-- Pseudo-legal move generation
-- ========================================================================

local function AddMove(moves, fromRow, fromCol, toRow, toCol, extra)
    local move = {
        fromRow = fromRow, fromCol = fromCol,
        toRow = toRow, toCol = toCol,
    }
    if extra then
        for k, v in pairs(extra) do
            move[k] = v
        end
    end
    moves[#moves + 1] = move
end

--- Generate pseudo-legal moves for a piece at (row, col).
-- Pseudo-legal = ignores whether the move leaves own king in check.
function Chess:GetPseudoLegalMoves(state, row, col)
    local moves = {}
    local board = state.board
    local piece = board[row][col]
    if piece == "" then return moves end

    local color = PieceColor(piece)
    local ptype = PieceType(piece)
    local enemy = EnemyColor(color)

    if ptype == "P" then
        -- Pawn moves
        local dir = (color == "w") and -1 or 1 -- white moves up (row decreases)
        local startRow = (color == "w") and 7 or 2
        local promoRow = (color == "w") and 1 or 8

        -- Forward one
        local fwdR = row + dir
        if InBounds(fwdR, col) and board[fwdR][col] == "" then
            if fwdR == promoRow then
                for _, promo in ipairs({ "Q", "R", "B", "N" }) do
                    AddMove(moves, row, col, fwdR, col, { promotion = promo })
                end
            else
                AddMove(moves, row, col, fwdR, col)
            end

            -- Forward two from start
            if row == startRow then
                local fwd2R = row + dir * 2
                if board[fwd2R][col] == "" then
                    AddMove(moves, row, col, fwd2R, col)
                end
            end
        end

        -- Diagonal captures
        for _, dc in ipairs({ -1, 1 }) do
            local cr, cc = row + dir, col + dc
            if InBounds(cr, cc) then
                local target = board[cr][cc]
                if target ~= "" and PieceColor(target) == enemy then
                    if cr == promoRow then
                        for _, promo in ipairs({ "Q", "R", "B", "N" }) do
                            AddMove(moves, row, col, cr, cc, { promotion = promo })
                        end
                    else
                        AddMove(moves, row, col, cr, cc)
                    end
                end

                -- En passant
                if state.enPassantTarget and state.enPassantTarget[1] == cr and state.enPassantTarget[2] == cc then
                    AddMove(moves, row, col, cr, cc, { enPassant = true })
                end
            end
        end

    elseif ptype == "N" then
        for _, off in ipairs(KNIGHT_OFFSETS) do
            local r, c = row + off[1], col + off[2]
            if InBounds(r, c) then
                local target = board[r][c]
                if target == "" or PieceColor(target) == enemy then
                    AddMove(moves, row, col, r, c)
                end
            end
        end

    elseif ptype == "B" then
        for _, dir in ipairs(DIAGONAL_OFFSETS) do
            local r, c = row + dir[1], col + dir[2]
            while InBounds(r, c) do
                local target = board[r][c]
                if target == "" then
                    AddMove(moves, row, col, r, c)
                elseif PieceColor(target) == enemy then
                    AddMove(moves, row, col, r, c)
                    break
                else
                    break
                end
                r, c = r + dir[1], c + dir[2]
            end
        end

    elseif ptype == "R" then
        for _, dir in ipairs(ORTHOGONAL_OFFSETS) do
            local r, c = row + dir[1], col + dir[2]
            while InBounds(r, c) do
                local target = board[r][c]
                if target == "" then
                    AddMove(moves, row, col, r, c)
                elseif PieceColor(target) == enemy then
                    AddMove(moves, row, col, r, c)
                    break
                else
                    break
                end
                r, c = r + dir[1], c + dir[2]
            end
        end

    elseif ptype == "Q" then
        -- Queen = bishop + rook
        local allDirs = {}
        for _, d in ipairs(DIAGONAL_OFFSETS) do allDirs[#allDirs + 1] = d end
        for _, d in ipairs(ORTHOGONAL_OFFSETS) do allDirs[#allDirs + 1] = d end

        for _, dir in ipairs(allDirs) do
            local r, c = row + dir[1], col + dir[2]
            while InBounds(r, c) do
                local target = board[r][c]
                if target == "" then
                    AddMove(moves, row, col, r, c)
                elseif PieceColor(target) == enemy then
                    AddMove(moves, row, col, r, c)
                    break
                else
                    break
                end
                r, c = r + dir[1], c + dir[2]
            end
        end

    elseif ptype == "K" then
        -- Normal king moves
        for _, off in ipairs(KING_OFFSETS) do
            local r, c = row + off[1], col + off[2]
            if InBounds(r, c) then
                local target = board[r][c]
                if target == "" or PieceColor(target) == enemy then
                    AddMove(moves, row, col, r, c)
                end
            end
        end

        -- Castling
        if not state.inCheck then
            -- Kingside
            if state.castleRights[color .. "K"] then
                local rookCol = 8
                local kingRow = row
                if board[kingRow][rookCol] == color .. "R"
                    and board[kingRow][6] == ""
                    and board[kingRow][7] == ""
                    and not self:IsSquareAttackedBy(state, kingRow, 6, enemy)
                    and not self:IsSquareAttackedBy(state, kingRow, 7, enemy)
                then
                    AddMove(moves, row, col, kingRow, 7, { isCastle = "K" })
                end
            end

            -- Queenside
            if state.castleRights[color .. "Q"] then
                local rookCol = 1
                local kingRow = row
                if board[kingRow][rookCol] == color .. "R"
                    and board[kingRow][2] == ""
                    and board[kingRow][3] == ""
                    and board[kingRow][4] == ""
                    and not self:IsSquareAttackedBy(state, kingRow, 3, enemy)
                    and not self:IsSquareAttackedBy(state, kingRow, 4, enemy)
                then
                    AddMove(moves, row, col, kingRow, 3, { isCastle = "Q" })
                end
            end
        end
    end

    return moves
end

-- ========================================================================
-- Legal move generation (filters out moves that leave king in check)
-- ========================================================================

function Chess:GetLegalMoves(state, fromRow, fromCol)
    local piece = state.board[fromRow][fromCol]
    if piece == "" then return {} end

    local color = PieceColor(piece)
    if color ~= state.turn then return {} end

    local pseudoMoves = self:GetPseudoLegalMoves(state, fromRow, fromCol)
    local legalMoves = {}

    for _, move in ipairs(pseudoMoves) do
        local testState = self:CopyState(state)
        self:ApplyMoveRaw(testState, move)
        if not self:IsKingInCheck(testState, color) then
            legalMoves[#legalMoves + 1] = move
        end
    end

    return legalMoves
end

function Chess:GetAllLegalMoves(state)
    local moves = {}
    local color = state.turn

    for row = 1, 8 do
        for col = 1, 8 do
            local piece = state.board[row][col]
            if piece ~= "" and PieceColor(piece) == color then
                local pieceMoves = self:GetLegalMoves(state, row, col)
                for _, move in ipairs(pieceMoves) do
                    moves[#moves + 1] = move
                end
            end
        end
    end

    return moves
end

-- ========================================================================
-- Move application
-- ========================================================================

--- Apply a move without legality checks (used for testing / AI).
function Chess:ApplyMoveRaw(state, move)
    local board = state.board
    local piece = board[move.fromRow][move.fromCol]
    local captured = board[move.toRow][move.toCol]
    local color = PieceColor(piece)
    local ptype = PieceType(piece)

    if move.isCastle then
        -- Move king
        board[move.toRow][move.toCol] = piece
        board[move.fromRow][move.fromCol] = ""

        -- Move rook
        if move.isCastle == "K" then
            board[move.fromRow][6] = color .. "R"
            board[move.fromRow][8] = ""
        else
            board[move.fromRow][4] = color .. "R"
            board[move.fromRow][1] = ""
        end

        state.castleRights[color .. "K"] = false
        state.castleRights[color .. "Q"] = false
        state.kingPositions[color] = { move.toRow, move.toCol }
        state.enPassantTarget = nil

    else
        -- En passant capture
        if move.enPassant then
            board[move.fromRow][move.toCol] = ""
            captured = "x" -- mark that a capture happened
        end

        -- Move piece
        board[move.toRow][move.toCol] = piece
        board[move.fromRow][move.fromCol] = ""

        -- Promotion
        if move.promotion then
            board[move.toRow][move.toCol] = color .. move.promotion
        end

        -- Update king position
        if ptype == "K" then
            state.kingPositions[color] = { move.toRow, move.toCol }
            state.castleRights[color .. "K"] = false
            state.castleRights[color .. "Q"] = false
        end

        -- Revoke castle rights if rook moved
        if ptype == "R" then
            if move.fromRow == 8 and move.fromCol == 1 then state.castleRights.wQ = false end
            if move.fromRow == 8 and move.fromCol == 8 then state.castleRights.wK = false end
            if move.fromRow == 1 and move.fromCol == 1 then state.castleRights.bQ = false end
            if move.fromRow == 1 and move.fromCol == 8 then state.castleRights.bK = false end
        end

        -- Revoke castle rights if rook captured
        if move.toRow == 8 and move.toCol == 1 then state.castleRights.wQ = false end
        if move.toRow == 8 and move.toCol == 8 then state.castleRights.wK = false end
        if move.toRow == 1 and move.toCol == 1 then state.castleRights.bQ = false end
        if move.toRow == 1 and move.toCol == 8 then state.castleRights.bK = false end

        -- En passant target
        if ptype == "P" and math.abs(move.toRow - move.fromRow) == 2 then
            state.enPassantTarget = { (move.fromRow + move.toRow) / 2, move.fromCol }
        else
            state.enPassantTarget = nil
        end
    end

    -- Half-move clock
    if ptype == "P" or captured ~= "" then
        state.halfMoveClock = 0
    else
        state.halfMoveClock = state.halfMoveClock + 1
    end

    -- Switch turn
    if state.turn == "b" then
        state.fullMoveNumber = state.fullMoveNumber + 1
    end
    state.turn = EnemyColor(state.turn)

    -- Update check status
    state.inCheck = self:IsKingInCheck(state, state.turn)
end

-- ========================================================================
-- Move validation (for player input)
-- ========================================================================

function Chess:ValidateMove(state, move)
    if not move or not move.fromRow then return false end

    local piece = state.board[move.fromRow][move.fromCol]
    if piece == "" then return false end
    if PieceColor(piece) ~= state.turn then return false end

    local legalMoves = self:GetLegalMoves(state, move.fromRow, move.fromCol)
    for _, legal in ipairs(legalMoves) do
        if legal.toRow == move.toRow and legal.toCol == move.toCol then
            -- Check promotion match
            if legal.promotion then
                if move.promotion == legal.promotion then
                    return true
                end
            else
                return true
            end
        end
    end
    return false
end

-- ========================================================================
-- Game status detection
-- ========================================================================

function Chess:GetGameStatus(state)
    local legalMoves = self:GetAllLegalMoves(state)

    if #legalMoves == 0 then
        if state.inCheck then
            return "checkmate"
        else
            return "stalemate"
        end
    end

    -- 50-move rule (100 half-moves)
    if state.halfMoveClock >= 100 then
        return "draw_50move"
    end

    -- Insufficient material
    if self:IsInsufficientMaterial(state) then
        return "draw_material"
    end

    return "playing"
end

function Chess:IsInsufficientMaterial(state)
    local board = state.board
    local whitePieces = {}
    local blackPieces = {}

    for row = 1, 8 do
        for col = 1, 8 do
            local p = board[row][col]
            if p ~= "" then
                local color = PieceColor(p)
                local ptype = PieceType(p)
                if ptype ~= "K" then
                    if color == "w" then
                        whitePieces[#whitePieces + 1] = ptype
                    else
                        blackPieces[#blackPieces + 1] = ptype
                    end
                end
            end
        end
    end

    local wCount = #whitePieces
    local bCount = #blackPieces

    -- King vs King
    if wCount == 0 and bCount == 0 then return true end

    -- King + Bishop vs King  or  King + Knight vs King
    if wCount == 0 and bCount == 1 and (blackPieces[1] == "B" or blackPieces[1] == "N") then return true end
    if bCount == 0 and wCount == 1 and (whitePieces[1] == "B" or whitePieces[1] == "N") then return true end

    return false
end

-- ========================================================================
-- Self-registration (Plumber pattern)
-- ========================================================================

do
    local gameData = {
        name = L["Chess"],
        dbKey = "Chess",
        description = L["Classic chess with full rules including castling, en passant, and promotion."],
        icon = "Interface\\Icons\\Inv_misc_chess_bishop_white",
        boardSize = { rows = 8, cols = 8 },
        minPlayers = 2,
        maxPlayers = 2,

        createGameFunc = function()
            return Chess:CreateGameState()
        end,

        validateMoveFunc = function(state, move)
            return Chess:ValidateMove(state, move)
        end,

        getLegalMovesFunc = function(state, row, col)
            return Chess:GetLegalMoves(state, row, col)
        end,

        getAllMovesFunc = function(state)
            return Chess:GetAllLegalMoves(state)
        end,

        applyMoveFunc = function(state, move)
            Chess:ApplyMoveRaw(state, move)
        end,

        getGameStatusFunc = function(state)
            return Chess:GetGameStatus(state)
        end,

        getAIFunc = function()
            return MonkeyGames.ChessAI
        end,

        createUIFunc = function(parent)
            MonkeyGames.ChessUI:Create(parent)
        end,

        copyStateFunc = function(state)
            return Chess:CopyState(state)
        end,
    }

    MonkeyGames.GameRegistry:AddGame(gameData)
end
