/*
 * Copyright (c) 2026-present, the Ladybird developers.
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include <AK/StringBuilder.h>
#include <LibCore/Environment.h>
#include <LibWebView/HistoryDebug.h>
#include <LibWebView/SessionHistory.h>

namespace WebView {

bool history_debug_enabled()
{
    static auto enabled = WEBVIEW_HISTORY_DEBUG || Core::Environment::has("LADYBIRD_SESSION_HISTORY_DEBUG"sv);
    return enabled;
}

static void append_history_log_entry(StringBuilder& builder, Web::HTML::SessionHistoryEntryDescriptor const& entry);

static StringView document_state_resource_type(Variant<Empty, String, Web::HTML::POSTResource> const& resource)
{
    if (resource.has<String>())
        return "string"sv;
    if (resource.has<Web::HTML::POSTResource>())
        return "post"sv;
    return "none"sv;
}

static void append_history_log_nested_histories(StringBuilder& builder, Vector<Web::HTML::SessionHistoryNestedHistoryDescriptor> const& nested_histories)
{
    if (nested_histories.is_empty())
        return;

    builder.append(" nested={"sv);
    for (size_t i = 0; i < nested_histories.size(); ++i) {
        if (i != 0)
            builder.append(", "sv);

        builder.appendff("{}=[", nested_histories[i].id);
        for (size_t j = 0; j < nested_histories[i].entries.size(); ++j) {
            if (j != 0)
                builder.append(", "sv);
            append_history_log_entry(builder, nested_histories[i].entries[j]);
        }
        builder.append(']');
    }
    builder.append('}');
}

static void append_history_log_entry(StringBuilder& builder, Web::HTML::SessionHistoryEntryDescriptor const& entry)
{
    builder.appendff("{}:{}", entry.step, entry.url);
    auto resource_type = document_state_resource_type(entry.document_state.resource);
    if (entry.document_state.id != 0
        || entry.document_state.reload_pending
        || !entry.document_state.navigable_target_name.is_empty()
        || resource_type != "none"sv) {
        builder.appendff(" document_state={{id={}", entry.document_state.id);
        if (resource_type != "none"sv)
            builder.appendff(", resource={}", resource_type);
        if (entry.document_state.reload_pending)
            builder.append(", reload_pending=true"sv);
        if (!entry.document_state.navigable_target_name.is_empty())
            builder.appendff(", target_name={}", entry.document_state.navigable_target_name);
        builder.append('}');
    }
    if (entry.scroll_position_data.viewport_scroll_position.has_value()) {
        auto const& viewport_scroll_position = *entry.scroll_position_data.viewport_scroll_position;
        builder.appendff(" scroll={{viewport=({}, {})}}", viewport_scroll_position.x(), viewport_scroll_position.y());
    }
    append_history_log_nested_histories(builder, entry.document_state.nested_histories);
}

static StringView scroll_restoration_mode_to_string(Web::HTML::ScrollRestorationMode mode)
{
    switch (mode) {
    case Web::HTML::ScrollRestorationMode::Auto:
        return "auto"sv;
    case Web::HTML::ScrollRestorationMode::Manual:
        return "manual"sv;
    }
    VERIFY_NOT_REACHED();
}

static JsonObject history_json_scroll_position_data(Web::HTML::SessionHistoryEntryScrollPositionData const& scroll_position_data)
{
    JsonObject serialized;
    if (scroll_position_data.viewport_scroll_position.has_value()) {
        auto const& viewport_scroll_position = *scroll_position_data.viewport_scroll_position;
        JsonArray serialized_viewport_scroll_position;
        serialized_viewport_scroll_position.must_append(viewport_scroll_position.x().to_double());
        serialized_viewport_scroll_position.must_append(viewport_scroll_position.y().to_double());
        serialized.set("viewport"sv, move(serialized_viewport_scroll_position));
    }
    return serialized;
}

static JsonArray history_json_nested_histories(Vector<Web::HTML::SessionHistoryNestedHistoryDescriptor> const& nested_histories)
{
    JsonArray serialized_nested_histories;
    serialized_nested_histories.ensure_capacity(nested_histories.size());
    for (auto const& nested_history : nested_histories) {
        JsonArray serialized_entries;
        serialized_entries.ensure_capacity(nested_history.entries.size());
        for (auto const& entry : nested_history.entries)
            serialized_entries.must_append(history_json_entry(entry));

        JsonObject serialized_nested_history;
        serialized_nested_history.set("id"sv, MUST(String::formatted("{}", nested_history.id)));
        serialized_nested_history.set("entries"sv, move(serialized_entries));
        serialized_nested_histories.must_append(move(serialized_nested_history));
    }
    return serialized_nested_histories;
}

JsonObject history_json_entry(Web::HTML::SessionHistoryEntryDescriptor const& entry, bool current)
{
    JsonObject serialized;
    serialized.set("step"sv, entry.step);
    serialized.set("url"sv, entry.url.serialize());
    serialized.set("documentStateId"sv, entry.document_state.id);
    serialized.set("resource"sv, document_state_resource_type(entry.document_state.resource));
    serialized.set("reloadPending"sv, entry.document_state.reload_pending);
    serialized.set("targetName"sv, entry.document_state.navigable_target_name.to_utf8());
    serialized.set("scrollRestoration"sv, scroll_restoration_mode_to_string(entry.scroll_restoration_mode));
    serialized.set("scrollPosition"sv, history_json_scroll_position_data(entry.scroll_position_data));
    serialized.set("nestedHistories"sv, history_json_nested_histories(entry.document_state.nested_histories));
    serialized.set("current"sv, current);
    return serialized;
}

JsonArray history_json_entries(TraversableSessionHistory const& history)
{
    return history_json_entries(history.entries(), history.current_top_level_entry_index());
}

JsonArray history_json_entries(Vector<Web::HTML::SessionHistoryEntryDescriptor> const& entries, Optional<size_t> current_entry_index)
{
    JsonArray serialized_entries;
    serialized_entries.ensure_capacity(entries.size());
    for (size_t i = 0; i < entries.size(); ++i)
        serialized_entries.must_append(history_json_entry(entries[i], current_entry_index.has_value() && *current_entry_index == i));
    return serialized_entries;
}

JsonArray history_json_steps(TraversableSessionHistory const& history)
{
    return history_json_steps(history.used_steps(), history.current_used_step_index());
}

JsonArray history_json_steps(Vector<i32> const& steps, Optional<size_t> current_step_index)
{
    JsonArray serialized_steps;
    serialized_steps.ensure_capacity(steps.size());
    for (size_t i = 0; i < steps.size(); ++i) {
        JsonObject serialized_step;
        serialized_step.set("step"sv, steps[i]);
        serialized_step.set("current"sv, current_step_index.has_value() && *current_step_index == i);
        serialized_steps.must_append(move(serialized_step));
    }
    return serialized_steps;
}

ByteString history_log_entries(TraversableSessionHistory const& history)
{
    StringBuilder builder;
    builder.append("entries=["sv);
    for (size_t i = 0; i < history.size(); ++i) {
        if (i != 0)
            builder.append(", "sv);

        auto const* entry = history.entry_at(i);
        VERIFY(entry);
        if (auto const* current_entry = history.current_entry(); current_entry == entry)
            builder.append("*"sv);
        builder.appendff("{}:", i);
        append_history_log_entry(builder, *entry);
    }
    builder.append("] used_steps="sv);
    builder.append(history_log_steps(history.used_steps(), history.current_used_step_index()));
    return builder.to_byte_string();
}

ByteString history_log_entries(Vector<Web::HTML::SessionHistoryEntryDescriptor> const& entries, Optional<size_t> current_entry_index)
{
    StringBuilder builder;
    builder.append('[');
    for (size_t i = 0; i < entries.size(); ++i) {
        if (i != 0)
            builder.append(", "sv);

        if (current_entry_index.has_value() && *current_entry_index == i)
            builder.append("*"sv);
        builder.appendff("{}:", i);
        append_history_log_entry(builder, entries[i]);
    }
    builder.append(']');
    return builder.to_byte_string();
}

ByteString history_log_steps(Vector<i32> const& steps, Optional<size_t> current_step_index)
{
    StringBuilder builder;
    builder.append('[');
    for (size_t i = 0; i < steps.size(); ++i) {
        if (i != 0)
            builder.append(", "sv);

        if (current_step_index.has_value() && *current_step_index == i)
            builder.append("*"sv);
        builder.appendff("{}:{}", i, steps[i]);
    }
    builder.append(']');
    return builder.to_byte_string();
}

}
