"""ChatGPT virtualizes the conversation DOM: sending a new turn unmounts older
turns as the view scrolls, so any rendered *count* (USER_COUNT, PROGRESS.turns) is
non-monotonic on a long/resumed conversation. Measured live: USER count 6 before
send, 4 two seconds after (it went DOWN); assistant-turn count pinned at 4 while the
pre-send baseline was 7. `submit()`/`send()` comparing a live count against a
pre-send baseline can never see it cross, and hang for the whole timeout.

These tests simulate that shrink and prove completion is read off the stop-button
lifecycle and the bottom turn's content instead — both immune, since the bottom
turn is never the one that unmounts.
"""

from __future__ import annotations

import pytest

import chat
from fake_browser import seat_for


@pytest.fixture
def fake(monkeypatch):
    monkeypatch.setattr(chat.time, "sleep", lambda *_: None)
    return seat_for([])[1]


def test_submit_accepts_when_rendered_user_count_never_rises_above_baseline(fake):
    """The core regression: USER_COUNT stays BELOW the pre-send baseline for the
    whole turn. Under the old `USER_COUNT > before` gate this never fires and
    submit() clicks send for the entire timeout before raising."""
    fake.user_turns = 6  # a resumed conversation already carries prior turns
    baseline = int(fake.sync_script(chat.USER_COUNT, [chat.USER]))
    assert baseline == 6
    fake.user_count_override = 4  # DOM virtualized: rendered count now sits below it
    chat.submit(fake)
    assert fake.clicks == 1
    assert fake.user_turns == 7, "the click was actually accepted despite the low count"


def test_send_completes_via_the_stop_lifecycle_despite_a_shrunk_turn_count(fake):
    """Same shrinking pattern as above, but for PROGRESS.turns (the signal
    `send()` used to gate on). Completion is read off `stop` transitioning
    True -> False instead, which stays reliable under virtualization."""
    fake.turns = 7
    fake.replies.append({"blocks": [], "text": "the answer despite virtualization"})
    fake.progress_turns_override = 4  # rendered assistant-turn count stays below 7
    fake.stop_sequence = [True, True, False, False]
    chat.send(fake, timeout=900)
    assert fake.user_turns == 1
    assert fake.progress_turns_override == 4, "turns never recovered, and it didn't need to"


def test_send_completes_on_a_fast_reply_that_never_shows_the_stop_button(fake):
    """A reply that finishes before any poll catches `stop` True must still
    complete, via content stable across the debounce window plus the composer
    back to idle — not via the stop-lifecycle at all."""
    fake.replies.append({"blocks": [], "text": "quick answer"})
    # stop_sequence left empty: PROGRESS reports stop=False on every poll.
    chat.send(fake, timeout=900)
    assert fake.user_turns == 1


def test_fresh_conversation_at_baseline_zero_still_completes(fake):
    """A fresh (never-virtualized) conversation is the common case and must keep
    working unchanged — the new signal is a superset of the old fast path."""
    fake.replies.append({"blocks": [], "text": "hello"})
    baseline = int(fake.sync_script(chat.TURN_COUNT, [chat.TURN, chat.USER]))
    assert baseline == 0
    chat.send(fake, timeout=900)
    assert fake.user_turns == 1
