#pragma once

#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::UIProcessServer {

enum class MessageID : i32 {
    CreateNewTab = 1,
    CreateNewTabResponse = 2,
    CreateNewWindow = 3,
    CreateNewWindowResponse = 4,
};

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

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

    static constexpr u32 ENDPOINT_MAGIC = 1725039581;

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

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

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

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

private:
};

class CreateNewTab final : public IPC::Message {
public:
    typedef class CreateNewTabResponse ResponseType;

    CreateNewTab(Vector<ByteString> urls)
        : m_urls(move(urls))
    {
    }

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

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, Vector<ByteString>>)
    CreateNewTab(WrappedReturnType&& value)
        : m_urls(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 1725039581;

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

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

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

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

    Vector<ByteString> const& urls() const { return m_urls; }
    Vector<ByteString> take_urls() { return move(m_urls); }

private:
    Vector<ByteString> m_urls;
};

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

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

    static constexpr u32 ENDPOINT_MAGIC = 1725039581;

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

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

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

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

private:
};

class CreateNewWindow final : public IPC::Message {
public:
    typedef class CreateNewWindowResponse ResponseType;

    CreateNewWindow(Vector<ByteString> urls)
        : m_urls(move(urls))
    {
    }

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

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, Vector<ByteString>>)
    CreateNewWindow(WrappedReturnType&& value)
        : m_urls(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 1725039581;

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

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

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

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

    Vector<ByteString> const& urls() const { return m_urls; }
    Vector<ByteString> take_urls() { return move(m_urls); }

private:
    Vector<ByteString> m_urls;
};

} // namespace Messages::UIProcessServer

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

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

    void create_new_tab(Vector<ByteString> urls)
    {
        (void)m_connection.template send_sync<Messages::UIProcessServer::CreateNewTab>(move(urls));
    }

    void async_create_new_tab(ReadonlySpan<ByteString> urls)
    {
        auto message_buffer = MUST(Messages::UIProcessServer::CreateNewTab::static_encode(urls));
        (void)m_connection.post_message(message_buffer);
    }

    IPC::IPCErrorOr<void> try_create_new_tab(Vector<ByteString> urls)
    {
        if (auto result = m_connection.template send_sync_but_allow_failure<Messages::UIProcessServer::CreateNewTab>(move(urls)))
            return {};
        m_connection.shutdown();
        return IPC::ErrorCode::PeerDisconnected;
    }

    void create_new_window(Vector<ByteString> urls)
    {
        (void)m_connection.template send_sync<Messages::UIProcessServer::CreateNewWindow>(move(urls));
    }

    void async_create_new_window(ReadonlySpan<ByteString> urls)
    {
        auto message_buffer = MUST(Messages::UIProcessServer::CreateNewWindow::static_encode(urls));
        (void)m_connection.post_message(message_buffer);
    }

    IPC::IPCErrorOr<void> try_create_new_window(Vector<ByteString> urls)
    {
        if (auto result = m_connection.template send_sync_but_allow_failure<Messages::UIProcessServer::CreateNewWindow>(move(urls)))
            return {};
        m_connection.shutdown();
        return IPC::ErrorCode::PeerDisconnected;
    }

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

template<typename LocalEndpoint, typename PeerEndpoint>
class UIProcessServerProxy;
class UIProcessServerStub;

class UIProcessServerEndpoint {
public:
    template<typename LocalEndpoint>
    using Proxy = UIProcessServerProxy<LocalEndpoint, UIProcessServerEndpoint>;
    using Stub = UIProcessServerStub;

    static u32 static_magic() { return 1725039581; }

    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::UIProcessServer::MessageID::CreateNewTab:
            return Messages::UIProcessServer::CreateNewTab::decode(stream, attachments);
        case (int)Messages::UIProcessServer::MessageID::CreateNewTabResponse:
            return Messages::UIProcessServer::CreateNewTabResponse::decode(stream, attachments);
        case (int)Messages::UIProcessServer::MessageID::CreateNewWindow:
            return Messages::UIProcessServer::CreateNewWindow::decode(stream, attachments);
        case (int)Messages::UIProcessServer::MessageID::CreateNewWindowResponse:
            return Messages::UIProcessServer::CreateNewWindowResponse::decode(stream, attachments);
        default:
            return Error::from_string_literal("Failed to decode UIProcessServer message");
        }

        VERIFY_NOT_REACHED();
    }
};

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

    virtual u32 magic() const override { return 1725039581; }
    virtual ByteString name() const override { return "UIProcessServer"; }

    virtual ErrorOr<OwnPtr<IPC::MessageBuffer>> handle(NonnullOwnPtr<IPC::Message> message) override
    {
        switch (message->message_id()) {
        case (int)Messages::UIProcessServer::MessageID::CreateNewTab:
            return handle_create_new_tab(*message);
        case (int)Messages::UIProcessServer::MessageID::CreateNewWindow:
            return handle_create_new_window(*message);
        default:
            return Error::from_string_literal("Unknown message ID for UIProcessServer endpoint");
        }
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_create_new_tab(IPC::Message& message)
    {
        auto& request = static_cast<Messages::UIProcessServer::CreateNewTab&>(message);
        create_new_tab(request.take_urls());
        auto response = Messages::UIProcessServer::CreateNewTabResponse {};
        return make<IPC::MessageBuffer>(TRY(response.encode()));
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_create_new_window(IPC::Message& message)
    {
        auto& request = static_cast<Messages::UIProcessServer::CreateNewWindow&>(message);
        create_new_window(request.take_urls());
        auto response = Messages::UIProcessServer::CreateNewWindowResponse {};
        return make<IPC::MessageBuffer>(TRY(response.encode()));
    }

    virtual void create_new_tab(Vector<ByteString> urls) = 0;
    virtual void create_new_window(Vector<ByteString> urls) = 0;
};

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