"""Module containing the Wale Braid Word Class.

This module provides the Wale_Braid_Word class which represents a single step in a braid operation, describing how loops cross over each other within a course of knitting.
"""

from __future__ import annotations

from collections.abc import Iterator
from typing import Generic, TypeVar

from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
from knit_graphs.Loop import Loop

LoopT = TypeVar("LoopT", bound=Loop)


class Wale_Braid_Word(Generic[LoopT]):
    """A representation of loop crossings over a set of loops in a common course.

    This class represents a single braid word in the mathematical sense, describing how a set of loops cross over or under each other within a horizontal course.
    Each braid word can be inverted and applied to determine the new ordering of loops after the crossing operations.

    Attributes:
        loops (list[LoopT]): The ordered list of loops that participate in this braid word.
        crossings (dict[int, Crossing_Direction]): A mapping from loop indices to their crossing directions, where each key represents the index of a loop that crosses with the loop at index+1.
    """

    def __init__(self, loops: list[LoopT], crossings: dict[int, Crossing_Direction]) -> None:
        """Initialize a wale braid word with the specified loops and crossing operations.

        Args:
            loops (list[LoopT]): The ordered list of loops that participate in this braid word.
            crossings (dict[int, Crossing_Direction]):
                A dictionary mapping loop indices to crossing directions.
                Each index-key indicates that the loop at that index crosses with the loop at index+1 in the specified direction.
        """
        self.loops: list[LoopT] = loops
        self.crossings: dict[int, Crossing_Direction] = crossings

    @property
    def new_loop_order(self) -> list[LoopT]:
        """Calculate the new ordering of loops after applying all crossing operations.

        This method applies all the crossing operations defined in this braid word to determine the final ordering of loops.
        Crossings are applied from right to left (highest index to lowest) to maintain proper braid semantics.

        Returns:
            list[Loop]: The list of loops in their new order after all crossing operations have been applied.
        """
        new_loops = [*self]
        for i in sorted(self.crossings.keys(), reverse=True):
            moves_left = new_loops[i + 1]
            new_loops[i + 1] = new_loops[i]
            new_loops[i] = moves_left

        return new_loops

    def is_inversion(self, other: Wale_Braid_Word) -> bool:
        """Check if another braid word is the inverse of this braid word.

        Args:
            other (Wale_Braid_Word): The other braid word to compare against for inversion relationship.

        Returns:
            bool: True if the other braid word is equal to the inverse of this braid word, False otherwise.
        """
        invert = ~self
        return other == invert

    def __invert__(self) -> Wale_Braid_Word[LoopT]:
        """Create the inverse braid word that undoes the operations of this braid word.

        The inverse braid word uses the new loop order as its starting configuration and inverts all crossing directions to reverse the effect of the original braid word.

        Returns:
            Wale_Braid_Word[LoopT]: A new braid word that represents the inverse operation of this braid word.
        """
        new_crossings = {i: ~c for i, c in self.crossings.items()}
        return Wale_Braid_Word[LoopT](self.new_loop_order, new_crossings)

    def __eq__(self, other: object) -> bool:
        """Check equality with another wale braid word.

        Two braid words are equal if they have the same loops in the same order and the same crossing operations at the same indices.

        Args:
            other (Wale_Braid_Word): The other braid word to compare with.

        Returns:
            bool: True if both braid words have identical loops, loop ordering, and crossing operations, False otherwise.
        """
        return (
            isinstance(other, Wale_Braid_Word)
            and len(self) == len(other)
            and all(l == o for l, o in zip(self.loops, other.loops, strict=False))
            and all(i in other.crossings for i in self.crossings)
            and all(other.crossings[i] == cd for i, cd in self.crossings.items())
        )

    def __iter__(self) -> Iterator[LoopT]:
        """
        Returns:
            Iterator[LoopT]: Iteration over the loops in the braid word.
        """
        return iter(self.loops)

    def __len__(self) -> int:
        """Get the number of loops in this braid word.

        Returns:
            int: The total number of loops participating in this braid word.
        """
        return len(self.loops)
