#include "ui_core/overlay_lifetime.h"

#include <algorithm>

namespace pp::ui {

UiOverlayLifetime::UiOverlayLifetime(NodeLifetimeTree& tree, NodeHandle root) noexcept
    : tree_(tree)
    , root_(root)
{
}

pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_popup()
{
    return open_overlay(root_, UiOverlayKind::popup, true, false);
}

pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_child_popup(NodeHandle parent_popup)
{
    if (!tracked_alive(parent_popup)) {
        return pp::foundation::Result<NodeHandle>::failure(
            pp::foundation::Status::invalid_argument("child popup requires a live tracked parent popup"));
    }

    return open_overlay(parent_popup, UiOverlayKind::popup, true, false);
}

pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_dialog(bool modal)
{
    return open_overlay(root_, UiOverlayKind::dialog, modal, modal);
}

pp::foundation::Status UiOverlayLifetime::close(NodeHandle overlay) noexcept
{
    if (!tracked_alive(overlay)) {
        return pp::foundation::Status::invalid_argument("overlay close requires a live tracked overlay");
    }

    const auto status = tree_.destroy_subtree(overlay);
    if (!status.ok()) {
        return status;
    }

    prune_dead_entries();
    restore_capture(UiCaptureKind::pointer);
    restore_capture(UiCaptureKind::keyboard);
    return pp::foundation::Status::success();
}

void UiOverlayLifetime::clear_for_layout_reload() noexcept
{
    tree_.clear();
    entries_.clear();
}

bool UiOverlayLifetime::tracks(NodeHandle overlay) const noexcept
{
    return tracked_alive(overlay);
}

std::size_t UiOverlayLifetime::overlay_count() const noexcept
{
    std::size_t count = 0;
    for (const auto& entry : entries_) {
        if (tree_.contains(entry.node)) {
            ++count;
        }
    }

    return count;
}

pp::foundation::Result<NodeHandle> UiOverlayLifetime::top_overlay() const noexcept
{
    for (auto index = entries_.size(); index > 0U; --index) {
        const auto& entry = entries_[index - 1U];
        if (tree_.contains(entry.node)) {
            return pp::foundation::Result<NodeHandle>::success(entry.node);
        }
    }

    return pp::foundation::Result<NodeHandle>::failure(
        pp::foundation::Status::invalid_argument("overlay stack is empty"));
}

pp::foundation::Result<NodeHandle> UiOverlayLifetime::open_overlay(
    NodeHandle parent,
    UiOverlayKind kind,
    bool captures_pointer,
    bool captures_keyboard)
{
    if (!tree_.contains(root_)) {
        return pp::foundation::Result<NodeHandle>::failure(
            pp::foundation::Status::invalid_argument("overlay root is not live"));
    }

    if (!tree_.contains(parent)) {
        return pp::foundation::Result<NodeHandle>::failure(
            pp::foundation::Status::invalid_argument("overlay parent is not live"));
    }

    auto node = tree_.create_child(parent);
    if (!node) {
        return node;
    }

    if (captures_pointer) {
        const auto status = tree_.capture(UiCaptureKind::pointer, node.value());
        if (!status.ok()) {
            (void)tree_.destroy_subtree(node.value());
            return pp::foundation::Result<NodeHandle>::failure(status);
        }
    }

    if (captures_keyboard) {
        const auto status = tree_.capture(UiCaptureKind::keyboard, node.value());
        if (!status.ok()) {
            (void)tree_.destroy_subtree(node.value());
            return pp::foundation::Result<NodeHandle>::failure(status);
        }
    }

    entries_.push_back(UiOverlayEntry {
        .node = node.value(),
        .parent = parent,
        .kind = kind,
        .captures_pointer = captures_pointer,
        .captures_keyboard = captures_keyboard,
    });
    return node;
}

void UiOverlayLifetime::prune_dead_entries() noexcept
{
    entries_.erase(
        std::remove_if(
            entries_.begin(),
            entries_.end(),
            [this](const UiOverlayEntry& entry) {
                return !tree_.contains(entry.node);
            }),
        entries_.end());
}

void UiOverlayLifetime::restore_capture(UiCaptureKind kind) noexcept
{
    for (auto index = entries_.size(); index > 0U; --index) {
        const auto& entry = entries_[index - 1U];
        const auto captures = kind == UiCaptureKind::pointer
            ? entry.captures_pointer
            : entry.captures_keyboard;
        if (captures && tree_.contains(entry.node)) {
            (void)tree_.capture(kind, entry.node);
            return;
        }
    }
}

bool UiOverlayLifetime::tracked_alive(NodeHandle overlay) const noexcept
{
    if (!tree_.contains(overlay)) {
        return false;
    }

    return std::any_of(
        entries_.begin(),
        entries_.end(),
        [overlay](const UiOverlayEntry& entry) {
            return entry.node == overlay;
        });
}

}
