#!/usr/bin/env python3
from __future__ import annotations

import argparse
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable, List, Optional
import re

ALLOWED_PROJ = {
    "CHORUS",
    "COOEE",
    "DHT",
    "SHHH",
    "KACHING",
    "HMMM",
    "UCXL",
    "SLURP",
    "RUSTLE",
    "WHOOSH",
    "BUBBLE",
}
ALLOWED_CAT = {"REQ", "INT", "SEC", "OBS", "PER", "COMP"}

REQ_PATTERN = re.compile(r"REQ:\s*([A-Z]+)-([A-Z]+)-(\d{3})")
UCXL_PATTERN = re.compile(r"UCXL:\s*ucxl://")

SKIP_DIRS = {
    ".git",
    "node_modules",
    "dist",
    "build",
    "venv",
    "__pycache__",
    ".venv",
    "target",
}


@dataclass
class Finding:
    path: Path
    line_no: int
    severity: str
    message: str
    line: str


def iter_files(paths: Iterable[Path]) -> Iterable[Path]:
    for p in paths:
        if p.is_dir():
            for sub in p.rglob("*"):
                if sub.is_dir():
                    if sub.name in SKIP_DIRS:
                        continue
                    continue
                yield sub
        elif p.is_file():
            yield p


def validate_file(path: Path, require_ucxl: bool, max_distance: int) -> List[Finding]:
    findings: List[Finding] = []
    try:
        text = path.read_text(errors="ignore")
    except Exception as e:
        findings.append(Finding(path, 0, "warn", f"unable to read file: {e}", line=""))
        return findings

    lines = text.splitlines()
    for idx, line in enumerate(lines, start=1):
        m = REQ_PATTERN.search(line)
        if not m:
            continue
        proj, cat, _ = m.group(1), m.group(2), m.group(3)
        if proj not in ALLOWED_PROJ:
            findings.append(Finding(path, idx, "error", f"unknown PROJ '{proj}' in ID", line=line))
        if cat not in ALLOWED_CAT:
            findings.append(Finding(path, idx, "error", f"unknown CAT '{cat}' in ID", line=line))
        if require_ucxl:
            start = max(1, idx - max_distance)
            end = min(len(lines), idx + max_distance)
            window = lines[start - 1 : end]
            if not any(UCXL_PATTERN.search(l) for l in window):
                findings.append(
                    Finding(
                        path,
                        idx,
                        "error",
                        f"missing nearby UCXL backlink (±{max_distance} lines)",
                        line=line,
                    )
                )
    return findings


def cmd_check(args: argparse.Namespace) -> int:
    paths = [Path(p) for p in args.paths]
    all_findings: List[Finding] = []
    for f in iter_files(paths):
        try:
            if f.stat().st_size > 3_000_000:
                continue
        except Exception:
            pass
        all_findings.extend(
            validate_file(f, require_ucxl=args.require_ucxl, max_distance=args.max_distance)
        )
    if all_findings:
        for fd in all_findings:
            print(f"{fd.path}:{fd.line_no}: {fd.severity}: {fd.message}")
            if fd.line:
                print(f"    {fd.line.strip()}")
        return 1
    return 0


def build_argparser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(prog="speclint-check", description="Suite 2.0.0 traceability linter (WHOOSH local)")
    sub = p.add_subparsers(dest="cmd", required=True)
    c = sub.add_parser("check", help="validate requirement IDs and UCXL backlinks")
    c.add_argument("paths", nargs="+", help="files or directories to scan")
    c.add_argument("--require-ucxl", action="store_true", help="require nearby UCXL backlink")
    c.add_argument("--max-distance", type=int, default=5, help="line distance for UCXL proximity check")
    c.set_defaults(func=cmd_check)
    return p


def main(argv: Optional[list[str]] = None) -> int:
    try:
        args = build_argparser().parse_args(argv)
        return args.func(args)
    except Exception as e:
        print(f"speclint-check: internal error: {e}", file=sys.stderr)
        return 2


if __name__ == "__main__":
    sys.exit(main())

