# Copyright (c) 2017-2026 Juancarlo Añez (apalala@gmail.com)
# SPDX-License-Identifier: BSD-4-Clause

from __future__ import annotations

import builtins
from collections import namedtuple

from .. import grammars
from ..mixins.indent import IndentPrintMixin
from ..objectmodel import Node
from ..util import safe_name
from ..util.abctools import compress_seq
from ..util.deprecate import deprecated_params
from ..util.itertools import topsort

HEADER = """\
    #!/usr/bin/env python3

    # CAVEAT UTILITOR
    #
    # This file was automatically generated by 竜TatSu.
    #
    #    https://pypi.python.org/pypi/tatsu/
    #
    # Any changes you make to it will be overwritten the next time
    # the file is generated.

    # ruff: noqa: PLC2401, PLC2402, PLC2403
    # fmt: off

    from __future__ import annotations

    from dataclasses import dataclass
    from typing import Any

    from tatsu.objectmodel import tatsudataclass
    from tatsu.builder import ModelBuilderSemantics, types_defined_in
    {basetype_import}


    class {name}ModelBuilderSemantics(ModelBuilderSemantics):
        def __init__(self, constructors=None, **kwargs):
            constructors = constructors or []
            constructors += types_defined_in(globals())
            super().__init__(basetype={basetype}, constructors=constructors, **kwargs)


    class {name}ModelBuilderSemantics({name}ModelBuilderSemantics):
        ...
"""


BaseClassSpec = namedtuple('BaseClassSpec', ['class_name', 'base'])


@deprecated_params(base_type='basetype')
def modelgen(
    model: grammars.Grammar,
    name: str = '',
    basetype: type = Node,
    base_type: type | None = None,
) -> str:
    if isinstance(base_type, type):
        basetype = base_type

    generator = PythonModelGenerator(name=name, basetype=basetype)
    return generator.generate_model(model)


class PythonModelGenerator(IndentPrintMixin):

    def __init__(
        self,
        name: str = '',
        basetype: type = Node,
        base_type: type | None = None,
    ):
        if isinstance(base_type, type):
            basetype = base_type
        basetype = basetype or Node
        super().__init__()
        self.basetype = basetype
        self.name = name or None

    def generate_model(self, grammar: grammars.Grammar):
        basetype = self.basetype
        basetype_name = basetype.__name__.split('.')[-1]
        basetype_import = f"from {basetype.__module__} import {basetype_name}"

        self.name = self.name or grammar.name
        self.print(
            HEADER.format(
                name=self.name,
                basetype=self.basetype.__name__,
                basetype_import=basetype_import,
            ),
        )

        rule_index = grammar.rulemap
        rule_specs = {rule.name: self._base_class_specs(rule) for rule in grammar.rules}
        rule_specs = {name: specs for name, specs in rule_specs.items() if specs}

        specs_by_name = {
            s.class_name: s.base for specs in rule_specs.values() for s in specs
        }
        base = self._model_base_name()
        specs_by_name[base] = basetype_name

        all_specs = {
            (s.class_name, s.base) for specs in rule_specs.values() for s in specs
        }
        model_names = list(reversed(topsort(specs_by_name, all_specs)))

        model_to_rule = {
            rule_specs[name][0].class_name: rule
            for name, rule in rule_index.items()
            if name in rule_specs
        }

        for model_name in model_names:
            if model_name in vars(builtins):
                continue
            if rule := model_to_rule.get(model_name):
                self._gen_rule_class(rule, rule_specs[rule.name])
            else:
                self._gen_base_class(model_name, specs_by_name.get(model_name))

        return self.printed_text()

    @staticmethod
    def _model_base_name():
        return 'ModelBase'

    def _print_dataclass(self):
        self.print('@tatsudataclass')

    def _gen_base_class(self, class_name: str, base: str | None):
        self.print()
        self.print()
        self._print_dataclass()
        if base:
            self.print(f'class {class_name}({base}):')
        else:
            self.print(f'class {class_name}:')
        with self.indent():
            self.print('pass')

    def _gen_rule_class(self, rule: grammars.Rule, specs: list[BaseClassSpec]):
        if not specs:
            return
        spec = specs[0]
        arguments = sorted({safe_name(d) for d, _ in compress_seq(rule.defines())})

        self.print()
        self.print()
        self._print_dataclass()
        self.print(f'class {spec.class_name}({spec.base}):')
        with self.indent():
            if not arguments:
                self.print('pass')
            for arg in arguments:
                self.print(f'{arg}: Any = None')

    def _base_class_specs(self, rule: grammars.Rule) -> list[BaseClassSpec]:
        if not rule.params or not isinstance(rule.params[0], str):
            return []
        spec = rule.params[0].split('::')
        base = [self._model_base_name()]
        class_names = [safe_name(n) for n in spec] + base
        return [
            BaseClassSpec(class_name, class_names[i + 1])
            for i, class_name in enumerate(class_names[:-1])
        ]
