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

#include <LibTest/TestCase.h>
#include <LibURL/Parser.h>
#include <LibWeb/HTML/NavigableId.h>
#include <LibWebView/HistoryDebug.h>
#include <LibWebView/SessionHistory.h>

static Web::HTML::NavigableId navigable_id(StringView id)
{
    if (id == "frame-1"sv)
        return { 1, 1 };
    if (id == "frame"sv)
        return { 1, 2 };
    if (id == "1"sv)
        return { 2, 1 };
    VERIFY_NOT_REACHED();
}

static Web::HTML::SessionHistoryNestedHistoryDescriptor nested_history(StringView id, Vector<Web::HTML::SessionHistoryEntryDescriptor> entries)
{
    return {
        .id = navigable_id(id),
        .entries = move(entries),
    };
}

static URL::URL parse_url(StringView url)
{
    auto parsed_url = URL::Parser::basic_parse(url);
    VERIFY(parsed_url.has_value());
    return parsed_url.release_value();
}

static Web::HTML::SerializationRecord state_record(u8 byte)
{
    Web::HTML::SerializationRecord record;
    record.append(byte);
    return record;
}

static Web::HTML::SessionHistoryEntryDescriptor create_test_entry(i32 step, URL::URL url)
{
    return {
        .step = step,
        .url = move(url),
        .document_state = {
            .id = 0,
            .history_policy_container = Web::HTML::DocumentState::Client::Tag,
            .request_referrer = Web::Fetch::Infrastructure::Request::Referrer::Client,
            .request_referrer_policy = Web::ReferrerPolicy::DEFAULT_REFERRER_POLICY,
            .initiator_origin = {},
            .origin = {},
            .about_base_url = {},
            .resource = {},
            .reload_pending = false,
            .ever_populated = false,
            .navigable_target_name = {},
            .nested_histories = {},
        },
        .classic_history_api_state = {},
        .navigation_api_state = {},
        .navigation_api_key = {},
        .navigation_api_id = {},
        .scroll_restoration_mode = Web::HTML::ScrollRestorationMode::Auto,
        .scroll_position_data = {},
    };
}

static Web::HTML::SessionHistoryEntryDescriptor entry(i32 step, StringView url)
{
    return create_test_entry(step, parse_url(url));
}

static Web::HTML::SessionHistoryEntryDescriptor entry(i32 step, StringView url, u8 classic_history_api_state, u8 navigation_api_state, StringView navigation_api_key, StringView navigation_api_id, Web::HTML::ScrollRestorationMode scroll_restoration_mode)
{
    auto entry = create_test_entry(step, parse_url(url));
    entry.classic_history_api_state = state_record(classic_history_api_state);
    entry.navigation_api_state = state_record(navigation_api_state);
    entry.navigation_api_key = MUST(String::from_utf8(navigation_api_key));
    entry.navigation_api_id = MUST(String::from_utf8(navigation_api_id));
    entry.scroll_restoration_mode = scroll_restoration_mode;
    return entry;
}

static Web::HTML::SessionHistoryEntryDescriptor entry_with_scroll_position(i32 step, StringView url, Web::CSSPixelPoint viewport_scroll_position)
{
    auto entry = create_test_entry(step, parse_url(url));
    entry.scroll_position_data.viewport_scroll_position = viewport_scroll_position;
    return entry;
}

static Web::HTML::SessionHistoryEntryDescriptor entry(i32 step, StringView url, u64 document_state_id, StringView navigable_target_name)
{
    auto entry = create_test_entry(step, parse_url(url));
    entry.document_state.id = document_state_id;
    entry.document_state.ever_populated = true;
    entry.document_state.navigable_target_name = Utf16String::from_utf8(navigable_target_name);
    return entry;
}

static Web::HTML::SessionHistoryEntryDescriptor entry_with_reload_pending(i32 step, StringView url, u64 document_state_id, StringView navigable_target_name, Vector<Web::HTML::SessionHistoryNestedHistoryDescriptor> nested_histories)
{
    auto entry = create_test_entry(step, parse_url(url));
    entry.document_state.id = document_state_id;
    entry.document_state.reload_pending = true;
    entry.document_state.ever_populated = true;
    entry.document_state.navigable_target_name = Utf16String::from_utf8(navigable_target_name);
    entry.document_state.nested_histories = move(nested_histories);
    return entry;
}

static Web::HTML::SessionHistoryEntryDescriptor entry_with_post_resource(i32 step, StringView url)
{
    auto entry = create_test_entry(step, parse_url(url));
    entry.document_state.resource = Web::HTML::POSTResource {
        .request_body = MUST(ByteBuffer::copy("field=value"sv.bytes())),
        .request_content_type = Web::HTML::POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded,
    };
    return entry;
}

static Web::HTML::SessionHistoryEntryDescriptor entry(i32 step, StringView url, Vector<Web::HTML::SessionHistoryNestedHistoryDescriptor> nested_histories)
{
    auto entry = create_test_entry(step, parse_url(url));
    entry.document_state.nested_histories = move(nested_histories);
    return entry;
}

static Web::HTML::SessionHistoryEntryDescriptor entry(i32 step, StringView url, u64 document_state_id, StringView navigable_target_name, Vector<Web::HTML::SessionHistoryNestedHistoryDescriptor> nested_histories)
{
    auto entry = create_test_entry(step, parse_url(url));
    entry.document_state.id = document_state_id;
    entry.document_state.ever_populated = true;
    entry.document_state.navigable_target_name = Utf16String::from_utf8(navigable_target_name);
    entry.document_state.nested_histories = move(nested_histories);
    return entry;
}

static Web::HTML::SessionHistoryEntryDescriptor entry(i32 step, URL::URL url)
{
    return create_test_entry(step, move(url));
}

static void expect_entry(WebView::TraversableSessionHistory const& history, size_t index, i32 expected_step, StringView expected_url)
{
    auto* entry = history.entry_at(index);
    VERIFY(entry);
    EXPECT_EQ(entry->step, expected_step);
    EXPECT_EQ(entry->url, parse_url(expected_url));
}

static void expect_current_entry(WebView::TraversableSessionHistory const& history, i32 expected_step, StringView expected_url)
{
    auto* entry = history.current_entry();
    VERIFY(entry);
    EXPECT_EQ(entry->step, expected_step);
    EXPECT_EQ(entry->url, parse_url(expected_url));
}

static void expect_entry_state(Web::HTML::SessionHistoryEntryDescriptor const& entry, u8 expected_classic_history_api_state, u8 expected_navigation_api_state, StringView expected_navigation_api_key, StringView expected_navigation_api_id, Web::HTML::ScrollRestorationMode expected_scroll_restoration_mode)
{
    EXPECT(entry.classic_history_api_state == state_record(expected_classic_history_api_state));
    EXPECT(entry.navigation_api_state == state_record(expected_navigation_api_state));
    EXPECT_EQ(entry.navigation_api_key, MUST(String::from_utf8(expected_navigation_api_key)));
    EXPECT_EQ(entry.navigation_api_id, MUST(String::from_utf8(expected_navigation_api_id)));
    EXPECT_EQ(entry.scroll_restoration_mode, expected_scroll_restoration_mode);
}

static void expect_entry_document_state(Web::HTML::SessionHistoryEntryDescriptor const& entry, u64 expected_document_state_id, StringView expected_navigable_target_name)
{
    EXPECT_EQ(entry.document_state.id, expected_document_state_id);
    EXPECT_EQ(entry.document_state.navigable_target_name, Utf16String::from_utf8(expected_navigable_target_name));
}

static void expect_entry_viewport_scroll_position(Web::HTML::SessionHistoryEntryDescriptor const& entry, Web::CSSPixelPoint expected_viewport_scroll_position)
{
    VERIFY(entry.scroll_position_data.viewport_scroll_position.has_value());
    EXPECT_EQ(*entry.scroll_position_data.viewport_scroll_position, expected_viewport_scroll_position);
}

static void expect_entry_resource(Web::HTML::SessionHistoryEntryDescriptor const& entry, StringView expected_resource)
{
    if (expected_resource == "post"sv) {
        EXPECT(entry.document_state.resource.has<Web::HTML::POSTResource>());
        return;
    }

    if (expected_resource == "string"sv) {
        EXPECT(entry.document_state.resource.has<String>());
        return;
    }

    VERIFY(expected_resource == "none"sv);
    EXPECT(entry.document_state.resource.has<Empty>());
}

static void expect_used_step(WebView::TraversableSessionHistory const& history, size_t index, i32 expected_step)
{
    auto step = history.step_at(index);
    VERIFY(step.has_value());
    EXPECT_EQ(*step, expected_step);
}

static void expect_step_to_restore(Optional<i32> step, i32 expected_step)
{
    VERIFY(step.has_value());
    EXPECT_EQ(*step, expected_step);
}

static void expect_nested_history(Web::HTML::SessionHistoryEntryDescriptor const& entry, size_t index, StringView expected_id, size_t expected_size)
{
    VERIFY(index < entry.document_state.nested_histories.size());
    EXPECT_EQ(entry.document_state.nested_histories[index].id, navigable_id(expected_id));
    EXPECT_EQ(entry.document_state.nested_histories[index].entries.size(), expected_size);
}

static void expect_nested_entry(Web::HTML::SessionHistoryNestedHistoryDescriptor const& nested_history, size_t index, i32 expected_step, StringView expected_url)
{
    VERIFY(index < nested_history.entries.size());
    EXPECT_EQ(nested_history.entries[index].step, expected_step);
    EXPECT_EQ(nested_history.entries[index].url, parse_url(expected_url));
}

TEST_CASE(complete_web_content_update_replaces_mirror)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 2uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
}

TEST_CASE(fresh_process_snapshot_does_not_drop_previous_entries)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 2uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
}

TEST_CASE(replace_navigation_snapshot_drops_speculative_entry)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://b.example/"sv),
                                                                 },
        { 0, 1 }, 1);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.navigate(parse_url("https://c.example/"sv));

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://c.example/"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 2uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://c.example/"sv);
}

TEST_CASE(replace_navigation_snapshot_drops_speculative_first_entry)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                 },
        { 0 }, 0);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.replace_current_entry_url(parse_url("https://b.example/"sv));

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://b.example/"sv),
                                                         },
        { 0 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 1uz);
    EXPECT_EQ(history.used_step_count(), 1uz);
    EXPECT(!history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_current_entry(history, 0, "https://b.example/"sv);
}

TEST_CASE(fresh_single_entry_snapshot_does_not_drop_previous_entries)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://b.example/"sv),
                                                         },
        { 0 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 2uz);
    EXPECT_EQ(history.used_step_count(), 2uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);

    auto web_content_known_used_steps = history.web_content_known_used_steps();
    EXPECT_EQ(web_content_known_used_steps.size(), 1uz);
    EXPECT_EQ(web_content_known_used_steps[0], 1);
    auto target_a = history.traversal_target_for_delta(-1);
    VERIFY(target_a.has_value());
    EXPECT(!history.web_content_can_traverse_to(*target_a));
}

TEST_CASE(complete_matching_snapshot_updates_current_index)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_current_entry(history, 1, "https://b.example/"sv);
}

TEST_CASE(matching_snapshot_updates_session_history_entry_state)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv, 1, 2, "key-a"sv, "id-a"sv, Web::HTML::ScrollRestorationMode::Auto),
                                                                 },
        { 0 }, 0);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, 3, 4, "key-b"sv, "id-b"sv, Web::HTML::ScrollRestorationMode::Manual),
                                                         },
        { 0 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 1uz);
    EXPECT_EQ(history.used_step_count(), 1uz);
    expect_current_entry(history, 0, "https://a.example/"sv);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_entry_state(*current_entry, 3, 4, "key-b"sv, "id-b"sv, Web::HTML::ScrollRestorationMode::Manual);
}

TEST_CASE(matching_snapshot_updates_session_history_entry_scroll_position)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry_with_scroll_position(0, "https://a.example/"sv, { 0, 100 }),
                                                                 },
        { 0 }, 0);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry_with_scroll_position(0, "https://a.example/"sv, { 0, 300 }),
                                                         },
        { 0 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 1uz);
    EXPECT_EQ(history.used_step_count(), 1uz);
    expect_current_entry(history, 0, "https://a.example/"sv);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_entry_viewport_scroll_position(*current_entry, { 0, 300 });
}

TEST_CASE(matching_snapshot_updates_document_state)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv, 1, "target-a"sv),
                                                                 },
        { 0 }, 0);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, 1, "target-b"sv),
                                                         },
        { 0 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 1uz);
    EXPECT_EQ(history.used_step_count(), 1uz);
    expect_current_entry(history, 0, "https://a.example/"sv);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_entry_document_state(*current_entry, 1, "target-b"sv);
}

TEST_CASE(partial_snapshot_preserves_forward_history_after_fallback_traversal)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://c.example/"sv);
}

TEST_CASE(partial_snapshot_preserves_entries_outside_web_content_known_history)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());

    auto web_content_known_used_steps = history.web_content_known_used_steps();
    EXPECT_EQ(web_content_known_used_steps.size(), 1uz);
    EXPECT_EQ(web_content_known_used_steps[0], 1);

    auto target_a = history.traversal_target_for_delta(-1);
    VERIFY(target_a.has_value());
    EXPECT(!history.web_content_can_traverse_to(*target_a));

    auto target_c = history.traversal_target_for_delta(1);
    VERIFY(target_c.has_value());
    EXPECT(!history.web_content_can_traverse_to(*target_c));
}

TEST_CASE(complete_snapshot_accepts_reassigned_document_state_ids)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv, 1, "top-a"sv),
                                                                     entry(1, "https://nested.example/"sv, 2, "top-nested"sv, {
                                                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                                                  entry(1, "https://frame.example/a"sv, 3, "frame"sv),
                                                                                                                                                                  entry(2, "https://frame.example/b"sv, 4, "frame"sv),
                                                                                                                                                              }),
                                                                                                                              }),
                                                                     entry(3, "https://c.example/"sv, 5, "top-c"sv),
                                                                 },
        { 0, 1, 2, 3 }, 2);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, 10, "top-a"sv),
                                                             entry(1, "https://nested.example/"sv, 20, "top-nested"sv, {
                                                                                                                           nested_history("frame-1"sv, {
                                                                                                                                                           entry(1, "https://frame.example/a"sv, 30, "frame"sv),
                                                                                                                                                           entry(2, "https://frame.example/b"sv, 40, "frame"sv),
                                                                                                                                                       }),
                                                                                                                       }),
                                                             entry(3, "https://c.example/"sv, 50, "top-c"sv),
                                                         },
        { 0, 1, 2, 3 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT_EQ(history.used_step_count(), 4uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_current_entry(history, 1, "https://nested.example/"sv);
    expect_used_step(history, 2, 2);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_entry_document_state(*current_entry, 2, "top-nested"sv);
    expect_nested_history(*current_entry, 0, "frame-1"sv, 2);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 0, 1, "https://frame.example/a"sv);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 1, 2, "https://frame.example/b"sv);
    expect_entry_document_state(current_entry->document_state.nested_histories[0].entries[0], 3, "frame"sv);
    expect_entry_document_state(current_entry->document_state.nested_histories[0].entries[1], 4, "frame"sv);
}

TEST_CASE(partial_snapshot_updates_nested_history_and_preserves_forward_history)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(1, "https://frame.example/a"sv),
                                                                                                                              }),
                                                                                              }),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    EXPECT(!history.has_only_top_level_used_steps());
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://c.example/"sv);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_nested_history(*current_entry, 0, "frame-1"sv, 1);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 0, 1, "https://frame.example/a"sv);
}

TEST_CASE(fresh_process_snapshot_preserves_forward_history_at_first_entry)
{
    WebView::TraversableSessionHistory history;
    history.navigate(URL::about_blank());
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.traverse_to(0);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                         },
        { 0 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT(!history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_current_entry(history, 0, "about:blank"sv);
    expect_entry(history, 1, 1, "https://a.example/"sv);
    expect_entry(history, 2, 2, "https://b.example/"sv);
}

TEST_CASE(partial_snapshot_extends_history_without_dropping_prefix)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_current_entry(history, 2, "https://c.example/"sv);
}

TEST_CASE(partial_snapshot_accepts_same_document_update_at_current_step)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv, 1, "main"sv),
                                                                     entry(1, "https://b.example/"sv, 2, "main"sv),
                                                                 },
        { 0, 1 }, 1);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry(1, "https://b.example/?replaced"sv, 20, "main"sv),
                                                             entry(2, "https://b.example/?pushed"sv, 20, "main"sv),
                                                         },
        { 1, 2 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/?replaced"sv);
    expect_current_entry(history, 2, "https://b.example/?pushed"sv);
    EXPECT(history.web_content_uses_ui_step_coordinates());

    auto web_content_known_used_steps = history.web_content_known_used_steps();
    EXPECT_EQ(web_content_known_used_steps.size(), 2uz);
    EXPECT_EQ(web_content_known_used_steps[0], 1);
    EXPECT_EQ(web_content_known_used_steps[1], 2);

    auto web_content_known_entries = history.web_content_known_entries();
    EXPECT_EQ(web_content_known_entries.size(), 2uz);
    EXPECT_EQ(web_content_known_entries[0].url, parse_url("https://b.example/?replaced"sv));
    EXPECT_EQ(web_content_known_entries[1].url, parse_url("https://b.example/?pushed"sv));
}

TEST_CASE(partial_snapshot_allows_web_content_traversal_within_known_suffix)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_current_entry(history, 2, "https://c.example/"sv);

    auto web_content_known_used_steps = history.web_content_known_used_steps();
    EXPECT_EQ(web_content_known_used_steps.size(), 2uz);
    EXPECT_EQ(web_content_known_used_steps[0], 1);
    EXPECT_EQ(web_content_known_used_steps[1], 2);
    EXPECT(history.web_content_uses_ui_step_coordinates());

    auto target_b = history.traversal_target_for_delta(-1);
    VERIFY(target_b.has_value());
    EXPECT(history.web_content_can_traverse_to(*target_b));

    auto target_a = history.traversal_target_for_delta(-2);
    VERIFY(target_a.has_value());
    EXPECT(!history.web_content_can_traverse_to(*target_a));
}

TEST_CASE(reseeded_partial_snapshot_preserves_ui_only_history)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://b.example/"sv),
                                                                     entry(2, "https://c.example/"sv),
                                                                     entry(3, "https://d.example/"sv),
                                                                 },
        { 0, 1, 2, 3 }, 3);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.traverse_to(1);
    history.forget_web_content_state();
    auto current_top_level_entry_index = history.current_top_level_entry_index();
    VERIFY(current_top_level_entry_index.has_value());
    history.did_seed_web_content_from_ui_process(*current_top_level_entry_index);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 4uz);
    EXPECT_EQ(history.used_step_count(), 4uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://c.example/"sv);
    expect_entry(history, 3, 3, "https://d.example/"sv);

    auto web_content_known_used_steps = history.web_content_known_used_steps();
    EXPECT_EQ(web_content_known_used_steps.size(), 1uz);
    EXPECT_EQ(web_content_known_used_steps[0], 1);
    auto web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 1);

    auto target_a = history.traversal_target_for_delta(-1);
    VERIFY(target_a.has_value());
    EXPECT(!history.web_content_can_traverse_to(*target_a));

    auto target_c = history.traversal_target_for_delta(1);
    VERIFY(target_c.has_value());
    EXPECT(!history.web_content_can_traverse_to(*target_c));
}

TEST_CASE(partial_snapshot_replaces_forward_history_after_new_navigation)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://d.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_current_entry(history, 2, "https://d.example/"sv);
}

TEST_CASE(partial_snapshot_anchors_to_current_duplicate_url_entry)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(2);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://a.example/"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 4uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_current_entry(history, 2, "https://a.example/"sv);
    expect_entry(history, 3, 3, "https://c.example/"sv);
}

TEST_CASE(partial_snapshot_prefers_duplicate_url_entry_with_matching_document_state)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv, 1, "main"sv),
                                                                     entry(1, "https://b.example/"sv, 2, "main"sv),
                                                                     entry(2, "https://b.example/"sv, 3, "main"sv),
                                                                     entry(3, "https://c.example/"sv, 4, "main"sv),
                                                                 },
        { 0, 1, 2, 3 }, 3);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.traverse_to(2);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv, 2, "main"sv),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 4uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://b.example/"sv);
    expect_entry(history, 3, 3, "https://c.example/"sv);
}

TEST_CASE(partial_snapshot_translates_fresh_process_steps_to_ui_steps)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(4, "https://a.example/"sv),
                                                                     entry(5, "https://b.example/"sv),
                                                                     entry(6, "https://c.example/"sv),
                                                                 },
        { 4, 5, 6 }, 2);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://d.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 4, "https://a.example/"sv);
    expect_entry(history, 1, 5, "https://b.example/"sv);
    expect_current_entry(history, 6, "https://d.example/"sv);

    auto target_b = history.traversal_target_for_delta(-1);
    VERIFY(target_b.has_value());
    EXPECT(!history.web_content_uses_ui_step_coordinates());
    EXPECT(!history.web_content_can_traverse_to(*target_b));
}

TEST_CASE(used_steps_include_nested_history_steps)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(1, "https://frame.example/a"sv),
                                                                                                                              }),
                                                                                              }),
                                                             entry(2, "https://b.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 2uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    EXPECT(!history.has_only_top_level_used_steps());
    EXPECT(history.current_step_is_top_level_entry());
    EXPECT(!history.current_step_to_restore_after_loading_top_level_entry().has_value());
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 2, "https://b.example/"sv);

    auto* first_entry = history.entry_at(0);
    VERIFY(first_entry);
    expect_nested_history(*first_entry, 0, "frame-1"sv, 1);
    expect_nested_entry(first_entry->document_state.nested_histories[0], 0, 1, "https://frame.example/a"sv);

    auto target_step_index = history.target_step_index_for_delta(-1);
    VERIFY(target_step_index.has_value());
    expect_used_step(history, *target_step_index, 1);
    EXPECT_EQ(history.entry_for_step(1), nullptr);
    auto* target_top_level_entry = history.top_level_entry_for_step(1);
    VERIFY(target_top_level_entry);
    EXPECT_EQ(target_top_level_entry->url, parse_url("https://a.example/"sv));

    auto traversal_target = history.traversal_target_for_delta(-1);
    VERIFY(traversal_target.has_value());
    EXPECT_EQ(traversal_target->target_step_index, *target_step_index);
    EXPECT_EQ(traversal_target->target_step, 1);
    EXPECT_EQ(traversal_target->target_top_level_entry, target_top_level_entry);
    EXPECT(!traversal_target->target_step_is_top_level_entry);
    EXPECT(traversal_target->changes_top_level_entry);

    history.traverse_to(*target_step_index);
    expect_current_entry(history, 0, "https://a.example/"sv);
    EXPECT(!history.current_step_is_top_level_entry());
    expect_step_to_restore(history.current_step_to_restore_after_loading_top_level_entry(), 1);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
}

TEST_CASE(traversal_target_for_top_level_step)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(1);

    EXPECT(history.current_step_is_top_level_entry());
    EXPECT(!history.current_step_to_restore_after_loading_top_level_entry().has_value());

    auto traversal_target = history.traversal_target_for_delta(1);
    VERIFY(traversal_target.has_value());
    EXPECT_EQ(traversal_target->target_step_index, 2uz);
    EXPECT_EQ(traversal_target->target_step, 2);
    VERIFY(traversal_target->target_top_level_entry);
    EXPECT_EQ(traversal_target->target_top_level_entry->url, parse_url("https://c.example/"sv));
    EXPECT(traversal_target->target_step_is_top_level_entry);
    EXPECT(traversal_target->changes_top_level_entry);
}

TEST_CASE(traversal_target_for_nested_step_in_current_top_level_entry)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(0, "https://frame.example/a"sv),
                                                                                                                                  entry(1, "https://frame.example/b"sv),
                                                                                                                              }),
                                                                                              }),
                                                         },
        { 0, 1 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto traversal_target = history.traversal_target_for_delta(1);
    VERIFY(traversal_target.has_value());
    EXPECT_EQ(traversal_target->target_step_index, 1uz);
    EXPECT_EQ(traversal_target->target_step, 1);
    VERIFY(traversal_target->target_top_level_entry);
    EXPECT_EQ(traversal_target->target_top_level_entry->url, parse_url("https://a.example/"sv));
    EXPECT(!traversal_target->target_step_is_top_level_entry);
    EXPECT(!traversal_target->changes_top_level_entry);

    history.traverse_to(traversal_target->target_step_index);
    EXPECT(!history.current_step_is_top_level_entry());
    expect_step_to_restore(history.current_step_to_restore_after_loading_top_level_entry(), 1);
}

TEST_CASE(current_top_level_step_can_need_nested_history_restore)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, 7, "main"sv, {
                                                                                                               nested_history("frame-1"sv, {
                                                                                                                                               entry(0, "https://frame.example/a"sv),
                                                                                                                                               entry(1, "https://frame.example/b"sv),
                                                                                                                                           }),
                                                                                                           }),
                                                             entry(2, "https://a.example/same-document"sv, 7, "main"sv, {
                                                                                                                            nested_history("frame-1"sv, {
                                                                                                                                                            entry(0, "https://frame.example/a"sv),
                                                                                                                                                            entry(1, "https://frame.example/b"sv),
                                                                                                                                                        }),
                                                                                                                        }),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT(history.current_step_is_top_level_entry());
    expect_step_to_restore(history.current_step_to_restore_after_loading_top_level_entry(), 2);

    auto traversal_target = history.traversal_target_for_delta(-1);
    VERIFY(traversal_target.has_value());
    EXPECT_EQ(traversal_target->target_step_index, 1uz);
    EXPECT_EQ(traversal_target->target_step, 1);
    VERIFY(traversal_target->target_top_level_entry);
    EXPECT_EQ(traversal_target->target_top_level_entry->url, parse_url("https://a.example/"sv));
    EXPECT(!traversal_target->target_step_is_top_level_entry);
    EXPECT(traversal_target->changes_top_level_entry);

    traversal_target = history.traversal_target_for_step(1);
    VERIFY(traversal_target.has_value());
    EXPECT_EQ(traversal_target->target_step_index, 1uz);
    EXPECT_EQ(traversal_target->target_step, 1);
    VERIFY(traversal_target->target_top_level_entry);
    EXPECT_EQ(traversal_target->target_top_level_entry->url, parse_url("https://a.example/"sv));
    EXPECT(!traversal_target->target_step_is_top_level_entry);
    EXPECT(traversal_target->changes_top_level_entry);

    EXPECT(!history.traversal_target_for_step(42).has_value());
}

TEST_CASE(seeded_web_content_must_restore_nested_current_step_before_traversing)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(0, "https://frame.example/a"sv),
                                                                                                                                  entry(1, "https://frame.example/b"sv),
                                                                                                                              }),
                                                                                              }),
                                                             entry(2, "https://b.example/"sv),
                                                         },
        { 0, 1, 2 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT(!history.current_step_is_top_level_entry());
    expect_step_to_restore(history.current_step_to_restore_after_loading_top_level_entry(), 1);

    history.forget_web_content_state();
    auto current_top_level_entry_index = history.current_top_level_entry_index();
    VERIFY(current_top_level_entry_index.has_value());
    history.did_seed_web_content_from_ui_process(*current_top_level_entry_index);

    auto web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 0);

    auto traversal_target = history.traversal_target_for_delta(1);
    VERIFY(traversal_target.has_value());
    EXPECT(!history.web_content_can_traverse_to(*traversal_target));

    update_result = history.update_from_web_content({
                                                        entry(0, "https://a.example/"sv, {
                                                                                             nested_history("frame-1"sv, {
                                                                                                                             entry(0, "https://frame.example/a"sv),
                                                                                                                             entry(1, "https://frame.example/b"sv),
                                                                                                                         }),
                                                                                         }),
                                                        entry(2, "https://b.example/"sv),
                                                    },
        { 0, 1, 2 }, 1);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 1);
    EXPECT(history.web_content_can_traverse_to(*traversal_target));
}

TEST_CASE(seed_ack_snapshot_preserves_nested_ui_current_step)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(0, "https://frame.example/a"sv),
                                                                                                                                  entry(1, "https://frame.example/b"sv),
                                                                                                                              }),
                                                                                              }),
                                                             entry(2, "https://b.example/"sv),
                                                         },
        { 0, 1, 2 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT(!history.current_step_is_top_level_entry());
    expect_step_to_restore(history.current_step_to_restore_after_loading_top_level_entry(), 1);

    history.forget_web_content_state();
    auto accepted = history.did_seed_web_content_from_ui_process({
                                                                     entry(0, "https://a.example/"sv, {
                                                                                                          nested_history("frame-1"sv, {
                                                                                                                                          entry(0, "https://frame.example/a"sv),
                                                                                                                                          entry(1, "https://frame.example/b"sv),
                                                                                                                                      }),
                                                                                                      }),
                                                                     entry(2, "https://b.example/"sv),
                                                                 },
        { 0, 1, 2 }, 0);

    EXPECT(accepted);
    EXPECT(!history.current_step_is_top_level_entry());
    expect_step_to_restore(history.current_step_to_restore_after_loading_top_level_entry(), 1);

    auto web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 0);
}

TEST_CASE(seed_ack_snapshot_rejects_mismatched_reconstructed_history)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1 }, 1);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.forget_web_content_state();
    auto accepted = history.did_seed_web_content_from_ui_process({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://c.example/"sv),
                                                                 },
        { 0, 1 }, 1);

    EXPECT(!accepted);
    EXPECT(!history.web_content_current_step().has_value());
    EXPECT(!history.web_content_uses_ui_step_coordinates());
    expect_current_entry(history, 1, "https://b.example/"sv);
}

TEST_CASE(seeded_web_content_restore_updates_current_step)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(0, "https://frame.example/a"sv),
                                                                                                                                  entry(1, "https://frame.example/b"sv),
                                                                                                                              }),
                                                                                              }),
                                                             entry(2, "https://b.example/"sv),
                                                         },
        { 0, 1, 2 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.forget_web_content_state();
    auto current_top_level_entry_index = history.current_top_level_entry_index();
    VERIFY(current_top_level_entry_index.has_value());
    history.did_seed_web_content_from_ui_process(*current_top_level_entry_index);

    auto web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 0);

    EXPECT(history.did_restore_web_content_to_current_step(1));
    web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 1);

    EXPECT(!history.did_restore_web_content_to_current_step(3));
    web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 1);
}

TEST_CASE(restored_web_content_step_must_match_current_ui_step)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.traverse_to(1);
    history.forget_web_content_state();
    auto current_top_level_entry_index = history.current_top_level_entry_index();
    VERIFY(current_top_level_entry_index.has_value());
    history.did_seed_web_content_from_ui_process(*current_top_level_entry_index);

    EXPECT(!history.did_restore_web_content_to_current_step(2));
    auto web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 1);
}

TEST_CASE(applied_web_content_traversal_updates_current_step)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    expect_current_entry(history, 2, "https://c.example/"sv);

    EXPECT(history.did_apply_web_content_traversal_to_step(1));
    expect_current_entry(history, 1, "https://b.example/"sv);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());

    auto web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 1);

    auto target_a = history.traversal_target_for_delta(-1);
    VERIFY(target_a.has_value());
    EXPECT(history.web_content_can_traverse_to(*target_a));

    EXPECT(!history.did_apply_web_content_traversal_to_step(42));
    expect_current_entry(history, 1, "https://b.example/"sv);
    web_content_current_step = history.web_content_current_step();
    VERIFY(web_content_current_step.has_value());
    EXPECT_EQ(*web_content_current_step, 1);
}

TEST_CASE(applied_web_content_traversal_rejects_unknown_web_content_state)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.traverse_to(1);
    EXPECT(!history.did_apply_web_content_traversal_to_step(0));
    expect_current_entry(history, 1, "https://b.example/"sv);
    EXPECT(!history.web_content_current_step().has_value());
}

TEST_CASE(same_document_reseed_snapshot_accepts_reassigned_document_state_ids)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://b.example/"sv),
                                                                     entry(2, "https://c.example/"sv),
                                                                     entry(3, "https://nested.example/"sv, 6, "main"sv, {
                                                                                                                            nested_history("frame-1"sv, {
                                                                                                                                                            entry(3, "https://frame.example/a"sv, 7, ""sv),
                                                                                                                                                            entry(4, "https://frame.example/b"sv, 8, ""sv),
                                                                                                                                                        }),
                                                                                                                        }),
                                                                     entry(5, "https://nested.example/same-document"sv, 6, "main"sv, {
                                                                                                                                         nested_history("frame-1"sv, {
                                                                                                                                                                         entry(3, "https://frame.example/a"sv, 7, ""sv),
                                                                                                                                                                         entry(4, "https://frame.example/b"sv, 8, ""sv),
                                                                                                                                                                     }),
                                                                                                                                     }),
                                                                 },
        { 0, 1, 2, 3, 4, 5 }, 5);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.forget_web_content_state();
    auto current_top_level_entry_index = history.current_top_level_entry_index();
    VERIFY(current_top_level_entry_index.has_value());
    history.did_seed_web_content_from_ui_process(*current_top_level_entry_index);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                             entry(3, "https://nested.example/"sv, 8, "main"sv, {
                                                                                                                    nested_history("1"sv, {
                                                                                                                                              entry(3, "https://frame.example/a"sv, 6, ""sv),
                                                                                                                                              entry(4, "https://frame.example/b"sv, 7, ""sv),
                                                                                                                                          }),
                                                                                                                }),
                                                             entry(5, "https://nested.example/same-document"sv, 8, "main"sv, {
                                                                                                                                 nested_history("1"sv, {
                                                                                                                                                           entry(3, "https://frame.example/a"sv, 6, ""sv),
                                                                                                                                                           entry(4, "https://frame.example/b"sv, 7, ""sv),
                                                                                                                                                       }),
                                                                                                                             }),
                                                         },
        { 0, 1, 2, 3, 4, 5 }, 5);

    EXPECT_NE(update_result, WebView::TraversableSessionHistory::UpdateResult::InvalidSnapshot);
    EXPECT_EQ(history.current_used_step_index(), 5uz);
    expect_current_entry(history, 5, "https://nested.example/same-document"sv);
    EXPECT(history.web_content_can_traverse_to(*history.traversal_target_for_delta(-1)));
}

TEST_CASE(seed_ack_accepts_reconstructed_document_state_for_unknown_current_entry)
{
    WebView::TraversableSessionHistory history;
    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://a.example/replaced"sv, 1, "main"sv),
                                                             entry(2, "https://a.example/pushed"sv, 1, "main"sv),
                                                         },
        { 0, 1, 2 }, 0);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.forget_web_content_state();

    EXPECT(history.did_seed_web_content_from_ui_process({
                                                            entry(0, "https://a.example/"sv, 1, "main"sv),
                                                            entry(1, "https://a.example/replaced"sv, 2, "main"sv),
                                                            entry(2, "https://a.example/pushed"sv, 2, "main"sv),
                                                        },
        { 0, 1, 2 }, 0));
    EXPECT(history.web_content_history_matches_mirror());
    EXPECT_EQ(history.web_content_known_entries().size(), 3uz);
    EXPECT_EQ(history.web_content_current_step().value(), 0);
}

TEST_CASE(seed_ack_rejects_reconstructed_history_with_mismatched_state)
{
    WebView::TraversableSessionHistory history;
    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, 1, 2, "key-a"sv, "id-a"sv, Web::HTML::ScrollRestorationMode::Manual),
                                                             entry(1, "https://a.example/replaced"sv, 3, 4, "key-b"sv, "id-b"sv, Web::HTML::ScrollRestorationMode::Auto),
                                                         },
        { 0, 1 }, 0);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.forget_web_content_state();

    EXPECT(!history.did_seed_web_content_from_ui_process({
                                                             entry(0, "https://a.example/"sv, 9, 2, "key-a"sv, "id-a"sv, Web::HTML::ScrollRestorationMode::Manual),
                                                             entry(1, "https://a.example/replaced"sv, 3, 4, "key-b"sv, "id-b"sv, Web::HTML::ScrollRestorationMode::Auto),
                                                         },
        { 0, 1 }, 0));
    EXPECT(!history.web_content_history_matches_mirror());
    EXPECT(history.web_content_known_entries().is_empty());
}

TEST_CASE(traversal_target_for_delta_outside_used_steps)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));

    EXPECT(!history.traversal_target_for_delta(-1).has_value());
    EXPECT(!history.traversal_target_for_delta(0).has_value());
    EXPECT(!history.traversal_target_for_delta(1).has_value());
}

TEST_CASE(complete_web_content_update_stores_nested_history_descriptors)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(0, "https://frame.example/a"sv),
                                                                                                                                  entry(1, "https://frame.example/b"sv),
                                                                                                                              }),
                                                                                              }),
                                                         },
        { 0, 1 }, 1);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);
    EXPECT_EQ(history.size(), 1uz);
    EXPECT_EQ(history.used_step_count(), 2uz);
    EXPECT(!history.has_only_top_level_used_steps());
    EXPECT(!history.current_step_is_top_level_entry());
    expect_step_to_restore(history.current_step_to_restore_after_loading_top_level_entry(), 1);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT_EQ(current_entry->step, 0);
    EXPECT_EQ(current_entry->url, parse_url("https://a.example/"sv));
    expect_nested_history(*current_entry, 0, "frame-1"sv, 2);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 0, 0, "https://frame.example/a"sv);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 1, 1, "https://frame.example/b"sv);
}

TEST_CASE(navigate_clears_forward_nested_history_entries)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(0, "https://frame.example/a"sv),
                                                                                                                                  entry(1, "https://frame.example/b"sv),
                                                                                                                                  entry(2, "https://frame.example/c"sv),
                                                                                                                              }),
                                                                                              }),
                                                             entry(3, "https://c.example/"sv),
                                                         },
        { 0, 1, 2, 3 }, 1);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.navigate(parse_url("https://b.example/"sv));

    EXPECT_EQ(history.size(), 2uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_current_entry(history, 2, "https://b.example/"sv);
    expect_used_step(history, 0, 0);
    expect_used_step(history, 1, 1);
    expect_used_step(history, 2, 2);

    auto* first_entry = history.entry_at(0);
    VERIFY(first_entry);
    expect_nested_history(*first_entry, 0, "frame-1"sv, 2);
    expect_nested_entry(first_entry->document_state.nested_histories[0], 0, 0, "https://frame.example/a"sv);
    expect_nested_entry(first_entry->document_state.nested_histories[0], 1, 1, "https://frame.example/b"sv);
}

TEST_CASE(partial_snapshot_missing_nested_history_descriptor_is_invalid)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::InvalidSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://c.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
    expect_used_step(history, 2, 2);
}

TEST_CASE(invalid_snapshot_forgets_known_web_content_state)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://b.example/"sv),
                                                                     entry(2, "https://c.example/"sv),
                                                                 },
        { 0, 1, 2 }, 2);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::InvalidSnapshot);

    EXPECT_EQ(history.size(), 3uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    expect_current_entry(history, 2, "https://c.example/"sv);

    auto web_content_known_used_steps = history.web_content_known_used_steps();
    EXPECT_EQ(web_content_known_used_steps.size(), 0uz);
    EXPECT(!history.web_content_current_step().has_value());

    auto target_b = history.traversal_target_for_delta(-1);
    VERIFY(target_b.has_value());
    EXPECT(!history.web_content_can_traverse_to(*target_b));
}

TEST_CASE(partial_snapshot_translates_nested_history_descriptors)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(4, "https://a.example/"sv),
                                                                     entry(5, "https://b.example/"sv),
                                                                     entry(6, "https://c.example/"sv),
                                                                 },
        { 4, 5, 6 }, 2);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, URL::about_blank()),
                                                             entry(1, "https://b.example/"sv, {
                                                                                                  nested_history("frame-1"sv, {
                                                                                                                                  entry(1, "https://frame.example/a"sv),
                                                                                                                                  entry(2, "https://frame.example/b"sv),
                                                                                                                              }),
                                                                                              }),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 2uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 4, "https://a.example/"sv);
    expect_current_entry(history, 5, "https://b.example/"sv);
    expect_used_step(history, 2, 6);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_nested_history(*current_entry, 0, "frame-1"sv, 2);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 0, 5, "https://frame.example/a"sv);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 1, 6, "https://frame.example/b"sv);
    EXPECT_EQ(history.entry_for_step(6), nullptr);
}

TEST_CASE(partial_snapshot_translates_seeded_nested_history_initial_step)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://b.example/"sv),
                                                                     entry(2, "https://c.example/"sv),
                                                                     entry(3, "https://nested.example/"sv, {
                                                                                                               nested_history("frame-1"sv, {
                                                                                                                                               entry(3, "https://frame.example/a"sv),
                                                                                                                                           }),
                                                                                                           }),
                                                                 },
        { 0, 1, 2, 3 }, 3);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                             entry(2, "https://c.example/"sv),
                                                             entry(3, "https://nested.example/"sv, {
                                                                                                       nested_history("frame-1"sv, {
                                                                                                                                       entry(0, "https://frame.example/a"sv),
                                                                                                                                       entry(4, "https://frame.example/b"sv),
                                                                                                                                   }),
                                                                                                   }),
                                                         },
        { 0, 1, 2, 3, 4 }, 4);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);
    EXPECT_EQ(history.size(), 4uz);
    EXPECT_EQ(history.used_step_count(), 5uz);
    EXPECT(!history.has_only_top_level_used_steps());
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://c.example/"sv);
    expect_current_entry(history, 3, "https://nested.example/"sv);
    expect_used_step(history, 4, 4);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_nested_history(*current_entry, 0, "frame-1"sv, 2);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 0, 3, "https://frame.example/a"sv);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 1, 4, "https://frame.example/b"sv);
    EXPECT_EQ(history.entry_for_step(4), nullptr);
}

TEST_CASE(partial_snapshot_missing_preserved_nested_history_descriptor_is_invalid)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://b.example/"sv),
                                                                     entry(2, "https://c.example/"sv),
                                                                     entry(3, "https://nested.example/"sv, {
                                                                                                               nested_history("frame-1"sv, {
                                                                                                                                               entry(3, "https://frame.example/a"sv),
                                                                                                                                               entry(4, "https://frame.example/b"sv),
                                                                                                                                           }),
                                                                                                           }),
                                                                 },
        { 0, 1, 2, 3, 4 }, 4);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://nested.example/"sv, {
                                                                                                       nested_history("frame-1"sv, {
                                                                                                                                       entry(0, "https://frame.example/a"sv),
                                                                                                                                   }),
                                                                                                   }),
                                                         },
        { 0 }, 0);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::InvalidSnapshot);
    EXPECT_EQ(history.size(), 4uz);
    EXPECT_EQ(history.used_step_count(), 5uz);
    EXPECT(history.can_go_back());
    EXPECT(!history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://c.example/"sv);
    expect_current_entry(history, 3, "https://nested.example/"sv);
    expect_used_step(history, 4, 4);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_nested_history(*current_entry, 0, "frame-1"sv, 2);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 0, 3, "https://frame.example/a"sv);
    expect_nested_entry(current_entry->document_state.nested_histories[0], 1, 4, "https://frame.example/b"sv);
    EXPECT_EQ(history.entry_for_step(4), nullptr);
}

TEST_CASE(matching_snapshot_missing_nested_history_descriptor_is_invalid)
{
    WebView::TraversableSessionHistory history;
    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv));
    history.navigate(parse_url("https://c.example/"sv));
    history.traverse_to(1);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1, 2 }, 2);

    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::InvalidSnapshot);
    EXPECT_EQ(history.size(), 3uz);
    EXPECT_EQ(history.used_step_count(), 3uz);
    EXPECT(history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_entry(history, 0, 0, "https://a.example/"sv);
    expect_entry(history, 1, 1, "https://b.example/"sv);
    expect_entry(history, 2, 2, "https://c.example/"sv);
    expect_current_entry(history, 1, "https://b.example/"sv);
    expect_used_step(history, 2, 2);
}

TEST_CASE(history_log_entries_marks_current_entries_steps_and_reload_pending)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry_with_post_resource(1, "https://b.example/"sv),
                                                             entry_with_reload_pending(2, "https://c.example/"sv, 7, "main"sv, {
                                                                                                                                   nested_history("frame"sv, {
                                                                                                                                                                 entry(3, "https://frame.example/"sv),
                                                                                                                                                             }),
                                                                                                                               }),
                                                         },
        { 0, 1, 2, 3 }, 1);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    EXPECT_EQ(history_log_entries(history), ByteString { "entries=[0:0:https://a.example/, *1:1:https://b.example/ "
                                                         "document_state={id=0, resource=post}, 2:2:https://c.example/ "
                                                         "document_state={id=1, reload_pending=true, target_name=main} "
                                                         "nested={1:2=[3:https://frame.example/]}] "
                                                         "used_steps=[0:0, *1:1, 2:2, 3:3]"sv });

    auto current_entry = history.current_entry();
    VERIFY(current_entry);
    auto serialized_entry = WebView::history_json_entry(*current_entry, true);
    EXPECT_EQ(serialized_entry.get_string("resource"sv), "post"sv);

    auto* reload_pending_entry = history.entry_at(2);
    VERIFY(reload_pending_entry);
    auto serialized_reload_pending_entry = WebView::history_json_entry(*reload_pending_entry, false);
    EXPECT_EQ(serialized_reload_pending_entry.get_bool("reloadPending"sv), true);
    EXPECT_EQ(serialized_reload_pending_entry.get_string("resource"sv), "none"sv);
}

TEST_CASE(history_log_entries_marks_nested_histories)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv, 7, "main"sv, {
                                                                                                               nested_history("frame"sv, {
                                                                                                                                             entry(2, "https://frame.example/"sv),
                                                                                                                                         }),
                                                                                                           }),
                                                         },
        { 0, 1, 2 }, 2);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    EXPECT_EQ(history_log_entries(history), ByteString { "entries=[0:0:https://a.example/, *1:1:https://b.example/ "
                                                         "document_state={id=1, target_name=main} "
                                                         "nested={1:2=[2:https://frame.example/]}] "
                                                         "used_steps=[0:0, 1:1, *2:2]"sv });
}

TEST_CASE(web_content_known_entries_describe_current_web_content_view)
{
    WebView::TraversableSessionHistory history;

    auto initial_update_result = history.update_from_web_content({
                                                                     entry(0, "https://a.example/"sv),
                                                                     entry(1, "https://b.example/"sv),
                                                                 },
        { 0, 1 }, 1);
    EXPECT_EQ(initial_update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.traverse_to(0);

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                         },
        { 0 }, 0);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::MergedPartialSnapshot);

    auto web_content_known_entries = history.web_content_known_entries();
    EXPECT_EQ(history_log_entries(history), ByteString { "entries=[*0:0:https://a.example/, 1:1:https://b.example/] used_steps=[*0:0, 1:1]"sv });
    EXPECT_EQ(WebView::history_log_entries(web_content_known_entries, 0), ByteString { "[*0:0:https://a.example/]"sv });
}

TEST_CASE(clear_current_entry_reload_pending)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry_with_reload_pending(1, "https://b.example/"sv, 7, "main"sv, {}),
                                                         },
        { 0, 1 }, 1);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT(current_entry->document_state.reload_pending);

    history.clear_current_entry_reload_pending();

    current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT(!current_entry->document_state.reload_pending);
}

TEST_CASE(mark_current_entry_reload_pending)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv),
                                                             entry(1, "https://b.example/"sv),
                                                         },
        { 0, 1 }, 1);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    auto current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT(!current_entry->document_state.reload_pending);

    history.mark_current_entry_reload_pending();

    current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT(current_entry->document_state.reload_pending);
}

TEST_CASE(navigate_preserves_document_resource)
{
    WebView::TraversableSessionHistory history;

    history.navigate(parse_url("https://a.example/"sv));
    history.navigate(parse_url("https://b.example/"sv), Web::HTML::POSTResource {
                                                            .request_body = MUST(ByteBuffer::copy("name=ladybird"sv.bytes())),
                                                            .request_content_type = Web::HTML::POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded,
                                                        });

    auto current_entry = history.current_entry();
    VERIFY(current_entry);
    expect_entry_resource(*current_entry, "post"sv);
}

TEST_CASE(replace_current_entry_preserves_document_resource)
{
    WebView::TraversableSessionHistory history;

    history.navigate(parse_url("https://a.example/"sv));
    history.replace_current_entry(parse_url("https://b.example/"sv), Web::HTML::POSTResource {
                                                                         .request_body = MUST(ByteBuffer::copy("name=ladybird"sv.bytes())),
                                                                         .request_content_type = Web::HTML::POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded,
                                                                     });

    auto current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT_EQ(current_entry->url, parse_url("https://b.example/"sv));
    expect_entry_resource(*current_entry, "post"sv);
}

TEST_CASE(replace_current_entry_discards_replaced_document_state)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, 7, "main"sv, {
                                                                                                               nested_history("frame-1"sv, {
                                                                                                                                               entry(0, "https://frame.example/a"sv),
                                                                                                                                               entry(1, "https://frame.example/b"sv),
                                                                                                                                           }),
                                                                                                           }),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 0);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.replace_current_entry(parse_url("https://b.example/"sv), Web::HTML::POSTResource {
                                                                         .request_body = MUST(ByteBuffer::copy("name=ladybird"sv.bytes())),
                                                                         .request_content_type = Web::HTML::POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded,
                                                                     });

    EXPECT_EQ(history.size(), 2uz);
    EXPECT_EQ(history.used_step_count(), 2uz);
    EXPECT(!history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_current_entry(history, 0, "https://b.example/"sv);
    expect_entry(history, 1, 2, "https://c.example/"sv);
    expect_used_step(history, 0, 0);
    expect_used_step(history, 1, 2);

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT_EQ(current_entry->document_state.id, 0u);
    EXPECT(current_entry->document_state.navigable_target_name.is_empty());
    EXPECT(current_entry->document_state.nested_histories.is_empty());
    expect_entry_resource(*current_entry, "post"sv);
}

TEST_CASE(replace_current_entry_at_nested_step_keeps_current_step_valid)
{
    WebView::TraversableSessionHistory history;

    auto update_result = history.update_from_web_content({
                                                             entry(0, "https://a.example/"sv, 7, "main"sv, {
                                                                                                               nested_history("frame-1"sv, {
                                                                                                                                               entry(0, "https://frame.example/a"sv),
                                                                                                                                               entry(1, "https://frame.example/b"sv),
                                                                                                                                           }),
                                                                                                           }),
                                                             entry(2, "https://c.example/"sv),
                                                         },
        { 0, 1, 2 }, 1);
    EXPECT_EQ(update_result, WebView::TraversableSessionHistory::UpdateResult::CompleteSnapshot);

    history.replace_current_entry(parse_url("https://b.example/"sv), Empty {});

    EXPECT_EQ(history.size(), 2uz);
    EXPECT_EQ(history.used_step_count(), 2uz);
    EXPECT(!history.can_go_back());
    EXPECT(history.can_go_forward());
    expect_current_entry(history, 1, "https://b.example/"sv);
    expect_entry(history, 1, 2, "https://c.example/"sv);
    expect_used_step(history, 0, 1);
    expect_used_step(history, 1, 2);
    EXPECT(history.current_step_is_top_level_entry());
    EXPECT(!history.current_step_to_restore_after_loading_top_level_entry().has_value());

    auto* current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT(current_entry->document_state.nested_histories.is_empty());
}

TEST_CASE(replace_current_entry_url_keeps_document_resource)
{
    WebView::TraversableSessionHistory history;

    history.navigate(parse_url("https://a.example/"sv), Web::HTML::POSTResource {
                                                            .request_body = MUST(ByteBuffer::copy("name=ladybird"sv.bytes())),
                                                            .request_content_type = Web::HTML::POSTResource::RequestContentType::ApplicationXWWWFormUrlencoded,
                                                        });
    history.replace_current_entry_url(parse_url("https://b.example/"sv));

    auto current_entry = history.current_entry();
    VERIFY(current_entry);
    EXPECT_EQ(current_entry->url, parse_url("https://b.example/"sv));
    expect_entry_resource(*current_entry, "post"sv);
}
