# -*- coding: utf-8 -*-
"""Build the Japanese translation handoff workbook.

    python scripts/i18n-export.py                ->  beyond-ja-todo-<today>.xlsx
    python scripts/i18n-export.py --full         ->  beyond-ja-full.xlsx (reference, everything)
    ... --out <name.xlsx>                        ->  write somewhere else instead

THE MODEL (one file, no baselines):

The default output is the TODO file: every string on the site that still needs
the translator's attention — untranslated strings, machine drafts she has not
yet reviewed, and strings whose English was rewritten after she translated them
(COPY CHANGED). Each TODO file is complete, so the NEWEST FILE REPLACES ALL
PREVIOUS FILES; the date in the filename says which one is newest. There is no
"delta since last time" and nothing depends on which file she is holding.

What has been done is tracked in src/i18n/translator-done.json: when a returned
workbook is imported (scripts/i18n-import.py), every row she filled in is
recorded there and stops appearing in future TODO files. Rows she left empty
are simply not done yet and come back next time. A string that should
deliberately stay English is marked by writing "keep English" in the Notes
column — the import records it as done without a translation.

One row per string. The translator edits ONLY the Japanese column (and Notes).
The ID column drives the import for nav/footer rows — never edit it. Statuses:

  TRANSLATE      no Japanese yet — the yellow cells, the actual work
  COPY CHANGED   the English was rewritten after she translated it —
                 previous Japanese is stale, translate fresh (yellow)
  REVIEW DRAFT   machine-drafted for this rebuild, needs a native pass (blue)
  FROM OLD SITE  the old Builder site's own shipped Japanese — spot-check
  FROM STORE     harvested from the live JA store — spot-check

Deliberately-English strings (product names, the legal line) are settled by
decision and simply not listed. Pages covered: the marketing set. Legal pages
(terms/privacy/hardwareterms) are excluded by decision — counsel review, not
translation.
"""
import io, json, re, sys, html as H, os, datetime
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.utils import get_column_letter

sys.stdout.reconfigure(encoding="utf-8")
FULL = "--full" in sys.argv
OUT = "beyond-ja-full.xlsx" if FULL else f"beyond-ja-todo-{datetime.date.today().isoformat()}.xlsx"

jp = re.compile(r"[぀-ヿ一-鿿]")
clean = lambda s: re.sub(r"\s+", " ", H.unescape(s).replace(" ", " ")).strip()

KEEP = re.compile(
    r"^(Beyond[ -]?2e?|Beyond|BEYOND.*|Bigscreen.*|My (Beyond|Halo Mount|Audio Strap)|SCROLL|OPTIMUM|TESTED"
    r"|UPLOAD VR|THRILLSEEKER|VR FLIGHT SIM GUY|BOOSTED MEDIA|MRTV.*|YouTube|Reddit|Facebook|X|English"
    r"|Dynamic Foveated Rendering|/ SteamVR|Valve.*|FluxPose.*|Tundra.*|SlimeVR.*|HTC.*|UDCAP.*|Copyright.*"
    r"|OLED|iPhone|Discord|SteamVR|VRChat|BlackMagic 6K.*|Optimum)$"
)

# Text inside these classes is a real-world proper noun: a YouTube channel name,
# the actual title of a published video, or a customer's own review words.
# Translating those would invent a title or a quote that nobody said, so they are
# left out of the workbook entirely (same treatment as the KEEP brand names).
KEEP_CLASS = re.compile(
    r'class="[^"]*\b(hl-rail__creator|hl-rail__title|hl-review__outlet|hl-review__quote'
    r'|xs-quote|rv-card__name|rv-card__title)\b[^"]*"[^>]*>([^<>]{2,300})<'
)

en = json.load(io.open("src/i18n/dictionaries/en.json", encoding="utf-8-sig"))
ja = json.load(io.open("src/i18n/dictionaries/ja.json", encoding="utf-8-sig"))
sources = ja.get("_sources", {})
harvested = json.load(io.open("src/i18n/harvested-body.ja.json", encoding="utf-8"))
drafted = json.load(io.open("src/i18n/drafted-body.ja.json", encoding="utf-8"))
harv_drafts = set(harvested.get("_drafts", []))

# what the translator has already returned (see i18n-import.py)
DONE_PATH = "src/i18n/translator-done.json"
done = {"chrome": {}, "body": []}
if os.path.exists(DONE_PATH):
    done = json.load(io.open(DONE_PATH, encoding="utf-8"))
done_body = set(done.get("body", []))
done_chrome = done.get("chrome", {})

rows = []  # (id, page, status, english, japanese)

# 1. chrome
SRC_STATUS = {"store": "FROM STORE", "builder": "FROM OLD SITE", "draft": "REVIEW DRAFT", "en": "KEEP ENGLISH"}
for grp in ("nav", "footer"):
    for key, e in en[grp].items():
        j = ja[grp].get(key, "")
        st = SRC_STATUS.get(sources.get(grp, {}).get(key, "draft"), "REVIEW DRAFT")
        rows.append((f"{grp}.{key}", "chrome", st, clean(e), "" if j == e and st != "KEEP ENGLISH" else j))

# 2. existing body pairs, attributed to the first page whose /ja output carries them
PAGES = ["index", "displays", "experiences", "experiences/enterprise", "about", "mybeyond",
         "myhalomount", "myaudiostrap", "remotedesktop", "press", "affiliate", "ticketpolicy"]
page_html = {}
for pg in PAGES:
    f = "public/landing/ja/index.html" if pg == "index" else f"public/landing/ja/{pg}/index.html"
    if os.path.exists(f):
        page_html[pg] = io.open(f, encoding="utf-8", errors="ignore").read()

def find_page(needle):
    for pg, h in page_html.items():
        if needle[:60] in h:
            return "home" if pg == "index" else pg
    return "shared"

for src_file, base_status in ((harvested, "FROM OLD SITE"), (drafted, "REVIEW DRAFT")):
    for raw_en, j in src_file["pairs"].items():
        st = "REVIEW DRAFT" if (src_file is harvested and raw_en in harv_drafts) else base_status
        rows.append((f"body", find_page(j), st, clean(raw_en), j))

# 3. still-untranslated strings per page
seen_en = {r[3] for r in rows}
for pg, h in page_html.items():
    body = re.sub(r"(?is)<(script|style|svg|noscript)[^>]*>.*?</\1>", " ", h)
    # honeypot fields (biz-hp / pf-hp): invisible spam traps, never shown to humans
    body = re.sub(r'(?is)<div class="[^"]*\b(?:biz-hp|pf-hp)\b[^"]*"[^>]*>.*?</div>', " ", body)
    keep_class = {clean(m.group(2)) for m in KEEP_CLASS.finditer(body)}
    added = set()
    for m in re.finditer(r">([^<>]{4,2000})<", body):
        s = clean(m.group(1))
        if (not s or jp.search(s) or not re.search(r"[A-Za-z]{3}", s)
                or KEEP.match(s) or s in keep_class or s in seen_en or s in added):
            continue
        added.add(s)
        rows.append(("body", "home" if pg == "index" else pg, "TRANSLATE", s, ""))

# ---- todo mode (default): drop what the translator has already returned ----
if not FULL:
    todo = []
    for rid, page, status, e, j in rows:
        if status == "KEEP ENGLISH":
            continue                               # settled by decision, not her work
        if rid == "body":
            if e not in done_body:
                todo.append((rid, page, status, e, j))
        elif rid in done_chrome:
            if done_chrome[rid] != e:              # English rewritten after she translated it
                todo.append((rid, page, "COPY CHANGED", e, ""))
        else:
            todo.append((rid, page, status, e, j))
    rows = todo
    if not rows:
        print("nothing outstanding — no file written")
        raise SystemExit(0)

# ---- workbook ----
wb = Workbook()
FONT = "Arial"
head_font = Font(name=FONT, bold=True, color="FFFFFF", size=10)
head_fill = PatternFill("solid", fgColor="1A1A1E")
cell_font = Font(name=FONT, size=10)
ja_font = Font(name="Yu Gothic", size=10)
todo_fill = PatternFill("solid", fgColor="FFF2A8")
draft_fill = PatternFill("solid", fgColor="E8F0FE")
wrap = Alignment(wrap_text=True, vertical="top")

ws = wb.active
ws.title = "Translations"
headers = ["ID (do not edit)", "Page", "Status", "English (do not edit)", "Japanese  ←  EDIT THIS COLUMN", "Notes (optional)"]
ws.append(headers)
for c in range(1, len(headers) + 1):
    cell = ws.cell(1, c); cell.font = head_font; cell.fill = head_fill; cell.alignment = wrap

ORDER = {"TRANSLATE": 0, "COPY CHANGED": 0, "REVIEW DRAFT": 1, "FROM OLD SITE": 2, "FROM STORE": 3, "KEEP ENGLISH": 4}
PAGE_ORDER = {p: i for i, p in enumerate(
    ["chrome", "home", "displays", "experiences", "experiences/enterprise", "about", "mybeyond",
     "myhalomount", "myaudiostrap", "remotedesktop", "press", "affiliate", "ticketpolicy", "shared"])}
rows.sort(key=lambda r: (PAGE_ORDER.get(r[1], 99), ORDER.get(r[2], 9)))

# human-readable page section headers
PAGE_TITLE = {
    "chrome": "SITE CHROME — nav + footer (appears on every page)",
    "home": "HOME — bigscreenvr.com",
    "displays": "DISPLAYS — /displays",
    "experiences": "EXPERIENCES — /experiences",
    "experiences/enterprise": "ENTERPRISE — /experiences/enterprise",
    "about": "ABOUT — /about",
    "mybeyond": "MY BEYOND (setup guide) — /mybeyond",
    "myhalomount": "HALO MOUNT (setup guide) — /myhalomount",
    "myaudiostrap": "AUDIO STRAP (setup guide) — /myaudiostrap",
    "remotedesktop": "REMOTE DESKTOP — /remotedesktop",
    "press": "PRESS — /press",
    "affiliate": "AFFILIATE — /affiliate",
    "ticketpolicy": "TICKET POLICY — /ticketpolicy",
    "shared": "SHARED — strings that appear on several pages",
}
section_font = Font(name=FONT, bold=True, color="FFFFFF", size=11)
section_fill = PatternFill("solid", fgColor="3D3F45")

prev_page = None
for rid, page, status, e, j in rows:
    if page != prev_page:
        prev_page = page
        todo_n = sum(1 for r2 in rows if r2[1] == page and r2[2] in ("TRANSLATE", "COPY CHANGED"))
        total = sum(1 for r2 in rows if r2[1] == page)
        label = PAGE_TITLE.get(page, page.upper())
        note = f"{total} strings" + (f", {todo_n} still to translate" if todo_n else "")
        ws.append([f"{label}   ({note})", "", "", "", "", ""])
        r = ws.max_row
        for c in range(1, 7):
            ws.cell(r, c).fill = section_fill
        ws.cell(r, 1).font = section_font
        ws.cell(r, 1).alignment = Alignment(vertical="center")
        ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=6)
        ws.row_dimensions[r].height = 22
    ws.append([rid, page, status, e, j, ""])
    r = ws.max_row
    for c in range(1, 7):
        ws.cell(r, c).font = cell_font; ws.cell(r, c).alignment = wrap
    ws.cell(r, 5).font = ja_font
    if status in ("TRANSLATE", "COPY CHANGED"):
        ws.cell(r, 5).fill = todo_fill
    elif status == "REVIEW DRAFT":
        ws.cell(r, 5).fill = draft_fill

for col, w in zip("ABCDEF", (16, 14, 15, 66, 66, 28)):
    ws.column_dimensions[col].width = w
ws.freeze_panes = "A2"
ws.auto_filter.ref = f"A1:F{ws.max_row}"

# legend sheet
lg = wb.create_sheet("How to use")
legend = [
    ("Beyond 2 site — Japanese translation handoff", ""),
    ("", ""),
    ("THIS FILE REPLACES ALL PREVIOUS FILES.", ""),
    ("It always contains everything that still needs your attention. The date in the", ""),
    ("filename says which file is newest — please work in the newest one and discard", ""),
    ("(or return) any older files.", ""),
    ("", ""),
    ("Edit ONLY the Japanese column (yellow = missing, blue = machine draft to review).", ""),
    ("Do not edit the ID or English columns — the import matches on them.", ""),
    ("A row you leave empty is fine — it simply comes back in the next file.", ""),
    ("When you return the file, every row that has Japanese in it counts as approved.", ""),
    ("So if you return it before finishing: clear the Japanese cell of any blue draft", ""),
    ("you have not checked yet, and it will come back in the next file.", ""),
    ("To deliberately keep a string in English, write:  keep English  in the Notes column.", ""),
    ("Add anything else for Max in the Notes column.", ""),
    ("Rows are grouped by page: each grey banner row names the page (and its URL)", ""),
    ("that the strings below it belong to, plus how many still need translating.", ""),
    ("Note: filtering by Status hides the grey page banners — clear the filter to see them again.", ""),
    ("", ""),
    ("Statuses:", ""),
    ("TRANSLATE", "No Japanese yet — needs writing (yellow cells)."),
    ("COPY CHANGED", "The English was rewritten after you translated it — please translate fresh (yellow cells)."),
    ("REVIEW DRAFT", "Machine-drafted; please correct freely (blue cells)."),
    ("FROM OLD SITE", "The previous site's own shipped Japanese — spot-check."),
    ("FROM STORE", "Taken from the live Japanese store — spot-check."),
    ("", ""),
    ("Not in this file at all:", "Product names and the legal line stay English on purpose. Reviewer names,"),
    ("", "video titles, and customer review quotes stay in their original English, so"),
    ("", "they are left out rather than listed as blank rows."),
    ("", ""),
    ("Example row (illustration only — the real rows are on the Translations sheet):", ""),
    ("English:  Order now", "Japanese:  今すぐ注文"),
]
for a, b in legend:
    lg.append([a, b])
for row in lg.iter_rows():
    for cell in row:
        cell.font = Font(name=FONT, size=10)
lg["A1"].font = Font(name=FONT, bold=True, size=13)
lg.column_dimensions["A"].width = 80
lg.column_dimensions["B"].width = 60

# "Answers to your notes" sheet: replies to questions raised in the Notes column
# of the previous handoff. Kept in the workbook so the answer travels with the
# file instead of living in an email thread. Edit this list each round.
ANSWERS = [
    ("Your note", "Answer"),
    ("Will Bigscreen Beyond Utility be called myBeyond in future?",
     "No. It stays Bigscreen Beyond Utility. Please keep that name as-is."),
    ("Valve Knuckles is a prototype name of Valve Index Controllers",
     "Correct, thank you. The English has been changed to Valve Index Controllers, "
     "so that row will come back to you with the corrected source text."),
    ("Common misconceptions about resolution at 75Hz — not Refresh Rate?",
     "Resolution is correct here. The section is about DSC (Display Stream Compression) "
     "and the confusion people have about resolution at the 75Hz mode, not about the refresh rate itself."),
    ("Email / Address — is this \"Email Address\"?",
     "They are two unrelated fields on different pages. \"Email\" is the login and signup "
     "email field, so メールアドレス is right. \"Address\" is a postal field on the press form "
     "(placeholder \"City, country\"), so it needs 住所, not アドレス. Both are already corrected."),
    ("I don't think we need to translate reviews on Yotpo manually.",
     "Agreed, and thank you. Customer reviews now stay in the reviewer's own words in English. "
     "You will not see them in this or future files."),
    ("(new) YouTube reviewer names and video titles",
     "Same principle: they are real channel names and real published video titles, so translating "
     "them would invent a title that does not exist. They now stay English automatically and have "
     "been left out of this file, so you will not see them as rows to fill in."),
    ("\"same\" — not sure how this text is used",
     "It is the bolded word inside a Remote Desktop FAQ sentence: \"Double-check you are logged "
     "into the same account in Bigscreen Remote Desktop and in Bigscreen on the Quest.\" The bold "
     "styling splits that sentence into separate rows, so please translate the pieces so they read "
     "as one sentence together — or leave them blank and note it, and we will handle it."),
    ("Used \"X\" instead of 247 (review count)",
     "Thank you — that string has since been replaced. The new review lines (e.g. \"Read 283 owner "
     "reviews\") show a live number that our site updates automatically, so please translate them "
     "keeping the number as plain digits exactly where it appears, not as X."),
]
aw = wb.create_sheet("Answers to your notes")
aw.append(["Answers to the questions you left in the Notes column"])
aw["A1"].font = Font(name=FONT, bold=True, size=13)
aw.append(["", ""])
for a, b in ANSWERS:
    aw.append([a, b])
    r = aw.max_row
    bold = (a == "Your note")
    for c in (1, 2):
        aw.cell(r, c).font = Font(name=FONT, size=10, bold=bold)
        aw.cell(r, c).alignment = wrap
aw.column_dimensions["A"].width = 52
aw.column_dimensions["B"].width = 92

if "--out" in sys.argv:
    OUT = sys.argv[sys.argv.index("--out") + 1]
try:
    wb.save(OUT)
except PermissionError:
    sys.exit(f"cannot write {OUT} — it is open in Excel. Close it, or pass --out <other-name.xlsx>")
from collections import Counter
c = Counter(r[2] for r in rows)
print(f"{OUT}: {len(rows)} rows -> {dict(c)}")
