import chess

# Стандартные ценности фигур
PIECE_VALUES = {
    chess.PAWN: 1,
    chess.KNIGHT: 3,
    chess.BISHOP: 3,
    chess.ROOK: 5,
    chess.QUEEN: 9,
    chess.KING: 100,
}


def get_piece_value(piece_type: chess.PieceType) -> int:
    """Возвращает ценность фигуры."""
    return PIECE_VALUES.get(piece_type, 0)


def is_piece_vulnerable(board: chess.Board, square: chess.Square) -> bool:
    """
    Фигура висит или может быть взята более дешёвой фигурой.

    Возвращает True если:
    - Атакована и не защищена (висит)
    - Может быть взята фигурой дешевле (невыгодный размен)
    """
    piece = board.piece_at(square)
    if not piece:
        return False

    enemy = not piece.color
    attackers = board.attackers(enemy, square)
    if not attackers:
        return False  # Не атакована

    defenders = board.attackers(piece.color, square)
    if not defenders:
        return True  # Висит

    # Может ли быть взята более дешёвой фигурой?
    piece_value = get_piece_value(piece.piece_type)
    for att_sq in attackers:
        att = board.piece_at(att_sq)
        if att and att.piece_type != chess.KING:
            if get_piece_value(att.piece_type) < piece_value:
                return True  # Берут дешевле

    return False


# === Лучевые утилиты (для связки, копья) ===

# Лучевые фигуры (могут создавать связку/копьё)
RAY_PIECE_TYPES = {chess.BISHOP, chess.ROOK, chess.QUEEN}

# Направления для разных фигур
BISHOP_DIRECTIONS = [(-1, -1), (-1, 1), (1, -1), (1, 1)]  # диагонали
ROOK_DIRECTIONS = [(-1, 0), (1, 0), (0, -1), (0, 1)]      # вертикали/горизонтали
QUEEN_DIRECTIONS = BISHOP_DIRECTIONS + ROOK_DIRECTIONS


def cast_ray(board: chess.Board, start_sq: int, direction: tuple[int, int], limit: int = 0) -> list[tuple[int, chess.Piece]]:
    """
    Пускает луч из start_sq в направлении direction.
    Возвращает список (поле, фигура) для всех фигур на луче.
    limit > 0 — остановиться после нахождения limit фигур.
    """
    pieces = []
    rank = chess.square_rank(start_sq)
    file = chess.square_file(start_sq)
    dr, df = direction

    rank += dr
    file += df

    while 0 <= rank < 8 and 0 <= file < 8:
        sq = chess.square(file, rank)
        piece = board.piece_at(sq)
        if piece:
            pieces.append((sq, piece))
            if limit and len(pieces) >= limit:
                break
        rank += dr
        file += df

    return pieces


def find_relative_pin(board: chess.Board, target_sq: int, pinner_side: bool,
                      exclude_sq: int | None = None) -> tuple[int, int, chess.Piece] | None:
    """
    Ищет относительную связку на фигуре target_sq.

    Относительная связка: лучевая фигура стороны pinner_side → target →
    более ценная фигура того же цвета, что target.

    exclude_sq — исключить это поле из поиска (напр. фигура, которая только что пришла).
    Возвращает (pinner_sq, behind_sq, behind_piece) или None.
    """
    target = board.piece_at(target_sq)
    if target is None:
        return None

    target_value = get_piece_value(target.piece_type)

    for direction in QUEEN_DIRECTIONS:
        backward = (-direction[0], -direction[1])

        # Назад от цели: ищем лучевую фигуру стороны pinner_side
        pieces_back = cast_ray(board, target_sq, backward, limit=1)
        if not pieces_back:
            continue
        pinner_sq, pinner = pieces_back[0]
        if exclude_sq is not None and pinner_sq == exclude_sq:
            continue
        if pinner.color != pinner_side:
            continue
        if pinner.piece_type not in RAY_PIECE_TYPES:
            continue
        if pinner.piece_type == chess.BISHOP and direction not in BISHOP_DIRECTIONS:
            continue
        if pinner.piece_type == chess.ROOK and direction not in ROOK_DIRECTIONS:
            continue

        # Вперёд от цели: ищем более ценную фигуру того же цвета
        pieces_fwd = cast_ray(board, target_sq, direction, limit=1)
        if not pieces_fwd:
            continue
        behind_sq, behind = pieces_fwd[0]
        if behind.color != target.color:
            continue
        behind_value = get_piece_value(behind.piece_type)
        if behind_value <= target_value:
            continue

        return (pinner_sq, behind_sq, behind)

    return None
