#!/usr/bin/env python3
"""Render a CV source YAML into HTML (print layout), Markdown and plain text.

    python3 profile/cv-build.py                        # default: cv.yaml -> western-cv-template.{html,md,txt}
    python3 profile/cv-build.py --src cv-ru-lead.yaml  # variant   -> cv-ru-lead.{html,md,txt}
    python3 profile/cv-build.py --check                # verify the outputs are up to date

Fixed strings (section titles, page title, footer) follow the `lang:` key in
the YAML (en/ru). For ru the Markdown and TXT mirrors keep typographic
characters («», —, ·). The TXT mirror has no markdown markup — ready to paste
into web forms (hh, application fields). All outputs are generated files:
edit the YAML, never them.
"""
from __future__ import annotations

import argparse
import html
import re
import sys
from pathlib import Path

import yaml

ROOT = Path(__file__).resolve().parent

# Fixed strings per language (lang: key in the YAML). Projects title is
# pre-escaped for HTML; the Markdown mirror builds its own headings.
I18N = {
    "en": {
        "page_title": "Resume",
        "summary": "Summary",
        "skills": "Skills",
        "experience": "Experience",
        "projects": "Projects &amp; Publications",
        "md_projects": "Projects & Publications",
        "education": "Education",
        "generated": "Generated",
    },
    "ru": {
        "page_title": "Резюме",
        "summary": "О себе",
        "skills": "Ключевые навыки",
        "experience": "Опыт работы",
        "projects": "Проекты и публикации",
        "md_projects": "Проекты и публикации",
        "education": "Образование",
        "generated": "Сгенерировано",
    },
}

# Set in main(): current language and source file name.
LANG = "en"
SRC_NAME = "cv.yaml"

# The build script replaces this placeholder with the build time (UTC).
GEN_STAMP = "__GEN_STAMP__"

# Plain-ASCII normalisation for the Markdown mirror (kept in the HTML as-is).
ASCII_MAP = {
    "\u2014": "-",
    "\u2013": "-",
    "\u2192": "->",
    "\u2026": "...",
    "\u00a0": " ",
    "\u00b7": "|",
    "\u2018": "'", "\u2019": "'",
    "\u201c": '"', "\u201d": '"',
}

CSS = """\
  /* ============ PAGE ============ */
  @page {
    size: A4;
    margin: 11mm 14mm 11mm 14mm;
  }
  * { box-sizing: border-box; }
  html, body {
    margin: 0; padding: 0;
    font-family: "Helvetica Neue", Helvetica, Arial, "DejaVu Sans", sans-serif;
    font-size: 9.8pt;
    line-height: 1.28;
    color: #202124;
    background: #ffffff;
  }
  .page { max-width: 180mm; margin: 0 auto; }

  /* ============ HEADER ============ */
  header.name {
    border-bottom: 2.5pt solid #2457B8;
    padding-bottom: 4pt;
    margin-bottom: 6pt;
  }
  header.name h1 {
    margin: 0;
    font-size: 15pt;
    letter-spacing: 0.2pt;
    color: #2457B8;
    display: inline;
  }
  header.name .role {
    font-size: 10pt;
    font-weight: 600;
    color: #5F6368;
    margin: 0;
    display: inline;
  }
  header.name .role::before { content: "  —  "; }
  header.name .contacts {
    font-size: 9pt;
    color: #5F6368;
    margin-top: 2pt;
  }
  header.name .contacts a { color: #2457B8; text-decoration: none; }
  header.name .contacts span.sep { color: #D6DCE5; padding: 0 2pt; }

  /* ============ SECTIONS ============ */
  section { margin-bottom: 5pt; }
  h2 {
    font-size: 10pt;
    text-transform: uppercase;
    letter-spacing: 0.8pt;
    color: #2457B8;
    border-bottom: 1pt solid #D6DCE5;
    padding-bottom: 1.5pt;
    margin: 0 0 3pt 0;
  }
  p { margin: 0 0 2pt 0; }
  ul { margin: 1pt 0 3pt 0; padding-left: 13pt; }
  li { margin-bottom: 0.5pt; }

  /* ============ EXPERIENCE ENTRY ============ */
  .job { margin-bottom: 6pt; page-break-inside: avoid; }
  .job .head { display: flex; justify-content: space-between; align-items: baseline; }
  .job .title { font-weight: 700; font-size: 10pt; }
  .job .company { font-weight: 600; color: #2457B8; }
  .job .dates { font-size: 9pt; color: #8E939B; white-space: nowrap; }
  .job .sub { font-size: 9pt; color: #5F6368; margin: 0 0 1.5pt 0; }

  /* ============ SKILLS ============ */
  .skills { overflow: hidden; }
  .skill-cat { display: flex; gap: 4mm; margin-bottom: 1px; }
  .skill-cat .cat { font-weight: 700; white-space: nowrap; }
  .skill-cat .val { color: #5F6368; }

  .edu { page-break-inside: avoid; }
  .edu .head { display: flex; justify-content: space-between; align-items: baseline; }
  .edu .school { font-weight: 700; }
  .edu .dates { font-size: 9.3pt; color: #5F6368; white-space: nowrap; }

  /* ============ GEN STAMP (build script replaces __GEN_STAMP__) ============ */
  footer.gen {
    margin-top: 4pt;
    padding-top: 2pt;
    border-top: 0.5pt solid #D6DCE5;
    font-size: 7.5pt;
    color: #5F6368;
    text-align: right;
  }
"""

HTML_TEMPLATE = """\
<!DOCTYPE html>
<html lang="{lang}">
<head>
<meta charset="utf-8">
<title>{name} — {page_title}</title>
<!--
  GENERATED FILE — do not edit. Source: profile/{src_name}{regen_arg}.
  Print-ready A4 resume, rendered with Playwright/Chromium.
  Conventions: no photo, no age, no marital status. Text stays real text (ATS-friendly).
{positioning_note}
  HOW TO BUILD PDF: bash profile/build-western-cv-pdf.sh{build_hint}
-->
<style>
{css}</style>
</head>
<body>
<div class="page">

  <!-- ============ HEADER ============ -->
  <header class="name">
    <h1>{name}</h1><span class="role">{title}</span>
    <p class="contacts">
{contacts}
    </p>
  </header>

  <!-- ============ SUMMARY ============ -->
  <section>
    <h2>{t_summary}</h2>
{summary}
  </section>

  <!-- ============ SKILLS ============ -->
  <section>
    <h2>{t_skills}</h2>
    <div class="skills">
{skills}
    </div>
  </section>

  <!-- ============ EXPERIENCE ============ -->
  <section>
    <h2>{t_experience}</h2>
{jobs}
  </section>

  <!-- ============ PROJECTS & PUBLICATIONS ============ -->
  <section>
    <h2>{t_projects}</h2>
    <ul>
{projects}
    </ul>
  </section>

  <!-- ============ EDUCATION ============ -->
  <section>
    <h2>{t_education}</h2>
    <div class="edu">
      <div class="head">
        <div class="school">{edu_school}</div>
        <div class="dates">{edu_dates}</div>
      </div>
      <p>{edu_details}</p>
    </div>
  </section>

  <footer class="gen">{t_generated} {stamp}</footer>

</div>
</body>
</html>
"""


def plain(text: str) -> str:
    """Typographic characters -> plain ASCII (Markdown mirror only)."""
    if LANG == "ru":
        return text  # Russian mirror keeps «», —, ·, →: natural for RU documents.
    for src, dst in ASCII_MAP.items():
        text = text.replace(src, dst)
    return text


def inline(text: str, *, md: bool = False) -> str:
    """Render **bold** for the target format, escaping HTML when needed."""
    if md:
        return plain(text)
    return re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", html.escape(text))


def esc(text: str) -> str:
    return html.escape(str(text))


def render_html(cv: dict) -> str:
    t = I18N[LANG]
    if LANG == "en":
        positioning_note = (
            "  Positioning: Senior Backend Engineer — Ruby on Rails, distributed systems.\n"
            "  AI/LLM is a secondary capability layer (Summary/Skills), not the headline."
        )
    else:
        positioning_note = "  Positioning and content come from the source YAML."
    regen_arg = (
        " (python3 profile/cv-build.py)"
        if SRC_NAME == "cv.yaml"
        else f" (python3 profile/cv-build.py --src {SRC_NAME})"
    )
    build_hint = "" if SRC_NAME == "cv.yaml" else f" profile/{SRC_NAME}"

    contacts = []
    for c in cv["contacts"]:
        label = esc(c["label"])
        if c.get("href"):
            contacts.append(f'      <a href="{esc(c["href"])}">{label}</a>')
        else:
            contacts.append(f"      {label}")
    contacts_html = '\n      <span class="sep">·</span>\n'.join(contacts)

    summary_html = "\n".join(f"    <p>{inline(p)}</p>" for p in cv["summary"])

    skills_html = "\n".join(
        f'      <div class="skill-cat"><span class="cat">{esc(s["category"])}:</span>'
        f'<span class="val">{inline(s["value"])}</span></div>'
        for s in cv["skills"]
    )

    jobs = []
    for job in cv["experience"]:
        lines = [
            '    <div class="job">',
            '      <div class="head">',
            f'        <div class="title">{inline(job["title"])} '
            f'<span class="company">· {esc(job["company"])}</span></div>',
            f'        <div class="dates">{esc(job["dates"])}</div>',
            "      </div>",
        ]
        if job.get("summary"):
            lines.append(f'      <p class="sub">{inline(job["summary"])}</p>')
        lines.append("      <ul>")
        lines += [f"        <li>{inline(b)}</li>" for b in job["bullets"]]
        lines += ["      </ul>", "    </div>"]
        jobs.append("\n".join(lines))

    return HTML_TEMPLATE.format(
        css=CSS,
        lang=LANG,
        page_title=t["page_title"],
        src_name=SRC_NAME,
        regen_arg=regen_arg,
        positioning_note=positioning_note,
        build_hint=build_hint,
        t_summary=t["summary"],
        t_skills=t["skills"],
        t_experience=t["experience"],
        t_projects=t["projects"],
        t_education=t["education"],
        t_generated=t["generated"],
        name=esc(cv["name"]),
        title=esc(cv["title"]),
        contacts=contacts_html,
        summary=summary_html,
        skills=skills_html,
        jobs="\n\n".join(jobs),
        projects="\n".join(f"      <li>{inline(p)}</li>" for p in cv["projects"]),
        edu_school=esc(cv["education"]["school"]),
        edu_dates=esc(cv["education"]["dates"]),
        edu_details=inline(cv["education"]["details"]),
        stamp=GEN_STAMP,
    )


def render_md(cv: dict) -> str:
    t = I18N[LANG]
    if SRC_NAME == "cv.yaml":
        src_line = "     Source: profile/cv.yaml (master CV).  Regenerate: python3 profile/cv-build.py"
        pdf_line = "     PDF:    bash profile/build-western-cv-pdf.sh"
    else:
        src_line = f"     Source: profile/{SRC_NAME}.  Regenerate: python3 profile/cv-build.py --src {SRC_NAME}"
        pdf_line = f"     PDF:    bash profile/build-western-cv-pdf.sh profile/{SRC_NAME}"
    out: list[str] = [
        "<!-- GENERATED FILE - do not edit.",
        src_line,
        pdf_line + " -->",
        "",
        f"# {plain(cv['name'])}",
        "",
        f"**{plain(cv['title'])}**",
        "",
    ]

    def contact(c: dict) -> str:
        label = plain(c["label"])
        return f"[{label}]({c['href']})" if c.get("href") else label

    out.append(" | ".join(contact(c) for c in cv["contacts"]))

    out += ["", f"## {t['summary']}"]
    for para in cv["summary"]:
        out += ["", plain(para)]

    out += ["", f"## {t['skills']}", ""]
    out += [f"- **{plain(s['category'])}:** {inline(s['value'], md=True)}" for s in cv["skills"]]

    out += ["", f"## {t['experience']}"]
    for job in cv["experience"]:
        out += ["", f"### {plain(job['title'])} - {plain(job['company'])}", "", f"*{plain(job['dates'])}*"]
        if job.get("summary"):
            out += ["", plain(job["summary"])]
        out += [""] + [f"- {inline(b, md=True)}" for b in job["bullets"]]

    out += ["", f"## {t['md_projects']}", ""]
    out += [f"- {inline(p, md=True)}" for p in cv["projects"]]

    edu = cv["education"]
    out += [
        "",
        f"## {t['education']}",
        "",
        f"**{plain(edu['school'])}** - {edu['dates']}",
        "",
        plain(edu["details"]),
    ]
    return "\n".join(out).rstrip() + "\n"


def strip_bold(text: str) -> str:
    """Remove **bold** markup (plain-text mirror keeps the words only)."""
    return re.sub(r"\*\*(.+?)\*\*", r"\1", text)


def render_txt(cv: dict) -> str:
    """Plain-text mirror: no markdown markup, ready to paste into web forms."""
    t = I18N[LANG]
    if SRC_NAME == "cv.yaml":
        src_line = "(Source: profile/cv.yaml - python3 profile/cv-build.py)"
        pdf_line = "(PDF: bash profile/build-western-cv-pdf.sh)"
    else:
        src_line = f"(Source: profile/{SRC_NAME} - python3 profile/cv-build.py --src {SRC_NAME})"
        pdf_line = f"(PDF: bash profile/build-western-cv-pdf.sh profile/{SRC_NAME})"

    def contact(c: dict) -> str:
        label = strip_bold(plain(c["label"]))
        href = c.get("href") or ""
        if not href or href.startswith(("mailto:", "tel:")):
            return label  # label already is the address/phone
        return f"{label} ({href})"

    out: list[str] = [
        "(GENERATED FILE - do not edit.)",
        src_line,
        pdf_line,
        "",
        plain(cv["name"]),
        plain(cv["title"]),
    ]

    out += ["", " | ".join(contact(c) for c in cv["contacts"])]

    out += ["", t["summary"].upper()]
    for para in cv["summary"]:
        out += ["", strip_bold(plain(para))]

    out += ["", t["skills"].upper(), ""]
    out += [f"{strip_bold(plain(s['category']))}: {strip_bold(plain(s['value']))}" for s in cv["skills"]]

    out += ["", t["experience"].upper()]
    for job in cv["experience"]:
        out += ["", f"{plain(job['title'])} - {plain(job['company'])}", plain(job["dates"])]
        if job.get("summary"):
            out += ["", strip_bold(plain(job["summary"]))]
        out += [""] + [f"- {strip_bold(plain(b))}" for b in job["bullets"]]

    out += ["", t["md_projects"].upper(), ""]
    out += [f"- {strip_bold(plain(p))}" for p in cv["projects"]]

    edu = cv["education"]
    out += [
        "",
        t["education"].upper(),
        "",
        f"{plain(edu['school'])} - {edu['dates']}",
        "",
        strip_bold(plain(edu["details"])),
    ]
    return "\n".join(out).rstrip() + "\n"


def main() -> int:
    global LANG, SRC_NAME
    parser = argparse.ArgumentParser(description="Render a CV YAML into HTML, Markdown and plain text.")
    parser.add_argument("--src", default="cv.yaml", help="source YAML inside profile/ (default: cv.yaml)")
    parser.add_argument("--check", action="store_true", help="verify generated outputs are up to date")
    args = parser.parse_args()

    src = Path(args.src)
    if not src.is_absolute():
        src = ROOT / src
    if not src.exists():
        print(f"Source not found: {src}", file=sys.stderr)
        return 2

    SRC_NAME = src.name
    stem = "western-cv-template" if src.stem == "cv" else src.stem
    out_html = ROOT / f"{stem}.html"
    out_md = ROOT / f"{stem}.md"
    out_txt = ROOT / f"{stem}.txt"

    cv = yaml.safe_load(src.read_text(encoding="utf-8"))
    LANG = cv.get("lang", "en")
    rendered = {out_html: render_html(cv), out_md: render_md(cv), out_txt: render_txt(cv)}

    if args.check:
        stale = [
            path.relative_to(ROOT.parent)
            for path, text in rendered.items()
            if not path.exists() or path.read_text(encoding="utf-8") != text
        ]
        if stale:
            print("STALE:", ", ".join(map(str, stale)))
            return 1
        print("OK: generated files are up to date")
        return 0

    for path, text in rendered.items():
        path.write_text(text, encoding="utf-8")
        print(f"OK: {path.relative_to(ROOT.parent)}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
