#!/usr/bin/env python3
"""Policz własne pliki UTF-8 tymi samymi przypiętymi tokenizerami co badanie.

Instalacja: python -m pip install -r requirements.txt
Przykład: python mierz_wlasny_tekst.py dokument.txt --output /tmp/moje-tokeny.json
Licencja: CC BY 4.0, Robert Marczyński, ekspertodsztucznejinteligencji.pl.
"""

import argparse
import importlib.metadata
import json
import os
from pathlib import Path
import platform
import re
import sys
import unicodedata

from mierz_tokenizery import KATALOG, sha256, sprawdz_sha


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("pliki", type=Path, nargs="+", help="Niepuste pliki tekstowe UTF-8.")
    parser.add_argument("--output", type=Path, required=True, help="JSON poza katalogiem badania.")
    parser.add_argument("--cache-dir", type=Path, help="Cache plików HF; domyślnie HF_HOME/hub.")
    parser.add_argument("--offline", action="store_true", help="Użyj wyłącznie już pobranych tokenizerów.")
    args = parser.parse_args()
    output_path = args.output.resolve()
    if output_path.is_relative_to(KATALOG):
        parser.error("--output musi wskazywać miejsce poza katalogiem zamrożonego badania.")
    if output_path in {path.resolve() for path in args.pliki}:
        parser.error("Plik wynikowy nie może nadpisać pliku wejściowego.")

    texts, corpus = [], []
    for path in args.pliki:
        raw = path.read_bytes()
        original = raw.decode("utf-8", errors="strict")
        text = unicodedata.normalize("NFC", original)
        if not text:
            parser.error(f"Plik jest pusty: {path}")
        texts.append(text)
        corpus.append({
            "plik": str(path),
            "sha256": sha256(raw),
            "bajty": len(raw),
            "znaki": len(text),
            "sha256_utf8_po_nfc": sha256(text.encode("utf-8")),
            "nfc_zmienilo_tekst": text != original,
        })

    os.environ["HF_HUB_DISABLE_IMPLICIT_TOKEN"] = "1"
    os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
    os.environ["HF_HUB_DISABLE_XET"] = "1"
    os.environ["TOKENIZERS_PARALLELISM"] = "false"
    if args.offline:
        os.environ["HF_HUB_OFFLINE"] = "1"
    from huggingface_hub import hf_hub_download
    from tokenizers import Tokenizer

    manifest_bytes = (KATALOG / "manifest.json").read_bytes()
    manifest = json.loads(manifest_bytes)
    results = []
    for model in manifest["modele"]:
        repo, revision = model["repozytorium"], model["rewizja"]
        if not re.fullmatch(r"[0-9a-f]{40}", revision) or model["gated"] is not False:
            raise ValueError(f"Wymagany publiczny tokenizer z pełnym SHA commita: {repo}")
        print(f"{repo}@{revision}", file=sys.stderr, flush=True)
        path = hf_hub_download(
            repo_id=repo, filename="tokenizer.json", revision=revision,
            token=False, local_files_only=args.offline, cache_dir=args.cache_dir,
        )
        raw = Path(path).read_bytes()
        sprawdz_sha(raw, model["tokenizer_sha256"], f"{repo}/tokenizer.json")
        if json.loads(raw).get("model", {}).get("dropout") not in (None, 0):
            raise ValueError(f"Losowy dropout w tokenizerze: {repo}")
        tokenizer = Tokenizer.from_file(str(path))
        tokenizer.no_padding()
        tokenizer.no_truncation()
        counts = []
        for info, text in zip(corpus, texts):
            encoded = tokenizer.encode(text, add_special_tokens=False)
            if encoded.overflowing or not all(encoded.attention_mask):
                raise ValueError(f"Obcięcie lub padding: {repo}, {info['plik']}")
            counts.append({
                "plik": info["plik"],
                "znaki": len(text),
                "tokeny": len(encoded.ids),
                "tokeny_na_1000_znakow": 1000 * len(encoded.ids) / len(text),
            })
        results.append({
            "nazwa": model["nazwa"], "repozytorium": repo, "rewizja": revision,
            "tokenizer_sha256": model["tokenizer_sha256"], "pliki": counts,
        })

    output = {
        "wersja_formatu": 1,
        "rodzaj_pomiaru": "Własne pliki; wynik odrębny od zamrożonego badania",
        "data_zamrozenia_tokenizerow": manifest["data_zamrozenia_zbioru"],
        "manifest_sha256": sha256(manifest_bytes),
        "srodowisko": {
            "python": platform.python_version(),
            "tokenizers": importlib.metadata.version("tokenizers"),
            "huggingface_hub": importlib.metadata.version("huggingface-hub"),
            "unicode": unicodedata.unidata_version,
        },
        "metoda": {
            "wejscie": "UTF-8, strict, zachowane białe znaki; NFC przed pomiarem",
            "znaki": "len(str) po NFC, punkty kodowe Unicode",
            "tokenizacja": "tokenizers.Tokenizer.from_file(tokenizer.json)",
            "add_special_tokens": False, "szablon_czatu": False,
            "padding": False, "truncation": False,
            "normalizer_tokenizera": "Zachowany z tokenizer.json",
            "tokeny_na_1000_znakow": "1000 * tokeny / znaki",
        },
        "korpus": corpus,
        "modele": results,
    }
    args.output.write_bytes((json.dumps(output, ensure_ascii=False, indent=2) + "\n").encode("utf-8"))
    print(f"Zapisano {args.output}", file=sys.stderr)


if __name__ == "__main__":
    try:
        main()
    except Exception as error:
        print(f"Błąd: {error}", file=sys.stderr)
        sys.exit(1)
