#pragma once

#include <LibWeb/WebDriver/Response.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::WebDriverServer {

enum class MessageID : i32 {
    DriverExecutionComplete = 1,
    DidSetWindowHandle = 2,
};

class DriverExecutionComplete final : public IPC::Message {
public:
    DriverExecutionComplete(Web::WebDriver::Response response)
        : m_response(move(response))
    {
    }

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

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, Web::WebDriver::Response>)
    DriverExecutionComplete(WrappedReturnType&& value)
        : m_response(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 2244075654;

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

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

    static ErrorOr<IPC::MessageBuffer> static_encode(Web::WebDriver::Response const& response)
    {
        IPC::MessageBuffer buffer;
        IPC::Encoder stream(buffer);
        TRY(stream.encode(ENDPOINT_MAGIC));
        TRY(stream.encode((int)MessageID::DriverExecutionComplete));
        TRY(stream.encode(response));
        return buffer;
    }

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

    Web::WebDriver::Response const& response() const { return m_response; }
    Web::WebDriver::Response take_response() { return move(m_response); }

private:
    Web::WebDriver::Response m_response;
};

class DidSetWindowHandle final : public IPC::Message {
public:
    DidSetWindowHandle(String handle)
        : m_handle(move(handle))
    {
    }

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

    template<typename WrappedReturnType>
    requires(!SameAs<WrappedReturnType, String>)
    DidSetWindowHandle(WrappedReturnType&& value)
        : m_handle(forward<WrappedReturnType>(value))
    {
    }

    static constexpr u32 ENDPOINT_MAGIC = 2244075654;

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

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

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

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

    String const& handle() const { return m_handle; }
    String take_handle() { return move(m_handle); }

private:
    String m_handle;
};

} // namespace Messages::WebDriverServer

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

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

    void async_driver_execution_complete(Web::WebDriver::Response const& response)
    {
        auto message_buffer = MUST(Messages::WebDriverServer::DriverExecutionComplete::static_encode(move(response)));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_set_window_handle(StringView handle)
    {
        VERIFY(Utf8View { handle }.validate());
        auto message_buffer = MUST(Messages::WebDriverServer::DidSetWindowHandle::static_encode(handle));
        (void)m_connection.post_message(message_buffer);
    }

    void async_did_set_window_handle(String const& handle)
    {
        auto message_buffer = MUST(Messages::WebDriverServer::DidSetWindowHandle::static_encode(handle));
        (void)m_connection.post_message(message_buffer);
    }

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

template<typename LocalEndpoint, typename PeerEndpoint>
class WebDriverServerProxy;
class WebDriverServerStub;

class WebDriverServerEndpoint {
public:
    template<typename LocalEndpoint>
    using Proxy = WebDriverServerProxy<LocalEndpoint, WebDriverServerEndpoint>;
    using Stub = WebDriverServerStub;

    static u32 static_magic() { return 2244075654; }

    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::WebDriverServer::MessageID::DriverExecutionComplete:
            return Messages::WebDriverServer::DriverExecutionComplete::decode(stream, attachments);
        case (int)Messages::WebDriverServer::MessageID::DidSetWindowHandle:
            return Messages::WebDriverServer::DidSetWindowHandle::decode(stream, attachments);
        default:
            return Error::from_string_literal("Failed to decode WebDriverServer message");
        }

        VERIFY_NOT_REACHED();
    }
};

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

    virtual u32 magic() const override { return 2244075654; }
    virtual ByteString name() const override { return "WebDriverServer"; }

    virtual ErrorOr<OwnPtr<IPC::MessageBuffer>> handle(NonnullOwnPtr<IPC::Message> message) override
    {
        switch (message->message_id()) {
        case (int)Messages::WebDriverServer::MessageID::DriverExecutionComplete:
            return handle_driver_execution_complete(*message);
        case (int)Messages::WebDriverServer::MessageID::DidSetWindowHandle:
            return handle_did_set_window_handle(*message);
        default:
            return Error::from_string_literal("Unknown message ID for WebDriverServer endpoint");
        }
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_driver_execution_complete(IPC::Message& message)
    {
        auto& request = static_cast<Messages::WebDriverServer::DriverExecutionComplete&>(message);
        driver_execution_complete(request.take_response());
        return nullptr;
    }

    NEVER_INLINE ErrorOr<OwnPtr<IPC::MessageBuffer>> handle_did_set_window_handle(IPC::Message& message)
    {
        auto& request = static_cast<Messages::WebDriverServer::DidSetWindowHandle&>(message);
        did_set_window_handle(request.take_handle());
        return nullptr;
    }

    virtual void driver_execution_complete(Web::WebDriver::Response response) = 0;
    virtual void did_set_window_handle(String handle) = 0;
};

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