"""Phase 2.6 — tokenize + pack cleaned corpus → data/cpt_packed/ (HF Arrow)

Concatenates all data/cpt_clean/*.jsonl, tokenizes with the Qwen3 tokenizer,
inserts EOS between docs, and packs into fixed 2048-token blocks (ragged tail
dropped). Output columns: input_ids (+ labels==input_ids added by the collator
at train time). corpus_pack is the final corpus artifact train_cpt.py loads.

Usage:  python corpus_pack.py
"""
import json

from datasets import Dataset
from transformers import AutoTokenizer

from corpus_common import CLEAN, PACKED

MODEL = "Qwen/Qwen3-1.7B-Base"
BLOCK = 2048


def iter_texts():
    for fp in sorted(CLEAN.glob("*.jsonl")):
        with open(fp, encoding="utf-8") as f:
            for ln in f:
                ln = ln.strip()
                if ln:
                    yield json.loads(ln)["text"]


def main():
    tok = AutoTokenizer.from_pretrained(MODEL)
    eos = tok.eos_token_id
    if eos is None:
        raise SystemExit("tokenizer has no eos_token_id")

    buf = []
    blocks = []
    n_docs = 0
    for text in iter_texts():
        buf.extend(tok(text, add_special_tokens=False)["input_ids"])
        buf.append(eos)
        n_docs += 1
        while len(buf) >= BLOCK:
            blocks.append(buf[:BLOCK])
            buf = buf[BLOCK:]
        if n_docs % 10000 == 0:
            print(f"[pack] {n_docs} docs -> {len(blocks)} blocks "
                  f"({len(blocks)*BLOCK/1e6:.0f}M tok)")

    ds = Dataset.from_dict({"input_ids": blocks})
    ds.save_to_disk(str(PACKED))
    total_tok = len(blocks) * BLOCK
    print(f"[pack] DONE {n_docs} docs -> {len(blocks)} blocks = {total_tok/1e6:.0f}M tokens")
    print(f"[pack] saved {PACKED}  (target ~300M ± 50M)")

    # sanity: decode a random block, must read as Russian
    import random
    sample = tok.decode(random.choice(blocks))
    print(f"\n--- random packed block (verify Russian) ---\n{sample[:400]}")


if __name__ == "__main__":
    main()
