#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Правки Редактора як дані: накладаються на переклад без участі розробника.

Панель зберігає правки в `editor/notes_<стем>.json` — по запису на блок, із
початковим текстом (`uk`) і виправленим (`note`). Більшість із них — це
виправлене слово чи фраза, і щоб їх застосувати, модель не потрібна: досить
підставити новий текст у наявну специфікацію, лишивши стилі, координати й
розбивку на сегменти як були.

    from edits import apply_notes
    skipped = apply_notes(TR, 'editor/notes_Level4_A12_UA_v01.json')

**Що застосовується само, а що ні.** Правило одне: беремо лише те, де
підстановка ОДНОЗНАЧНА. Якщо в рядку кілька сегментів (різні стилі, свої
якорі x) або змінилася кількість рядків — незрозуміло, який шматок куди
покласти, і вгадувати не можна: помилка тут тиха й доїде до друкарні.
Такі правки лишаються розробникові з написаною причиною.

Біблійні цитати навмисно НЕ відсіюються тут: правку застосовуємо, а потім
`quotes_check.py` звіряє її з Огієнком і валить збірку, якщо не збіглося.
Так Редактор може виправити цитату сам — але лише на правильну.
"""
import json
import os
import re

STYLE_RE = re.compile(r'\{s\d+\}')


def _lines_of(spec):
    """Рядки специфікації у вигляді, придатному для підстановки."""
    out = []
    for entry in spec['lines']:
        if entry is None:
            out.append(None)
            continue
        segs = entry['segs'] if isinstance(entry, dict) else entry
        out.append(segs)
    return out


def classify(spec, new_text):
    """Чи можна підставити цей текст самотужки. -> (можна, причина відмови)."""
    if not isinstance(spec, dict):
        return False, 'блока немає в перекладі'
    if spec.get('drop'):
        return False, 'блок навмисно стерто, тексту в ньому немає'

    if 'flow' in spec:
        if STYLE_RE.search(spec['flow']):
            return False, ('в абзаці є перемикання стилю ({s…}) — незрозуміло, '
                           'де воно має стояти в новому тексті')
        return True, None

    if 'lines' in spec:
        segs = _lines_of(spec)
        real = [s for s in segs if s is not None]
        want = [l for l in new_text.split('\n')]
        if len(want) != len(real):
            return False, ('рядків у правці %d, а в макеті %d — кожен рядок '
                           'стоїть на своїй базовій лінії, автоматично не '
                           'перекладеш' % (len(want), len(real)))
        bad = [i + 1 for i, s in enumerate(real) if len(s) != 1]
        if bad:
            return False, ('рядок %s зібрано з кількох частин різного стилю — '
                           'незрозуміло, як поділити новий текст'
                           % ', '.join(map(str, bad)))
        # Сегмент із третім елементом несе ЗАДАНУ ВРУЧНУ координату x —
        # так набрані центровані рядки (цитата на обкладинці): x пораховано
        # по ширині СТАРОГО тексту. Підставити новий, лишивши стару
        # координату, означає тихо зсунути напис. Візуально це помітно, а
        # геометричний контроль такого зсуву не ловить.
        fixed = [i + 1 for i, s in enumerate(real) if len(s[0]) > 2]
        if fixed:
            return False, ('рядок %s має пораховану координату (центрування '
                           'чи вирівнювання праворуч) — після зміни тексту її '
                           'треба перерахувати. Якщо це просто ліва межа, '
                           'клади її в опцію рядка `x`, а не третім елементом '
                           'сегмента: тоді правка застосується сама'
                           % ', '.join(map(str, fixed)))
        return True, None

    return False, 'невідомий різновид блока'


def apply_one(spec, new_text):
    """Нова специфікація з підставленим текстом. Стилі й координати — старі."""
    if 'flow' in spec:
        out = dict(spec)
        out['flow'] = new_text
        return out

    out = dict(spec)
    lines, want = [], new_text.split('\n')
    i = 0
    for entry in spec['lines']:
        if entry is None:
            lines.append(None)
            continue
        segs = entry['segs'] if isinstance(entry, dict) else entry
        seg = segs[0]
        # Сегмент — кортеж (текст, стиль[, x]); міняємо лише текст.
        new_seg = (want[i],) + tuple(seg[1:])
        if isinstance(entry, dict):
            e = dict(entry)
            e['segs'] = [new_seg]
            lines.append(e)
        else:
            lines.append([new_seg])
        i += 1
    out['lines'] = lines
    return out


def apply_notes(TR, path):
    """Накласти правки на `TR`. -> перелік того, що лишилося людині.

    У записі два різні поля, і плутати їх не можна:

    * `note` — виправлений ТЕКСТ блока. Його підставляємо в урок;
    * `comment` — зауваження Редактора («перевірте цитату», «тут інший
      малюнок»). Це не текст уроку, і в збірку воно не потрапляє **ніколи**.

    Доти поле було одне, і зауваження, написане в нього, для однорядкового
    блока проходило як звичайна правка — тобто друкувалося в буклеті
    замість тексту. Геометричний контроль такого не ловить: текст як текст.

    Повертає перелік записів із `kind`:
      'skipped'  — правку не можна підставити однозначно (у `why` причина);
      'comment'  — зауваження, на яке має відповісти розробник.
    """
    if not os.path.exists(path):
        return []
    doc = json.load(open(path, encoding='utf-8'))
    left = []
    for n in doc.get('notes') or []:
        key = n.get('key')
        if not key:
            continue
        text = (n.get('note') or '').strip()
        comment = (n.get('comment') or '').strip()
        spec = TR.get(key)

        if text:
            ok, why = classify(spec, text)
            if ok:
                TR[key] = apply_one(spec, text)
            else:
                left.append({'kind': 'skipped', 'key': key,
                             'page': n.get('page'), 'why': why, 'note': text})
        if comment:
            left.append({'kind': 'comment', 'key': key, 'page': n.get('page'),
                         'comment': comment})
    return left

# ── постійний накладний файл ──────────────────────────────────────────────
#
# Правка, застосована Редактором, доти жила лише у файлі зауважень — а він
# після застосування очищався. Тобто наступна перезбірка уроку з будь-якої
# причини мовчки ВІДКОЧУВАЛА все, що Редактор виправив: у PDF правка була,
# бо той конкретний build її наклав, а в джерелі — ні.
# Так за один день загубилося п'ять правок.
#
# Тепер застосоване лягає в `ua_build/edits_<буклет>.json` і накладається
# при КОЖНІЙ збірці. Файл перекладу лишається тим, що написав розробник;
# те, що виправив Редактор, лежить окремо, видно в git і переглядається.


def overlay_path(stem):
    """Файл постійних правок лежить в `editor/`, а не в `ua_build/`.

    Це дані Редактора, а не код, і на сервері `editor/` — це ТОМ. У
    `ua_build/` файл жив би в шарі контейнера й зникав при кожній
    перезбірці образу; entrypoint кладе поверх тому лише `*.html`, `*.py`
    і `text_*.json`, тож правок він не зачепить.
    """
    root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(root, 'editor', 'edits_%s.json' % stem)


def load_overlay(stem):
    p = overlay_path(stem)
    if not os.path.exists(p):
        return {}
    return json.load(open(p, encoding='utf-8')).get('blocks') or {}


def save_overlay(stem, blocks):
    p = overlay_path(stem)
    doc = {'format': 'bibletime-edits/1', 'lesson': stem,
           'note': 'правки Редактора з панелі; накладаються поверх '
                   'tr_%s.py при кожній збірці' % stem,
           'blocks': blocks}
    tmp = p + '.tmp'
    with open(tmp, 'w', encoding='utf-8') as fh:
        json.dump(doc, fh, ensure_ascii=False, indent=1, sort_keys=True)
    os.replace(tmp, p)
    return p


def apply_overlay(TR, stem):
    """Накласти постійні правки. -> скільки застосовано, перелік відхилених."""
    blocks = load_overlay(stem)
    done, bad = 0, []
    for key, text in sorted(blocks.items()):
        spec = TR.get(key)
        ok, why = classify(spec, (text or '').strip())
        if ok:
            TR[key] = apply_one(spec, text.strip())
            done += 1
        else:
            bad.append((key, why))
    return done, bad


# ── правки Редактора до ЕКРАННОГО тексту ─────────────────────────────────
#
# Блоки, оголошені в `SCREEN_TEXT`, мають два тексти: друкований (у файлі
# перекладу) і екранний. Правити треба обидва, але класти їх в один файл не
# можна: правка екранного тексту, потрапивши в звичайний накладний файл,
# мовчки замінила б ДРУКОВАНИЙ — тобто зробила б рівно те, чого ми
# уникали, розводячи ці дві версії.


def screen_overlay_path(stem):
    root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    return os.path.join(root, 'editor', 'screen_%s.json' % stem)


def load_screen_overlay(stem):
    p = screen_overlay_path(stem)
    if not os.path.exists(p):
        return {}
    return json.load(open(p, encoding='utf-8')).get('blocks') or {}


def save_screen_overlay(stem, blocks):
    p = screen_overlay_path(stem)
    doc = {'format': 'bibletime-screen-edits/1', 'lesson': stem,
           'note': 'правки Редактора до ЕКРАННОГО тексту; накладаються поверх '
                   'SCREEN_TEXT у tr_%s.py при збірці --screen' % stem,
           'blocks': blocks}
    tmp = p + '.tmp'
    with open(tmp, 'w', encoding='utf-8') as fh:
        json.dump(doc, fh, ensure_ascii=False, indent=1, sort_keys=True)
    os.replace(tmp, p)
    return p
