"""Tests for the core delta_reduce algorithm (single-threaded, callable test_fn)."""
from delta_reduce.core import reduce_file, run_to_fixpoint


def test_removes_deletable_lines_basic():
    """Lines b/c/d are dispensable; 'a' is required by the test."""
    lines = ["a\n", "b\n", "c\n", "d\n"]

    def test_fn(candidate: list[str], _) -> bool:
        return "a\n" in candidate

    result, progress = reduce_file(lines, test_fn)
    assert progress is True
    assert result == ["a\n"]


def test_chunk_size_shrinks_to_find_removal():
    """Large chunk fails (would delete 'a'); smaller chunk succeeds."""
    # 2 lines: whole-file chunk fails, but chunk_size=1 can remove "b\n"
    lines = ["a\n", "b\n"]

    def test_fn(candidate: list[str], _) -> bool:
        return "a\n" in candidate

    result, progress = reduce_file(lines, test_fn)
    assert progress is True
    assert result == ["a\n"]


def test_no_progress_when_nothing_removable():
    """If every single-line removal fails, made_progress is False."""
    lines = ["a\n", "b\n"]

    # test_fn passes only when both lines are present
    def test_fn(candidate: list[str], _) -> bool:
        return "a\n" in candidate and "b\n" in candidate

    result, progress = reduce_file(lines, test_fn)
    assert progress is False
    assert result == ["a\n", "b\n"]


def test_chunk_size_grows_after_4_successes():
    """After 4 removals the chunk size should increase.

    We verify this indirectly: a 10-line file where lines 0-8 are all
    individually removable and only line 9 is essential. With growth the
    algorithm should converge in fewer test_fn calls than without.
    We just assert the final result here; the growth logic is covered by
    checking we actually reach it (would loop forever if broken).
    """
    lines = [f"line{i}\n" for i in range(10)]
    essential = "line9\n"

    def test_fn(candidate: list[str], _) -> bool:
        return essential in candidate

    result, progress = reduce_file(lines, test_fn)
    assert progress is True
    assert result == [essential]


def test_fixed_point_iteration():
    """run_to_fixpoint keeps calling reduce_file until no further progress."""
    # 3 lines: "a" and "b" are individually removable, only "c" is essential.
    lines = ["a\n", "b\n", "c\n"]

    def test_fn(candidate: list[str], _) -> bool:
        return "c\n" in candidate

    result = run_to_fixpoint(lines, test_fn)
    assert result == ["c\n"]


def test_offset_catches_missed_chunks():
    """Offset=1 lets us catch reductions at positions offset=0 would miss.

    With 3 lines and offset=0 the bottom chunk is lines[1:3]; with offset=1
    it's lines[0:2].  We just verify the public run_to_fixpoint result is
    fully reduced regardless.
    """
    lines = ["a\n", "b\n", "c\n"]

    def test_fn(candidate: list[str], _) -> bool:
        return "c\n" in candidate

    result = run_to_fixpoint(lines, test_fn)
    assert result == ["c\n"]
