# Copyright (C) 2026 GoodData Corporation

"""Helpers for grouping and truncating large error outputs."""

from __future__ import annotations

import re

_STACKTRACE_ANCHOR_RE = re.compile(r"(AssertionError|^Error:|^ERROR:|^FAILED:|Traceback|Exception)", re.IGNORECASE)
_EXPECTED_ACTUAL_RE = re.compile(r"^(Expected|Actual):")


def get_max_error_lines(command: str) -> int:
    """
    Get maximum error lines to show based on command type.

    Tool-specific limits based on typical error verbosity and usefulness:
    - Linting: 100 lines (compact format, many similar errors)
    - Type checking: 60 lines (medium verbosity, related errors)
    - Tests: 40 lines (detailed per-test output)
    - Build/compile: 30 lines (often cascading from one root cause)
    """
    command_lower = command.lower()

    if "ruff" in command_lower or "lint" in command_lower:
        return 100
    if "pyright" in command_lower or "mypy" in command_lower or "type" in command_lower:
        return 60
    if "test" in command_lower or "pytest" in command_lower:
        return 40
    if (
        "gradle" in command_lower
        or "spotless" in command_lower
        or "detekt" in command_lower
        or "docker" in command_lower
    ):
        return 30
    return 50


def find_primary_error_index(lines: list[str]) -> int | None:
    """
    Locate the first high-signal error line to anchor truncated output.

    Some toolchains interleave long progress output before the real failure.
    Centering on the first error ensures the snippet shows
    actionable details instead of only the start/end of the log.
    """
    keyword_hits = [
        "error",
        "failed",
        "failure",
        "exception",
        "traceback",
        "npm err",
        "pnpm err",
        "module not found",
        "cannot find module",
        "_phase:build",
        "_phase:test",
        "phase:build",
        "phase:test",
    ]

    for idx, line in enumerate(lines):
        lower_line = line.lower()
        if any(keyword in lower_line for keyword in keyword_hits):
            return idx

        if "✗" in line or "×" in line or "FAIL" in line or "ERROR" in line:
            return idx

    return None


def group_error_lines(lines: list[str]) -> list[tuple[int, int]]:
    """
    Group error lines into semantic blocks to preserve multi-line context.

    Returns list of (start_index, end_index) tuples representing error groups.
    """
    groups: list[tuple[int, int]] = []
    i = 0

    while i < len(lines):
        line = lines[i]

        # Stack trace or assertion
        if _STACKTRACE_ANCHOR_RE.search(line):
            start = i
            i += 1
            while i < len(lines) and (
                lines[i].startswith("  ") or lines[i].startswith("\t") or "at " in lines[i] or "File " in lines[i]
            ):
                i += 1
            groups.append((start, i))
            continue

        # Expected/Actual blocks
        if _EXPECTED_ACTUAL_RE.search(line):
            start = i
            i += 1
            while (
                i < len(lines)
                and lines[i]
                and not _STACKTRACE_ANCHOR_RE.search(lines[i])
                and not _EXPECTED_ACTUAL_RE.search(lines[i])
            ):
                i += 1
            groups.append((start, i))
            continue

        # Single line
        groups.append((i, i + 1))
        i += 1

    return groups


def format_grouped_errors(error_lines: list[str], groups: list[tuple[int, int]], max_lines: int) -> list[str]:
    """Format error lines with group-aware truncation."""
    first_target = int(max_lines * 0.8)
    last_target = max_lines - first_target

    shown_lines: list[str] = []
    current_count = 0
    first_group_idx = 0

    for i, (start, end) in enumerate(groups):
        group_size = end - start
        if current_count + group_size <= first_target:
            shown_lines.extend(error_lines[start:end])
            current_count += group_size
            first_group_idx = i + 1
        else:
            break

    last_group_idx = len(groups)
    result: list[str] = []

    if first_group_idx < len(groups):
        last_lines: list[list[str]] = []
        last_count = 0
        for i in range(len(groups) - 1, first_group_idx - 1, -1):
            start, end = groups[i]
            group_size = end - start
            if last_count + group_size <= last_target:
                last_lines.insert(0, error_lines[start:end])
                last_count += group_size
                last_group_idx = i
            else:
                break

        omitted_groups = last_group_idx - first_group_idx
        omitted_lines = sum(end - start for start, end in groups[first_group_idx:last_group_idx])

        result.extend(shown_lines)
        result.append("")
        result.append(f"... ({omitted_lines} lines in {omitted_groups} error groups omitted) ...")
        result.append("Fix the errors above and re-run validation to see remaining issues")
        result.append("")
        for group_lines in last_lines:
            result.extend(group_lines)
    else:
        result.extend(shown_lines)

    return result
