#!/usr/bin/env python3
"""exp16 — banknote-v1 in-band footnote channel: format, instruction, parser (research/20 §B3). miner-v1.

The translator, after the translation, may emit a versioned separator + tab-delimited term lines for
NEW terms only (not in the injected glossary block). Code slices the block off BEFORE any gate/editor
(here: the polygon reads the parsed artifact; the editor never sees the block). Parser is tolerant of a
truncated final line (flags banknote_truncated) and flags malformed residual (banknote_parse_fail).

Separator ⟦TM-BANK-v1⟧ is chosen to (a) not occur in natural text, (b) NOT be the words
'Примечание/Сноска/Комментарий' which the backend sanitizer's trailing-note class reserves (§B3-1).
"""
from __future__ import annotations

import re

SEP = "⟦TM-BANK-v1⟧"
MAX_LINES = 12   # max banknote lines budgeted per chunk (§B3-5 max_tokens sizing)

# instruction appended to the translator system prompt for the banknote (config-b) arm.
BANKNOTE_INSTRUCTION = (
    "\n\nПОСЛЕ полного перевода, если в этом фрагменте встретились НОВЫЕ имена собственные, "
    "топонимы или уникальные термины мира книги (которых нет в уже данном тебе глоссарии, если он "
    "был), добавь В САМОМ КОНЦЕ ответа ОТДЕЛЬНЫЙ блок ровно в таком формате:\n"
    f"{SEP}\n"
    "исходный_термин<TAB>твой_перевод<TAB>тип\n"
    "где тип — одно из: name (имя), place (топоним), title (титул/ранг), term (термин). Каждый термин "
    "на своей строке, поля разделены символом табуляции. Не более " + str(MAX_LINES) + " строк, только "
    "самые важные новые термины. Если новых терминов нет — НЕ добавляй ни блок, ни разделитель. "
    "Сам перевод не должен содержать этот блок внутри себя — только в самом конце."
)

_TYPE_OK = {"name", "place", "title", "term", "nickname"}


def split_banknote(model_output: str):
    """Slice the banknote block off the raw model output. Returns (clean_translation, raw_block_or_'')."""
    idx = model_output.find(SEP)
    if idx < 0:
        return model_output.rstrip(), ""
    clean = model_output[:idx].rstrip()
    block = model_output[idx + len(SEP):].strip("\n")
    return clean, block


def parse_banknote(block: str, truncated_generation: bool = False):
    """Parse the tab-delimited block. Returns (entries, flags).
    entries: [{'src','dst','type'}]. flags: {'n_lines','parse_fail','truncated'}."""
    entries, bad = [], 0
    flags = {"n_banknote_lines": 0, "banknote_parse_fail": False, "banknote_truncated": False}
    if not block.strip():
        return entries, flags
    lines = [ln for ln in block.split("\n") if ln.strip()]
    for i, ln in enumerate(lines):
        # tolerate the LAST line being truncated by generation length (§B3-5)
        parts = re.split(r"\t| {2,}|\s*\|\s*", ln.strip())
        parts = [p.strip() for p in parts if p.strip()]
        if len(parts) < 2:
            if i == len(lines) - 1 and truncated_generation:
                flags["banknote_truncated"] = True
                continue
            bad += 1
            continue
        src, dst = parts[0], parts[1]
        typ = parts[2].lower() if len(parts) >= 3 else "term"
        if typ not in _TYPE_OK:
            typ = "term"
        # src should contain a Han char (this is a zh->ru channel); else malformed
        if not re.search(r"[㐀-鿿]", src):
            bad += 1
            continue
        entries.append({"src": src, "dst": dst, "type": typ})
    flags["n_banknote_lines"] = len(entries)
    if bad > 0:
        flags["banknote_parse_fail"] = True
    return entries, flags


if __name__ == "__main__":
    demo = ("Перевод фрагмента... последнее предложение.\n\n"
            f"{SEP}\n方源\tФан Юань\tname\n蛊师\tгу-мастер\ttitle\n青茅山\tгора Цинмао\tplace")
    clean, block = split_banknote(demo)
    ents, flags = parse_banknote(block)
    print("clean tail:", repr(clean[-40:]))
    print("entries:", ents)
    print("flags:", flags)
    # truncated last line
    demo2 = f"текст\n{SEP}\n方源\tФан Юань\tname\n蛊"
    _, b2 = split_banknote(demo2)
    print("\ntruncated:", parse_banknote(b2, truncated_generation=True))
