"""Text to speech."""

from dataclasses import dataclass
from enum import Enum
from typing import Any, Dict, Optional, Union

from .event import Event, Eventable

DOMAIN = "tts"
_SYNTHESIZE_TYPE = "synthesize"

# streaming
_SYNTHESIZE_START_TYPE = "synthesize-start"
_SYNTHESIZE_CHUNK_TYPE = "synthesize-chunk"
_SYNTHESIZE_STOP_TYPE = "synthesize-stop"
_SYNTHESIZE_STOPPED_TYPE = "synthesize-stopped"


class SynthesizeTextFormat(str, Enum):
    """Format of text to synthesize."""

    TEXT = "text"
    SSML = "ssml"


@dataclass
class SynthesizeVoice:
    """Information about the desired voice for synthesis."""

    name: Optional[str] = None
    """Voice name from tts info (overrides language)."""

    language: Optional[str] = None
    """Voice language from tts info."""

    speaker: Optional[str] = None
    """Voice speaker from tts info."""

    def to_dict(self) -> Dict[str, str]:
        if self.name is not None:
            voice = {"name": self.name}
            if self.speaker is not None:
                voice["speaker"] = self.speaker
        elif self.language is not None:
            voice = {"language": self.language}
        else:
            voice = {}

        return voice

    @staticmethod
    def from_dict(voice: Dict[str, Any]) -> "Optional[SynthesizeVoice]":
        if "name" in voice:
            return SynthesizeVoice(
                name=voice["name"],
                speaker=voice.get("speaker"),
            )

        if "language" in voice:
            return SynthesizeVoice(language=voice["language"])

        return None


@dataclass
class Synthesize(Eventable):
    """Request to synthesize audio from text."""

    text: str
    """Text to synthesize."""

    voice: Optional[SynthesizeVoice] = None
    """Voice to use during synthesis."""

    text_format: Optional[Union[str, SynthesizeTextFormat]] = None
    """Format of text."""

    context: Optional[Dict[str, Any]] = None
    """Context for next interaction."""

    @staticmethod
    def is_type(event_type: str) -> bool:
        return event_type == _SYNTHESIZE_TYPE

    def event(self) -> Event:
        data: Dict[str, Any] = {"text": self.text}
        if self.voice is not None:
            data["voice"] = self.voice.to_dict()
        if isinstance(self.text_format, SynthesizeTextFormat):
            data["text_format"] = self.text_format.value
        elif self.text_format is not None:
            data["text_format"] = self.text_format
        if self.context is not None:
            data["context"] = self.context

        return Event(type=_SYNTHESIZE_TYPE, data=data)

    @staticmethod
    def from_event(event: Event) -> "Synthesize":
        text_format: Optional[Union[str, SynthesizeTextFormat]] = event.data.get(
            "text_format"
        )
        if text_format is not None:
            try:
                text_format = SynthesizeTextFormat(text_format)
            except ValueError:
                # Leave original value
                pass

        return Synthesize(
            text=event.data["text"],
            voice=SynthesizeVoice.from_dict(event.data.get("voice", {})),
            text_format=text_format,
            context=event.data.get("context"),
        )


@dataclass
class SynthesizeStart(Eventable):
    """Start of streaming request to synthesize audio from text."""

    voice: Optional[SynthesizeVoice] = None
    """Voice to use during synthesis."""

    text_format: Optional[Union[str, SynthesizeTextFormat]] = None
    """Format of text."""

    context: Optional[Dict[str, Any]] = None
    """Context for next interaction."""

    @staticmethod
    def is_type(event_type: str) -> bool:
        return event_type == _SYNTHESIZE_START_TYPE

    def event(self) -> Event:
        data: Dict[str, Any] = {}
        if self.voice is not None:
            data["voice"] = self.voice.to_dict()
        if isinstance(self.text_format, SynthesizeTextFormat):
            data["text_format"] = self.text_format.value
        elif self.text_format is not None:
            data["text_format"] = self.text_format
        if self.context is not None:
            data["context"] = self.context

        return Event(type=_SYNTHESIZE_START_TYPE, data=data)

    @staticmethod
    def from_event(event: Event) -> "SynthesizeStart":
        text_format: Optional[Union[str, SynthesizeTextFormat]] = event.data.get(
            "text_format"
        )
        if text_format is not None:
            try:
                text_format = SynthesizeTextFormat(text_format)
            except ValueError:
                # Leave original value
                pass

        return SynthesizeStart(
            voice=SynthesizeVoice.from_dict(event.data.get("voice", {})),
            text_format=text_format,
            context=event.data.get("context"),
        )


@dataclass
class SynthesizeChunk(Eventable):
    """Text chunk from streaming request to synthesize audio from text."""

    text: str
    """Chunk of text to synthesize."""

    @staticmethod
    def is_type(event_type: str) -> bool:
        return event_type == _SYNTHESIZE_CHUNK_TYPE

    def event(self) -> Event:
        return Event(type=_SYNTHESIZE_CHUNK_TYPE, data={"text": self.text})

    @staticmethod
    def from_event(event: Event) -> "SynthesizeChunk":
        return SynthesizeChunk(text=event.data["text"])


@dataclass
class SynthesizeStop(Eventable):
    """End of streaming request to synthesize audio from text."""

    @staticmethod
    def is_type(event_type: str) -> bool:
        return event_type == _SYNTHESIZE_STOP_TYPE

    def event(self) -> Event:
        return Event(type=_SYNTHESIZE_STOP_TYPE)

    @staticmethod
    def from_event(event: Event) -> "SynthesizeStop":
        return SynthesizeStop()


@dataclass
class SynthesizeStopped(Eventable):
    """End of streaming response to streaming request."""

    @staticmethod
    def is_type(event_type: str) -> bool:
        return event_type == _SYNTHESIZE_STOPPED_TYPE

    def event(self) -> Event:
        return Event(type=_SYNTHESIZE_STOPPED_TYPE)

    @staticmethod
    def from_event(event: Event) -> "SynthesizeStopped":
        return SynthesizeStopped()
