/*
 * Copyright (c) 2024-2025, stelar7 <dudedbz@gmail.com>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#pragma once

#include <LibGC/Ptr.h>
#include <LibWeb/Bindings/IDBDatabase.h>
#include <LibWeb/DOM/EventTarget.h>
#include <LibWeb/HTML/DOMStringList.h>
#include <LibWeb/IndexedDB/ConnectionState.h>
#include <LibWeb/IndexedDB/IDBRequest.h>
#include <LibWeb/IndexedDB/IDBTransaction.h>
#include <LibWeb/IndexedDB/Internal/Database.h>
#include <LibWeb/IndexedDB/Internal/ObjectStore.h>
#include <LibWeb/StorageAPI/StorageKey.h>

namespace Web::IndexedDB {

using KeyPath = Variant<String, Vector<String>>;
using NullableKeyPath = Variant<String, Vector<String>, Empty>;

// https://w3c.github.io/IndexedDB/#IDBDatabase-interface
// https://www.w3.org/TR/IndexedDB/#database-connection
class IDBDatabase : public DOM::EventTarget {
    WEB_PLATFORM_OBJECT(IDBDatabase, DOM::EventTarget);
    GC_DECLARE_ALLOCATOR(IDBDatabase);

public:
    static constexpr bool OVERRIDES_FINALIZE = true;

    virtual ~IDBDatabase() override;
    virtual void finalize() override;

    [[nodiscard]] static GC::Ref<IDBDatabase> create(JS::Realm&, Database&);

    void set_version(u64 version) { m_version = version; }
    void set_close_pending(bool close_pending) { m_close_pending = close_pending; }
    void set_state(ConnectionState state);

    [[nodiscard]] String uuid() const { return m_uuid; }
    [[nodiscard]] String name() const { return m_name; }
    [[nodiscard]] u64 version() const { return m_version; }
    [[nodiscard]] bool close_pending() const { return m_close_pending; }
    [[nodiscard]] ConnectionState state() const { return m_state; }
    [[nodiscard]] GC::Ref<Database> associated_database() { return m_associated_database; }
    [[nodiscard]] ReadonlySpan<GC::Ref<ObjectStore>> object_store_set() { return m_object_store_set; }
    void add_to_object_store_set(GC::Ref<ObjectStore> object_store) { m_object_store_set.append(object_store); }
    void remove_from_object_store_set(GC::Ref<ObjectStore> object_store)
    {
        m_object_store_set.remove_first_matching([&](auto& entry) { return entry == object_store; });
    }

    [[nodiscard]] ReadonlySpan<GC::Ref<IDBTransaction>> transactions() { return m_transactions; }
    void add_transaction(GC::Ref<IDBTransaction> transaction) { m_transactions.append(transaction); }

    [[nodiscard]] GC::Ref<HTML::DOMStringList> object_store_names();
    WebIDL::ExceptionOr<GC::Ref<IDBObjectStore>> create_object_store(String const&, Bindings::IDBObjectStoreParameters const&);
    WebIDL::ExceptionOr<void> delete_object_store(String const&);

    WebIDL::ExceptionOr<GC::Ref<IDBTransaction>> transaction(Variant<String, Vector<String>>, Bindings::IDBTransactionMode = Bindings::IDBTransactionMode::Readonly, Bindings::IDBTransactionOptions = { .durability = Bindings::IDBTransactionDurability::Default });

    void close();

    void set_onabort(WebIDL::CallbackType*);
    WebIDL::CallbackType* onabort();
    void set_onclose(WebIDL::CallbackType*);
    WebIDL::CallbackType* onclose();
    void set_onerror(WebIDL::CallbackType*);
    WebIDL::CallbackType* onerror();
    void set_onversionchange(WebIDL::CallbackType*);
    WebIDL::CallbackType* onversionchange();

    void wait_for_transactions_to_finish(ReadonlySpan<GC::Ref<IDBTransaction>>, GC::Ref<GC::Function<void()>> on_complete);
    void check_pending_transaction_waits();
    void block_on_conflicting_transactions(GC::Ref<IDBTransaction>);

protected:
    explicit IDBDatabase(JS::Realm&, Database&);

    virtual void initialize(JS::Realm&) override;
    virtual void visit_edges(Visitor& visitor) override;

private:
    struct PendingTransactionWait {
        Vector<GC::Ref<IDBTransaction>> transactions;
        GC::Ref<GC::Function<void()>> callback;
    };

    Vector<PendingTransactionWait> m_pending_transaction_waits;

    u64 m_version { 0 };
    String m_name;

    // Each connection has a close pending flag which is initially false.
    bool m_close_pending { false };

    // When a connection is initially created it is in an opened state.
    ConnectionState m_state { ConnectionState::Open };

    // A connection has an object store set, which is initialized to the set of object stores in the associated database when the connection is created.
    // The contents of the set will remain constant except when an upgrade transaction is live.
    Vector<GC::Ref<ObjectStore>> m_object_store_set;

    // NOTE: There is an associated database in the spec, but there is no mention where it is assigned, nor where its from
    //       So we stash the one we have when opening a connection.
    GC::Ref<Database> m_associated_database;

    // NOTE: We need to keep track of what transactions were created by this connection
    Vector<GC::Ref<IDBTransaction>> m_transactions;

    // NOTE: Used for debug purposes
    String m_uuid;
};

}
