from __future__ import annotations

from collections.abc import Iterable
from dataclasses import dataclass
from typing import Literal


def _inverse_map(mapping: dict[str, str]) -> dict[str, str]:
    return {v: k for k, v in mapping.items()}


Color = Literal["b", "w"]
SetupMode = Literal["intro", "beginner", "intermediate", "advanced"]
MoveType = Literal["route", "capture", "tsuke", "betray", "arata"]

# Piece glyphs use kanji; keep them as explicit unicode escapes to avoid charset issues
TSUKE = "\u4ed8"
TAKE = "\u53d6"
BETRAY = "\u8fd4"
ARATA = "\u65b0"

MARSHAL = "\u5e25"
GENERAL = "\u5927"
LIEUTENANT_GENERAL = "\u4e2d"
MAJOR_GENERAL = "\u5c0f"
WARRIOR = "\u4f8d"
LANCER = "\u69cd"
RIDER = "\u99ac"
SPY = "\u5fcd"
FORTRESS = "\u7826"
SOLDIER = "\u5175"
CANNON = "\u7832"
ARCHER = "\u5f13"
MUSKETEER = "\u7b52"
TACTICIAN = "\u8b00"

WHITE: Color = "w"
BLACK: Color = "b"

PieceType = Literal[
    "\u5e25",
    "\u5927",
    "\u4e2d",
    "\u5c0f",
    "\u4f8d",
    "\u69cd",
    "\u99ac",
    "\u5fcd",
    "\u7826",
    "\u5175",
    "\u7832",
    "\u5f13",
    "\u7b52",
    "\u8b00",
]

PieceCode = Literal[
    "m",
    "g",
    "i",
    "j",
    "w",
    "n",
    "r",
    "s",
    "f",
    "d",
    "c",
    "a",
    "k",
    "t",
]

Name = Literal[
    "marshal",
    "general",
    "lieutenant_general",
    "major_general",
    "warrior",
    "lancer",
    "rider",
    "spy",
    "fortress",
    "soldier",
    "cannon",
    "archer",
    "musketeer",
    "tactician",
]

piece: dict[Name, PieceType] = {
    "marshal": MARSHAL,
    "general": GENERAL,
    "lieutenant_general": LIEUTENANT_GENERAL,
    "major_general": MAJOR_GENERAL,
    "warrior": WARRIOR,
    "lancer": LANCER,
    "rider": RIDER,
    "spy": SPY,
    "fortress": FORTRESS,
    "soldier": SOLDIER,
    "cannon": CANNON,
    "archer": ARCHER,
    "musketeer": MUSKETEER,
    "tactician": TACTICIAN,
}

symbol_to_name: dict[PieceType, Name] = _inverse_map(piece)  # type: ignore[arg-type]

piece_to_fen_code: dict[Name, PieceCode] = {
    "marshal": "m",
    "general": "g",
    "lieutenant_general": "i",
    "major_general": "j",
    "warrior": "w",
    "lancer": "n",
    "rider": "r",
    "spy": "s",
    "fortress": "f",
    "soldier": "d",
    "cannon": "c",
    "archer": "a",
    "musketeer": "k",
    "tactician": "t",
}

fen_code_to_piece: dict[PieceCode, Name] = _inverse_map(piece_to_fen_code)  # type: ignore[arg-type]


@dataclass
class Piece:
    square: str
    tier: int
    type: PieceType
    color: Color
    id: str | None = None


@dataclass
class HandPiece:
    type: PieceType
    color: Color
    count: int
    id: str | None = None


@dataclass
class Move:
    color: Color
    piece: PieceType
    to: str
    san: str
    before: str
    after: str
    type: MoveType
    from_: str = ""
    draft_finished: bool | None = None
    captured: list[Piece] | None = None


Board = list[list[list[Piece | None]]]

setup_mode_to_code: dict[SetupMode, int] = {
    "intro": 0,
    "beginner": 1,
    "intermediate": 2,
    "advanced": 3,
}
setup_code_to_mode: dict[int, SetupMode] = {v: k for k, v in setup_mode_to_code.items()}

SQUARES: list[str] = [f"{x}-{9 - y}" for x in range(1, 10) for y in range(0, 9)]

CANONICAL_NAMES: dict[PieceType, str] = {
    MARSHAL: "sui",
    GENERAL: "taishou",
    LIEUTENANT_GENERAL: "chuujou",
    MAJOR_GENERAL: "shoushou",
    WARRIOR: "samurai",
    LANCER: "yari",
    RIDER: "kiba",
    SPY: "shinobi",
    FORTRESS: "toride",
    SOLDIER: "hyou",
    CANNON: "oodzutsu",
    ARCHER: "yumi",
    MUSKETEER: "tsutsu",
    TACTICIAN: "boushou",
}

ENGLISH_NAMES: dict[PieceType, str] = {
    MARSHAL: "marshal",
    GENERAL: "general",
    LIEUTENANT_GENERAL: "lieutenant general",
    MAJOR_GENERAL: "major general",
    WARRIOR: "warrior",
    LANCER: "lancer",
    RIDER: "rider",
    SPY: "spy",
    FORTRESS: "fortress",
    SOLDIER: "soldier",
    CANNON: "cannon",
    ARCHER: "archer",
    MUSKETEER: "musketeer",
    TACTICIAN: "tactician",
}

FEN_CODES: dict[PieceType, str] = {
    MARSHAL: "m",
    GENERAL: "g",
    LIEUTENANT_GENERAL: "i",
    MAJOR_GENERAL: "j",
    WARRIOR: "w",
    LANCER: "n",
    RIDER: "r",
    SPY: "s",
    FORTRESS: "f",
    SOLDIER: "d",
    CANNON: "c",
    ARCHER: "a",
    MUSKETEER: "k",
    TACTICIAN: "t",
}


def create_piece_from_fen_code(code: str, coords: tuple[int, int, int]) -> Piece:
    y, x, z = coords
    color: Color = "b" if code.islower() else "w"
    name = fen_code_to_piece[code.lower()]  # type: ignore[arg-type]
    return Piece(square=f"{y}-{x}", tier=z, type=piece[name], color=color)


def create_hand_piece_from_fen_code(code: str, count: int) -> HandPiece:
    color: Color = "b" if code.islower() else "w"
    name = fen_code_to_piece[code.lower()]  # type: ignore[arg-type]
    return HandPiece(type=piece[name], count=count, color=color)


def _pieces_equal(a: Piece, b: Piece) -> bool:
    return (
        a.square == b.square
        and a.tier == b.tier
        and a.type == b.type
        and a.color == b.color
        and a.id == b.id
    )


def update_hand(
    pieces: Iterable[Piece], hand: list[HandPiece], opposite_color: bool = False
) -> list[HandPiece]:
    for p in pieces:
        c: Color = (
            "w"
            if (opposite_color and p.color == "b")
            else "b" if (opposite_color and p.color == "w") else p.color
        )
        for idx, hp in enumerate(list(hand)):
            if hp.type == p.type and hp.color == c:
                hp.count -= 1
                if hp.count == 0:
                    hand.pop(idx)
                break
    return hand


def _parse_rank_file(square: str) -> tuple[int, int]:
    parts = square.split("-")
    return int(parts[0]), int(parts[1])


def convert(square: str, pieces: list[Piece], board: Board) -> None:
    rank, file = _parse_rank_file(square)
    tower = get(square, board)
    if tower is None:
        return

    new_tower: list[Piece | None] = []
    for piece_on_board in tower:
        match = next((p for p in pieces if _pieces_equal(p, piece_on_board)), None)  # type: ignore[arg-type]
        if match:
            flipped = Piece(
                square=piece_on_board.square,
                tier=piece_on_board.tier,
                type=piece_on_board.type,
                color="w" if piece_on_board.color == "b" else "b",
                id=piece_on_board.id,
            )
            new_tower.append(flipped)
        else:
            new_tower.append(piece_on_board)
    board[rank - 1][9 - file] = new_tower  # type: ignore[list-item]


def remove(square: str, pieces: list[Piece], board: Board) -> None:
    rank, file = _parse_rank_file(square)
    tower = get(square, board)
    if tower is None:
        return

    new_tower: list[Piece | None] = [
        piece
        for piece in tower
        if not any(_pieces_equal(p, piece) for p in pieces)  # type: ignore[arg-type]
    ]
    board[rank - 1][9 - file] = new_tower or [None]


def remove_top(square: str, board: Board) -> None:
    rank, file = _parse_rank_file(square)
    tower = get(square, board)
    if tower is None:
        return
    tower.pop()
    board[rank - 1][9 - file] = tower or [None]


def put(piece: Piece, board: Board) -> None:
    rank, file = _parse_rank_file(piece.square)
    occupied = board[rank - 1][9 - file]
    if occupied and occupied[0] is not None:
        occupied.append(piece)
    else:
        board[rank - 1][9 - file] = [piece]


def get_top(square: str, board: Board) -> Piece | None:
    tower = get(square, board)
    return tower[-1] if tower else None


def get(square: str, board: Board) -> list[Piece] | None:
    rank, file = _parse_rank_file(square)
    if rank < 1 or rank > 9 or file < 1 or file > 9:
        return None
    tower = board[rank - 1][9 - file]
    if not tower or tower[0] is None:
        return None
    return tower  # type: ignore[return-value]


def is_game_over(board: Board) -> bool:
    marshal_count = sum(
        1 for rank in board for file in rank for p in file if p and p.type == MARSHAL
    )
    return marshal_count != 2


# JS-style alias
createPieceFromFenCode = create_piece_from_fen_code
createHandPieceFromFenCode = create_hand_piece_from_fen_code
updateHand = update_hand
removeTop = remove_top
getTop = get_top
isGameOver = is_game_over
