"""Domain exceptions used by dotpromptz services and parser."""

from __future__ import annotations


class DotpromptzError(ValueError):
    """Base class for all dotpromptz domain exceptions."""

    def __init__(
        self,
        message: str,
        *,
        code: str,
        lineno: int | None = None,
        raw_line: str | None = None,
    ) -> None:
        """Build a structured domain exception."""
        super().__init__(message)
        self.code = code
        self.lineno = lineno
        self.raw_line = raw_line


class PromptParseError(DotpromptzError):
    """Raised when parsing a ``.prompt`` document fails."""


class FrontmatterMissingError(PromptParseError):
    """Raised when no opening frontmatter marker is found."""


class FrontmatterUnterminatedError(PromptParseError):
    """Raised when opening frontmatter marker has no closing marker."""


class FrontmatterValidationError(PromptParseError):
    """Raised when frontmatter YAML or schema validation fails."""


class MessageBlockParseError(PromptParseError):
    """Raised when rendered message block syntax is invalid."""


class InputResolutionError(DotpromptzError):
    """Raised when resolving runtime input records fails."""


class InputVariableError(DotpromptzError):
    """Raised when template/input variables are missing or invalid."""


class InputVariableNamingError(InputVariableError):
    """Raised when input keys violate naming constraints."""

    def __init__(self, invalid: set[str]) -> None:
        """Initialize with invalid input keys."""
        names = ', '.join(sorted(invalid))
        super().__init__(
            (
                'Input variable naming validation failed. '
                f'Invalid key(s): {names}. '
                'Input variables cannot use ALL_CAPS reserved auto-variable names.'
            ),
            code='input_variable_reserved',
        )
        self.invalid = invalid


class TemplateVariableNamingError(InputVariableError):
    """Raised when template variables reference unknown ALL_CAPS auto variables."""

    def __init__(
        self,
        *,
        unknown_auto: set[str] | None = None,
        allowed_auto: set[str] | None = None,
    ) -> None:
        """Initialize with unknown auto names and allowed registry names."""
        unknown_auto = unknown_auto or set()
        allowed_auto = allowed_auto or set()

        msg = 'Template variable naming validation failed.'
        if unknown_auto:
            allowed = ', '.join(sorted(allowed_auto)) if allowed_auto else '<none>'
            msg += (
                ' Unknown auto variable(s): '
                + ', '.join(sorted(unknown_auto))
                + f'. Registered auto variables: {allowed}.'
            )

        super().__init__(msg, code='template_variable_unknown_auto')
        self.unknown_auto = unknown_auto


class TemplateMissingVariableError(InputVariableError):
    """Raised when required template variables are missing from input context."""

    def __init__(self, *, missing: set[str], template_snippet: str = '') -> None:
        """Initialize with missing names and an optional template preview."""
        names = ', '.join(sorted(missing))
        message = f'Template references undefined variable(s): {names}'
        if template_snippet:
            message += f' (template starts with: {template_snippet!r})'
        super().__init__(message, code='template_variable_missing')
        self.missing = missing


class ExecutionConfigError(DotpromptzError):
    """Raised when runtime execution configuration is invalid."""


class UpgradeError(DotpromptzError):
    """Raised when upgrade/update service operations fail."""


class GitError(DotpromptzError):
    """Raised when git operations fail."""


class GhCliError(DotpromptzError):
    """Raised when GitHub CLI operations fail."""


class WorkflowError(DotpromptzError):
    """Raised when workflow installation or submission fails."""


class PromptVersionError(PromptParseError):
    """Raised when a prompt version does not match current engine version."""

    def __init__(self, prompt_version: int, engine_version: int) -> None:
        """Initialize with prompt and engine major versions."""
        self.prompt_version = prompt_version
        self.engine_version = engine_version
        super().__init__(
            (f'.prompt file requires dotpromptz v{prompt_version}.x, but the current engine is v{engine_version}.x.'),
            code='prompt_version_mismatch',
        )
