#!/usr/bin/env python3
"""Групування завдань Bibletime за механікою виконання.

Питання, на яке відповідає цей скрипт: **що дитина фізично робить** —
пише в рамку, вписує літери в клітинки, обводить один варіант, обводить
кілька, фарбує гліф, фарбує малюнок, проводить лінію. Саме механіка, а не
тема, визначає, чи можна зробити завдання інтерактивним і яким типом поля.

Правила впорядковані: перше, що збіглося, виграє. Порядок має значення —
«Colour the correct words» це вибір, а не розмальовка, тому вибір перевіряється
раніше.

    .venv/bin/python ua_build/inventory_types.py data/inventory_en.json
"""
import argparse
import collections
import json
import pathlib
import re

# (код, придатність до поля форми, опис, регулярний вираз)
#   поле: yes  — робиться полем форми
#         part — робиться, але з застереженням
#         no   — на екрані не виконується
RULES = [
    ("wordsearch",  "yes",  "філворд: знайти слова в сітці",
     r"wordsearch|word search|hidden in the (grid|wordsearch)"),
    ("acrostic",    "no",   "перша літера кожного предмета (назви задані картинками)",
     r"first letter of each|initial letter of each"),
    ("join-line",   "no",   "з'єднати лінією / провести лінію",
     r"\bjoin\b.*\b(dot|line|up)|draw a (straight )?line|link up|match .* with a line"),
    ("draw",        "no",   "намалювати",
     r"^draw(?! a (straight )?line)|draw (some|four|more|pictures|a picture)"),
    ("choose",      "yes",  "обвести / підкреслити / позначити ПРАВИЛЬНИЙ варіант",
     r"(circle|underline|tick|colou?r|shade|highlight)\b.*\b(correct|right|"
     r"true|best)\b|circle the word|circle what"),
    ("cross-out",   "yes",  "викреслити неправильні",
     r"cross out|cross through|delete the"),
    ("yes-no",      "yes",  "ТАК/НІ або ПРАВДА/НЕПРАВДА",
     r"\byes or no\b|true or false|tick .*(✓|cross)|\bt or f\b|"
     r"put a tick .* or a cross"),
    ("paint-words", "yes",  "розфарбувати надруковані слова/літери (контурний текст)",
     r"colou?r (in )?(and learn )?(the |these |this )?"
     r"(word|words|verse|letter|letters|key verse|squares?|dotted)"),
    ("cells",       "yes",  "вписати літери в клітинки (кросворд, сітка, пропущені літери)",
     r"missing letters|word grid|crossword|puzzle to fill|"
     r"complete the words|fill in the (letters|blanks)"),
    ("unjumble",    "yes",  "переставити літери / слова",
     r"unjumble|jumbled|rearrange|unscramble"),
    ("order",       "yes",  "пронумерувати / поставити в порядку",
     r"\bnumber the\b|in the (correct|right) order|put .* in order"),
    ("word-bank",   "yes",  "вставити слова з банку слів",
     r"word bank|from the (following |list of )?words|choose answers|"
     r"using words from|words below to complete|"
     r"(use|put) (the |these |the following )?(words|key verses?|story)\b.*"
     r"(complete|fill|blank|place|space)"),
    # Механіки, що діють на ілюстрації, а не на тексті. Саме їх закриває
    # тип `art` (§3.2a): область малюнка стає клікабельною.
    ("art-circle",  "part", "обвести предмет НА МАЛЮНКУ",
     r"^(circle|put a (red |blue )?circle (a)?round|draw a circle (a)?round)"),
    ("art-paint",   "part", "зафарбувати названий предмет на малюнку "
                            "(колір задано в інструкції)",
     r"^(colou?r|shade)\b"),
    ("art-label",   "part", "підписати предмет на малюнку / карті",
     r"^label\b"),
    ("write",       "yes",  "написати відповідь у рамку або на лінійку",
     r"^(write|answer|complete|fill in|name|list|summarise|explain|describe|"
     r"find|give|count|copy|tell|finish|state|say)|"
     r"^(what|who|why|when|where|which|how|whose)\b"),
    ("look-up",     "part", "знайти у Біблії / прочитати",
     r"^(read|look up|look back|look at)"),
    ("learn",       "no",   "вивчити напам'ять",
     r"^learn|learn the key verse|memorise"),
]
COMPILED = [(c, f, d, re.compile(p, re.I)) for c, f, d, p in RULES]


def classify(text):
    for code, fit, _desc, rx in COMPILED:
        if rx.search(text):
            return code, fit
    return "?", "?"


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("json")
    ap.add_argument("--unknown", default=None,
                    help="куди виписати нерозпізнані формулювання")
    args = ap.parse_args()

    recs = json.loads(pathlib.Path(args.json).read_text(encoding="utf-8"))

    per_type = collections.Counter()
    per_type_level = collections.defaultdict(collections.Counter)
    fit_of = {}
    unknown = collections.Counter()
    books_with = collections.defaultdict(set)

    for r in recs:
        for t in r["tasks"]:
            code, fit = classify(t["text"])
            per_type[code] += 1
            per_type_level[code][r["level"]] += 1
            fit_of[code] = fit
            books_with[code].add(r["file"])
            if code == "?":
                unknown[t["text"]] += 1

    total = sum(per_type.values())
    desc = {c: d for c, _f, d, _p in RULES}

    print(f"\nзавдань усього: {total}   буклетів: {len(recs)}\n")
    head = f"{'тип':<13}{'поле':<6}{'разом':>6}" + "".join(
        f"{'L'+str(i):>6}" for i in range(5)) + f"{'буклетів':>9}  опис"
    print(head)
    print("-" * len(head))
    for code, n in per_type.most_common():
        row = f"{code:<13}{fit_of[code]:<6}{n:>6}"
        row += "".join(f"{per_type_level[code].get(i, 0):>6}" for i in range(5))
        row += f"{len(books_with[code]):>9}  {desc.get(code, '—')}"
        print(row)

    ok = sum(n for c, n in per_type.items() if fit_of[c] == "yes")
    part = sum(n for c, n in per_type.items() if fit_of[c] == "part")
    no = sum(n for c, n in per_type.items() if fit_of[c] == "no")
    unk = per_type.get("?", 0)
    print(f"\nполем форми робиться:      {ok:>5}  ({ok/total:.0%})")
    print(f"робиться з застереженням:  {part:>5}  ({part/total:.0%})")
    print(f"на екрані не виконується:  {no:>5}  ({no/total:.0%})")
    print(f"не розпізнано:             {unk:>5}  ({unk/total:.0%})")

    if unknown:
        print(f"\nнайчастіші нерозпізнані ({len(unknown)} різних):")
        for s, n in unknown.most_common(25):
            print(f"  {n:>4}  {s[:95]}")
    if args.unknown:
        pathlib.Path(args.unknown).write_text(
            "\n".join(f"{n}\t{s}" for s, n in unknown.most_common()),
            encoding="utf-8")
        print(f"\n→ {args.unknown}")


if __name__ == "__main__":
    main()
