from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Sequence

from codegen.models import Program
from loguru import logger

from sera.typing import Language


@dataclass
class Package:
    """Represent an target generated module in the application"""

    app: App
    path: str
    dir: Path
    language: Language

    def ensure_exists(self):
        """Ensure the module exists"""
        self.dir.mkdir(parents=True, exist_ok=True)
        if self.language == Language.Python:
            if not (self.dir / "__init__.py").exists():
                (self.dir / "__init__.py").touch()

    def pkg(self, name: str) -> Package:
        """Create a package in this package"""
        return Package(self.app, f"{self.path}.{name}", self.dir / name, self.language)

    def module(self, name: str) -> Module:
        """Create a module in this package"""
        return Module(self, name, self.language)

    def parent(self) -> Package:
        """Get the parent package"""
        assert self.path.count(".") > 0, "Cannot get parent of top-level package"
        return Package(
            self.app, self.path.rsplit(".", 1)[0], self.dir.parent, self.language
        )


@dataclass
class Module:
    """Represent a module in a package"""

    package: Package
    name: str
    language: Language

    @property
    def path(self) -> str:
        return f"{self.package.path}.{self.name}"

    def write(self, program: Program):
        """Write the module to disk by registering with FileWriter.

        The actual writing happens when FileWriter.process() is called at the
        end of the program. This allows:
        1. Parallel formatting of TypeScript files
        2. Proper diff generation after formatting
        3. Git-style conflict markers for manual edit conflicts
        """
        from sera.misc._file_writer import FileWriter, WriteJob

        self.package.ensure_exists()

        # Determine output file path and copyright statement
        if self.language == Language.Python:
            outfile = self.package.dir / f"{self.name}.py"
            copyright_statement = "# Generated by SERA\n\n"
        else:
            assert self.language == Language.Typescript
            outfile = self.package.dir / f"{self.name}.ts"
            copyright_statement = "/// Generated by SERA\n\n"

        # Generate raw code (no formatting - FileWriter will handle it)
        try:
            if self.language == Language.Python:
                code = program.root.to_python()
            else:
                code = program.root.to_typescript()
        except:
            logger.error("Error generating code for module {}", self.path)
            print(">>> Program")
            if self.language == Language.Python:
                print(program.root.to_python())
            else:
                print(program.root.to_typescript())
            print("<<<")
            raise

        # Register with FileWriter for deferred processing
        # Include copyright statement in content
        FileWriter.get_instance().register(
            WriteJob(
                outfile=outfile,
                content=copyright_statement + code,
                language=self.language,
                first_party_packages=[self.package.app.name],
            )
        )

    def exists(self) -> bool:
        """Check if the module exists"""
        if self.language == Language.Python:
            return (self.package.dir / f"{self.name}.py").exists()
        else:
            return (self.package.dir / f"{self.name}.ts").exists()


@dataclass
class AppModels(Package):
    data: Package
    db: Package

    @staticmethod
    def from_pkg(pkg: Package) -> AppModels:
        """Create an AppModels from a package"""
        return AppModels(
            pkg.app,
            path=pkg.path,
            dir=pkg.dir,
            language=pkg.language,
            data=pkg.pkg("data"),
            db=pkg.pkg("db"),
        )


@dataclass
class App:
    """Represent the generated application"""

    # top-level package of the application
    root: Package

    # application configuration
    config: Module

    # models of the application
    models: AppModels

    # services of the application, which encode the business logic
    services: Package

    # API of the application
    api: Package

    schema_files: Sequence[Path]

    language: Language

    def __init__(
        self, name: str, dir: Path, schema_files: Sequence[Path], language: Language
    ):
        self.root = Package(self, name, dir, language)

        self.config = self.root.module("config")
        self.models = AppModels.from_pkg(self.root.pkg("models"))
        self.services = self.root.pkg("services")
        self.api = self.root.pkg("api")

        self.schema_files = schema_files

        self.language = language

    @property
    def name(self) -> str:
        """Get the name of the application"""
        return self.root.dir.name
