import os
from pathlib import Path

import jinja2

from .field_maps import FieldInfo, introspect_field


class TypeGenerator:
    def __init__(self, registry: dict[str, type], registry_cls):
        self.registry = registry
        self.registry_cls = registry_cls
        template_dir = Path(__file__).parent / "templates"
        self.env = jinja2.Environment(
            loader=jinja2.FileSystemLoader(str(template_dir)),
            keep_trailing_newline=True,
            trim_blocks=True,
            lstrip_blocks=True,
        )

    def generate(self) -> dict[str, str]:
        files: dict[str, str] = {}

        for model_key, model_class in sorted(self.registry.items()):
            model_name = model_class.__name__
            filepath = f"models/{model_name}.ts"
            files[filepath] = self._generate_model_file(model_key, model_class)

        files["index.ts"] = self._generate_barrel_export()
        return files

    def _generate_model_file(self, model_key: str, model_class) -> str:
        model_name = model_class.__name__
        app_label = model_class._meta.app_label
        meta = self.registry_cls.get_meta(model_key)

        fields: list[FieldInfo] = []
        for field in model_class._meta.get_fields():
            info = introspect_field(field, model_name)
            if info is not None:
                fields.append(info)

        # Sort fields: pk first, then _djino_ fields, then alphabetical
        fields.sort(
            key=lambda f: (
                0 if f.is_primary_key else (1 if f.name.startswith("_djino_") else 2),
                f.name,
            )
        )

        # Collect unique descriptor classes for imports
        descriptor_imports = set()
        for f in fields:
            descriptor_imports.add(f.descriptor_class)

        # Collect choice types
        choice_types = []
        for f in fields:
            if f.choices and f.choice_type_name:
                union = " | ".join(f"'{c}'" for c in f.choices)
                choice_types.append({"name": f.choice_type_name, "union": union})

        # Collect model imports for relation types
        model_imports = []  # Not needed in Phase 0 since we don't import related models

        owner_field = None
        if meta.owner_field:
            # Use the column name (e.g., author_id) not the field name
            for f in fields:
                if f.name == meta.owner_field:
                    owner_field = f.column
                    break

        template = self.env.get_template("model.ts.j2")
        return template.render(
            model_key=model_key,
            model_name=model_name,
            app_label=app_label,
            db_table=model_class._meta.db_table,
            fields=fields,
            descriptor_imports=descriptor_imports,
            choice_types=choice_types,
            model_imports=model_imports,
            sync_scope=meta.scope.value,
            owner_field=owner_field,
        )

    def _generate_barrel_export(self) -> str:
        models_info = []
        for model_key, model_class in sorted(self.registry.items()):
            model_name = model_class.__name__

            # Collect choice type names for this model
            choice_type_names = []
            for field in model_class._meta.get_fields():
                info = introspect_field(field, model_name)
                if info and info.choice_type_name:
                    choice_type_names.append(info.choice_type_name)

            models_info.append(
                {
                    "name": model_name,
                    "choice_types": choice_type_names,
                }
            )

        template = self.env.get_template("index.ts.j2")
        return template.render(models=models_info)

    def write(self, output_dir: str, files: dict[str, str]) -> list[str]:
        written = []
        for filepath, content in files.items():
            full_path = Path(output_dir) / filepath
            full_path.parent.mkdir(parents=True, exist_ok=True)
            full_path.write_text(content)
            written.append(str(filepath))
        return written

    def check_for_changes(self, output_dir: str, files: dict[str, str]) -> bool:
        for filepath, content in files.items():
            full_path = Path(output_dir) / filepath
            if not full_path.exists():
                return True
            if full_path.read_text() != content:
                return True
        return False
