"""Symmetry transformations for applying cube symmetry operations."""
from cubing_algs.algorithm import Algorithm
from cubing_algs.constants import DOUBLE_CHAR
from cubing_algs.constants import INVERT_CHAR
from cubing_algs.constants import SYMMETRY_TABLE
from cubing_algs.constants import WIDE_CHAR
from cubing_algs.move import Move


def symmetry_moves(
        old_moves: Algorithm,
        ignore_moves: set[str],
        symmetry_table: dict[str, str],
) -> Algorithm:
    """
    Apply symmetry transformation to an algorithm using a symmetry table.

    Transforms each move according to the provided symmetry mapping,
    preserving move modifiers and notation style.

    Args:
        old_moves: Algorithm to transform.
        ignore_moves: Set of move names to leave unchanged.
        symmetry_table: Mapping of move names to their symmetry transformations.

    Returns:
        Algorithm with symmetry transformation applied.

    """
    moves: list[Move] = []

    for move in old_moves:
        if move.is_pause or move.base_move in ignore_moves:
            moves.append(move)
        else:
            symmetry_move = symmetry_table[move.base_move]

            if move.is_wide_move:
                symmetry_move += WIDE_CHAR

            modifier = ''
            if move.is_double:
                modifier = DOUBLE_CHAR
            elif move.is_clockwise:
                modifier = INVERT_CHAR

            new_move = Move(
                move.layer + symmetry_move
                + modifier + move.time,
            )

            if move.is_sign_move:
                new_move = new_move.to_sign

            moves.append(new_move)

    return Algorithm(moves)


def symmetry_type_moves(
        old_moves: Algorithm,
        symmetry_type: str,
) -> Algorithm:
    """
    Apply a specific type of symmetry transformation to moves.

    Uses predefined symmetry tables to transform the algorithm.

    Args:
        old_moves: Algorithm to transform.
        symmetry_type: Type of symmetry to apply (M, S, E, or C).

    Returns:
        Algorithm with specified symmetry transformation applied.

    """
    ignore_moves, symmetry_table = SYMMETRY_TABLE[symmetry_type]
    return symmetry_moves(old_moves, ignore_moves, symmetry_table)


def symmetry_m_moves(old_moves: Algorithm) -> Algorithm:
    """
    Apply M-slice symmetry transformation to moves.

    Args:
        old_moves: Algorithm to transform.

    Returns:
        Algorithm with M-slice symmetry applied.

    """
    return symmetry_type_moves(old_moves, 'M')


def symmetry_s_moves(old_moves: Algorithm) -> Algorithm:
    """
    Apply S-slice symmetry transformation to moves.

    Args:
        old_moves: Algorithm to transform.

    Returns:
        Algorithm with S-slice symmetry applied.

    """
    return symmetry_type_moves(old_moves, 'S')


def symmetry_e_moves(old_moves: Algorithm) -> Algorithm:
    """
    Apply E-slice symmetry transformation to moves.

    Args:
        old_moves: Algorithm to transform.

    Returns:
        Algorithm with E-slice symmetry applied.

    """
    return symmetry_type_moves(old_moves, 'E')


def symmetry_c_moves(old_moves: Algorithm) -> Algorithm:
    """
    Apply combined symmetry transformation using M and S symmetries.

    Args:
        old_moves: Algorithm to transform.

    Returns:
        Algorithm with combined M and S symmetry applied.

    """
    # M symmetry mirrors L↔R, then S symmetry mirrors F↔B,
    # yielding a diagonal "corner" symmetry across both slices.
    moves = symmetry_m_moves(old_moves)
    return symmetry_s_moves(moves)
