"""Laboratory assistant for composing gaggles with behaviour-focused helpers."""

from __future__ import annotations

from collections.abc import Iterable, Sequence
from typing import Collection, Literal

from .constants import DEFAULT_REDACTYL_CHAR
from .zoo.core import Gaggle, Glitchling
from .zoo.hokey import Hokey
from .zoo.jargoyle import (
    DEFAULT_LEXEMES,
    DEFAULT_MODE,
    Jargoyle,
    JargoyleMode,
)
from .zoo.mim1c import Mim1c
from .zoo.pedant import Pedant
from .zoo.pedant.stones import PedantStone
from .zoo.redactyl import Redactyl
from .zoo.rushmore import Rushmore, RushmoreMode
from .zoo.scannequin import Scannequin
from .zoo.typogre import Typogre
from .zoo.wherewolf import Wherewolf
from .zoo.zeedub import Zeedub


class Auggie(Gaggle):
    """Assistant that incrementally assembles glitchlings into a gaggle."""

    def __init__(
        self,
        glitchlings: Iterable[Glitchling] | None = None,
        *,
        seed: int = 151,
    ) -> None:
        self._blueprint: list[Glitchling] = []
        initial = list(glitchlings or [])
        super().__init__(initial, seed=seed)
        if initial:
            self._blueprint = [glitchling.clone() for glitchling in initial]
            self._rebuild_plan()
        else:
            self._blueprint = []

    def _rebuild_plan(self) -> None:
        self._clones_by_index = []
        for index, glitchling in enumerate(self._blueprint):
            clone = glitchling.clone()
            setattr(clone, "_gaggle_index", index)
            self._clones_by_index.append(clone)
        self.sort_glitchlings()
        self._invalidate_pipeline_cache()

    def _enqueue(self, glitchling: Glitchling) -> "Auggie":
        self._blueprint.append(glitchling)
        self._rebuild_plan()
        return self

    def clone(self, seed: int | None = None) -> "Auggie":
        clone_seed = seed if seed is not None else self.seed
        resolved_seed = 151 if clone_seed is None else int(clone_seed)
        blueprint = [glitch.clone() for glitch in self._blueprint]
        return Auggie(blueprint, seed=resolved_seed)

    def typo(
        self,
        *,
        rate: float | None = None,
        keyboard: str = "CURATOR_QWERTY",
        seed: int | None = None,
    ) -> "Auggie":
        """Add :class:`Typogre` using behaviour-driven nomenclature."""

        return self._enqueue(Typogre(rate=rate, keyboard=keyboard, seed=seed))

    def confusable(
        self,
        *,
        rate: float | None = None,
        classes: list[str] | Literal["all"] | None = None,
        banned_characters: Collection[str] | None = None,
        seed: int | None = None,
    ) -> "Auggie":
        """Add :class:`Mim1c` for homoglyph substitutions."""

        return self._enqueue(
            Mim1c(
                rate=rate,
                classes=classes,
                banned_characters=banned_characters,
                seed=seed,
            )
        )

    def curly_quotes(self, *, seed: int | None = None) -> "Auggie":
        """Add :class:`Pedant` evolved with Curlite to smarten punctuation."""

        return self._enqueue(Pedant(stone=PedantStone.CURLITE, seed=seed))

    def stretch(
        self,
        *,
        rate: float = 0.3,
        extension_min: int = 2,
        extension_max: int = 5,
        word_length_threshold: int = 6,
        base_p: float = 0.45,
        seed: int | None = None,
    ) -> "Auggie":
        """Add :class:`Hokey` for elongated, expressive words."""

        return self._enqueue(
            Hokey(
                rate=rate,
                extension_min=extension_min,
                extension_max=extension_max,
                word_length_threshold=word_length_threshold,
                base_p=base_p,
                seed=seed,
            )
        )

    def homophone(
        self,
        *,
        rate: float | None = None,
        seed: int | None = None,
    ) -> "Auggie":
        """Add :class:`Wherewolf` to swap words for homophones."""

        return self._enqueue(Wherewolf(rate=rate, seed=seed))

    def pedantry(
        self,
        *,
        stone: PedantStone | str = PedantStone.COEURITE,
        seed: int | None = None,
    ) -> "Auggie":
        """Add :class:`Pedant` to evolve text via a chosen stone."""

        return self._enqueue(Pedant(stone=stone, seed=seed))

    def remix(
        self,
        *,
        modes: RushmoreMode | str | Iterable[RushmoreMode | str] | None = None,
        rate: float | None = None,
        delete_rate: float | None = None,
        duplicate_rate: float | None = None,
        swap_rate: float | None = None,
        seed: int | None = None,
        unweighted: bool = False,
        delete_unweighted: bool | None = None,
        duplicate_unweighted: bool | None = None,
    ) -> "Auggie":
        """Add :class:`Rushmore` for deletion, duplication, and swap attacks."""

        return self._enqueue(
            Rushmore(
                modes=modes,
                rate=rate,
                delete_rate=delete_rate,
                duplicate_rate=duplicate_rate,
                swap_rate=swap_rate,
                seed=seed,
                unweighted=unweighted,
                delete_unweighted=delete_unweighted,
                duplicate_unweighted=duplicate_unweighted,
            )
        )

    def redact(
        self,
        *,
        replacement_char: str = DEFAULT_REDACTYL_CHAR,
        rate: float | None = None,
        merge_adjacent: bool = False,
        seed: int | None = 151,
        unweighted: bool = False,
    ) -> "Auggie":
        """Add :class:`Redactyl` to blackout words."""

        return self._enqueue(
            Redactyl(
                replacement_char=replacement_char,
                rate=rate,
                merge_adjacent=merge_adjacent,
                seed=seed if seed is not None else 151,
                unweighted=unweighted,
            )
        )

    def recolor(self, *, mode: JargoyleMode = "literal", seed: int | None = None) -> "Auggie":
        """Add :class:`Jargoyle` with ``lexemes="colors"`` to remap colour terms.

        Args:
            mode: "literal" for deterministic first-entry swaps,
                  "drift" for random selection from palette.
            seed: Seed for deterministic randomness.

        Returns:
            Self for method chaining.
        """
        return self._enqueue(Jargoyle(lexemes="colors", mode=mode, rate=1.0, seed=seed))

    def drift(
        self,
        *,
        lexemes: str = DEFAULT_LEXEMES,
        mode: JargoyleMode = DEFAULT_MODE,
        rate: float | None = None,
        seed: int | None = None,
    ) -> "Auggie":
        """Add :class:`Jargoyle` for dictionary-based word drift.

        Swaps words with alternatives from the specified lexeme dictionary.

        Args:
            lexemes: Dictionary to use. One of:
                "colors" (color term swapping),
                "synonyms" (general synonyms),
                "corporate" (business jargon),
                "academic" (scholarly terms).
            mode: "literal" for deterministic first-entry swaps,
                  "drift" for random selection.
            rate: Probability of transforming each matching word.
            seed: Seed for deterministic randomness.

        Returns:
            Self for method chaining.
        """
        return self._enqueue(Jargoyle(lexemes=lexemes, mode=mode, rate=rate, seed=seed))

    def ocr(
        self,
        *,
        rate: float | None = None,
        seed: int | None = None,
    ) -> "Auggie":
        """Add :class:`Scannequin` to simulate OCR artefacts."""

        return self._enqueue(Scannequin(rate=rate, seed=seed))

    def zero_width(
        self,
        *,
        rate: float | None = None,
        seed: int | None = None,
        characters: Sequence[str] | None = None,
    ) -> "Auggie":
        """Add :class:`Zeedub` to hide zero-width glyphs inside text."""

        return self._enqueue(Zeedub(rate=rate, seed=seed, characters=characters))

    def synonym(
        self,
        *,
        rate: float | None = None,
        seed: int | None = None,
        lexemes: str = "synonyms",
        mode: JargoyleMode = "drift",
    ) -> "Auggie":
        """Add :class:`Jargoyle` for synonym substitutions.

        Args:
            rate: Probability of transforming each matching word.
            seed: Seed for deterministic randomness.
            lexemes: Dictionary to use (default "synonyms").
            mode: "literal" or "drift" (default "drift").

        Returns:
            Self for method chaining.
        """
        return self._enqueue(
            Jargoyle(
                rate=rate,
                seed=seed,
                lexemes=lexemes,
                mode=mode,
            )
        )


__all__ = ["Auggie"]
