# Copyright (c) 2026-present, the Ladybird developers.
#
# SPDX-License-Identifier: BSD-2-Clause


from typing import TextIO

from Generators.libweb_bindings import overload_resolution
from Generators.libweb_bindings.attributes import attribute_getter_callback_name
from Generators.libweb_bindings.attributes import attribute_has_setter
from Generators.libweb_bindings.attributes import attribute_setter_callback_name
from Generators.libweb_bindings.attributes import define_the_regular_attributes
from Generators.libweb_bindings.attributes import define_the_unforgeable_attributes
from Generators.libweb_bindings.attributes import write_attribute_getter
from Generators.libweb_bindings.attributes import write_attribute_setter
from Generators.libweb_bindings.constants import define_the_constants
from Generators.libweb_bindings.context import GenerationContext
from Generators.libweb_bindings.cpp_types import idl_identifier_cpp_name
from Generators.libweb_bindings.includes import GeneratedIncludes
from Generators.libweb_bindings.operations import define_the_regular_operations
from Generators.libweb_bindings.operations import define_the_stringifier
from Generators.libweb_bindings.operations import write_regular_operations_for_receiver
from Generators.libweb_bindings.operations import write_stringifier
from Utils.webidl_parser import Interface


def write_global_mixin_declaration(out: TextIO, context: GenerationContext, interface: Interface) -> None:
    out.write(
        f"""class {interface.name}GlobalMixin {{
public:
    void initialize(JS::Realm&, JS::Object&);
    void define_unforgeable_attributes(JS::Realm&, JS::Object&);
    {interface.name}GlobalMixin();
    virtual ~{interface.name}GlobalMixin();

private:
"""
    )
    declared_callbacks: set[str] = set()
    for attribute in interface.regular_attributes:
        if "FIXME" in attribute.extended_attributes:
            continue
        getter_callback = attribute_getter_callback_name(attribute)
        if getter_callback not in declared_callbacks:
            declared_callbacks.add(getter_callback)
            out.write(f"    JS_DECLARE_NATIVE_FUNCTION({getter_callback});\n")
        if attribute_has_setter(attribute):
            setter_callback = attribute_setter_callback_name(attribute)
            if setter_callback not in declared_callbacks:
                declared_callbacks.add(setter_callback)
                out.write(f"    JS_DECLARE_NATIVE_FUNCTION({setter_callback});\n")
    for operations in overload_resolution.operation_overload_sets(interface).values():
        operation = operations[0]
        callback = idl_identifier_cpp_name(operation)
        if callback not in declared_callbacks:
            declared_callbacks.add(callback)
            out.write(f"    JS_DECLARE_NATIVE_FUNCTION({callback});\n")
        if len(operations) > 1:
            for overload_index, overloaded_operation in enumerate(operations):
                overloaded_callback = idl_identifier_cpp_name(overloaded_operation, suffix=overload_index)
                if overloaded_callback not in declared_callbacks:
                    declared_callbacks.add(overloaded_callback)
                    out.write(f"    JS_DECLARE_NATIVE_FUNCTION({overloaded_callback});\n")
    if interface.stringifier is not None and "to_string" not in declared_callbacks:
        declared_callbacks.add("to_string")
        out.write("    JS_DECLARE_NATIVE_FUNCTION(to_string);\n")
    out.write(
        """};

"""
    )


def write_global_mixin_implementation(
    out: TextIO, context: GenerationContext, includes: GeneratedIncludes, interface: Interface
) -> None:
    if "Global" not in interface.extended_attributes:
        return

    includes.add("LibWeb/Bindings/Intrinsics.h")

    out.write(
        f"""{interface.name}GlobalMixin::{interface.name}GlobalMixin() = default;
{interface.name}GlobalMixin::~{interface.name}GlobalMixin() = default;

void {interface.name}GlobalMixin::initialize(JS::Realm& realm, [[maybe_unused]] JS::Object& object)
{{
    [[maybe_unused]] auto& vm = realm.vm();
    [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable | JS::Attribute::Configurable | JS::Attribute::Writable;

    object.set_prototype(&ensure_web_prototype<{interface.prototype_class}>(realm, "{interface.namespaced_name}"_fly_string));
"""
    )
    define_the_regular_attributes(out, includes, interface)
    define_the_regular_operations(out, includes, interface)
    define_the_stringifier(out, includes, interface)
    define_the_constants(out, context, includes, interface)
    out.write(
        f"""}}

void {interface.name}GlobalMixin::define_unforgeable_attributes(JS::Realm& realm, [[maybe_unused]] JS::Object& object)
{{
    [[maybe_unused]] auto& vm = realm.vm();
    [[maybe_unused]] u8 default_attributes = JS::Attribute::Enumerable;
"""
    )
    define_the_unforgeable_attributes(out, includes, interface)
    define_the_regular_operations(out, includes, interface, unforgeable=True)
    define_the_stringifier(out, includes, interface, unforgeable=True)
    out.write(
        """}

"""
    )

    defined_callbacks: set[str] = set()
    for attribute in interface.regular_attributes:
        if "FIXME" in attribute.extended_attributes:
            continue
        getter_callback = attribute_getter_callback_name(attribute)
        if getter_callback not in defined_callbacks:
            defined_callbacks.add(getter_callback)
            write_attribute_getter(out, context, includes, interface, attribute, f"{interface.name}GlobalMixin")
        if attribute_has_setter(attribute):
            setter_callback = attribute_setter_callback_name(attribute)
            if setter_callback not in defined_callbacks:
                defined_callbacks.add(setter_callback)
                write_attribute_setter(out, context, includes, interface, attribute, f"{interface.name}GlobalMixin")
    write_regular_operations_for_receiver(
        out, context, includes, interface, f"{interface.name}GlobalMixin", defined_callbacks
    )
    if interface.stringifier is not None and "to_string" not in defined_callbacks:
        defined_callbacks.add("to_string")
        write_stringifier(out, context, includes, interface, f"{interface.name}GlobalMixin")
