#!/usr/bin/env python3
"""Build a deterministic punctuation-balanced fine-tuning corpus."""

from __future__ import annotations

import argparse
import hashlib
import heapq
import json
import sys
from pathlib import Path


CATEGORIES = {
    "question": lambda text: "?" in text,
    "exclamation": lambda text: "!" in text,
    "quotes": lambda text: "«" in text or "»" in text,
    "ellipsis": lambda text: "…" in text or "..." in text,
    "neutral": lambda text: not any(mark in text for mark in ("?", "!", "«", "»", "…", "...")),
}


def build(args: argparse.Namespace) -> None:
    if args.output.exists() or args.metadata.exists():
        raise FileExistsError("Refusing to overwrite output or metadata")
    excluded = set()
    if args.exclude:
        excluded = {line.strip() for line in args.exclude.read_text(encoding="utf-8-sig").splitlines() if line.strip()}
    heaps: dict[str, list[tuple[int, str]]] = {name: [] for name in CATEGORIES}
    scanned = 0
    for text in args.source.read_text(encoding="utf-8").splitlines():
        text = text.strip()
        if not text or text in excluded:
            continue
        scanned += 1
        for name, predicate in CATEGORIES.items():
            if not predicate(text):
                continue
            score = int.from_bytes(hashlib.blake2b(f"{name}\0{text}".encode("utf-8"), digest_size=8).digest(), "big")
            heap = heaps[name]
            if len(heap) < args.per_category:
                heapq.heappush(heap, (-score, text))
            elif score < -heap[0][0]:
                heapq.heapreplace(heap, (-score, text))
    selected: dict[str, set[str]] = {
        name: {text for _, text in heap} for name, heap in heaps.items()
    }
    combined = sorted(set().union(*selected.values()), key=lambda text: hashlib.sha256(text.encode("utf-8")).digest())
    args.output.write_text("\n".join(combined) + "\n", encoding="utf-8")
    metadata = {
        "source": str(args.source), "exclude": str(args.exclude) if args.exclude else None,
        "scanned": scanned, "per_category_limit": args.per_category,
        "category_counts": {name: len(values) for name, values in selected.items()},
        "unique_sentences": len(combined), "output_bytes": args.output.stat().st_size,
    }
    args.metadata.write_text(json.dumps(metadata, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps(metadata, ensure_ascii=False, indent=2))


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("--source", type=Path, required=True)
    parser.add_argument("--exclude", type=Path)
    parser.add_argument("--output", type=Path, required=True)
    parser.add_argument("--metadata", type=Path, required=True)
    parser.add_argument("--per-category", type=int, default=4000)
    return parser.parse_args()


if __name__ == "__main__":
    sys.exit(build(parse_args()) or 0)
