"""Sentence and word parsing helpers for voice generation."""

import logging
from dataclasses import dataclass
from typing import Dict, List, Optional

from .voice_audio import Audio
from .voice_modifiers import MODIFIERS, Modifier, Modifiers

log = logging.getLogger(__name__)

# How much delay should be added in place of punctuation (in milliseconds)
PUNCTUATION_TIMING_SECONDS = {
    ",": 0.250,
    ".": 0.500,
}


class NoVoiceSpecified(Exception):
    """Raised when no voice is specified."""


@dataclass
class Sentence:
    """Represents a sentence and its parts."""

    sentence: str
    sayable: List["Word"]
    unsayable: List["Word"]
    sayable_sentence: List["Word"]
    audio: Optional[Audio] = None
    id: Optional[str] = None


class Word:
    """Represents a word (or punctuation)."""

    def __init__(
        self,
        word: str,
        voice: Optional[str] = None,
        is_punctuation: bool = False,
        modifiers: Optional[Modifiers] = None,
    ) -> None:
        self.word = word
        self.voice = voice
        self.modifiers = modifiers if modifiers else []
        self.modifiers.sort(key=lambda modifier: modifier.IDENTIFIER)
        self.is_punctuation = is_punctuation

    def as_str(self, with_voice: bool = False) -> str:
        voice_string = f"{self.voice}:" if (with_voice and self.voice) else ""
        modifiers_string = ""
        if self.modifiers:
            modifier_strings = ",".join(
                [modifier.as_str() for modifier in self.modifiers]
            )
            modifiers_string = f"|{modifier_strings}"
        return f"{voice_string}{self.word}{modifiers_string}"

    def __str__(self) -> str:
        return self.as_str(with_voice=True)

    def __repr__(self) -> str:
        return self.as_str(with_voice=True)

    def without_modifiers(self) -> "Word":
        return Word(
            word=self.word, voice=self.voice, is_punctuation=self.is_punctuation
        )

    def __lt__(self, other: "Word") -> bool:
        return self.word < other.word

    def __gt__(self, other: "Word") -> bool:
        return self.word > other.word

    def __le__(self, other: "Word") -> bool:
        return self.word <= other.word

    def __ge__(self, other: "Word") -> bool:
        return self.word >= other.word

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Word):
            return False
        return (
            self.word == other.word
            and self.modifiers == other.modifiers
            and self.is_punctuation == other.is_punctuation
            and self.voice == other.voice
        )

    def __hash__(self) -> int:
        return hash(repr(self))


def split_sentence(sentence: str) -> List[Word]:
    return [Word(word=word) for word in sentence.lower().rstrip().split(" ")]


def _parse_modifiers(word: str) -> tuple[str, list[Modifier]]:
    if "|" not in word:
        return word, []

    word_string, _, modifiers_string = word.rpartition("|")
    modifier_strings = modifiers_string.split(",")

    # dict keeps insertion order and dedupes by identifier
    modifiers: Dict[str, Modifier] = {}
    for modifier_string in modifier_strings:
        if not modifier_string:
            continue
        modifier_class = MODIFIERS.get(modifier_string[0])
        if modifier_class is None:
            continue
        modifier = modifier_class.from_str(modifier_string)
        if modifier.IDENTIFIER in modifiers:
            continue
        modifiers[modifier.IDENTIFIER] = modifier

    return word_string, list(modifiers.values())


def extract_modifiers(words: List[Word]) -> List[Word]:
    """Extract modifiers from raw words without punctuation processing."""

    processed_words: list[Word] = []
    for word in words:
        word_string, modifiers = _parse_modifiers(word.word)
        processed_words.append(
            Word(
                word=word_string,
                voice=word.voice,
                is_punctuation=word.is_punctuation,
                modifiers=modifiers,
            )
        )
    return processed_words


def process_sentence(split_sent: List[Word], voice: Optional[str]) -> List[Word]:
    """Break sentence into word/punctuation elements and extract modifiers in one pass."""

    log.info("Processing sentence '%s'", split_sent)
    reduced_sent: list[Word] = []

    for item in split_sent:
        word_string, modifiers = _parse_modifiers(item.word)

        first_punct: Optional[str] = None
        try:
            first_punct = next(
                punct for punct in PUNCTUATION_TIMING_SECONDS if punct in word_string
            )
        except StopIteration:
            pass

        if first_punct:
            first_punct_ind = word_string.find(first_punct)

            if first_punct_ind >= 2 and word_string[first_punct_ind - 1] == ":":
                reduced_sent.append(
                    Word(
                        word=word_string[: first_punct_ind + 1],
                        voice=voice,
                        modifiers=modifiers,
                    )
                )
                if len(word_string) >= first_punct_ind:
                    first_punct_ind += 1
            else:
                if word_string[:first_punct_ind]:
                    reduced_sent.append(
                        Word(
                            word=word_string[:first_punct_ind],
                            voice=voice,
                            modifiers=modifiers,
                        )
                    )

            for punct in word_string[first_punct_ind:]:
                if punct in PUNCTUATION_TIMING_SECONDS:
                    reduced_sent.append(
                        Word(word=punct, voice=voice, is_punctuation=True)
                    )
        else:
            reduced_sent.append(
                Word(word=word_string, voice=voice, modifiers=modifiers)
            )

    reduced_sent = [value for value in reduced_sent if value.word != ""]
    log.info("Sentence processed: '%s'", reduced_sent)
    return reduced_sent
