#pragma once

#include <LibURL/URL.h>
#include <LibIPC/File.h>
#include <LibIPC/TransportHandle.h>
#include <LibWeb/Bindings/AgentType.h>
#include <LibWeb/HTML/BroadcastChannelMessage.h>
#include <LibWeb/Bindings/Worker.h>
#include <LibWeb/HTML/StructuredSerialize.h>
#include <LibWeb/HTML/Scripting/SerializedEnvironmentSettingsObject.h>
#include <AK/Error.h>
#include <AK/MemoryStream.h>
#include <AK/OwnPtr.h>
#include <AK/Platform.h>
#include <AK/Result.h>
#include <AK/Utf8View.h>
#include <LibIPC/Attachment.h>
#include <LibIPC/Connection.h>
#include <LibIPC/Decoder.h>
#include <LibIPC/Encoder.h>
#include <LibIPC/File.h>
#include <LibIPC/Message.h>
#include <LibIPC/Stub.h>

#if defined(AK_COMPILER_CLANG)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdefaulted-function-deleted"
#endif

namespace Messages::WebWorkerServer {

enum class MessageID : i32 {
    ConnectToRequestServer = 1,
    ConnectToImageDecoder = 2,
    StartWorker = 3,
    CloseWorker = 4,
    BroadcastChannelMessage = 5,
    HandleFileReturn = 6,
};

class ConnectToRequestServer final : public IPC::Message {
public:
    ConnectToRequestServer(IPC::TransportHandle handle)
        : m_handle(move(handle))
    {
    }

    ConnectToRequestServer(ConnectToRequestServer const&) = default;
    ConnectToRequestServer(ConnectToRequestServer&&) = default;
    ConnectToRequestServer& operator=(ConnectToRequestServer const&) = default;

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, IPC::TransportHandle>)
    ConnectToRequestServer(WrappedReturnType&& value)
        : m_handle(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 3309463393;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::ConnectToRequestServer; }
    static i32 static_message_id() { return (int)MessageID::ConnectToRequestServer; }
    virtual StringView message_name() const override { return "WebWorkerServer::ConnectToRequestServer"sv; }

    static ErrorOr<NonnullOwnPtr<ConnectToRequestServer>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto handle = TRY((decoder.decode<IPC::TransportHandle>()));
        return make<ConnectToRequestServer>(move(handle));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(IPC::TransportHandle const& handle)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::ConnectToRequestServer));
        TRY(stream.encode(handle));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_handle);
    }

    IPC::TransportHandle const& handle() const { return m_handle; }
    IPC::TransportHandle take_handle() { return move(m_handle); }

private:
    IPC::TransportHandle m_handle;
};

class ConnectToImageDecoder final : public IPC::Message {
public:
    ConnectToImageDecoder(IPC::TransportHandle handle)
        : m_handle(move(handle))
    {
    }

    ConnectToImageDecoder(ConnectToImageDecoder const&) = default;
    ConnectToImageDecoder(ConnectToImageDecoder&&) = default;
    ConnectToImageDecoder& operator=(ConnectToImageDecoder const&) = default;

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, IPC::TransportHandle>)
    ConnectToImageDecoder(WrappedReturnType&& value)
        : m_handle(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 3309463393;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::ConnectToImageDecoder; }
    static i32 static_message_id() { return (int)MessageID::ConnectToImageDecoder; }
    virtual StringView message_name() const override { return "WebWorkerServer::ConnectToImageDecoder"sv; }

    static ErrorOr<NonnullOwnPtr<ConnectToImageDecoder>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto handle = TRY((decoder.decode<IPC::TransportHandle>()));
        return make<ConnectToImageDecoder>(move(handle));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(IPC::TransportHandle const& handle)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::ConnectToImageDecoder));
        TRY(stream.encode(handle));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_handle);
    }

    IPC::TransportHandle const& handle() const { return m_handle; }
    IPC::TransportHandle take_handle() { return move(m_handle); }

private:
    IPC::TransportHandle m_handle;
};

class StartWorker final : public IPC::Message {
public:
    StartWorker(URL::URL url, Web::Bindings::WorkerType type, Web::Bindings::RequestCredentials credentials, String name, Web::HTML::TransferDataEncoder message_port, Web::HTML::SerializedEnvironmentSettingsObject outside_settings, Web::Bindings::AgentType agent_type)
        : m_url(move(url))
        , m_type(move(type))
        , m_credentials(move(credentials))
        , m_name(move(name))
        , m_message_port(move(message_port))
        , m_outside_settings(move(outside_settings))
        , m_agent_type(move(agent_type))
    {
    }

    StartWorker(StartWorker const&) = default;
    StartWorker(StartWorker&&) = default;
    StartWorker& operator=(StartWorker const&) = default;

    static constexpr u32 ENDPOINT_MAGIC = 3309463393;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::StartWorker; }
    static i32 static_message_id() { return (int)MessageID::StartWorker; }
    virtual StringView message_name() const override { return "WebWorkerServer::StartWorker"sv; }

    static ErrorOr<NonnullOwnPtr<StartWorker>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto url = TRY((decoder.decode<URL::URL>()));
        auto type = TRY((decoder.decode<Web::Bindings::WorkerType>()));
        auto credentials = TRY((decoder.decode<Web::Bindings::RequestCredentials>()));
        auto name = TRY((decoder.decode<String>()));
        auto message_port = TRY((decoder.decode<Web::HTML::TransferDataEncoder>()));
        auto outside_settings = TRY((decoder.decode<Web::HTML::SerializedEnvironmentSettingsObject>()));
        auto agent_type = TRY((decoder.decode<Web::Bindings::AgentType>()));
        return make<StartWorker>(move(url), move(type), move(credentials), move(name), move(message_port), move(outside_settings), move(agent_type));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(URL::URL const& url, Web::Bindings::WorkerType const& type, Web::Bindings::RequestCredentials const& credentials, StringView name, Web::HTML::TransferDataEncoder const& message_port, Web::HTML::SerializedEnvironmentSettingsObject const& outside_settings, Web::Bindings::AgentType const& agent_type)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::StartWorker));
        TRY(stream.encode(url));
        TRY(stream.encode(type));
        TRY(stream.encode(credentials));
        TRY(stream.encode(name));
        TRY(stream.encode(message_port));
        TRY(stream.encode(outside_settings));
        TRY(stream.encode(agent_type));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_url, m_type, m_credentials, m_name, m_message_port, m_outside_settings, m_agent_type);
    }

    URL::URL const& url() const { return m_url; }
    URL::URL take_url() { return move(m_url); }

    Web::Bindings::WorkerType const& type() const { return m_type; }
    Web::Bindings::WorkerType take_type() { return move(m_type); }

    Web::Bindings::RequestCredentials const& credentials() const { return m_credentials; }
    Web::Bindings::RequestCredentials take_credentials() { return move(m_credentials); }

    String const& name() const { return m_name; }
    String take_name() { return move(m_name); }

    Web::HTML::TransferDataEncoder const& message_port() const { return m_message_port; }
    Web::HTML::TransferDataEncoder take_message_port() { return move(m_message_port); }

    Web::HTML::SerializedEnvironmentSettingsObject const& outside_settings() const { return m_outside_settings; }
    Web::HTML::SerializedEnvironmentSettingsObject take_outside_settings() { return move(m_outside_settings); }

    Web::Bindings::AgentType const& agent_type() const { return m_agent_type; }
    Web::Bindings::AgentType take_agent_type() { return move(m_agent_type); }

private:
    URL::URL m_url;
    Web::Bindings::WorkerType m_type;
    Web::Bindings::RequestCredentials m_credentials;
    String m_name;
    Web::HTML::TransferDataEncoder m_message_port;
    Web::HTML::SerializedEnvironmentSettingsObject m_outside_settings;
    Web::Bindings::AgentType m_agent_type;
};

class CloseWorker final : public IPC::Message {
public:
    CloseWorker() = default;

    CloseWorker(CloseWorker const&) = default;
    CloseWorker(CloseWorker&&) = default;
    CloseWorker& operator=(CloseWorker const&) = default;

    static constexpr u32 ENDPOINT_MAGIC = 3309463393;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::CloseWorker; }
    static i32 static_message_id() { return (int)MessageID::CloseWorker; }
    virtual StringView message_name() const override { return "WebWorkerServer::CloseWorker"sv; }

    static ErrorOr<NonnullOwnPtr<CloseWorker>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        return make<CloseWorker>();
    }

    static ErrorOr<IPC::MessageBuffer> static_encode()
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::CloseWorker));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode();
    }

private:
};

class BroadcastChannelMessage final : public IPC::Message {
public:
    BroadcastChannelMessage(Web::HTML::BroadcastChannelMessage message)
        : m_message(move(message))
    {
    }

    BroadcastChannelMessage(BroadcastChannelMessage const&) = default;
    BroadcastChannelMessage(BroadcastChannelMessage&&) = default;
    BroadcastChannelMessage& operator=(BroadcastChannelMessage const&) = default;

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, Web::HTML::BroadcastChannelMessage>)
    BroadcastChannelMessage(WrappedReturnType&& value)
        : m_message(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 3309463393;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::BroadcastChannelMessage; }
    static i32 static_message_id() { return (int)MessageID::BroadcastChannelMessage; }
    virtual StringView message_name() const override { return "WebWorkerServer::BroadcastChannelMessage"sv; }

    static ErrorOr<NonnullOwnPtr<BroadcastChannelMessage>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto message = TRY((decoder.decode<Web::HTML::BroadcastChannelMessage>()));
        return make<BroadcastChannelMessage>(move(message));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(Web::HTML::BroadcastChannelMessage const& message)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::BroadcastChannelMessage));
        TRY(stream.encode(message));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_message);
    }

    Web::HTML::BroadcastChannelMessage const& message() const { return m_message; }
    Web::HTML::BroadcastChannelMessage take_message() { return move(m_message); }

private:
    Web::HTML::BroadcastChannelMessage m_message;
};

class HandleFileReturn final : public IPC::Message {
public:
    HandleFileReturn(i32 error, Optional<IPC::File> file, i32 request_id)
        : m_error(move(error))
        , m_file(move(file))
        , m_request_id(move(request_id))
    {
    }

    HandleFileReturn(HandleFileReturn const&) = default;
    HandleFileReturn(HandleFileReturn&&) = default;
    HandleFileReturn& operator=(HandleFileReturn const&) = default;

    static constexpr u32 ENDPOINT_MAGIC = 3309463393;

    virtual u32 endpoint_magic() const override { return ENDPOINT_MAGIC; }
    virtual i32 message_id() const override { return (int)MessageID::HandleFileReturn; }
    static i32 static_message_id() { return (int)MessageID::HandleFileReturn; }
    virtual StringView message_name() const override { return "WebWorkerServer::HandleFileReturn"sv; }

    static ErrorOr<NonnullOwnPtr<HandleFileReturn>> decode(Stream& stream, Queue<IPC::Attachment>& attachments)
    {
        IPC::Decoder decoder { stream, attachments };
        auto error = TRY((decoder.decode<i32>()));
        auto file = TRY((decoder.decode<Optional<IPC::File>>()));
        auto request_id = TRY((decoder.decode<i32>()));
        return make<HandleFileReturn>(move(error), move(file), move(request_id));
    }

    static ErrorOr<IPC::MessageBuffer> static_encode(i32 error, Optional<IPC::File> const& file, i32 request_id)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::HandleFileReturn));
        TRY(stream.encode(error));
        TRY(stream.encode(file));
        TRY(stream.encode(request_id));
        return buffer;
    }

    virtual ErrorOr<IPC::MessageBuffer> encode() const override
    {
        return static_encode(m_error, m_file, m_request_id);
    }

    i32 error() const { return m_error; }

    Optional<IPC::File> const& file() const { return m_file; }
    Optional<IPC::File> take_file() { return move(m_file); }

    i32 request_id() const { return m_request_id; }

private:
    i32 m_error;
    Optional<IPC::File> m_file;
    i32 m_request_id;
};

} // namespace Messages::WebWorkerServer

template<typename LocalEndpoint, typename PeerEndpoint>
class WebWorkerServerProxy {
public:
    // Used to disambiguate the constructor call.
    struct Tag { };

    WebWorkerServerProxy(IPC::Connection<LocalEndpoint, PeerEndpoint>& connection, Tag)
        : m_connection(connection)
    {
    }

    void async_connect_to_request_server(IPC::TransportHandle const& handle)
    {
        auto message_buffer = MUST(Messages::WebWorkerServer::ConnectToRequestServer::static_encode(move(handle)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_connect_to_image_decoder(IPC::TransportHandle const& handle)
    {
        auto message_buffer = MUST(Messages::WebWorkerServer::ConnectToImageDecoder::static_encode(move(handle)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_start_worker(URL::URL const& url, Web::Bindings::WorkerType const& type, Web::Bindings::RequestCredentials const& credentials, StringView name, Web::HTML::TransferDataEncoder const& message_port, Web::HTML::SerializedEnvironmentSettingsObject const& outside_settings, Web::Bindings::AgentType const& agent_type)
    {
        VERIFY(Utf8View { name }.validate());
        auto message_buffer = MUST(Messages::WebWorkerServer::StartWorker::static_encode(move(url), move(type), move(credentials), name, move(message_port), move(outside_settings), move(agent_type)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_start_worker(URL::URL const& url, Web::Bindings::WorkerType const& type, Web::Bindings::RequestCredentials const& credentials, String const& name, Web::HTML::TransferDataEncoder const& message_port, Web::HTML::SerializedEnvironmentSettingsObject const& outside_settings, Web::Bindings::AgentType const& agent_type)
    {
        auto message_buffer = MUST(Messages::WebWorkerServer::StartWorker::static_encode(move(url), move(type), move(credentials), name, move(message_port), move(outside_settings), move(agent_type)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_close_worker()
    {
        auto message_buffer = MUST(Messages::WebWorkerServer::CloseWorker::static_encode());
        (void)m_connection.post_message(message_buffer);
    }

    void async_broadcast_channel_message(Web::HTML::BroadcastChannelMessage const& message)
    {
        auto message_buffer = MUST(Messages::WebWorkerServer::BroadcastChannelMessage::static_encode(move(message)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_handle_file_return(i32 error, Optional<IPC::File> const& file, i32 request_id)
    {
        auto message_buffer = MUST(Messages::WebWorkerServer::HandleFileReturn::static_encode(error, move(file), request_id));
        (void)m_connection.post_message(message_buffer);
    }

private:
    IPC::Connection<LocalEndpoint, PeerEndpoint>& m_connection;
};

template<typename LocalEndpoint, typename PeerEndpoint>
class WebWorkerServerProxy;
class WebWorkerServerStub;

class WebWorkerServerEndpoint {
public:
    template<typename LocalEndpoint>
    using Proxy = WebWorkerServerProxy<LocalEndpoint, WebWorkerServerEndpoint>;
    using Stub = WebWorkerServerStub;

    static u32 static_magic() { return 3309463393; }

    static ErrorOr<NonnullOwnPtr<IPC::Message>> decode_message(ReadonlyBytes buffer, [[maybe_unused]] Queue<IPC::Attachment>& attachments)
    {
        FixedMemoryStream stream { buffer };
        auto message_endpoint_magic = TRY(stream.read_value<u32>());

        if (message_endpoint_magic != static_magic())
            return Error::from_string_literal("Endpoint magic number mismatch, not my message!");

        auto message_id = TRY(stream.read_value<i32>());

        switch (message_id) {
        case (int)Messages::WebWorkerServer::MessageID::ConnectToRequestServer:
            return Messages::WebWorkerServer::ConnectToRequestServer::decode(stream, attachments);
        case (int)Messages::WebWorkerServer::MessageID::ConnectToImageDecoder:
            return Messages::WebWorkerServer::ConnectToImageDecoder::decode(stream, attachments);
        case (int)Messages::WebWorkerServer::MessageID::StartWorker:
            return Messages::WebWorkerServer::StartWorker::decode(stream, attachments);
        case (int)Messages::WebWorkerServer::MessageID::CloseWorker:
            return Messages::WebWorkerServer::CloseWorker::decode(stream, attachments);
        case (int)Messages::WebWorkerServer::MessageID::BroadcastChannelMessage:
            return Messages::WebWorkerServer::BroadcastChannelMessage::decode(stream, attachments);
        case (int)Messages::WebWorkerServer::MessageID::HandleFileReturn:
            return Messages::WebWorkerServer::HandleFileReturn::decode(stream, attachments);
        default:
            return Error::from_string_literal("Failed to decode WebWorkerServer message");
        }

        VERIFY_NOT_REACHED();
    }
};

class WebWorkerServerStub : public IPC::Stub {
public:
    WebWorkerServerStub() { }
    virtual ~WebWorkerServerStub() override { }

    virtual u32 magic() const override { return 3309463393; }
    virtual ByteString name() const override { return "WebWorkerServer"; }

    virtual ErrorOr<OwnPtr<IPC::MessageBuffer>> handle(NonnullOwnPtr<IPC::Message> message) override
    {
        switch (message->message_id()) {
        case (int)Messages::WebWorkerServer::MessageID::ConnectToRequestServer:
            return handle_connect_to_request_server(*message);
        case (int)Messages::WebWorkerServer::MessageID::ConnectToImageDecoder:
            return handle_connect_to_image_decoder(*message);
        case (int)Messages::WebWorkerServer::MessageID::StartWorker:
            return handle_start_worker(*message);
        case (int)Messages::WebWorkerServer::MessageID::CloseWorker:
            return handle_close_worker();
        case (int)Messages::WebWorkerServer::MessageID::BroadcastChannelMessage:
            return handle_broadcast_channel_message(*message);
        case (int)Messages::WebWorkerServer::MessageID::HandleFileReturn:
            return handle_handle_file_return(*message);
        default:
            return Error::from_string_literal("Unknown message ID for WebWorkerServer endpoint");
        }
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_connect_to_request_server(IPC::Message& message)
    {
        auto& request = static_cast<Messages::WebWorkerServer::ConnectToRequestServer&>(message);
        connect_to_request_server(request.take_handle());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_connect_to_image_decoder(IPC::Message& message)
    {
        auto& request = static_cast<Messages::WebWorkerServer::ConnectToImageDecoder&>(message);
        connect_to_image_decoder(request.take_handle());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_start_worker(IPC::Message& message)
    {
        auto& request = static_cast<Messages::WebWorkerServer::StartWorker&>(message);
        start_worker(request.take_url(), request.take_type(), request.take_credentials(), request.take_name(), request.take_message_port(), request.take_outside_settings(), request.take_agent_type());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_close_worker()
    {
        close_worker();
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_broadcast_channel_message(IPC::Message& message)
    {
        auto& request = static_cast<Messages::WebWorkerServer::BroadcastChannelMessage&>(message);
        broadcast_channel_message(request.take_message());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_handle_file_return(IPC::Message& message)
    {
        auto& request = static_cast<Messages::WebWorkerServer::HandleFileReturn&>(message);
        handle_file_return(request.error(), request.take_file(), request.request_id());
        return nullptr;
    }

    virtual void connect_to_request_server(IPC::TransportHandle handle) = 0;
    virtual void connect_to_image_decoder(IPC::TransportHandle handle) = 0;
    virtual void start_worker(URL::URL url, Web::Bindings::WorkerType type, Web::Bindings::RequestCredentials credentials, String name, Web::HTML::TransferDataEncoder message_port, Web::HTML::SerializedEnvironmentSettingsObject outside_settings, Web::Bindings::AgentType agent_type) = 0;
    virtual void close_worker() = 0;
    virtual void broadcast_channel_message(Web::HTML::BroadcastChannelMessage message) = 0;
    virtual void handle_file_return(i32 error, Optional<IPC::File> file, i32 request_id) = 0;
};

#if defined(AK_COMPILER_CLANG)
#pragma clang diagnostic pop
#endif
