"""Naming definitions generator for Prism.

Generates naming convention modules that provide project-specific
naming helpers and constants for both backend (Python) and frontend
(TypeScript) code.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from prisme.generators.base import GeneratedFile, ModelGenerator, create_init_file
from prisme.spec.naming import CaseStyle, PluralStyle
from prisme.spec.stack import FileStrategy
from prisme.utils.case_conversion import (
    pluralize,
    to_camel_case,
    to_kebab_case,
    to_pascal_case,
    to_snake_case,
)
from prisme.utils.template_engine import TemplateRenderer

if TYPE_CHECKING:
    from prisme.spec.model import ModelSpec
    from prisme.spec.naming import NamingConfig


def apply_case_style(name: str, style: CaseStyle) -> str:
    """Apply a case style to a name string."""
    converters = {
        CaseStyle.SNAKE_CASE: to_snake_case,
        CaseStyle.CAMEL_CASE: to_camel_case,
        CaseStyle.PASCAL_CASE: to_pascal_case,
        CaseStyle.KEBAB_CASE: to_kebab_case,
        CaseStyle.SCREAMING_SNAKE: lambda s: to_snake_case(s).upper(),
    }
    return converters[style](name)


def apply_abbreviations(name: str, abbreviations: dict[str, str]) -> str:
    """Apply abbreviation mappings to a name."""
    result = name
    for full, abbrev in abbreviations.items():
        result = result.replace(full, abbrev)
    return result


class NamingGenerator(ModelGenerator):
    """Generator for naming convention definition modules.

    Produces a Python module and a TypeScript module that codify
    the project's naming conventions as constants and helper
    look-ups. Other generated code (and user code) can import
    from these modules to stay consistent.
    """

    REQUIRED_TEMPLATES = [
        "backend/naming/naming.py.jinja2",
        "backend/naming/naming.ts.jinja2",
    ]

    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
        super().__init__(*args, **kwargs)
        backend_base = Path(self.generator_config.backend_output)
        package_name = self.get_package_name()
        self.naming_path = backend_base / package_name / "naming"

        frontend_base = Path(self.generator_config.frontend_output)
        self.frontend_naming_path = frontend_base / "naming"

        self.renderer = TemplateRenderer()
        self.renderer.validate_templates_exist(self.REQUIRED_TEMPLATES)

    @property
    def naming_config(self) -> NamingConfig:
        """Get naming config from project_spec."""
        if self.context.project_spec:
            return self.context.project_spec.naming
        from prisme.spec.naming import NamingConfig

        return NamingConfig()

    def generate_shared_files(self) -> list[GeneratedFile]:
        """Generate the main naming definitions modules."""
        return [
            self._generate_python_naming(),
            self._generate_typescript_naming(),
        ]

    def generate_model_files(self, model: ModelSpec) -> list[GeneratedFile]:
        """No per-model files needed — all definitions are in the shared module."""
        return []

    def generate_index_files(self) -> list[GeneratedFile]:
        """Generate __init__.py for the naming package."""
        imports = [
            "from .naming import NAMING_CONFIG",
            "from .naming import get_table_name",
            "from .naming import get_endpoint_path",
            "from .naming import get_class_name",
            "from .naming import get_variable_name",
        ]
        exports = [
            "NAMING_CONFIG",
            "get_table_name",
            "get_endpoint_path",
            "get_class_name",
            "get_variable_name",
        ]
        return [
            create_init_file(
                self.naming_path,
                imports,
                exports,
                "Naming convention definitions and helpers.",
            ),
        ]

    def _build_model_naming_map(self) -> list[dict[str, str]]:
        """Build a naming map for all models based on NamingConfig."""
        cfg = self.naming_config
        entries = []
        for model in self.spec.models:
            raw = model.name
            snake = to_snake_case(raw)
            name_for_table = apply_abbreviations(snake, cfg.abbreviations)
            name_for_endpoint = apply_abbreviations(snake, cfg.abbreviations)

            # Table name
            table = apply_case_style(name_for_table, cfg.tables.style)
            if cfg.tables.plural == PluralStyle.PLURAL:
                table = pluralize(table)
            if cfg.tables.prefix:
                table = cfg.tables.prefix + table

            # Override with explicit table_name if set
            if model.table_name:
                table = model.table_name

            # Endpoint path segment
            endpoint_segment = apply_case_style(name_for_endpoint, cfg.endpoints.style)
            if cfg.endpoints.plural == PluralStyle.PLURAL:
                endpoint_segment = pluralize(endpoint_segment)

            # Class names
            model_class = to_pascal_case(raw) + cfg.classes.model_suffix
            schema_class = to_pascal_case(raw) + cfg.classes.schema_suffix
            service_class = to_pascal_case(raw) + cfg.classes.service_suffix

            # Variable names
            backend_var = apply_case_style(snake, cfg.variables.backend_style)
            frontend_var = apply_case_style(snake, cfg.variables.frontend_style)
            constant_var = apply_case_style(snake, cfg.variables.constant_style)

            entries.append(
                {
                    "model_name": raw,
                    "table_name": table,
                    "endpoint_segment": endpoint_segment,
                    "endpoint_full": f"{cfg.endpoints.prefix}/{endpoint_segment}",
                    "model_class": model_class,
                    "schema_class": schema_class,
                    "service_class": service_class,
                    "backend_var": backend_var,
                    "frontend_var": frontend_var,
                    "constant_var": constant_var,
                }
            )
        return entries

    def _generate_python_naming(self) -> GeneratedFile:
        """Generate the Python naming definitions module."""
        cfg = self.naming_config
        entries = self._build_model_naming_map()

        content = self.renderer.render_file(
            "backend/naming/naming.py.jinja2",
            context={
                "entries": entries,
                "config": {
                    "table_style": cfg.tables.style.value,
                    "table_plural": cfg.tables.plural.value,
                    "table_prefix": cfg.tables.prefix,
                    "column_style": cfg.columns.style.value,
                    "fk_suffix": cfg.columns.foreign_key_suffix,
                    "timestamp_names": cfg.columns.timestamp_names,
                    "endpoint_style": cfg.endpoints.style.value,
                    "endpoint_plural": cfg.endpoints.plural.value,
                    "endpoint_prefix": cfg.endpoints.prefix,
                    "model_suffix": cfg.classes.model_suffix,
                    "schema_suffix": cfg.classes.schema_suffix,
                    "service_suffix": cfg.classes.service_suffix,
                    "backend_var_style": cfg.variables.backend_style.value,
                    "frontend_var_style": cfg.variables.frontend_style.value,
                    "constant_style": cfg.variables.constant_style.value,
                    "abbreviations": cfg.abbreviations,
                    "reserved_words": cfg.reserved_words,
                },
            },
        )

        return GeneratedFile(
            path=self.naming_path / "naming.py",
            content=content,
            strategy=FileStrategy.ALWAYS_OVERWRITE,
            description="Python naming convention definitions",
        )

    def _generate_typescript_naming(self) -> GeneratedFile:
        """Generate the TypeScript naming definitions module."""
        cfg = self.naming_config
        entries = self._build_model_naming_map()

        content = self.renderer.render_file(
            "backend/naming/naming.ts.jinja2",
            context={
                "entries": entries,
                "config": {
                    "endpoint_prefix": cfg.endpoints.prefix,
                    "abbreviations": cfg.abbreviations,
                },
            },
        )

        return GeneratedFile(
            path=self.frontend_naming_path / "naming.ts",
            content=content,
            strategy=FileStrategy.ALWAYS_OVERWRITE,
            description="TypeScript naming convention definitions",
        )


__all__ = ["NamingGenerator"]
