"""Tests for ll-issues refine-status sub-command."""

from __future__ import annotations

import json
import sys
from pathlib import Path
from typing import Any
from unittest.mock import patch

import pytest


def _write_config(temp_project_dir: Path, sample_config: dict[str, Any]) -> None:
    config_path = temp_project_dir / ".ll" / "ll-config.json"
    config_path.write_text(json.dumps(sample_config))


def _make_issue(
    directory: Path,
    filename: str,
    title: str,
    *,
    confidence_score: int | None = None,
    outcome_confidence: int | None = None,
    session_commands: list[str] | None = None,
) -> None:
    """Write a minimal issue file with optional frontmatter and Session Log."""
    frontmatter_lines: list[str] = []
    if confidence_score is not None:
        frontmatter_lines.append(f"confidence_score: {confidence_score}")
    if outcome_confidence is not None:
        frontmatter_lines.append(f"outcome_confidence: {outcome_confidence}")

    parts: list[str] = []
    if frontmatter_lines:
        parts.append("---")
        parts.extend(frontmatter_lines)
        parts.append("---")
        parts.append("")

    parts.append(f"# {title}")
    parts.append("")
    parts.append("## Summary")
    parts.append("Test issue.")

    if session_commands:
        parts.append("")
        parts.append("## Session Log")
        for cmd in session_commands:
            parts.append(f"- `{cmd}` - 2026-01-01T00:00:00 - `/session.jsonl`")

    (directory / filename).write_text("\n".join(parts))


class TestRefineStatusTable:
    """Tests for refine-status table output."""

    def test_no_issues(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status prints message when no active issues exist."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        assert "No active issues found" in capsys.readouterr().out

    def test_table_has_header_and_separator(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        issues_dir: Path,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status table output has header row and separator line."""
        _write_config(temp_project_dir, sample_config)

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        lines = [ln for ln in out.splitlines() if ln.strip()]
        assert any("ID" in ln and "Pri" in ln and "Title" in ln for ln in lines), (
            "Header row missing"
        )
        assert any(set(ln.strip()).issubset({"-", " "}) and len(ln.strip()) > 5 for ln in lines), (
            "Separator line missing"
        )

    def test_table_shows_issue_ids(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        issues_dir: Path,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status table contains issue IDs from active issues."""
        _write_config(temp_project_dir, sample_config)

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "BUG-001" in out
        assert "FEAT-001" in out

    def test_sort_order_by_total_desc(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Issues with more session commands appear before those with fewer."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        # BUG-010: 3 session commands; BUG-011: 0 commands
        _make_issue(
            bugs_dir,
            "P2-BUG-010-refined.md",
            "BUG-010: Refined issue",
            session_commands=["/ll:scan-codebase", "/ll:refine-issue", "/ll:ready-issue"],
        )
        _make_issue(bugs_dir, "P2-BUG-011-new.md", "BUG-011: Brand new issue")

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        lines = out.splitlines()
        positions = {
            line: i for i, line in enumerate(lines) if "BUG-010" in line or "BUG-011" in line
        }
        bug010_pos = next((i for line, i in positions.items() if "BUG-010" in line), None)
        bug011_pos = next((i for line, i in positions.items() if "BUG-011" in line), None)
        assert bug010_pos is not None
        assert bug011_pos is not None
        assert bug010_pos < bug011_pos, "Higher Total should sort first"

    def test_refine_column_shows_integer_count(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Refine column shows integer count (1 for refined once, 0 for untouched)."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P1-BUG-020-partially-refined.md",
            "BUG-020: Partially refined",
            session_commands=["/ll:refine-issue"],
        )
        _make_issue(bugs_dir, "P2-BUG-021-untouched.md", "BUG-021: Untouched")

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        lines = out.splitlines()
        bug020_line = next((ln for ln in lines if "BUG-020" in ln), None)
        bug021_line = next((ln for ln in lines if "BUG-021" in ln), None)
        assert bug020_line is not None
        assert bug021_line is not None
        assert "1" in bug020_line, "Refined once should show '1' in refine column"
        assert "0" in bug021_line, "Untouched should show '0' in refine column"

    def test_refine_column_shows_count_for_multiple_runs(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Refine column shows count > 1 when /ll:refine-issue appears multiple times."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P2-BUG-022-multi-refined.md",
            "BUG-022: Multi refined",
            session_commands=[
                "/ll:refine-issue",
                "/ll:refine-issue",
                "/ll:refine-issue",
            ],
        )

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        lines = out.splitlines()
        bug022_line = next((ln for ln in lines if "BUG-022" in ln), None)
        assert bug022_line is not None
        assert "3" in bug022_line, "Three refine runs should show '3' in refine column"

    def test_ready_and_outconf_columns(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """confidence_score and outcome_confidence are shown in Ready/OutConf columns."""
        _write_config(temp_project_dir, sample_config)
        features_dir = temp_project_dir / ".issues" / "features"
        features_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            features_dir,
            "P2-FEAT-030-scored.md",
            "FEAT-030: Scored feature",
            confidence_score=88,
            outcome_confidence=72,
            session_commands=["/ll:confidence-check"],
        )

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "88" in out, "confidence_score should appear"
        assert "72" in out, "outcome_confidence should appear"

    def test_type_filter(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        issues_dir: Path,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--type BUG filters to bugs only."""
        _write_config(temp_project_dir, sample_config)

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--type", "BUG", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "BUG-001" in out
        assert "FEAT-001" not in out

    def test_norm_column_header_present(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        issues_dir: Path,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status table header includes 'Norm' column."""
        _write_config(temp_project_dir, sample_config)

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "norm" in out, "norm column header should appear"

    def test_norm_checkmark_for_normalized_filename(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """✓ appears in Norm column for filenames matching naming convention."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        # Use a fully-formatted BUG so both norm and fmt show ✓ (no ✗ in output)
        _make_fully_formatted_bug(
            bugs_dir, "P2-BUG-080-normalized.md", "BUG-080: Normalized filename"
        )

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        # Both norm and fmt should show ✓; ✗ must not appear
        assert "\u2713" in out, "Checkmark ✓ should appear for normalized filename"
        assert "\u2717" not in out, "X ✗ should not appear when all filenames are normalized"

    def test_norm_x_for_non_normalized_filename(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """✗ appears in Norm column for filenames not matching naming convention."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "invalid-filename.md", "Non-normalized issue")

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "\u2717" in out, "X ✗ should appear for non-normalized filename"

    def test_key_section_in_default_output(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Key section appears after the table in default (table) mode."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        # Use /ll:verify-issues which is a dynamic (non-source) command
        _make_issue(
            bugs_dir,
            "P2-BUG-100-with-cmd.md",
            "BUG-100: Key section test",
            session_commands=["/ll:verify-issues"],
        )

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "Key:" in out, "Key section header should appear"
        assert "source" in out, "source column entry should appear in Key"
        assert "/ll:verify-issues" in out, (
            "Full command name should appear in Key for non-source cmds"
        )
        assert "fmt" in out, "fmt column entry should appear in Key"
        assert "Readiness score" in out, "Ready explanation should appear in Key"
        assert "Outcome confidence" in out, "OutConf explanation should appear in Key"

    def test_no_key_flag_suppresses_key_section(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--no-key suppresses the Key section."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P2-BUG-101-no-key.md",
            "BUG-101: No key test",
            session_commands=["/ll:scan-codebase"],
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "Key:" not in out, "--no-key should suppress the Key section"

    def test_json_output_omits_key_section(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--format json output does not include a Key section."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P2-BUG-102-json-key.md",
            "BUG-102: JSON no key",
            session_commands=["/ll:scan-codebase"],
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "Key:" not in out, "JSON output should not include Key section"

    def test_dynamic_columns_from_session_log(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Column headers are derived from session log commands, not hardcoded."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P1-BUG-040-with-cmd.md",
            "BUG-040: Issue with unusual command",
            session_commands=["/ll:my-custom-cmd"],
        )

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        # Header should contain (at least the prefix of) the short command name
        # "my-custom-cmd" stripped of "/ll:" is "my-custom-cmd"; truncated to _CMD_WIDTH=6 → "my-cu…"
        assert "my-cu" in out

    def test_tradeoff_column_shows_checkmark(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """tradeoff column appears and shows ✓ only for issues that had tradeoff-review run."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P1-BUG-110-tradeoff.md",
            "BUG-110: Tradeoff reviewed",
            session_commands=["/ll:tradeoff-review-issues"],
        )
        _make_issue(bugs_dir, "P2-BUG-111-no-tradeoff.md", "BUG-111: No tradeoff review")

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "tradeoff" in out, "'tradeoff' column header should appear"
        lines = out.splitlines()
        bug110_line = next((ln for ln in lines if "BUG-110" in ln), None)
        assert bug110_line is not None
        assert "\u2713" in bug110_line, "BUG-110 should show ✓ in tradeoff column"

    def test_map_column_shows_checkmark(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """map column appears and shows ✓ only for issues that had map-dependencies run."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P1-BUG-120-mapped.md",
            "BUG-120: Dependencies mapped",
            session_commands=["/ll:map-dependencies"],
        )
        _make_issue(bugs_dir, "P2-BUG-121-no-map.md", "BUG-121: No dependency map")

        with patch.object(
            sys, "argv", ["ll-issues", "refine-status", "--config", str(temp_project_dir)]
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "map" in out, "'map' column header should appear"
        lines = out.splitlines()
        bug120_line = next((ln for ln in lines if "BUG-120" in ln), None)
        assert bug120_line is not None
        assert "\u2713" in bug120_line, "BUG-120 should show ✓ in map column"

    def test_table_row_width_fits_terminal(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Table rows must not exceed the terminal width."""
        from unittest.mock import patch

        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        # Issue with several cmd columns to stress-test width calculation
        _make_issue(
            bugs_dir,
            "P2-BUG-200-width-test.md",
            "BUG-200: Width test issue with a moderately long title",
            session_commands=[
                "/ll:format-issue",
                "/ll:verify-issues",
                "/ll:refine-issue",
                "/ll:tradeoff-review-issues",
            ],
        )

        import os

        terminal_width = 120
        fake_size = os.terminal_size((terminal_width, 40))

        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(
                sys,
                "argv",
                ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
            ),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        data_lines = [ln for ln in out.splitlines() if ln.strip() and not ln.startswith("\n")]
        for line in data_lines:
            assert len(line) <= terminal_width, (
                f"Row width {len(line)} exceeds terminal width {terminal_width}: {line!r}"
            )

    def test_four_digit_id_not_truncated(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """4-digit issue IDs like FEAT-8885 are displayed without truncation."""
        _write_config(temp_project_dir, sample_config)
        features_dir = temp_project_dir / ".issues" / "features"
        features_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            features_dir,
            "P2-FEAT-8885-four-digit-id.md",
            "FEAT-8885: Feature with 4-digit ID",
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "FEAT-8885" in out, "Full 4-digit ID must appear untruncated"
        assert "FEAT-888 " not in out and "FEAT-888\t" not in out, (
            "Truncated ID FEAT-888 must not appear"
        )


class TestRefineStatusJson:
    """Tests for refine-status --format json output."""

    def test_json_output_is_jsonl(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--format json emits one JSON object per line (JSONL)."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P1-BUG-050-json-test.md",
            "BUG-050: JSON test issue",
            confidence_score=90,
            outcome_confidence=80,
            session_commands=["/ll:refine-issue", "/ll:ready-issue"],
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        lines = [ln for ln in out.splitlines() if ln.strip()]
        assert len(lines) == 1
        record = json.loads(lines[0])
        assert record["id"] == "BUG-050"
        assert record["priority"] == "P1"
        assert record["confidence_score"] == 90
        assert record["outcome_confidence"] == 80
        assert "refine-issue" in record["commands"][0]
        assert record["total"] == 2
        assert record["refine_count"] == 1

    def test_json_missing_scores_are_null(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """JSON output uses null for absent confidence/outcome scores."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "P3-BUG-060-no-scores.md", "BUG-060: No scores")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        record = json.loads(out.strip())
        assert record["confidence_score"] is None
        assert record["outcome_confidence"] is None
        assert record["total"] == 0

    def test_json_normalized_field(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """JSON output includes 'normalized' boolean field per record."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "P1-BUG-090-normal.md", "BUG-090: Normalized")
        _make_issue(bugs_dir, "invalid-filename.md", "Non-normalized issue")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        lines = [ln for ln in capsys.readouterr().out.splitlines() if ln.strip()]
        records = [json.loads(ln) for ln in lines]
        by_id = {r["id"]: r for r in records if "id" in r}

        # BUG-090 has normalized filename
        assert "BUG-090" in by_id
        assert by_id["BUG-090"]["normalized"] is True

        # The invalid-filename.md issue won't have a standard ID, check via normalized=False
        non_norm = [r for r in records if r.get("normalized") is False]
        assert len(non_norm) == 1, "One record should have normalized=False"

    def test_json_sort_order(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """JSON output is sorted descending by total, then ascending by priority."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P2-BUG-070-many-cmds.md",
            "BUG-070: Many commands",
            session_commands=["/ll:refine-issue", "/ll:ready-issue", "/ll:confidence-check"],
        )
        _make_issue(bugs_dir, "P1-BUG-071-no-cmds.md", "BUG-071: No commands")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        lines = [ln for ln in capsys.readouterr().out.splitlines() if ln.strip()]
        records = [json.loads(ln) for ln in lines]
        assert records[0]["id"] == "BUG-070", "Highest total should be first"
        assert records[1]["id"] == "BUG-071"


def _make_fully_formatted_bug(directory: Path, filename: str, title: str) -> None:
    """Write a BUG issue file with all required v2.0 sections."""
    content = "\n".join(
        [
            "---",
            "discovered_by: capture-issue",
            "---",
            "",
            f"# {title}",
            "",
            "## Summary",
            "Test issue.",
            "",
            "## Current Behavior",
            "It does X.",
            "",
            "## Expected Behavior",
            "It should do Y.",
            "",
            "## Steps to Reproduce",
            "1. Do the thing.",
            "",
            "## Impact",
            "- **Priority**: P2 - Medium priority",
            "- **Effort**: Small",
            "- **Risk**: Low",
            "- **Breaking Change**: No",
            "",
            "## Labels",
            "`bug`",
            "",
            "## Status",
            "**Open** | Created: 2026-01-01 | Priority: P2",
        ]
    )
    (directory / filename).write_text(content)


class TestRefineStatusFormatColumn:
    """Tests for the fmt column using dual-criterion detection (session log or structural)."""

    def test_fmt_checkmark_for_fully_formatted_bug(
        self,
        temp_project_dir: Path,
        sample_config: dict,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """✓ appears in fmt column for a BUG with all required v2.0 sections."""
        import json as json_module

        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_fully_formatted_bug(bugs_dir, "P2-BUG-300-formatted.md", "BUG-300: Fully formatted")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        lines = [ln for ln in capsys.readouterr().out.splitlines() if ln.strip()]
        record = json_module.loads(lines[0])
        assert record["id"] == "BUG-300"
        assert record["formatted"] is True, "Fully formatted BUG should report formatted=True"

    def test_fmt_x_for_missing_required_sections(
        self,
        temp_project_dir: Path,
        sample_config: dict,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """✗ appears in fmt column for an issue missing required sections."""
        import json as json_module

        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        # Minimal file: only has Summary — missing Impact, Status, Steps to Reproduce, etc.
        _make_issue(bugs_dir, "P2-BUG-301-sparse.md", "BUG-301: Sparse issue")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        lines = [ln for ln in capsys.readouterr().out.splitlines() if ln.strip()]
        record = json_module.loads(lines[0])
        assert record["id"] == "BUG-301"
        assert record["formatted"] is False, "Sparse BUG should report formatted=False"

    def test_fmt_checkmark_when_format_issue_in_session_log(
        self,
        temp_project_dir: Path,
        sample_config: dict,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """✓ for an issue with /ll:format-issue in session log, even without required sections."""
        import json as json_module

        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        # Has /ll:format-issue in session log but is still missing required sections
        _make_issue(
            bugs_dir,
            "P2-BUG-302-log-not-content.md",
            "BUG-302: Log not content",
            session_commands=["/ll:format-issue"],
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        lines = [ln for ln in capsys.readouterr().out.splitlines() if ln.strip()]
        record = json_module.loads(lines[0])
        assert record["id"] == "BUG-302"
        assert record["formatted"] is True, (
            "Session log /ll:format-issue entry should mark issue as formatted"
        )

    def test_format_cmd_no_longer_creates_dynamic_column(
        self,
        temp_project_dir: Path,
        sample_config: dict,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """'format' does not appear as a dynamic command column header in table output."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P2-BUG-303-has-format-cmd.md",
            "BUG-303: Has format-issue cmd",
            session_commands=["/ll:format-issue"],
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        header_line = next((ln for ln in out.splitlines() if "ID" in ln and "Pri" in ln), None)
        assert header_line is not None
        # "fmt" is the static column; "format" as a standalone dynamic header must not appear
        # The header contains "fmt" (static), not "format" as a dynamic command alias
        assert "format" not in header_line.split(), (
            "/ll:format-issue must not create a dynamic 'format' column header"
        )

    def test_fmt_checkmark_after_append_session_log_entry(
        self,
        temp_project_dir: Path,
        sample_config: dict,
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """is_formatted() returns True after append_session_log_entry() writes /ll:format-issue.

        Regression test for BUG-743: format-issue --auto never sets the formatted flag.
        The fix requires a programmatic append_session_log_entry() call (not prose instructions)
        so the session log entry is always written in both the "changes made" and
        "no changes needed" code paths.
        """
        import json as json_module

        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        # Sparse issue: no /ll:format-issue in session log, no required sections
        _make_issue(bugs_dir, "P2-BUG-304-append-log.md", "BUG-304: Append log test")
        issue_path = bugs_dir / "P2-BUG-304-append-log.md"

        # Simulate what the fixed format-issue skill does: programmatic session log write
        from little_loops.session_log import append_session_log_entry

        written = append_session_log_entry(
            issue_path, "/ll:format-issue", Path("/mock/session.jsonl")
        )
        assert written is True, (
            "append_session_log_entry should succeed with explicit session_jsonl"
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--format", "json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        lines = [ln for ln in capsys.readouterr().out.splitlines() if ln.strip()]
        record = json_module.loads(lines[0])
        assert record["id"] == "BUG-304"
        assert record["formatted"] is True, (
            "append_session_log_entry('/ll:format-issue') must make refine-status report formatted=True"
        )


class TestRefineStatusConfigColumns:
    """Tests for refine-status configurable columns via refine_status.columns config."""

    def test_custom_columns_only_renders_specified(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Only configured columns appear in the table header."""
        custom_config = dict(sample_config)
        custom_config["refine_status"] = {"columns": ["id", "priority", "title", "ready"]}
        _write_config(temp_project_dir, custom_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P2-BUG-400-col-test.md",
            "BUG-400: Column config test",
            confidence_score=77,
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        header_line = next((ln for ln in out.splitlines() if "ID" in ln and "Pri" in ln), None)
        assert header_line is not None
        assert "ID" in header_line
        assert "Pri" in header_line
        assert "Title" in header_line
        assert "ready" in header_line
        assert "source" not in header_line
        assert "norm" not in header_line
        assert "fmt" not in header_line
        assert "confidence" not in header_line
        assert "total" not in header_line

    def test_empty_columns_uses_defaults(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Empty columns list falls back to the default column set."""
        custom_config = dict(sample_config)
        custom_config["refine_status"] = {"columns": []}
        _write_config(temp_project_dir, custom_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "P2-BUG-401-default-cols.md", "BUG-401: Default columns test")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "source" in out
        assert "norm" in out
        assert "fmt" in out

    def test_unknown_column_renders_em_dash(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Unknown column names render as em-dash in data rows without crashing."""
        custom_config = dict(sample_config)
        custom_config["refine_status"] = {"columns": ["id", "priority", "nonexistent_col", "total"]}
        _write_config(temp_project_dir, custom_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "P2-BUG-402-unknown-col.md", "BUG-402: Unknown column test")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        lines = out.splitlines()
        data_line = next((ln for ln in lines if "BUG-402" in ln), None)
        assert data_line is not None
        assert "\u2014" in data_line  # em-dash in the unknown column cell


class TestRefineStatusJsonFlag:
    """Tests for refine-status --json flag (JSON array output)."""

    def test_json_flag_outputs_array(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--json outputs a valid JSON array (not NDJSON)."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P1-BUG-070-json-flag.md",
            "BUG-070: JSON flag test",
            confidence_score=85,
            outcome_confidence=75,
            session_commands=["/ll:refine-issue", "/ll:ready-issue"],
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        data = json.loads(out)
        assert isinstance(data, list)
        assert len(data) == 1

    def test_json_flag_fields(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--json records contain the same fields as --format json."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(
            bugs_dir,
            "P2-BUG-071-json-fields.md",
            "BUG-071: JSON fields test",
            confidence_score=90,
            outcome_confidence=80,
            session_commands=["/ll:refine-issue"],
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        data = json.loads(out)
        record = data[0]
        assert record["id"] == "BUG-071"
        assert record["priority"] == "P2"
        assert record["confidence_score"] == 90
        assert record["outcome_confidence"] == 80
        assert record["total"] == 1
        assert record["refine_count"] == 1
        assert "normalized" in record
        assert "formatted" in record
        assert "source" in record
        assert "commands" in record

    def test_json_flag_no_color_codes(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--json output contains no ANSI escape sequences."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "P3-BUG-072-no-color.md", "BUG-072: No color test")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--json", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "\x1b[" not in out

    def test_json_flag_and_format_json_coexist(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--json takes precedence when combined with --format json (no error)."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "P3-BUG-073-coexist.md", "BUG-073: Coexist test")

        with patch.object(
            sys,
            "argv",
            [
                "ll-issues",
                "refine-status",
                "--json",
                "--format",
                "json",
                "--config",
                str(temp_project_dir),
            ],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        # --json takes precedence: output is a JSON array, not NDJSON
        data = json.loads(out)
        assert isinstance(data, list)


class TestColumnElision:
    """Tests for dynamic column elision when the table overflows the terminal."""

    def _setup_issues_dir(self, temp_project_dir: Path) -> Path:
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)
        return bugs_dir

    def _run_refine_status(
        self,
        temp_project_dir: Path,
        terminal_cols: int,
        extra_args: list[str] | None = None,
    ) -> str:
        """Run refine-status with a mocked terminal width and return stdout."""
        import os

        fake_size = os.terminal_size((terminal_cols, 40))
        argv = [
            "ll-issues",
            "refine-status",
            "--no-key",
            "--config",
            str(temp_project_dir),
        ] + (extra_args or [])
        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(sys, "argv", argv),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        return ""  # capsys used by caller

    def test_narrow_terminal_elides_columns(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """At 60 cols, lower-priority columns are elided so the table fits."""
        import os

        _write_config(temp_project_dir, sample_config)
        bugs_dir = self._setup_issues_dir(temp_project_dir)

        _make_issue(
            bugs_dir,
            "P2-BUG-801-narrow-test.md",
            "A moderately long title for the narrow test",
            session_commands=[
                "/ll:verify-issues",
                "/ll:refine-issue",
                "/ll:tradeoff-review-issues",
            ],
        )

        terminal_cols = 60
        fake_size = os.terminal_size((terminal_cols, 40))

        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(
                sys,
                "argv",
                ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
            ),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        table_lines = [ln for ln in out.splitlines() if ln.strip() and not ln.startswith("\n")]
        for line in table_lines:
            assert len(line) <= terminal_cols, (
                f"Line width {len(line)} exceeds terminal {terminal_cols}: {line!r}"
            )

    def test_wide_terminal_shows_all_columns(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """At 160 cols, no columns are elided and the full header is present."""
        import os

        _write_config(temp_project_dir, sample_config)
        bugs_dir = self._setup_issues_dir(temp_project_dir)

        _make_issue(
            bugs_dir,
            "P2-BUG-802-wide-test.md",
            "Wide terminal test issue",
            session_commands=["/ll:verify-issues", "/ll:refine-issue"],
        )

        terminal_cols = 160
        fake_size = os.terminal_size((terminal_cols, 40))

        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(
                sys,
                "argv",
                ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
            ),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        header = out.splitlines()[0]
        # Default columns: all of these should appear at 160 cols
        for col_header in ("ID", "Pri", "Title", "source", "norm", "fmt"):
            assert col_header in header, (
                f"Expected '{col_header}' in wide-terminal header: {header!r}"
            )

    def test_medium_terminal_fits(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """At 80 cols, the table must still fit (rows <= 80 chars)."""
        import os

        _write_config(temp_project_dir, sample_config)
        bugs_dir = self._setup_issues_dir(temp_project_dir)

        _make_issue(
            bugs_dir,
            "P2-BUG-803-medium-test.md",
            "Medium terminal test issue",
            session_commands=[
                "/ll:verify-issues",
                "/ll:refine-issue",
                "/ll:tradeoff-review-issues",
            ],
        )

        terminal_cols = 80
        fake_size = os.terminal_size((terminal_cols, 40))

        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(
                sys,
                "argv",
                ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
            ),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        table_lines = [ln for ln in out.splitlines() if ln.strip() and not ln.startswith("\n")]
        for line in table_lines:
            assert len(line) <= terminal_cols, (
                f"Line width {len(line)} exceeds terminal {terminal_cols}: {line!r}"
            )

    def test_pinned_columns_never_elided(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """id, priority, and title are never elided even at very narrow terminals."""
        import os

        _write_config(temp_project_dir, sample_config)
        bugs_dir = self._setup_issues_dir(temp_project_dir)

        _make_issue(
            bugs_dir,
            "P2-BUG-804-pinned-test.md",
            "Pinned column test",
            session_commands=[
                "/ll:verify-issues",
                "/ll:refine-issue",
                "/ll:tradeoff-review-issues",
                "/ll:map-dependencies",
            ],
        )

        # Extremely narrow terminal: only pinned columns can fit
        terminal_cols = 40
        fake_size = os.terminal_size((terminal_cols, 40))

        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(
                sys,
                "argv",
                ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
            ),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        header = out.splitlines()[0]
        # Pinned columns must always be present
        assert "ID" in header, f"'ID' column must be pinned: {header!r}"
        assert "Pri" in header, f"'Pri' column must be pinned: {header!r}"
        assert "Title" in header, f"'Title' column must be pinned: {header!r}"

    def test_json_mode_unaffected_by_narrow_terminal(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--json output is never affected by terminal width or column elision."""
        import os

        _write_config(temp_project_dir, sample_config)
        bugs_dir = self._setup_issues_dir(temp_project_dir)

        _make_issue(
            bugs_dir,
            "P2-BUG-805-json-narrow.md",
            "JSON mode narrow terminal test",
            confidence_score=80,
            outcome_confidence=70,
            session_commands=["/ll:verify-issues", "/ll:refine-issue"],
        )

        # Very narrow terminal — elision would strip most columns in table mode
        terminal_cols = 40
        fake_size = os.terminal_size((terminal_cols, 40))

        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(
                sys,
                "argv",
                ["ll-issues", "refine-status", "--json", "--config", str(temp_project_dir)],
            ),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        records = json.loads(out)
        assert isinstance(records, list)
        assert len(records) == 1
        record = records[0]
        # All standard fields must be present regardless of terminal width
        for field in ("id", "priority", "title", "source", "commands", "confidence_score", "total"):
            assert field in record, f"JSON field '{field}' missing in narrow-terminal output"

    def test_custom_elide_order_respected(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """Custom elide_order config pins columns absent from the list."""
        import os

        # Configure elide_order that does NOT include "norm" — it should be pinned
        config = {**sample_config, "refine_status": {"elide_order": ["source", "fmt"]}}
        _write_config(temp_project_dir, config)
        bugs_dir = self._setup_issues_dir(temp_project_dir)

        _make_issue(
            bugs_dir,
            "P2-BUG-806-custom-elide.md",
            "Custom elide order test",
            session_commands=[
                "/ll:verify-issues",
                "/ll:refine-issue",
                "/ll:tradeoff-review-issues",
            ],
        )

        # Narrow enough to trigger elision
        terminal_cols = 70
        fake_size = os.terminal_size((terminal_cols, 40))

        with (
            patch("shutil.get_terminal_size", return_value=fake_size),
            patch.object(
                sys,
                "argv",
                ["ll-issues", "refine-status", "--no-key", "--config", str(temp_project_dir)],
            ),
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        header = out.splitlines()[0]
        # "norm" is not in elide_order so it is pinned — must be present
        assert "norm" in header, f"'norm' should be pinned (not in elide_order): {header!r}"
        # Table must still fit
        table_lines = [ln for ln in out.splitlines() if ln.strip()]
        for line in table_lines:
            assert len(line) <= terminal_cols, (
                f"Line width {len(line)} exceeds terminal {terminal_cols}: {line!r}"
            )


class TestRefineStatusSingleIssue:
    """Tests for refine-status with an optional ISSUE-ID positional filter."""

    def _setup_dir(self, temp_project_dir: Path, sample_config: dict[str, Any]) -> Path:
        """Write config and create issue dirs; return features dir."""
        _write_config(temp_project_dir, sample_config)
        features_dir = temp_project_dir / ".issues" / "features"
        features_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "bugs").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)
        return features_dir

    def test_single_issue_table_output(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status FEAT-873 prints a one-row table for the specified issue."""
        features_dir = self._setup_dir(temp_project_dir, sample_config)
        _make_issue(
            features_dir,
            "P3-FEAT-873-test-issue.md",
            "Test Feature Issue",
            confidence_score=82,
            outcome_confidence=74,
            session_commands=["/ll:refine-issue"],
        )
        # Add a second issue to confirm only one row is shown
        _make_issue(
            features_dir,
            "P2-FEAT-900-other-issue.md",
            "Other Issue",
            confidence_score=50,
            outcome_confidence=50,
        )

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "FEAT-873", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        non_empty = [ln for ln in out.splitlines() if ln.strip()]
        # header + separator + data row = 3 non-empty lines (key section may follow)
        data_lines = [ln for ln in non_empty if not ln.startswith("-") and "─" not in ln]
        assert any("FEAT-873" in ln for ln in data_lines), (
            f"FEAT-873 should appear in output: {out!r}"
        )
        assert not any("FEAT-900" in ln for ln in data_lines), (
            f"FEAT-900 should not appear in output: {out!r}"
        )

    def test_single_issue_not_found(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status with a non-existent ID prints error and exits 1."""
        features_dir = self._setup_dir(temp_project_dir, sample_config)
        _make_issue(features_dir, "P3-FEAT-873-test-issue.md", "Test Feature Issue")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "FEAT-999", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 1
        out = capsys.readouterr().out
        assert "not found" in out, f"Expected 'not found' in output: {out!r}"
        assert "FEAT-999" in out, f"Expected issue ID in error message: {out!r}"

    def test_single_issue_json_flag(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status FEAT-873 --json outputs a single JSON object, not an array."""
        features_dir = self._setup_dir(temp_project_dir, sample_config)
        _make_issue(
            features_dir,
            "P3-FEAT-873-test-issue.md",
            "Test Feature Issue",
            confidence_score=82,
            outcome_confidence=74,
        )

        with patch.object(
            sys,
            "argv",
            [
                "ll-issues",
                "refine-status",
                "FEAT-873",
                "--json",
                "--config",
                str(temp_project_dir),
            ],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        data = json.loads(out)
        assert isinstance(data, dict), f"Expected JSON object, got {type(data)}: {out!r}"
        assert data["id"] == "FEAT-873"

    def test_single_issue_format_json(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status FEAT-873 --format json outputs one NDJSON line."""
        features_dir = self._setup_dir(temp_project_dir, sample_config)
        _make_issue(
            features_dir,
            "P3-FEAT-873-test-issue.md",
            "Test Feature Issue",
            confidence_score=82,
            outcome_confidence=74,
        )

        with patch.object(
            sys,
            "argv",
            [
                "ll-issues",
                "refine-status",
                "FEAT-873",
                "--format",
                "json",
                "--config",
                str(temp_project_dir),
            ],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        lines = [ln for ln in out.splitlines() if ln.strip()]
        assert len(lines) == 1, f"Expected 1 NDJSON line, got {len(lines)}: {out!r}"
        record = json.loads(lines[0])
        assert record["id"] == "FEAT-873"

    def test_single_issue_no_key(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """refine-status FEAT-873 --no-key suppresses the key section."""
        features_dir = self._setup_dir(temp_project_dir, sample_config)
        _make_issue(
            features_dir,
            "P3-FEAT-873-test-issue.md",
            "Test Feature Issue",
            confidence_score=82,
            outcome_confidence=74,
        )

        with patch.object(
            sys,
            "argv",
            [
                "ll-issues",
                "refine-status",
                "FEAT-873",
                "--no-key",
                "--config",
                str(temp_project_dir),
            ],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "Key" not in out and "key" not in out.lower().replace("no-key", "").replace(
            "no_key", ""
        ), f"Key section should be suppressed: {out!r}"

    def test_type_filter_unaffected_without_id(
        self,
        temp_project_dir: Path,
        sample_config: dict[str, Any],
        capsys: pytest.CaptureFixture[str],
    ) -> None:
        """--type filter without ISSUE-ID still works as before."""
        _write_config(temp_project_dir, sample_config)
        bugs_dir = temp_project_dir / ".issues" / "bugs"
        bugs_dir.mkdir(parents=True, exist_ok=True)
        features_dir = temp_project_dir / ".issues" / "features"
        features_dir.mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "completed").mkdir(parents=True, exist_ok=True)
        (temp_project_dir / ".issues" / "deferred").mkdir(parents=True, exist_ok=True)

        _make_issue(bugs_dir, "P1-BUG-001-bug-one.md", "Bug One")
        _make_issue(features_dir, "P3-FEAT-873-feature.md", "Feature")

        with patch.object(
            sys,
            "argv",
            ["ll-issues", "refine-status", "--type", "BUG", "--config", str(temp_project_dir)],
        ):
            from little_loops.cli import main_issues

            result = main_issues()

        assert result == 0
        out = capsys.readouterr().out
        assert "BUG-001" in out
        assert "FEAT-873" not in out
