# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

import functools
import hashlib
from os.path import dirname
from pathlib import Path

import jinja2
from buildconfig import config, topsrcdir  # type: ignore
from schema_parser import parse_and_validate

THIS_DIR = Path(dirname(__file__))
TEMPLATES = THIS_DIR / "templates"


@functools.cache
def get_deps():
    # Any imported python module is added as a dependency automatically,
    # so we only need the templates.
    return {
        *[str(p) for p in TEMPLATES.iterdir()],
        str(THIS_DIR / "gecko-trace.schema.yaml"),
    }


def generate_cpp_events(output_fd, *inputs):
    """Generate C++ events header"""

    events = parse_and_validate(inputs if inputs else load_schema_index())

    template = _jinja2_env().get_template("GeckoTraceEvents.h.jinja2")
    output_fd.write(
        template.render(
            events=events,
            enabled=config.substs.get("GECKO_TRACE_ENABLE", False),
            # Generate a unique hash to prevent include guard conflicts when
            # multiple event files are generated and included together (e.g., in gtests).
            # This ensures each generated header has a distinct include guard.
            input_hash=hashlib
            .sha256("".join(inputs).encode())
            .hexdigest()
            .upper()[:15],
        )
    )

    return get_deps().union(load_schema_index() if not inputs else {})


def generate_glean_metrics(output_fd, *inputs):
    events = parse_and_validate(inputs if inputs else load_schema_index())

    template = _jinja2_env().get_template("generated-metrics.yaml.jinja2")
    output_fd.write(
        template.render(
            events=events,
        )
    )


def generate_glean_adapter(output_fd, *inputs):
    events = parse_and_validate(inputs if inputs else load_schema_index())

    template = _jinja2_env().get_template("glean_adapter.rs.jinja2")

    output_fd.write(
        template.render(
            events=events,
        )
    )

    return get_deps().union(load_schema_index() if not inputs else {})


@functools.cache
def load_schema_index():
    index = THIS_DIR.parent / "index.py"

    with open(index) as f:
        index_src = f.read()

    namespace = {}
    exec(index_src, namespace)

    return [str(Path(topsrcdir) / x) for x in namespace["gecko_trace_files"]]


@functools.cache
def _jinja2_env():
    from jinja2.exceptions import TemplateRuntimeError
    from jinja2.ext import Extension

    class RaiseExtension(Extension):
        tags = set(["raise"])

        def parse(self, parser):
            lineno = next(parser.stream).lineno

            message_node = parser.parse_expression()

            return jinja2.nodes.CallBlock(
                self.call_method("_raise", [message_node], lineno=lineno),
                [],
                [],
                [],
                lineno=lineno,
            )

        def _raise(self, msg, caller):
            raise TemplateRuntimeError(msg)

    def camelize(name):
        """Convert snake_case to PascalCase"""
        return "".join(
            word.capitalize()
            for word in name.replace("-", "_").replace(".", "_").split("_")
        )

    def snake_case(name):
        """Convert name to snake_case"""
        return name.replace(".", "_").replace("-", "_").lower()

    def debug(value):
        print(value)
        return value

    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(TEMPLATES),
        trim_blocks=True,
        lstrip_blocks=True,
        extensions=[RaiseExtension],
    )

    env.filters["camelize"] = camelize
    env.filters["snake_case"] = snake_case
    env.filters["debug"] = debug

    return env


def main(*args):  # mach requires this
    pass
