/*
 * Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/FlyString.h>
#include <AK/JsonObject.h>
#include <AK/NeverDestroyed.h>
#include <LibWeb/DOM/CustomEvent.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/Event.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Internals/WebUI.h>
#include <LibWeb/WebDriver/JSON.h>
#include <WebContent/WebUIConnection.h>

namespace WebContent {

static JS::PropertyKey const& ladybird_property()
{
    static NeverDestroyed<JS::PropertyKey> property { "ladybird"_utf16_fly_string };
    return *property;
}

static FlyString const& web_ui_loaded_event()
{
    static NeverDestroyed<FlyString> event { "WebUILoaded"_fly_string };
    return *event;
}

static FlyString const& web_ui_message_event()
{
    static NeverDestroyed<FlyString> event { "WebUIMessage"_fly_string };
    return *event;
}

ErrorOr<NonnullRefPtr<WebUIConnection>> WebUIConnection::connect(IPC::TransportHandle handle, Web::DOM::Document& document)
{
    auto transport = TRY(handle.create_transport());
    return adopt_ref(*new WebUIConnection(move(transport), document));
}

WebUIConnection::WebUIConnection(NonnullOwnPtr<IPC::Transport> transport, Web::DOM::Document& document)
    : IPC::ConnectionFromClient<WebUIClientEndpoint, WebUIServerEndpoint>(*this, move(transport), 1)
    , m_document(document)
{
    auto& realm = m_document->realm();
    m_document->window()->define_direct_property(ladybird_property(), realm.create<Web::Internals::WebUI>(realm), JS::default_attributes);

    Web::HTML::queue_a_task(Web::HTML::Task::Source::Unspecified, nullptr, m_document, GC::create_function(realm.heap(), [&document = *m_document]() {
        document.dispatch_event(Web::DOM::Event::create(document.realm(), web_ui_loaded_event()));
    }));
}

WebUIConnection::~WebUIConnection()
{
    if (!m_document->window())
        return;

    (void)m_document->window()->internal_delete(ladybird_property());
}

void WebUIConnection::visit_edges(JS::Cell::Visitor& visitor)
{
    visitor.visit(m_document);
}

void WebUIConnection::send_message(String name, JsonValue data)
{
    if (!m_document->browsing_context())
        return;

    JsonObject detail;
    detail.set("name"sv, move(name));
    detail.set("data"sv, move(data));

    auto& realm = m_document->realm();
    Web::HTML::TemporaryExecutionContext context { realm };

    auto serialized_detail = Web::WebDriver::json_deserialize(*m_document->browsing_context(), detail);
    if (serialized_detail.is_error()) {
        warnln("Unable to serialize JSON data from browser: {}", serialized_detail.error());
        return;
    }

    Web::Bindings::CustomEventInit event_init {};
    event_init.detail = serialized_detail.value();

    m_document->dispatch_event(Web::DOM::CustomEvent::create(realm, web_ui_message_event(), event_init));
}

void WebUIConnection::received_message_from_web_ui(String const& name, JS::Value data)
{
    if (!m_document->browsing_context())
        return;

    auto deserialized_data = Web::WebDriver::json_clone(*m_document->browsing_context(), data);
    if (deserialized_data.is_error()) {
        warnln("Unable to deserialize JS data from WebUI: {}", deserialized_data.error());
        return;
    }

    async_received_message(name, deserialized_data.value());
}

}
