from dataclasses import dataclass
from enum import Enum
from typing import Generic, TypeVar, List, Optional
from typing_extensions import TypeAlias


MessageTypeT = TypeVar("MessageTypeT", bound="MessageType")


class MessageType(Enum):
    pass


class CtlMessageType(MessageType):
    Listen = "listen"
    Stop = "stop"


class SttMessageType(MessageType):
    Start = "start"
    Utterance = "utterance"
    Reset = "reset"
    Commit = "commit"
    Stop = "stop"


class LlmMessageType(MessageType):
    FeedbackPos = "positive"
    FeedbackNeg = "negative"

    Start = "start"
    Token = "token"
    Utterance = "utterance"
    End = "end"
    Stop = "stop"


@dataclass(frozen=True)
class Message(Generic[MessageTypeT]):
    msg: MessageTypeT
    data: Optional[str] = None

    def __str__(self) -> str:
        if self.data is None:
            return f"{self.msg.__class__.__name__}.{self.msg.name}"
        else:
            return f"{self.msg.__class__.__name__}.{self.msg.name}: {self.data.strip()}"


CtlMessage: TypeAlias = Message[CtlMessageType]
SttMessage: TypeAlias = Message[SttMessageType]
LlmMessage: TypeAlias = Message[LlmMessageType]


class ConfigError(Exception):
    pass


class ConfigValueError(ConfigError):
    def __str__(self) -> str:
        return f"Invalid configuration value: {super().__str__()}"


class ModuleError(Exception):
    def __init__(self, module: str, msg: str) -> None:
        super().__init__(f"{module.lstrip('_')}: {msg}")


class ModelError(ModuleError):
    def __init__(self, module: str, model: Optional[str], msg: str) -> None:
        super().__init__(f"{module}[{model}]" if model is not None else module, msg)


class ModelNotFoundError(ModelError):
    def __init__(self, module: str, model: Optional[str], msg: Optional[str], options: Optional[List[str]]) -> None:
        parts: List[str] = []
        if model is None:
            parts.append("No model specified.")
        else:
            parts.append("Model not found.")
        if msg is not None:
            parts.append(msg)
        if options is None:
            parts.append("Not checked for options.")
        elif options:
            parts.append(f"Available options: {', '.join(options)}")
        super().__init__(module, model, " ".join(parts))