"""Tests for _build_experiment_context in abreka.cli.agent.propose."""

from __future__ import annotations

from pathlib import Path

import pytest

from abreka.cli.agent.propose import (
    _build_backlog_context,
    _build_branch_overview,
    _build_experiment_context,
    _build_focus_constraint,
    _build_focused_branch_summary,
    get_subtree_ids,
)


def _make_experiment(
    eid: str,
    status: str = "completed",
    hypothesis: str = "",
    method: str = "",
    parents: list[str] | None = None,
    tags: list[str] | None = None,
    metrics: dict | None = None,
    created_at: str = "2026-04-08T10:00:00+00:00",
) -> dict:
    """Build an index entry dict matching Experiment.summary() format."""
    entry: dict = {
        "id": eid,
        "status": status,
        "hypothesis": hypothesis,
        "method": method,
        "parents": parents or [],
        "tags": tags or [],
        "created_at": created_at,
    }
    if metrics:
        entry["metrics"] = metrics
    return entry


def _setup_findings(abreka_dir: Path, eid: str, content: str) -> None:
    """Write a findings.md file for a given experiment."""
    exp_dir = abreka_dir / "experiments" / eid
    exp_dir.mkdir(parents=True, exist_ok=True)
    (exp_dir / "findings.md").write_text(content)


class TestBuildExperimentContext:
    def test_empty_index(self, tmp_path):
        result = _build_experiment_context({}, tmp_path, "val:accuracy", "maximize")
        assert "No experiments yet" in result

    def test_current_best_in_preamble(self, tmp_path):
        index = {
            "0001": _make_experiment("0001", metrics={"val": {"accuracy": 0.99}}),
            "0002": _make_experiment("0002", metrics={"val": {"accuracy": 0.995}}),
            "0003": _make_experiment("0003", metrics={"val": {"accuracy": 0.992}}),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        assert "0002" in result.split("\n")[0] or "0002" in result.split("\n")[1]
        assert "0.995" in result[:200]

    def test_method_included(self, tmp_path):
        index = {
            "0001": _make_experiment(
                "0001",
                method="Change lr to 1e-4",
                metrics={"val": {"accuracy": 0.99}},
            ),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        assert "Change lr to 1e-4" in result

    def test_tags_included(self, tmp_path):
        index = {
            "0001": _make_experiment(
                "0001",
                tags=["cnn", "baseline"],
                metrics={"val": {"accuracy": 0.99}},
            ),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        assert "cnn" in result
        assert "baseline" in result

    def test_frontier_sorted_by_metric_descending(self, tmp_path):
        """Frontier experiments (no children) should appear sorted by metric, best first."""
        index = {
            "0001": _make_experiment(
                "0001", parents=[], metrics={"val": {"accuracy": 0.99}},
            ),
            "0002": _make_experiment(
                "0002", parents=["0001"], metrics={"val": {"accuracy": 0.993}},
            ),
            "0003": _make_experiment(
                "0003", parents=["0001"], metrics={"val": {"accuracy": 0.997}},
            ),
            "0004": _make_experiment(
                "0004", parents=["0001"], metrics={"val": {"accuracy": 0.991}},
            ),
        }
        # 0001 has children so it's NOT frontier; 0002, 0003, 0004 are frontier
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        # 0003 (best frontier) should appear before 0002 and 0004 in the frontier section
        pos_0003 = result.index("0003")
        pos_0002 = result.index("0002")
        pos_0004 = result.index("0004")
        assert pos_0003 < pos_0002
        assert pos_0003 < pos_0004

    def test_frontier_limited_to_5(self, tmp_path):
        """At most 5 frontier experiments should get full detail."""
        index = {}
        for i in range(1, 9):
            eid = f"000{i}"
            index[eid] = _make_experiment(
                eid,
                parents=["0000"] if i > 0 else [],
                metrics={"val": {"accuracy": 0.99 + i * 0.001}},
            )
        # Add the root so it's not frontier
        index["0000"] = _make_experiment(
            "0000", parents=[], metrics={"val": {"accuracy": 0.98}},
        )
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        # The "Frontier" section should contain detail for at most 5 experiments
        # Experiments 0004-0008 are the top 5 by metric
        # Lower ones (0001-0003) should be in compact form
        assert "Frontier" in result or "frontier" in result.lower()

    def test_findings_truncated_to_500_chars(self, tmp_path):
        abreka_dir = tmp_path / "abreka"
        (abreka_dir / "experiments").mkdir(parents=True)
        long_findings = "A" * 1000
        _setup_findings(abreka_dir, "0001", long_findings)

        index = {
            "0001": _make_experiment(
                "0001", metrics={"val": {"accuracy": 0.99}},
            ),
        }
        result = _build_experiment_context(index, abreka_dir, "val:accuracy", "maximize")
        # The full 1000-char findings should not appear; it should be truncated
        assert "A" * 1000 not in result
        # But some portion should be present
        assert "A" * 100 in result

    def test_most_recent_experiment_shown(self, tmp_path):
        """The most recently created experiment should appear prominently."""
        index = {
            "0001": _make_experiment(
                "0001",
                hypothesis="Old experiment",
                created_at="2026-04-08T10:00:00+00:00",
                metrics={"val": {"accuracy": 0.99}},
            ),
            "0002": _make_experiment(
                "0002",
                hypothesis="Latest experiment",
                created_at="2026-04-09T10:00:00+00:00",
                metrics={"val": {"accuracy": 0.98}},
            ),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        assert "Latest experiment" in result

    def test_minimize_direction(self, tmp_path):
        """When direction is 'minimize', lower metric should be the best."""
        index = {
            "0001": _make_experiment(
                "0001", metrics={"val": {"loss": 0.05}},
            ),
            "0002": _make_experiment(
                "0002", metrics={"val": {"loss": 0.01}},
            ),
        }
        result = _build_experiment_context(index, tmp_path, "val:loss", "minimize")
        # 0002 (lower loss) should be identified as best
        assert "0002" in result[:200]

    def test_remaining_experiments_compact(self, tmp_path):
        """Non-frontier experiments should appear but in compact form."""
        index = {
            "0001": _make_experiment(
                "0001",
                hypothesis="Root experiment",
                parents=[],
                metrics={"val": {"accuracy": 0.99}},
            ),
            "0002": _make_experiment(
                "0002",
                hypothesis="Child experiment",
                parents=["0001"],
                metrics={"val": {"accuracy": 0.995}},
            ),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        # 0001 has a child (0002), so it's not frontier
        # 0001 should still appear somewhere in the context
        assert "0001" in result

    def test_non_completed_experiments_shown(self, tmp_path):
        """Non-completed experiments should appear in the output."""
        index = {
            "0001": _make_experiment(
                "0001",
                status="completed",
                hypothesis="Done experiment",
                metrics={"val": {"accuracy": 0.99}},
            ),
            "0002": _make_experiment(
                "0002",
                status="proposed",
                hypothesis="Pending experiment",
                created_at="2026-04-09T12:00:00+00:00",
            ),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        assert "proposed" in result
        assert "Pending experiment" in result

    def test_non_completed_most_recent_not_duplicated(self, tmp_path):
        """If the most recent experiment is non-completed, it should not appear twice."""
        index = {
            "0001": _make_experiment(
                "0001",
                status="completed",
                hypothesis="Old done",
                created_at="2026-04-08T10:00:00+00:00",
                metrics={"val": {"accuracy": 0.99}},
            ),
            "0002": _make_experiment(
                "0002",
                status="proposed",
                hypothesis="Brand new proposal",
                created_at="2026-04-09T12:00:00+00:00",
            ),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        # "Brand new proposal" should appear exactly once (in Most Recent section)
        assert result.count("Brand new proposal") == 1

    def test_no_best_when_no_metrics(self, tmp_path):
        """When no experiment has the primary metric, 'Current best' should not show None."""
        index = {
            "0001": _make_experiment("0001", hypothesis="No metrics yet"),
        }
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        assert "None" not in result

    def test_frontier_top5_get_detail_rest_compact(self, tmp_path):
        """Top 5 frontier experiments get Method: lines; the 6th does not."""
        index = {}
        # Root with many children
        index["0000"] = _make_experiment(
            "0000", parents=[], metrics={"val": {"accuracy": 0.90}},
        )
        for i in range(1, 8):
            eid = f"000{i}"
            index[eid] = _make_experiment(
                eid,
                parents=["0000"],
                method=f"Method for {eid}",
                metrics={"val": {"accuracy": 0.90 + i * 0.01}},
            )
        result = _build_experiment_context(index, tmp_path, "val:accuracy", "maximize")
        # Top 5 by metric are 0007, 0006, 0005, 0004, 0003
        # 0002 and 0001 should be in compact section (no "Method:" prefix)
        frontier_section = result.split("### Frontier")[1].split("### All Other")[0]
        assert "Method for 0007" in frontier_section
        assert "Method for 0003" in frontier_section
        # The compact section should have 0001 and 0002 but without "Method:" lines
        compact_section = result.split("### All Other")[1]
        assert "0001" in compact_section
        assert "Method for 0001" not in compact_section


class TestGetSubtreeIds:
    def test_single_node_no_children(self):
        index = {
            "0001": _make_experiment("0001"),
        }
        result = get_subtree_ids(index, "0001")
        assert result == {"0001"}

    def test_chain(self):
        index = {
            "0001": _make_experiment("0001", parents=[]),
            "0002": _make_experiment("0002", parents=["0001"]),
            "0003": _make_experiment("0003", parents=["0002"]),
        }
        result = get_subtree_ids(index, "0001")
        assert result == {"0001", "0002", "0003"}

    def test_branching(self):
        index = {
            "0001": _make_experiment("0001", parents=[]),
            "0002": _make_experiment("0002", parents=["0001"]),
            "0003": _make_experiment("0003", parents=["0001"]),
            "0004": _make_experiment("0004", parents=["0002"]),
        }
        result = get_subtree_ids(index, "0001")
        assert result == {"0001", "0002", "0003", "0004"}

    def test_subtree_from_middle(self):
        """Starting from a non-root should only include descendants."""
        index = {
            "0001": _make_experiment("0001", parents=[]),
            "0002": _make_experiment("0002", parents=["0001"]),
            "0003": _make_experiment("0003", parents=["0001"]),
            "0004": _make_experiment("0004", parents=["0002"]),
        }
        result = get_subtree_ids(index, "0002")
        assert result == {"0002", "0004"}

    def test_nonexistent_root_raises(self):
        index = {"0001": _make_experiment("0001")}
        with pytest.raises(ValueError, match="not found"):
            get_subtree_ids(index, "9999")


class TestBuildFocusConstraint:
    def test_lists_completed_subtree_ids(self):
        index = {
            "0001": _make_experiment("0001", parents=[]),
            "0002": _make_experiment("0002", parents=["0001"]),
            "0003": _make_experiment("0003", parents=["0001"]),
        }
        result = _build_focus_constraint(index, "0001")
        assert "0001" in result
        assert "0002" in result
        assert "0003" in result
        assert "--parent" in result

    def test_excludes_outside_experiments(self):
        index = {
            "0001": _make_experiment("0001", parents=[]),
            "0002": _make_experiment("0002", parents=["0001"]),
            "0003": _make_experiment("0003", parents=[]),  # separate tree
        }
        result = _build_focus_constraint(index, "0001")
        assert "0001" in result
        assert "0002" in result
        # 0003 should NOT be in the valid parent list
        # It might appear in the text as the constraint mentions the root,
        # but not as a valid parent ID
        assert "0003" not in result

    def test_excludes_failed_from_valid_parents(self):
        index = {
            "0001": _make_experiment("0001", parents=[]),
            "0002": _make_experiment("0002", parents=["0001"], status="failed"),
            "0003": _make_experiment("0003", parents=["0001"]),
        }
        result = _build_focus_constraint(index, "0001")
        assert "0001" in result
        assert "0003" in result
        # 0002 is failed — should not be a valid parent
        # Check that 0002 does not appear in the valid parent list line
        for line in result.splitlines():
            if "MUST" in line and "--parent" in line:
                assert "0002" not in line

    def test_leaf_root_is_only_valid_parent(self):
        index = {
            "0001": _make_experiment("0001", parents=[]),
        }
        result = _build_focus_constraint(index, "0001")
        assert "0001" in result


class TestFocusedBuildExperimentContext:
    def test_focus_label_in_output(self, tmp_path):
        index = {
            "0001": _make_experiment("0001", parents=[], metrics={"val": {"accuracy": 0.99}}),
            "0002": _make_experiment("0002", parents=["0001"], metrics={"val": {"accuracy": 0.995}}),
            "0003": _make_experiment("0003", parents=[], metrics={"val": {"accuracy": 0.998}}),
        }
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        assert "FOCUS" in result or "focus" in result.lower()
        assert "0001" in result

    def test_subtree_best_shown(self, tmp_path):
        index = {
            "0001": _make_experiment("0001", parents=[], metrics={"val": {"accuracy": 0.90}}),
            "0002": _make_experiment("0002", parents=["0001"], metrics={"val": {"accuracy": 0.95}}),
            "0003": _make_experiment("0003", parents=[], metrics={"val": {"accuracy": 0.99}}),
        }
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        # Should show best within subtree (0002) and best overall (0003)
        assert "0.95" in result
        assert "0.99" in result

    def test_subtree_frontier_gets_detail(self, tmp_path):
        index = {
            "0001": _make_experiment(
                "0001", parents=[], method="Root method",
                metrics={"val": {"accuracy": 0.90}},
            ),
            "0002": _make_experiment(
                "0002", parents=["0001"], method="Child method",
                metrics={"val": {"accuracy": 0.95}},
            ),
            "0003": _make_experiment(
                "0003", parents=[], method="Outside method",
                metrics={"val": {"accuracy": 0.99}},
            ),
        }
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        # Subtree frontier (0002) should get detail (Method: line)
        assert "Child method" in result

    def test_outside_experiments_present_but_compact(self, tmp_path):
        index = {
            "0001": _make_experiment(
                "0001", parents=[], method="Root method",
                metrics={"val": {"accuracy": 0.90}},
            ),
            "0002": _make_experiment(
                "0002", parents=["0001"], method="Child method",
                metrics={"val": {"accuracy": 0.95}},
            ),
            "0003": _make_experiment(
                "0003", parents=[], method="Outside method",
                metrics={"val": {"accuracy": 0.99}},
            ),
        }
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        # Outside experiment 0003 should still appear
        assert "0003" in result

    def test_most_recent_within_subtree(self, tmp_path):
        index = {
            "0001": _make_experiment(
                "0001", parents=[],
                hypothesis="Old root",
                created_at="2026-04-08T10:00:00+00:00",
                metrics={"val": {"accuracy": 0.90}},
            ),
            "0002": _make_experiment(
                "0002", parents=["0001"],
                hypothesis="Recent child",
                created_at="2026-04-09T10:00:00+00:00",
                metrics={"val": {"accuracy": 0.95}},
            ),
            "0003": _make_experiment(
                "0003", parents=[],
                hypothesis="Even more recent outside",
                created_at="2026-04-10T10:00:00+00:00",
                metrics={"val": {"accuracy": 0.99}},
            ),
        }
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        # Most recent section should show the subtree's most recent (0002), not 0003
        # Find the "Most Recent" section
        assert "Recent child" in result


class TestOutsideSubtreeCondensed:
    """Tests for condensed outside subtree rendering in focused context."""

    def test_outside_condensed_at_scale(self, tmp_path):
        """With many outside experiments, only top 5 by metric appear individually."""
        # Subtree: 0001 -> 0002, 0003
        index = {
            "0001": _make_experiment(
                "0001", parents=[], metrics={"val": {"accuracy": 0.90}},
            ),
            "0002": _make_experiment(
                "0002", parents=["0001"], metrics={"val": {"accuracy": 0.92}},
            ),
            "0003": _make_experiment(
                "0003", parents=["0001"], metrics={"val": {"accuracy": 0.93}},
            ),
        }
        # 20 outside experiments with varying metrics
        for i in range(1, 21):
            eid = f"{i + 3:04d}"
            index[eid] = _make_experiment(
                eid, parents=[], metrics={"val": {"accuracy": 0.80 + i * 0.005}},
            )
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        # Top 5 outside by metric: 0023 (0.90), 0022 (0.895), 0021 (0.890), 0020 (0.885), 0019 (0.880)
        assert "0023" in result
        assert "0022" in result
        assert "0021" in result
        assert "0020" in result
        assert "0019" in result
        # Experiments ranked 6-20 should NOT appear as bold individual entries
        for i in range(1, 16):
            eid = f"{i + 3:04d}"
            assert f"**{eid}**" not in result, f"{eid} should not appear individually"
        # Summary line should exist
        assert "Remaining" in result
        assert "15" in result  # 20 - 5 = 15 remaining

    def test_outside_all_fit_within_limit(self, tmp_path):
        """When fewer than 5 outside experiments, all appear and no Remaining line."""
        index = {
            "0001": _make_experiment(
                "0001", parents=[], metrics={"val": {"accuracy": 0.90}},
            ),
            "0002": _make_experiment(
                "0002", parents=["0001"], metrics={"val": {"accuracy": 0.92}},
            ),
            # 3 outside experiments
            "0003": _make_experiment(
                "0003", parents=[], metrics={"val": {"accuracy": 0.95}},
            ),
            "0004": _make_experiment(
                "0004", parents=[], metrics={"val": {"accuracy": 0.96}},
            ),
            "0005": _make_experiment(
                "0005", parents=[], metrics={"val": {"accuracy": 0.97}},
            ),
        }
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        assert "0003" in result
        assert "0004" in result
        assert "0005" in result
        assert "Remaining" not in result

    def test_outside_no_completed(self, tmp_path):
        """When all outside experiments are non-completed, show status summary only."""
        index = {
            "0001": _make_experiment(
                "0001", parents=[], metrics={"val": {"accuracy": 0.90}},
            ),
            "0002": _make_experiment(
                "0002", parents=["0001"], metrics={"val": {"accuracy": 0.92}},
            ),
            "0003": _make_experiment(
                "0003", parents=[], status="proposed",
                hypothesis="Proposed outside experiment",
            ),
            "0004": _make_experiment(
                "0004", parents=[], status="running",
                hypothesis="Running outside experiment",
            ),
        }
        result = _build_experiment_context(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001",
        )
        assert "Outside Subtree" in result
        # Should not have "Top" sub-section header
        assert "Top" not in result.split("Outside Subtree")[1].split("\n")[0]
        # Should mention status counts
        assert "proposed" in result
        assert "running" in result


class TestBacklogContext:
    def test_empty_backlog(self, abreka_project):
        abreka_dir = abreka_project / "abreka"
        result = _build_backlog_context(abreka_dir)
        assert result == "No backlog ideas."

    def test_open_items_shown(self, abreka_project):
        import json
        abreka_dir = abreka_project / "abreka"
        backlog_index = {
            "0001": {
                "id": "0001", "title": "Try attention", "description": "Might help",
                "priority": 5, "status": "open", "source_exp": "0002",
                "tags": ["arch"], "resolved_exp": "",
                "created_at": "2026-04-08T10:00:00+00:00",
                "updated_at": "2026-04-08T10:00:00+00:00",
            },
            "0002": {
                "id": "0002", "title": "Dismissed idea", "description": "",
                "priority": 3, "status": "dismissed", "source_exp": "",
                "tags": [], "resolved_exp": "",
                "created_at": "2026-04-08T10:00:00+00:00",
                "updated_at": "2026-04-08T10:00:00+00:00",
            },
        }
        (abreka_dir / "backlog_index.json").write_text(json.dumps(backlog_index))

        result = _build_backlog_context(abreka_dir)
        assert "B0001" in result
        assert "Try attention" in result
        assert "B0002" not in result  # dismissed, not shown

    def test_sorted_by_priority(self, abreka_project):
        import json
        abreka_dir = abreka_project / "abreka"
        backlog_index = {
            "0001": {
                "id": "0001", "title": "Low priority", "description": "",
                "priority": 1, "status": "open", "source_exp": "",
                "tags": [], "resolved_exp": "",
                "created_at": "2026-04-08T10:00:00+00:00",
                "updated_at": "2026-04-08T10:00:00+00:00",
            },
            "0002": {
                "id": "0002", "title": "High priority", "description": "",
                "priority": 10, "status": "open", "source_exp": "",
                "tags": [], "resolved_exp": "",
                "created_at": "2026-04-08T10:00:00+00:00",
                "updated_at": "2026-04-08T10:00:00+00:00",
            },
        }
        (abreka_dir / "backlog_index.json").write_text(json.dumps(backlog_index))

        result = _build_backlog_context(abreka_dir)
        # High priority should come first
        pos_high = result.index("B0002")
        pos_low = result.index("B0001")
        assert pos_high < pos_low

    def test_focus_filters_to_subtree(self, abreka_project):
        import json
        abreka_dir = abreka_project / "abreka"

        # Create an experiment index with a subtree
        exp_index = {
            "0001": _make_experiment("0001", parents=[]),
            "0002": _make_experiment("0002", parents=["0001"]),
            "0003": _make_experiment("0003", parents=[]),  # outside subtree
        }
        (abreka_dir / "index.json").write_text(json.dumps(exp_index))

        # Backlog items: one from subtree, one from outside, one with no source
        backlog_index = {
            "0001": {
                "id": "0001", "title": "In subtree", "description": "",
                "priority": 5, "status": "open", "source_exp": "0002",
                "tags": [], "resolved_exp": "",
                "created_at": "2026-04-08T10:00:00+00:00",
                "updated_at": "2026-04-08T10:00:00+00:00",
            },
            "0002": {
                "id": "0002", "title": "Outside subtree", "description": "",
                "priority": 3, "status": "open", "source_exp": "0003",
                "tags": [], "resolved_exp": "",
                "created_at": "2026-04-08T10:00:00+00:00",
                "updated_at": "2026-04-08T10:00:00+00:00",
            },
            "0003": {
                "id": "0003", "title": "No source", "description": "",
                "priority": 1, "status": "open", "source_exp": "",
                "tags": [], "resolved_exp": "",
                "created_at": "2026-04-08T10:00:00+00:00",
                "updated_at": "2026-04-08T10:00:00+00:00",
            },
        }
        (abreka_dir / "backlog_index.json").write_text(json.dumps(backlog_index))

        result = _build_backlog_context(abreka_dir, index=exp_index, focus_root="0001")
        assert "In subtree" in result
        assert "Outside subtree" not in result
        assert "No source" not in result  # excluded under focus


class TestBuildBranchOverview:
    def test_empty_when_only_singletons(self, tmp_path):
        index = {"0001": _make_experiment("0001", metrics={"val": {"accuracy": 0.9}})}
        result = _build_branch_overview(index, tmp_path, "val:accuracy", "maximize")
        assert "No multi-experiment branches" in result

    def test_shows_real_branches(self, tmp_path):
        index = {
            "0001": _make_experiment("0001", metrics={"val": {"accuracy": 0.8}}),
            "0002": _make_experiment(
                "0002", parents=["0001"], metrics={"val": {"accuracy": 0.9}}
            ),
            "0003": _make_experiment("0003", metrics={"val": {"accuracy": 0.7}}),
            "0004": _make_experiment(
                "0004", parents=["0003"], metrics={"val": {"accuracy": 0.75}}
            ),
        }
        result = _build_branch_overview(index, tmp_path, "val:accuracy", "maximize")
        assert "Branch Overview" in result
        assert "Branch 0001" in result
        assert "Branch 0003" in result

    def test_excludes_focus_root_from_overview(self, tmp_path):
        index = {
            "0001": _make_experiment("0001", metrics={"val": {"accuracy": 0.8}}),
            "0002": _make_experiment(
                "0002", parents=["0001"], metrics={"val": {"accuracy": 0.9}}
            ),
            "0003": _make_experiment("0003", metrics={"val": {"accuracy": 0.7}}),
            "0004": _make_experiment(
                "0004", parents=["0003"], metrics={"val": {"accuracy": 0.75}}
            ),
        }
        result = _build_branch_overview(
            index, tmp_path, "val:accuracy", "maximize", focus_root="0001"
        )
        assert "Branch 0001" not in result
        assert "Branch 0003" in result


class TestBuildFocusedBranchSummary:
    def test_empty_when_no_focus(self, tmp_path):
        assert _build_focused_branch_summary({}, tmp_path, "val:accuracy", "maximize", None) == ""

    def test_renders_focused_subtree_summary(self, tmp_path):
        index = {
            "0001": _make_experiment("0001", metrics={"val": {"accuracy": 0.8}}),
            "0002": _make_experiment(
                "0002", parents=["0001"], metrics={"val": {"accuracy": 0.9}}
            ),
        }
        result = _build_focused_branch_summary(
            index, tmp_path, "val:accuracy", "maximize", "0001"
        )
        assert "Focused Branch Summary" in result
        assert "Branch Summary: 0001" in result
        assert "Performance" in result
