#!/usr/bin/env python
"""Regenera sql/store_catalog.sql desde el HTML real del store original.

USO:  /root/NightSpire/.venv/bin/python web-next/sql/db2/gen_store_catalog.py
      (necesita /root/store1.html y la BD, para los ítems)

Por qué se regenera desde el HTML y no se parchea la BD: el seed anterior dedujo
el árbol de los CÓDIGOS de las hojas ("11-1-1" => padre "11-1"), y eso inventa
categorías fantasma. En el original, 45 hojas cuelgan DIRECTAS de su raíz: el
"-1-" del código no corresponde a ningún nivel pintado. Aquí se lee el
anidamiento de verdad (<ul>/<li>).

Además el nombre de una categoría NO es texto plano, lleva HTML:
  - color en la cabecera:    <span class="store-subcat warlock">Brujo</span>
  - color dentro del nombre: <span class="no-toggle warrior">Guerrero</span>, ...
  - etiquetas:               Armas PvE <span class="no-toggle third-brown">[Nivel 232 a 245]</span>
Se conserva tal cual (columna `name`) + la clase de la cabecera (`css`). El
inglés (`name_en`) se genera traduciendo SOLO el texto y respetando las etiquetas.

Los ÍTEMS se vuelcan de la BD, no del HTML: ya están verificados contra el
original (554 hojas, 3068 ítems, mismo contenido en cada una).
"""
import os
import re
import subprocess
import sys

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from translate_categories import tr  # noqa: E402

HERE = os.path.dirname(os.path.abspath(__file__))
OUT = os.path.join(HERE, '..', 'store_catalog.sql')
SRC = '/root/store1.html'

TOKEN = re.compile(
    r'<span class="store-(cat|subcat|sub-subcat) ([a-z0-9\- ]+)">'
    r'|<div class="item-list" id="([0-9\-]+)">'
)
SPAN_TAG = re.compile(r'</?span\b')


def span_inner(s, start):
    """Contenido de un <span>, respetando anidamiento."""
    i = s.index('>', start) + 1
    depth, j = 1, i
    while True:
        m = SPAN_TAG.search(s, j)
        if not m:
            return s[i:]
        if m.group(0) == '<span':
            depth += 1
        else:
            depth -= 1
            if depth == 0:
                return s[i:m.start()]
        j = m.end()


def clean(name):
    name = re.sub(r'^\s*<i class="fas fa-angle-right green-info"></i>\s*', '', name)
    return re.sub(r'\s+', ' ', name).strip()


def parse_tree(html):
    """[(code, parent, name_html, css, depth)] en orden de documento."""
    out, seen = [], set()
    root = sub = pending = None
    for m in TOKEN.finditer(html):
        kind, cls, code = m.group(1), m.group(2), m.group(3)
        if kind:
            entry = (clean(span_inner(html, m.start())), cls)
            if kind == 'cat':
                root, sub, pending = entry, None, None
            elif kind == 'subcat':
                sub, pending = entry, None
            else:
                pending = entry
        elif code and pending:
            p = code.split('-')
            if len(p) != 3:
                continue
            rc, sc = p[0], f'{p[0]}-{p[1]}'
            if root and rc not in seen:
                seen.add(rc); out.append((rc, None, root[0], root[1], 1))
            # La subcategoría solo existe si el HTML la pinta de verdad.
            if sub and sc not in seen:
                seen.add(sc); out.append((sc, rc, sub[0], sub[1], 2))
            if code not in seen:
                seen.add(code)
                out.append((code, sc if sub else rc, pending[0], pending[1], 3))
            pending = None
    return out


# Traducción respetando el HTML. Solo hay dos formas en los datos reales:
#   A) "TEXTO <span class="no-toggle second|third-brown">(SUFIJO)</span>"
#   B) "<span class="no-toggle CLASE">Clase</span>, <span ...>..."
TAG_A = re.compile(r'^(.*?)\s*<span class="no-toggle ([a-z\-]+)">([\(\[].*?[\)\]])</span>$')
SPAN_B = re.compile(r'<span class="no-toggle ([a-z\-]+)">([^<]+)</span>')
SUFIJO = re.compile(r'\s*([\(\[][^\)\]]*[\)\]])\s*$')


def tr_html(name):
    """Traduce el nombre conservando las etiquetas. None si no sabe."""
    if '<' not in name:
        return tr(name)

    m = TAG_A.match(name)
    if m:
        # Se traduce la frase ENTERA (el traductor tiene reglas de frase: "Nivel
        # de objeto 232 (DPS)"), y luego se vuelve a envolver el sufijo.
        base, cls, _tag = m.groups()
        full = tr(f'{base} {_tag}'.strip())
        if not full:
            return None
        s = SUFIJO.search(full)
        if not s:
            return full
        return f'{full[:s.start()].strip()} <span class="no-toggle {cls}">{s.group(1)}</span>'

    if name.startswith('<span'):
        # Lista de clases: se traduce cada una por separado.
        def one(mm):
            t = tr(mm.group(2))
            return f'<span class="no-toggle {mm.group(1)}">{t or mm.group(2)}</span>'
        return SPAN_B.sub(one, name)
    return None


def esc(s):
    return s.replace('\\', '\\\\').replace("'", "''")


def db_items():
    """Ítems desde la BD (ya verificados contra el original)."""
    env = os.path.join(HERE, '..', '..', '.env.local')
    cfg = dict(re.findall(r'^(\w+)=(.*)$', open(env, encoding='utf-8').read(), re.M))
    q = ('SELECT category_code,item_id,name,IFNULL(icon,""),quantity,currency,price,'
         'IFNULL(preview,""),sort FROM home_store_item ORDER BY sort,id')
    out = subprocess.run(
        ['mysql', '-h', cfg.get('DB_HOST', '127.0.0.1'), '-u', cfg['DB_USER'],
         f'-p{cfg["DB_PASSWORD"]}', '--default-character-set=utf8mb4', '-N', '--raw',
         cfg.get('DB_NAME_DEFAULT', 'django_wow'), '-e', q],
        capture_output=True, text=True, check=True)
    return [l.split('\t') for l in out.stdout.splitlines() if l]


cats = parse_tree(open(SRC, encoding='utf-8', errors='replace').read())
items = db_items()
sin_tr = [c for c in cats if tr_html(c[2]) is None]
print(f'categorías: {len(cats)} | ítems: {len(items)} | sin traducir: {len(sin_tr)}', file=sys.stderr)
for c in sin_tr[:10]:
    print('   sin traducir:', c[0], c[2][:60], file=sys.stderr)

with open(OUT, 'w', encoding='utf-8') as f:
    f.write("""-- Catálogo de la tienda (categorías + ítems), extraído del sistema antiguo (BENNU).
-- NO editar a mano: regenerar con `sql/db2/gen_store_catalog.py` (ver su cabecera).
--
-- El nombre de una categoría es HTML a propósito: el original colorea los
-- nombres de clase y pone las etiquetas ([Nivel 232 a 245], (DPS)) en otro tono.
-- `css` es la clase de la CABECERA, que a veces sustituye a first-brown para
-- colorear la categoría entera (p.ej. "Brujo" en color de brujo).
SET NAMES utf8mb4;
DROP TABLE IF EXISTS home_store_item;
DROP TABLE IF EXISTS home_store_category;
CREATE TABLE home_store_category (
  id INT AUTO_INCREMENT PRIMARY KEY,
  code VARCHAR(32) NOT NULL UNIQUE,
  parent_code VARCHAR(32) NULL,
  -- 512 y no 191: los nombres llevan HTML y el más largo pasa de 191.
  name VARCHAR(512) NOT NULL COMMENT 'HTML: puede llevar <span class="no-toggle X">',
  name_en VARCHAR(512) DEFAULT NULL COMMENT 'igual que name, en inglés; NULL = usar name',
  css VARCHAR(32) NOT NULL DEFAULT 'first-brown' COMMENT 'clase de la cabecera',
  depth TINYINT NOT NULL,
  sort INT NOT NULL,
  KEY k_parent (parent_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE home_store_item (
  id INT AUTO_INCREMENT PRIMARY KEY,
  category_code VARCHAR(32) NOT NULL,
  item_id INT NOT NULL,
  name VARCHAR(191) NOT NULL,
  icon VARCHAR(128) NULL,
  quantity INT NOT NULL DEFAULT 1,
  currency ENUM('pd','pv') NOT NULL DEFAULT 'pd',
  price INT NOT NULL,
  preview VARCHAR(64) NULL,
  sort INT NOT NULL,
  KEY k_cat (category_code),
  KEY k_item (item_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

""")
    CH = 200
    for i in range(0, len(cats), CH):
        f.write('INSERT INTO home_store_category (code,parent_code,name,name_en,css,depth,sort) VALUES\n')
        rows = []
        for n, (code, parent, name, css, depth) in enumerate(cats[i:i + CH], start=i):
            en = tr_html(name)
            rows.append("('%s',%s,'%s',%s,'%s',%d,%d)" % (
                esc(code), f"'{esc(parent)}'" if parent else 'NULL', esc(name),
                f"'{esc(en)}'" if en else 'NULL', esc(css), depth, n))
        f.write(',\n'.join(rows) + ';\n')
    for i in range(0, len(items), CH):
        f.write('INSERT INTO home_store_item (category_code,item_id,name,icon,quantity,currency,price,preview,sort) VALUES\n')
        rows = []
        for cc, iid, nm, ic, qty, cur, pr, pv, so in items[i:i + CH]:
            rows.append("('%s',%s,'%s',%s,%s,'%s',%s,%s,%s)" % (
                esc(cc), iid, esc(nm), f"'{esc(ic)}'" if ic else 'NULL', qty, cur, pr,
                f"'{esc(pv)}'" if pv else 'NULL', so))
        f.write(',\n'.join(rows) + ';\n')
print(f'escrito {OUT}', file=sys.stderr)
