"""errors-*.jsonl 글롭·파싱·집계 공용 코어 (read-only).

error_report(단일 task)와 error_zip(cross-project)이 공유한다.
errorType 값에 분기하지 않는 data-driven 집계.
"""
from __future__ import annotations

import json
from pathlib import Path

from .paths import runs_dir_of


def glob_error_logs(task_root: Path) -> list[Path]:
    runs = runs_dir_of(task_root)
    if not runs.exists():
        return []
    flat = runs.glob("*/logs/errors-*.jsonl")
    staged = runs.glob("*/stage-*/logs/errors-*.jsonl")
    return sorted(set(flat) | set(staged))


def parse_records(paths: list[Path]) -> tuple[list[dict], int]:
    records: list[dict] = []
    skipped = 0
    for path in paths:
        # cross-project zip 은 글로벌 run-index 를 순회하므로, 깨지거나 비-UTF8/잘린
        # 로그 한 개가 전체 수집을 중단시키지 않도록 해당 파일만 건너뛴다.
        try:
            text = path.read_text(encoding="utf-8")
        except (OSError, UnicodeDecodeError):
            skipped += 1
            continue
        for line in text.splitlines():
            line = line.strip()
            if not line:
                continue
            try:
                rec = json.loads(line)
            except Exception:
                skipped += 1
                continue
            if isinstance(rec, dict):
                rec["_sourceLog"] = str(path)
                records.append(rec)
            else:
                skipped += 1
    return records, skipped


def tally(records: list[dict], key: str) -> dict:
    out: dict[str, int] = {}
    for rec in records:
        name = str(rec.get(key, ""))
        out[name] = out.get(name, 0) + 1
    return out


def rows(counts: dict, label: str) -> list[dict]:
    return [
        {label: name, "count": n}
        for name, n in sorted(counts.items(), key=lambda kv: (-kv[1], kv[0]))
    ]


def aggregate(records: list[dict]) -> dict:
    return {
        "errorCount": len(records),
        "runCount": len({rec.get("_sourceLog", "") for rec in records}),
        "byErrorType": tally(records, "errorType"),
        "bySource": tally(records, "source"),
        "byPhase": rows(tally(records, "phase"), "phase"),
        "byAgent": rows(tally(records, "agent"), "agent"),
    }
