"""
Pydantic models for nblite configuration.

Defines the schema for nblite.toml configuration files.
"""

from __future__ import annotations

from enum import Enum
from typing import Literal

from pydantic import BaseModel, Field, model_validator

__all__ = [
    "ExportRule",
    "CodeLocationConfig",
    "TemplatesConfig",
    "ExtensionEntry",
    "ExportConfig",
    "GitConfig",
    "CleanConfig",
    "FillConfig",
    "DocsConfig",
    "NbliteConfig",
    "CodeLocationFormat",
    "ExportMode",
    "CellReferenceStyle",
]


class CodeLocationFormat(str, Enum):
    """Format types for code locations."""

    IPYNB = "ipynb"
    PERCENT = "percent"
    MODULE = "module"


class ExportMode(str, Enum):
    """Export mode for module code locations."""

    PERCENT = "percent"  # Export as percent-style Python with cell markers
    PY = "py"  # Export as plain Python without cell markers


class CellReferenceStyle(str, Enum):
    """Style for cell references in exported code."""

    RELATIVE = "relative"
    ABSOLUTE = "absolute"
    NONE = "none"


class ExportRule(BaseModel):
    """
    A single export rule in the pipeline.

    Attributes:
        from_key: Source code location key
        to_key: Destination code location key
    """

    from_key: str = Field(..., description="Source code location key")
    to_key: str = Field(..., description="Destination code location key")


class CodeLocationConfig(BaseModel):
    """
    Configuration for a code location.

    Attributes:
        path: Directory path relative to project root
        format: Format type (ipynb, percent, module)
        export_mode: Export mode for module format (percent or py)
    """

    path: str = Field(..., description="Directory path relative to project root")
    format: CodeLocationFormat = Field(..., description="Format type")
    export_mode: ExportMode = Field(
        default=ExportMode.PERCENT,
        description="Export mode for module format",
    )


class TemplatesConfig(BaseModel):
    """
    Configuration for notebook templates.

    Attributes:
        folder: Template folder path relative to project root
        default: Default template name
    """

    folder: str = Field(default="templates", description="Template folder path")
    default: str | None = Field(
        default=None,
        description="Default template name",
    )


class ExtensionEntry(BaseModel):
    """
    Configuration for a single extension.

    Either path or module must be specified, but not both.

    Attributes:
        path: File path to extension
        module: Python module path to extension
    """

    path: str | None = Field(
        default=None,
        description="File path to extension",
    )
    module: str | None = Field(
        default=None,
        description="Python module path to extension",
    )

    @model_validator(mode="after")
    def validate_path_or_module(self) -> ExtensionEntry:
        """Validate that exactly one of path or module is specified."""
        if self.path is None and self.module is None:
            raise ValueError("Either 'path' or 'module' must be specified")
        if self.path is not None and self.module is not None:
            raise ValueError("Cannot specify both 'path' and 'module'")
        return self


class ExportConfig(BaseModel):
    """
    Configuration for export options.

    Attributes:
        include_autogenerated_warning: Include autogenerated warning header
        cell_reference_style: Style for cell references
    """

    include_autogenerated_warning: bool = Field(
        default=True,
        description="Include autogenerated warning header in exports",
    )
    cell_reference_style: CellReferenceStyle = Field(
        default=CellReferenceStyle.RELATIVE,
        description="Style for cell references in exported code",
    )


class GitConfig(BaseModel):
    """
    Configuration for git integration.

    Attributes:
        auto_clean: Clean notebooks on commit
        auto_export: Run export on commit
        validate_staging: Validate staging state on commit
    """

    auto_clean: bool = Field(
        default=True,
        description="Clean notebooks on commit",
    )
    auto_export: bool = Field(
        default=True,
        description="Run export on commit",
    )
    validate_staging: bool = Field(
        default=True,
        description="Validate staging state on commit",
    )


class CleanConfig(BaseModel):
    """
    Configuration for notebook cleaning.

    Defaults are based on notebookx.CleanOptions.for_vcs() which provides
    sensible defaults for version control (removes execution counts and
    metadata that causes noisy diffs, but preserves outputs and kernel info).

    Note: preserve_cell_ids defaults to True (unlike for_vcs) for idempotency.

    Attributes:
        remove_outputs: Remove all outputs from code cells
        remove_execution_counts: Remove execution counts from code cells
        remove_cell_metadata: Remove cell-level metadata
        remove_notebook_metadata: Remove notebook-level metadata
        remove_kernel_info: Remove kernel specification
        preserve_cell_ids: Preserve cell IDs (if False, cell IDs are removed)
        remove_output_metadata: Remove metadata from outputs
        remove_output_execution_counts: Remove execution counts from output results
        keep_only_metadata: Keep only these metadata keys (None = keep all)
        exclude_dunders: Exclude __* notebooks
        exclude_hidden: Exclude .* notebooks
    """

    remove_outputs: bool = Field(
        default=False,
        description="Remove all outputs from code cells",
    )
    remove_execution_counts: bool = Field(
        default=True,
        description="Remove execution counts from code cells",
    )
    remove_cell_metadata: bool = Field(
        default=True,
        description="Remove cell-level metadata",
    )
    remove_notebook_metadata: bool = Field(
        default=False,
        description="Remove notebook-level metadata",
    )
    remove_kernel_info: bool = Field(
        default=False,
        description="Remove kernel specification",
    )
    preserve_cell_ids: bool = Field(
        default=True,
        description="Preserve cell IDs",
    )
    remove_output_metadata: bool = Field(
        default=True,
        description="Remove metadata from outputs (ExecuteResult, DisplayData)",
    )
    remove_output_execution_counts: bool = Field(
        default=True,
        description="Remove execution counts from output results",
    )
    keep_only_metadata: list[str] | None = Field(
        default=None,
        description="Keep only these metadata keys (None = keep all)",
    )
    exclude_dunders: bool = Field(
        default=True,
        description="Exclude __* notebooks",
    )
    exclude_hidden: bool = Field(
        default=True,
        description="Exclude .* notebooks",
    )


class FillConfig(BaseModel):
    """
    Configuration for notebook fill (execution).

    Attributes:
        timeout: Cell execution timeout in seconds (None = no timeout)
        n_workers: Number of parallel workers for execution
        skip_unchanged: Skip notebooks that haven't changed
        remove_outputs_first: Clear existing outputs before execution
        code_locations: List of code locations to fill (None = all ipynb locations)
        exclude_patterns: Glob patterns to exclude from fill
        exclude_dunders: Exclude __* notebooks
        exclude_hidden: Exclude .* notebooks
    """

    timeout: int | None = Field(
        default=None,
        description="Cell execution timeout in seconds (None = no timeout)",
    )
    n_workers: int = Field(
        default=4,
        description="Number of parallel workers for execution",
        ge=1,
    )
    skip_unchanged: bool = Field(
        default=True,
        description="Skip notebooks that haven't changed",
    )
    remove_outputs_first: bool = Field(
        default=False,
        description="Clear existing outputs before execution",
    )
    code_locations: list[str] | None = Field(
        default=None,
        description="List of code locations to fill (None = all ipynb locations)",
    )
    exclude_patterns: list[str] = Field(
        default_factory=list,
        description="Glob patterns to exclude from fill",
    )
    exclude_dunders: bool = Field(
        default=True,
        description="Exclude __* notebooks",
    )
    exclude_hidden: bool = Field(
        default=True,
        description="Exclude .* notebooks",
    )


class DocsConfig(BaseModel):
    """
    Configuration for documentation generation.

    Attributes:
        code_location: Code location to use for documentation
        title: Documentation title
        author: Documentation author
        output_folder: Output folder for documentation
        execute_notebooks: Execute notebooks during build
        exclude_patterns: Patterns to exclude from docs
    """

    code_location: str | None = Field(
        default=None,
        description="Code location to use for documentation",
    )
    title: str | None = Field(
        default=None,
        description="Documentation title",
    )
    author: str | None = Field(
        default=None,
        description="Documentation author",
    )
    output_folder: str = Field(
        default="_docs",
        description="Output folder for documentation",
    )
    execute_notebooks: bool = Field(
        default=False,
        description="Execute notebooks during build",
    )
    exclude_patterns: list[str] = Field(
        default_factory=lambda: ["__*", ".*"],
        description="Patterns to exclude from docs",
    )


class NbliteConfig(BaseModel):
    """
    Top-level nblite configuration.

    This represents the full configuration from nblite.toml.

    Attributes:
        export_pipeline: List of export rules
        code_locations: Dictionary of code location configs
        docs_cl: Code location for documentation
        docs_title: Documentation title
        docs_generator: Documentation generator (jupyterbook, mkdocs)
        readme_nb_path: Path to notebook for README generation
        templates: Template configuration
        extensions: List of extensions
        export: Export options
        git: Git integration options
        clean: Clean options
        docs: Documentation options
    """

    export_pipeline: list[ExportRule] = Field(
        default_factory=list,
        description="List of export rules",
    )
    code_locations: dict[str, CodeLocationConfig] = Field(
        default_factory=dict,
        description="Dictionary of code location configs",
    )
    docs_cl: str | None = Field(
        default=None,
        description="Code location for documentation",
    )
    docs_title: str | None = Field(
        default=None,
        description="Documentation title",
    )
    docs_generator: Literal["mkdocs", "jupyterbook", "quarto"] = Field(
        default="mkdocs",
        description="Documentation generator (mkdocs, jupyterbook, or quarto)",
    )
    readme_nb_path: str | None = Field(
        default=None,
        description="Path to notebook for README generation (e.g., 'nbs/index.ipynb')",
    )
    templates: TemplatesConfig = Field(
        default_factory=TemplatesConfig,
        description="Template configuration",
    )
    extensions: list[ExtensionEntry] = Field(
        default_factory=list,
        description="List of extensions",
    )
    export: ExportConfig = Field(
        default_factory=ExportConfig,
        description="Export options",
    )
    git: GitConfig = Field(
        default_factory=GitConfig,
        description="Git integration options",
    )
    clean: CleanConfig = Field(
        default_factory=CleanConfig,
        description="Clean options",
    )
    fill: FillConfig = Field(
        default_factory=FillConfig,
        description="Fill (execution) options",
    )
    docs: DocsConfig = Field(
        default_factory=DocsConfig,
        description="Documentation options",
    )
