"""Simplification — file collection and diff utilities."""

from __future__ import annotations

import subprocess
from typing import TYPE_CHECKING

from mantle.core import issues

if TYPE_CHECKING:
    from pathlib import Path


# ── Exception ────────────────────────────────────────────────────


class NoCommitsFoundError(Exception):
    """Raised when no commits match the issue pattern.

    Attributes:
        issue_number: The issue number that had no matching commits.
    """

    def __init__(self, issue_number: int) -> None:
        self.issue_number = issue_number
        super().__init__(f"No commits found matching issue {issue_number}")


# ── Constants ────────────────────────────────────────────────────


LLM_BLOAT_CHECKLIST: str = """\
## Bloat Pattern Checklist

1. **Unnecessary abstractions** — interfaces, base classes, \
or wrapper layers that have only one implementation
2. **Defensive over-engineering** — error handling, retries, \
or fallback paths that the current requirements don't need
3. **Code duplication** — repeated logic that could be a \
single function or shared constant
4. **Unnecessary conditionals** — branches that are always \
true/false given current usage
5. **Dead code** — functions, variables, or imports that are \
never called or used
6. **Comment noise** — restating what the code already says, \
or TODO/FIXME with no actionable context
7. **Slop scaffolding** — boilerplate, config, or glue code \
generated by LLMs that adds no value
8. **Over-parameterisation** — function arguments or config \
options that are never varied in practice
"""


# ── Public API ───────────────────────────────────────────────────


def collect_issue_files(
    project_root: Path,
    issue: int,
) -> tuple[str, ...]:
    """Collect files changed by an issue's implementation.

    Loads the issue to verify it exists, then finds commits
    matching the conventional commit pattern ``feat(issue-N):``
    and collects all files changed across those commits.

    Args:
        project_root: Directory containing .mantle/.
        issue: Issue number to collect files for.

    Returns:
        Tuple of file paths relative to project root.

    Raises:
        NoCommitsFoundError: If no matching commits exist.
        FileNotFoundError: If the issue file does not exist.
    """
    issue_path = issues.find_issue_path(project_root, issue)
    if issue_path is None:
        msg = f"Issue {issue} not found"
        raise FileNotFoundError(msg)
    issues.load_issue(issue_path)

    result = subprocess.run(
        [
            "git",
            "log",
            "--oneline",
            f"--grep=issue-{issue}",
            "--format=%H",
        ],
        capture_output=True,
        text=True,
        check=True,
        cwd=project_root,
    )

    commit_hashes = result.stdout.strip().splitlines()
    if not commit_hashes:
        raise NoCommitsFoundError(issue)

    first_commit = commit_hashes[-1]
    last_commit = commit_hashes[0]

    diff_result = subprocess.run(
        [
            "git",
            "diff",
            "--name-only",
            f"{first_commit}^..{last_commit}",
        ],
        capture_output=True,
        text=True,
        check=True,
        cwd=project_root,
    )

    files = [f for f in diff_result.stdout.strip().splitlines() if f]
    return tuple(files)


def collect_changed_files(
    project_root: Path,
) -> tuple[str, ...]:
    """Collect all changed files in the working tree.

    Includes staged changes, unstaged changes, and untracked
    files (excluding ignored files).

    Args:
        project_root: Directory containing the git repo.

    Returns:
        Tuple of file paths relative to project root.
    """
    diff_result = subprocess.run(
        ["git", "diff", "--name-only", "HEAD"],
        capture_output=True,
        text=True,
        check=True,
        cwd=project_root,
    )

    untracked_result = subprocess.run(
        ["git", "ls-files", "--others", "--exclude-standard"],
        capture_output=True,
        text=True,
        check=True,
        cwd=project_root,
    )

    all_files: set[str] = set()
    for line in diff_result.stdout.strip().splitlines():
        if line:
            all_files.add(line)
    for line in untracked_result.stdout.strip().splitlines():
        if line:
            all_files.add(line)

    return tuple(sorted(all_files))
