"""Playback-order state for continuous, shuffle, and repeat modes."""

from __future__ import annotations

import random
from typing import Generic, Sequence, TypeVar


T = TypeVar("T")


def _identity_index(items: Sequence[T], target: T | None) -> int:
    if target is None:
        return -1
    return next(
        (index for index, item in enumerate(items) if item is target),
        -1,
    )


class PlaybackOrder(Generic[T]):
    """Choose the next track without duplicating items within a shuffle cycle."""

    VALID_ORDERS = {"continuous", "shuffle"}
    VALID_REPEATS = {"off", "all", "one"}

    def __init__(self, rng: random.Random | None = None) -> None:
        self.order_mode = "continuous"
        self.repeat_mode = "off"
        self._rng = rng or random.Random()
        self._shuffle_order: list[T] = []
        self._shuffle_position = -1
        self._pending_shuffle_cycle: list[T] = []

    def configure(self, order_mode: str, repeat_mode: str) -> None:
        order_mode = (
            order_mode if order_mode in self.VALID_ORDERS else "continuous"
        )
        repeat_mode = (
            repeat_mode if repeat_mode in self.VALID_REPEATS else "off"
        )
        if order_mode != self.order_mode:
            self.reset()
        self.order_mode = order_mode
        self.repeat_mode = repeat_mode

    def reset(self) -> None:
        self._shuffle_order = []
        self._shuffle_position = -1
        self._pending_shuffle_cycle = []

    def begin(self, tracks: Sequence[T], current: T) -> None:
        if self.order_mode != "shuffle":
            self.reset()
            return
        remaining = [track for track in tracks if track is not current]
        self._rng.shuffle(remaining)
        self._shuffle_order = [current, *remaining]
        self._shuffle_position = 0
        self._pending_shuffle_cycle = []

    def peek_next(
        self, tracks: Sequence[T], current: T | None, *, automatic: bool
    ) -> T | None:
        if not tracks or current is None:
            return None
        if automatic and self.repeat_mode == "one":
            return current
        if self.order_mode == "shuffle":
            self._ensure_shuffle_position(tracks, current)
            next_position = self._shuffle_position + 1
            if next_position < len(self._shuffle_order):
                return self._shuffle_order[next_position]
            if not self._pending_shuffle_cycle:
                self._pending_shuffle_cycle = self._new_shuffle_cycle(
                    tracks, current
                )
            return (
                self._pending_shuffle_cycle[0]
                if self._pending_shuffle_cycle
                else None
            )

        current_index = _identity_index(tracks, current)
        if current_index < 0:
            return tracks[0]
        if current_index + 1 < len(tracks):
            return tracks[current_index + 1]
        if self.repeat_mode == "all":
            return tracks[0]
        return None

    def next_track(
        self, tracks: Sequence[T], current: T | None, *, automatic: bool
    ) -> T | None:
        result = self.peek_next(tracks, current, automatic=automatic)
        if (
            result is not None
            and self.order_mode == "shuffle"
            and not (automatic and self.repeat_mode == "one")
        ):
            if self._shuffle_position + 1 < len(self._shuffle_order):
                self._shuffle_position += 1
            else:
                self._shuffle_order = self._pending_shuffle_cycle
                self._pending_shuffle_cycle = []
                self._shuffle_position = 0
        return result

    def previous_track(
        self, tracks: Sequence[T], current: T | None
    ) -> T | None:
        if not tracks:
            return None
        if current is None:
            return tracks[0]
        if self.order_mode == "shuffle":
            self._ensure_shuffle_position(tracks, current)
            if self._shuffle_position > 0:
                self._shuffle_position -= 1
                return self._shuffle_order[self._shuffle_position]
            return current

        current_index = _identity_index(tracks, current)
        if current_index <= 0:
            return tracks[-1] if self.repeat_mode == "all" else tracks[0]
        return tracks[current_index - 1]

    def _ensure_shuffle_position(
        self, tracks: Sequence[T], current: T
    ) -> None:
        if (
            0 <= self._shuffle_position < len(self._shuffle_order)
            and self._shuffle_order[self._shuffle_position] is current
        ):
            return
        self.begin(tracks, current)

    def _new_shuffle_cycle(
        self, tracks: Sequence[T], current: T
    ) -> list[T]:
        new_cycle = list(tracks)
        self._rng.shuffle(new_cycle)
        if len(new_cycle) > 1 and new_cycle[0] is current:
            swap_index = next(
                index
                for index, track in enumerate(new_cycle[1:], start=1)
                if track is not current
            )
            new_cycle[0], new_cycle[swap_index] = (
                new_cycle[swap_index],
                new_cycle[0],
            )
        return new_cycle
