"""TypeScript types generator for Prism.

Generates TypeScript interfaces and types from model specifications.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from prism.generators.base import GeneratedFile, ModelGenerator
from prism.spec.fields import FieldType
from prism.spec.stack import FileStrategy
from prism.utils.case_conversion import to_camel_case, to_pascal_case
from prism.utils.template_engine import TemplateRenderer

if TYPE_CHECKING:
    from prism.spec.fields import FieldSpec
    from prism.spec.model import ModelSpec


# TypeScript type mapping
TYPESCRIPT_TYPE_MAP: dict[FieldType, str] = {
    FieldType.STRING: "string",
    FieldType.TEXT: "string",
    FieldType.INTEGER: "number",
    FieldType.FLOAT: "number",
    FieldType.DECIMAL: "number",
    FieldType.BOOLEAN: "boolean",
    FieldType.DATETIME: "string",  # ISO date string
    FieldType.DATE: "string",
    FieldType.TIME: "string",
    FieldType.UUID: "string",
    FieldType.JSON: "Record<string, unknown>",
    FieldType.ENUM: "string",
    FieldType.FOREIGN_KEY: "number",
}


class TypeScriptGenerator(ModelGenerator):
    """Generator for TypeScript type definitions."""

    REQUIRED_TEMPLATES = [
        "frontend/types/common_types.ts.jinja2",
        "frontend/types/model_types.ts.jinja2",
    ]

    def __init__(self, *args, **kwargs) -> None:  # type: ignore[no-untyped-def]
        super().__init__(*args, **kwargs)
        frontend_base = Path(self.spec.generator.frontend_output)
        self.types_path = frontend_base / self.spec.generator.types_path

        # Initialize template renderer
        self.renderer = TemplateRenderer()
        self.renderer.validate_templates_exist(self.REQUIRED_TEMPLATES)

    def generate_shared_files(self) -> list[GeneratedFile]:
        """Generate shared type utilities."""
        return []

    def generate_model_files(self, model: ModelSpec) -> list[GeneratedFile]:
        """No per-model files - all types in one file."""
        return []

    def generate_index_files(self) -> list[GeneratedFile]:
        """Generate the main generated.ts file with all types."""
        return [
            self._generate_types_file(),
        ]

    def _generate_types_file(self) -> GeneratedFile:
        """Generate the main types file."""
        sections = []

        # Header
        sections.append("""/**
 * Generated TypeScript types for API models.
 *
 * ⚠️ AUTO-GENERATED BY PRISM - DO NOT EDIT
 * This file is regenerated on every `prism generate` run.
 */

""")

        # Common types
        sections.append(self._generate_common_types())

        # Generate types for each model
        for model in self.spec.models:
            if model.frontend.enabled:
                sections.append(self._generate_model_types(model))

        content = "\n".join(sections)

        return GeneratedFile(
            path=self.types_path / "generated.ts",
            content=content,
            strategy=FileStrategy.ALWAYS_OVERWRITE,
            description="TypeScript types",
        )

    def _generate_common_types(self) -> str:
        """Generate common utility types."""
        return self.renderer.render_file(
            "frontend/types/common_types.ts.jinja2",
            context={},
        )

    def _generate_model_types(self, model: ModelSpec) -> str:
        """Generate TypeScript types for a model."""
        name = model.name

        # Build enum types
        enum_types_list = []
        for field in model.fields:
            if field.type == FieldType.ENUM and field.enum_values:
                enum_name = f"{name}{to_pascal_case(field.name)}"
                values = " | ".join(f"'{v}'" for v in field.enum_values)
                enum_types_list.append(f"export type {enum_name} = {values};\n")

        enum_types = "\n".join(enum_types_list) if enum_types_list else ""

        # Generate interfaces
        main_interface = self._generate_interface(
            model, suffix="", include_id=True, include_timestamps=True
        )
        create_interface = self._generate_interface(
            model, suffix="Create", include_id=False, include_timestamps=False
        )
        update_interface = self._generate_interface(
            model, suffix="Update", include_id=False, include_timestamps=False, all_optional=True
        )
        filter_interface = self._generate_filter_interface(model)

        return self.renderer.render_file(
            "frontend/types/model_types.ts.jinja2",
            context={
                "model_name": name,
                "enum_types": enum_types,
                "main_interface": main_interface,
                "create_interface": create_interface,
                "update_interface": update_interface,
                "filter_interface": filter_interface,
            },
        )

    def _generate_interface(
        self,
        model: ModelSpec,
        suffix: str,
        include_id: bool,
        include_timestamps: bool,
        all_optional: bool = False,
    ) -> str:
        """Generate a TypeScript interface."""
        name = f"{model.name}{suffix}"
        lines = []

        # Interface declaration
        description = model.description or f"{model.name} type"
        if suffix == "Create":
            description = f"Input for creating a {model.name}"
        elif suffix == "Update":
            description = f"Input for updating a {model.name}"

        lines.append(f"/** {description} */")
        lines.append(f"export interface {name} {{")

        # ID field
        if include_id:
            lines.append("  id: number;")

        # Model fields
        for field in model.fields:
            ts_type = self._get_typescript_type(field)
            field_name = to_camel_case(field.name)
            optional = "?" if all_optional or not field.required else ""
            null_suffix = " | null" if not field.required and not all_optional else ""

            if field.description:
                lines.append(f"  /** {field.description} */")
            lines.append(f"  {field_name}{optional}: {ts_type}{null_suffix};")

        # Relationship fields
        for rel in model.relationships:
            rel_field_name = to_camel_case(rel.name)
            target_type = rel.target_model

            if suffix == "" and include_timestamps:
                # Main interface: Include full related objects (optional)
                if rel.type in ("one_to_many", "many_to_many"):
                    lines.append(f"  /** Related {target_type} entities */")
                    lines.append(f"  {rel_field_name}?: {target_type}[];")
                else:
                    lines.append(f"  /** Related {target_type} entity */")
                    lines.append(f"  {rel_field_name}?: {target_type} | null;")
            elif suffix in ("Create", "Update"):
                # Create/Update interfaces: Use ID arrays for m2m
                if rel.type in ("one_to_many", "many_to_many"):
                    id_field_name = f"{rel_field_name}Ids"
                    lines.append(f"  /** IDs of related {target_type} entities */")
                    lines.append(f"  {id_field_name}?: number[];")
                elif rel.type == "many_to_one":
                    # Foreign key field should already be handled via the FK field
                    pass

        # Timestamp fields
        if include_timestamps and model.timestamps:
            lines.append("  createdAt: string;")
            lines.append("  updatedAt: string;")

        if include_timestamps and model.soft_delete:
            lines.append("  deletedAt?: string | null;")

        lines.append("}")
        lines.append("")

        return "\n".join(lines)

    def _generate_filter_interface(self, model: ModelSpec) -> str:
        """Generate a filter interface for a model."""
        name = f"{model.name}Filter"
        lines = []

        lines.append(f"/** Filter options for listing {model.name}s */")
        lines.append(f"export interface {name} {{")

        for field in model.fields:
            if not field.filterable:
                continue

            ts_type = self._get_typescript_type(field)
            field_name = to_camel_case(field.name)

            # Add filter fields based on operators
            for op in field.filter_operators:
                # Handle BETWEEN specially - generates Min and Max fields
                if op.value == "between":
                    lines.append(f"  {field_name}Min?: {ts_type};")
                    lines.append(f"  {field_name}Max?: {ts_type};")
                    continue

                suffix = self._get_filter_suffix(op.value)
                filter_field = f"{field_name}{suffix}"

                if op.value == "in" or op.value == "not_in":
                    lines.append(f"  {filter_field}?: {ts_type}[];")
                elif op.value == "is_null":
                    lines.append(f"  {filter_field}?: boolean;")
                else:
                    lines.append(f"  {filter_field}?: {ts_type};")

        # Common filters
        if model.timestamps:
            lines.append("  createdAfter?: string;")
            lines.append("  createdBefore?: string;")

        if model.soft_delete:
            lines.append("  includeDeleted?: boolean;")

        lines.append("}")
        lines.append("")

        return "\n".join(lines)

    def _get_typescript_type(self, field: FieldSpec) -> str:
        """Get TypeScript type for a field."""
        if field.type == FieldType.ENUM and field.enum_values:
            # Use union type for enums
            return " | ".join(f"'{v}'" for v in field.enum_values)

        # Handle typed JSON arrays
        if field.type == FieldType.JSON and field.json_item_type:
            item_type_map = {
                "str": "string",
                "string": "string",
                "int": "number",
                "integer": "number",
                "float": "number",
                "number": "number",
                "bool": "boolean",
                "boolean": "boolean",
            }
            ts_item_type = item_type_map.get(field.json_item_type, "unknown")
            return f"{ts_item_type}[]"

        return TYPESCRIPT_TYPE_MAP.get(field.type, "unknown")

    def _get_filter_suffix(self, operator: str) -> str:
        """Get field suffix for a filter operator."""
        suffixes = {
            "eq": "",
            "ne": "Ne",
            "gt": "Gt",
            "gte": "Gte",
            "lt": "Lt",
            "lte": "Lte",
            "like": "Like",
            "ilike": "Ilike",
            "in": "In",
            "not_in": "NotIn",
            "is_null": "IsNull",
            "contains": "Contains",
            "starts_with": "StartsWith",
            "ends_with": "EndsWith",
        }
        return suffixes.get(operator, "")


__all__ = ["TypeScriptGenerator"]
