# -*- coding: utf-8 -*-
"""Genera flashcards.html, cards.csv y PDFs (completo + por tema) desde cards.json y cards-<tema>-<n>.json"""
import json, csv, os, datetime, glob

BASE = os.path.dirname(os.path.abspath(__file__))
data = json.load(open(os.path.join(BASE, "cards.json"), encoding="utf-8"))
cats = {c["id"]: c for c in data["categorias"]}

# ---------- Carga de temas por partes ----------
THEMES = ["java", "spring", "gcp", "micro"]
tarjetas, subtemas_por_cat = [], {}
for theme in THEMES:
    parts = sorted(glob.glob(os.path.join(BASE, "cards-%s-*.json" % theme)),
                   key=lambda p: int(p.rsplit("-", 1)[1].split(".")[0]))
    assert parts, "faltan partes de %s" % theme
    subs, sub_old, nueva = [], {}, []
    for i, p in enumerate(parts):
        d = json.load(open(p, encoding="utf-8"))
        if i == 0:
            subs = d.get("subtemas", [])
            sub_old = d.get("sub_old", {})
        nueva.extend(d["tarjetas"])
    subtemas_por_cat[theme] = subs
    prefijo = cats[theme]["prefijo"]
    viejas = [t for t in data["tarjetas"] if t["cat"] == theme]
    todas = viejas + nueva
    for t in todas:
        t["cat"] = theme
    orden_sub = {s: i for i, s in enumerate(subs)}
    rk = {"basico": 0, "intermedio": 1, "avanzado": 2}
    todas.sort(key=lambda t: (orden_sub.get(t.get("sub", ""), 99),
                              rk.get(t.get("nivel", "intermedio"), 1)))
    n = 0
    for t in todas:
        n += 1
        t["id"] = "%s%03d" % (prefijo, n)
    tarjetas.extend(todas)
N = len(tarjetas)
for t in tarjetas:
    if not t.get("sub"):
        t["sub"] = subtemas_por_cat[t["cat"]][0] if subtemas_por_cat[t["cat"]] else "General"

print("Totales por tema:", {k: sum(1 for t in tarjetas if t["cat"] == k) for k in THEMES}, "=> TOTAL", N)

# ---------- HTML ----------
html = open(os.path.join(BASE, "template.html"), encoding="utf-8").read()
html = html.replace("/*__CARDS__*/[]", json.dumps(tarjetas, ensure_ascii=False))
html = html.replace("/*__SUBS__*/{}", json.dumps(subtemas_por_cat, ensure_ascii=False))
open(os.path.join(BASE, "flashcards.html"), "w", encoding="utf-8").write(html)

# ---------- CSV (Anki / Quizlet) ----------
with open(os.path.join(BASE, "cards.csv"), "w", encoding="utf-8-sig", newline="") as f:
    w = csv.writer(f, delimiter=";")
    w.writerow(["Frente", "Reverso", "Categoría", "Subtema", "Nivel", "Clave", "Id"])
    for t in tarjetas:
        w.writerow([t["q"], t["a"].replace("\n", "<br>"), cats[t["cat"]]["nombre"], t["sub"],
                    t.get("nivel", "intermedio"), "SI" if t.get("clave") else "", t["id"]])

# ---------- PDF ----------
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.colors import HexColor, white
from reportlab.pdfgen import canvas
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import Paragraph, Frame
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT
import math

NIV_TXT = {"basico": "BASICO", "intermedio": "INTERMEDIO", "avanzado": "AVANZADO"}

def draw_star(c, cx_pt, cy_pt, r_pt, color):
    pts = []
    for i in range(10):
        ang = math.pi / 2 + i * math.pi / 5
        rr = r_pt if i % 2 == 0 else r_pt * 0.45
        pts.append((cx_pt + rr * math.cos(ang), cy_pt + rr * math.sin(ang)))
    p = c.beginPath()
    p.moveTo(*pts[0])
    for pt in pts[1:]:
        p.lineTo(*pt)
    p.close()
    c.setFillColor(color)
    c.drawPath(p, stroke=0, fill=1)

def try_font(name, path):
    try:
        pdfmetrics.registerFont(TTFont(name, path))
        return True
    except Exception:
        return False

FONT_OK = try_font("SUI", "C:/Windows/Fonts/segoeui.ttf") and try_font("SUI-B", "C:/Windows/Fonts/segoeuib.ttf")
if not FONT_OK:
    FONT_OK = try_font("SUI", "C:/Windows/Fonts/arial.ttf") and try_font("SUI-B", "C:/Windows/Fonts/arialbd.ttf")
if FONT_OK:
    pdfmetrics.registerFontFamily("SUI", normal="SUI", bold="SUI-B", italic="SUI", boldItalic="SUI-B")
    F, FB = "SUI", "SUI-B"
else:
    F, FB = "Helvetica", "Helvetica-Bold"

PW, PH = A4
PWW, PHH = 210, 297
M, HDR = 7, 9
COLS, ROWS = 2, 4
GW, GH = PWW - 2 * M, PHH - 2 * M - HDR
CW, CH = GW / COLS, GH / ROWS
INS, PAD = 1.2, 4.0

INK   = HexColor("#1c2433")
MUT   = HexColor("#7c8698")
LINE  = HexColor("#c8cfda")
CUT   = HexColor("#aab3c2")
NAVY  = HexColor("#16233a")

def esc(s):
    s = s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    s = s.replace(" ·", " ·")
    return s.replace(" —", " —")
def style(name, size, lead, font=F, color=INK, align=TA_LEFT, after=0):
    return ParagraphStyle(name, fontName=font, fontSize=size, leading=lead,
                          textColor=color, alignment=align, spaceAfter=after)

def build_pdf(cards, filename, tema_nombre, tema_subtemas):
    c = canvas.Canvas(os.path.join(BASE, filename), pagesize=A4)
    c.setTitle("Flashcards Examen Técnico — %s" % tema_nombre)
    c.setAuthor("Z.ai")
    c.setSubject("Tarjetas de repaso: %s" % tema_nombre)
    c.setCreator("Z.ai")

    per = ROWS * COLS
    n_card_pages = (len(cards) + per - 1) // per
    TOTAL_PAGES = 2 + n_card_pages * 2
    page_no = [0]

    def header_strip(kind, page_label):
        c.setFillColor(INK); c.setFont(FB, 6.8)
        c.drawString(M * mm, (PHH - M - 3.4) * mm, "FLASHCARDS · EXAMEN TÉCNICO — %s" % tema_nombre.upper())
        c.setFillColor(MUT); c.setFont(F, 6.8)
        c.drawRightString((PWW - M) * mm, (PHH - M - 3.4) * mm, page_label)
        c.setFont(F, 6.2)
        msg = {"frente": "FRENTE — imprime a DOBLE CARA volteando por el BORDE LARGO · escala 100%",
               "reverso": "REVERSO — cada respuesta queda al dorso de su pregunta (misma numeración)"}[kind]
        c.drawString(M * mm, (PHH - M - 7.0) * mm, msg)

    def footer(page_num):
        c.setFillColor(MUT); c.setFont(F, 6.2)
        c.drawCentredString(PWW / 2 * mm, 3.4 * mm, "Página %d de %d" % (page_num, TOTAL_PAGES))

    def draw_guides():
        c.setStrokeColor(CUT); c.setLineWidth(0.3); c.setDash(2.4, 2.0)
        for r in range(ROWS + 1):
            y = (M + HDR + r * CH) * mm
            c.line(M * mm, y, (M + GW) * mm, y)
        for col in range(COLS + 1):
            x = (M + col * CW) * mm
            c.line(x, (M + HDR) * mm, x, (M + HDR + GH) * mm)
        c.setDash()

    def chip(x_mm, y_mm, cat):
        nombre = cats[cat]["nombre"].upper()
        w = pdfmetrics.stringWidth(nombre, FB, 6.2) / mm + 5.0
        c.setFillColor(HexColor(cats[cat]["color"]))
        c.roundRect(x_mm * mm, y_mm * mm, w * mm, 4.8 * mm, 2.4 * mm, stroke=0, fill=1)
        c.setFillColor(white); c.setFont(FB, 6.2)
        c.drawCentredString((x_mm + w / 2) * mm, (y_mm + 1.6) * mm, nombre)
        return w

    def card_cell(row, col, kind, t):
        cx0 = M + col * CW + INS
        cy0 = M + HDR + row * CH + INS
        w, h = CW - 2 * INS, CH - 2 * INS
        y_bot = (PHH - cy0 - h) * mm
        c.setFillColor(white); c.setStrokeColor(LINE); c.setLineWidth(0.9)
        c.roundRect(cx0 * mm, y_bot, w * mm, h * mm, 2.2 * mm, stroke=1, fill=1)
        c.setFillColor(MUT); c.setFont(FB, 4.6)
        c.drawString((cx0 + PAD - 1.0) * mm, (y_bot / mm + 2.0) * mm, NIV_TXT.get(t.get("nivel", ""), ""))
        chip_w = chip(cx0 + PAD - 1.0, (PHH - cy0 - 3.0 - 4.8), t["cat"])
        if t.get("clave"):
            draw_star(c, (cx0 + PAD - 1.0 + chip_w + 2.2) * mm, (PHH - cy0 - 3.0 - 4.8 + 2.4) * mm, 2.1 * mm, HexColor("#e0a800"))
        c.setFillColor(MUT); c.setFont(FB, 7)
        c.drawRightString((cx0 + w - PAD + 1.0) * mm, (PHH - cy0 - 3.0 - 4.8 + 1.6) * mm, t["id"])
        sub = t.get("sub", "")
        mid_x0 = (cx0 + PAD - 1.0 + chip_w) * mm
        mid_x1 = (cx0 + w - PAD + 1.0 - 11) * mm
        if sub and mid_x1 > mid_x0 + 8 * mm:
            c.setFont(F, 5.4); c.setFillColor(MUT)
            c.drawCentredString((mid_x0 + mid_x1) / 2, (PHH - cy0 - 3.0 - 4.8 + 1.6) * mm, sub[:40])
        ct_y = cy0 + 3.0 + 4.8 + 2.6
        cb_y = cy0 + h - 3.2
        avail_w = (w - 2 * PAD) * mm
        avail_h = (cb_y - ct_y) * mm
        x0 = (cx0 + PAD) * mm
        if kind == "q":
            for size in (12, 11, 10, 9.5):
                p = Paragraph(esc(t["q"]), style("q", size, size * 1.35, FB, INK, TA_CENTER))
                pw, ph = p.wrap(avail_w, avail_h)
                if ph <= avail_h:
                    p.drawOn(c, x0, (PHH - ct_y) * mm - (avail_h + ph) / 2)
                    return size
            p.drawOn(c, x0, (PHH - ct_y) * mm - (avail_h + ph) / 2)
            return 9.5
        else:
            c.setFillColor(MUT); c.setFont(FB, 5.4)
            c.drawString(x0, (PHH - ct_y + 0.4) * mm, "RESPUESTA")
            a_y = ct_y + 3.6
            a_h = (cb_y - a_y) * mm
            for size in (8.6, 8.2, 7.8, 7.4, 7.0):
                p = Paragraph(esc(t["a"]).replace("\n", "<br/>"),
                              style("a", size, size * 1.30, F, HexColor("#26324a"), TA_LEFT))
                pw, ph = p.wrap(avail_w, 99999)
                if ph <= a_h:
                    p.drawOn(c, x0, (PHH - a_y) * mm - ph)
                    return size
            p.drawOn(c, x0, (PHH - a_y) * mm - min(ph, a_h))
            return 7.0

    def card_page(kind, grupo):
        page_no[0] += 1
        header_strip(kind, "Tarjetas %s – %s · %d/%d" % (grupo[0]["id"], grupo[-1]["id"], page_no[0], TOTAL_PAGES))
        draw_guides()
        for i, t in enumerate(grupo):
            row, col = divmod(i, COLS)
            if kind == "reverso":
                col = COLS - 1 - col
            card_cell(row, col, "q" if kind == "frente" else "a", t)
        footer(page_no[0])
        c.showPage()

    # ---------- Portada ----------
    page_no[0] += 1
    BAND = 46
    c.setFillColor(NAVY); c.rect(0, (PHH - BAND) * mm, PWW * mm, BAND * mm, stroke=0, fill=1)
    c.setFillColor(HexColor("#9fb4d8")); c.setFont(FB, 7)
    t = c.beginText(16 * mm, (PHH - 12) * mm); t.setCharSpace(2.2)
    t.textOut("KIT DE ESTUDIO PARA EXAMEN TÉCNICO"); t.setCharSpace(0); c.drawText(t)
    c.setFillColor(white); c.setFont(FB, 29)
    c.drawString(16 * mm, (PHH - 27) * mm, "FLASHCARDS")
    c.setFont(FB, 12.5)
    c.drawString(16 * mm, (PHH - 37) * mm, tema_nombre)

    y = PHH - BAND - 11
    c.setFillColor(INK); c.setFont(FB, 12.5)
    c.drawString(16 * mm, y * mm, "%d tarjetas de repaso rápido" % len(cards))
    c.setFillColor(MUT); c.setFont(F, 9)
    extra = " · %d subtemas" % len(tema_subtemas) if tema_subtemas else ""
    c.drawString(16 * mm, (y - 7) * mm, "frente = pregunta, dorso = respuesta · numeradas%s" % extra)
    draw_star(c, 17.2 * mm, (y - 12.6) * mm, 2.2 * mm, HexColor("#e0a800"))
    c.setFillColor(INK); c.setFont(FB, 8.2)
    c.drawString(21 * mm, (y - 13.9) * mm, "ESTRELLA = pregunta clave de examen")
    c.setFillColor(MUT); c.setFont(F, 8.2)
    c.drawString(16 * mm, (y - 17.5) * mm, "Cada tarjeta indica su nivel abajo a la izquierda; el mazo viene ordenado de lo BASICO a lo AVANZADO.")

    cx = 16.0
    for cid in THEMES:
        grupo = [t for t in cards if t["cat"] == cid]
        if not grupo:
            continue
        cat = cats[cid]
        w = pdfmetrics.stringWidth("%s · %d" % (cat["nombre"], len(grupo)), FB, 8) / mm + 8
        c.setFillColor(HexColor(cat["color"]))
        c.roundRect(cx * mm, (y - 21) * mm, w * mm, 8.4 * mm, 4.2 * mm, stroke=0, fill=1)
        c.setFillColor(white); c.setFont(FB, 8)
        c.drawCentredString((cx + w / 2) * mm, (y - 18.3) * mm, "%s · %d" % (cat["nombre"], len(grupo)))
        cx += w + 4

    y = y - 36
    c.setFillColor(INK); c.setFont(FB, 9.5)
    t = c.beginText(16 * mm, y * mm); t.setCharSpace(1.6); t.textOut("CÓMO USARLO"); t.setCharSpace(0); c.drawText(t)
    bloques = [
        ("1 · IMPRIMIR", "A4 a doble cara, volteando por el BORDE LARGO, escala 100% (sin \"ajustar a página\"). "
         "Corta por las líneas discontinuas: cada pregunta queda con su respuesta al dorso, verificable por la numeración."),
        ("2 · CELULAR", "Abre flashcards.html en el navegador (sin internet): toca para voltear, marca «La sé» o «Repasar» "
         "y el progreso queda guardado. Incluye búsqueda, filtros por subtema, modo aleatorio y lista completa."),
        ("3 · ANKI / QUIZLET", "Importa cards.csv (Frente ; Reverso ; Categoría ; Subtema) para estudiar con repetición espaciada."),
    ]
    yb = y - 8
    for titulo, texto in bloques:
        c.setFillColor(INK); c.setFont(FB, 8.6)
        c.drawString(16 * mm, yb * mm, titulo)
        p = Paragraph(esc(texto), style("b", 8.6, 11.6, F, HexColor("#3a465c")))
        p.wrap(150 * mm, 200)
        p.drawOn(c, 16 * mm, (yb - 1.5) * mm - p.height)
        yb = yb - 4 - (p.height / mm) - 7

    c.setStrokeColor(LINE); c.setLineWidth(0.7)
    c.line(16 * mm, (yb - 2) * mm, (PWW - 16) * mm, (yb - 2) * mm)

    def mini_card(x_mm, yb_mm, w_mm, h_mm, side, tt):
        c.setFillColor(white); c.setStrokeColor(LINE); c.setLineWidth(0.9)
        c.roundRect(x_mm * mm, yb_mm * mm, w_mm * mm, h_mm * mm, 2.5 * mm, stroke=1, fill=1)
        nw = pdfmetrics.stringWidth(cats[tt["cat"]]["nombre"].upper(), FB, 5.6) / mm + 4.4
        c.setFillColor(HexColor(cats[tt["cat"]]["color"]))
        c.roundRect((x_mm + 4) * mm, (yb_mm + h_mm - 8.2) * mm, nw * mm, 4.2 * mm, 2.1 * mm, stroke=0, fill=1)
        c.setFillColor(white); c.setFont(FB, 5.6)
        c.drawCentredString((x_mm + 4 + nw / 2) * mm, (yb_mm + h_mm - 8.2 + 1.35) * mm, cats[tt["cat"]]["nombre"].upper())
        c.setFillColor(MUT); c.setFont(FB, 6.2)
        c.drawRightString((x_mm + w_mm - 4) * mm, (yb_mm + h_mm - 8.2 + 1.35) * mm, tt["id"])
        top_mm = yb_mm + h_mm - 11.2
        bot_mm = yb_mm + 3.5
        if side == "q":
            p = Paragraph(esc(tt["q"]), style("mq", 9.5, 12.6, FB, INK, TA_CENTER))
            pw, ph = p.wrap((w_mm - 9) * mm, 500)
            p.drawOn(c, (x_mm + 4.5) * mm, (bot_mm * mm) + ((top_mm - bot_mm) * mm - ph) / 2)
        else:
            c.setFillColor(MUT); c.setFont(FB, 4.9)
            c.drawString((x_mm + 4.5) * mm, (top_mm + 1.2) * mm - 2, "RESPUESTA")
            p = Paragraph(esc(tt["a"]).replace("\n", "<br/>"), style("ma", 6.1, 8.0, F, HexColor("#26324a")))
            pw, ph = p.wrap((w_mm - 9) * mm, 500)
            p.drawOn(c, (x_mm + 4.5) * mm, (top_mm - 4.0) * mm - ph)

    c.setFillColor(INK); c.setFont(FB, 9.5)
    t = c.beginText(16 * mm, (yb - 9) * mm); t.setCharSpace(1.6); t.textOut("MUESTRA A ESCALA REDUCIDA"); t.setCharSpace(0); c.drawText(t)
    c.setFillColor(MUT); c.setFont(F, 6.6)
    c.drawString(16 * mm, (yb - 14) * mm, "FRENTE (página impar)")
    c.drawString(112 * mm, (yb - 14) * mm, "REVERSO (página par, espejado)")
    j05 = cards[4] if len(cards) > 4 else cards[0]
    mini_card(16, 48, 82, 58, "q", j05)
    mini_card(112, 48, 82, 58, "a", j05)
    c.setFillColor(HexColor("#3a465c")); c.setFont(F, 7.6)
    c.drawString(16 * mm, 40 * mm, "Al imprimir a doble cara, cada respuesta queda exactamente detrás de su pregunta; la numeración lo verifica.")

    c.setFillColor(NAVY); c.rect(0, 0, PWW * mm, 4.5 * mm, stroke=0, fill=1)
    c.setFillColor(MUT); c.setFont(F, 7.2)
    c.drawString(16 * mm, 30 * mm, "Contenido: %d tarjetas + chuleta exprés al dorso de esta portada · %d páginas · generado %s"
                 % (len(cards), TOTAL_PAGES, datetime.date.today().strftime("%d/%m/%Y")))
    footer(page_no[0])
    c.showPage()

    # ---------- Chuleta ----------
    page_no[0] += 1
    c.setFillColor(INK); c.setFont(FB, 15)
    c.drawString(16 * mm, (PHH - 18) * mm, "CHULETA EXPRÉS")
    c.setFillColor(MUT); c.setFont(F, 8.2)
    c.drawString(16 * mm, (PHH - 24) * mm, "Lo básico que siempre se pregunta: HTTP, seguridad, testing, datos, Docker/K8s y GCP.")
    c.setStrokeColor(LINE); c.setLineWidth(0.7)
    c.line(16 * mm, (PHH - 28) * mm, (PWW - 16) * mm, (PHH - 28) * mm)

    paras = [Paragraph("•&nbsp;" + esc(s), style("ch", 8.0, 10.9, F, HexColor("#2c3850"), after=2.8))
             for s in data["chuleta"]]
    top_mm, bot_mm = 33, 10
    fh = (PHH - top_mm - bot_mm) * mm
    COLW = 86 * mm
    def _ph(p):
        w, h = p.wrap(COLW, 10000)
        return h
    hs = [_ph(p) for p in paras]
    tot = sum(hs); acc = 0.0; split = len(paras)
    for i, h in enumerate(hs):
        acc += h
        if acc >= tot / 2:
            split = i + 1
            break
    groups = [paras[:split], paras[split:]]
    for fr, grp in zip([(16 * mm, (PHH - top_mm) * mm - fh, COLW, fh),
                        ((16 + 92) * mm, (PHH - top_mm) * mm - fh, COLW, fh)], groups):
        lst = list(grp)
        Frame(fr[0], fr[1], fr[2], fr[3], leftPadding=0, rightPadding=0, topPadding=0, bottomPadding=0,
              showBoundary=0).addFromList(lst, c)
        if lst:
            print("AVISO chuleta (%s): %d items sin lugar" % (filename, len(lst)))
    footer(page_no[0])
    c.showPage()

    # ---------- Tarjetas ----------
    for i in range(0, len(cards), per):
        grupo = cards[i:i + per]
        card_page("frente", grupo)
        card_page("reverso", grupo)

    c.save()
    print("PDF %s: %d paginas, %d tarjetas" % (filename, page_no[0], len(cards)))

build_pdf(tarjetas, "flashcards-imprimir.pdf", "Java · Spring Boot · GCP · Microservicios", [])
for theme in THEMES:
    grupo = [t for t in tarjetas if t["cat"] == theme]
    nombre = cats[theme]["nombre"]
    build_pdf(grupo, "flashcards-%s.pdf" % theme, nombre, subtemas_por_cat[theme])

print("Fuente TTF Segoe UI registrada:", FONT_OK)
