#!/usr/bin/env python3
"""Convert textutil Word HTML export to ct-word-doc seed for ContractTemplate."""

import json
import re
from pathlib import Path
from typing import Dict, List, Optional, Set

from bs4 import BeautifulSoup, NavigableString, Tag

SRC = Path("/tmp/v266-contract.html")
OUT = Path(__file__).resolve().parent.parent / "src/prototypes/contract-template-management/v266-lease-document.js"

# Semantic ct-word classes for key blocks (layout still driven by embedded CSS)
SEMANTIC = {
    "p2": "ct-word-title",
    "p3": "ct-word-contract-no",
    "p5": "ct-word-party-line",
    "p6": "ct-word-party-line",
    "p7": "ct-word-party-line",
    "p8": "ct-word-party-line",
}


def parse_css_rules(css_text: str) -> Dict[str, str]:
    rules: Dict[str, str] = {}
    for block in re.findall(r"([^{]+)\{([^}]+)\}", css_text):
        selector = block[0].strip()
        props = block[1].strip()
        if "," in selector:
            continue
        selector = selector.lstrip(".")
        if re.match(r"^(p|span|td|table)\.", selector):
            rules[selector] = props
    return rules


def is_red_style(style: str) -> bool:
    return bool(re.search(r"color\s*:\s*#ff0000", style, re.I))


def build_red_classes(rules: Dict[str, str]) -> Set[str]:
    red = set()
    for cls, props in rules.items():
        if is_red_style(props):
            red.add(cls.split(".")[-1])
    return red


def class_list(tag: Tag) -> List[str]:
    raw = tag.get("class") or []
    if isinstance(raw, str):
        return raw.split()
    return list(raw)


def primary_class(tag: Tag) -> Optional[str]:
    classes = class_list(tag)
    for c in classes:
        if c in SEMANTIC or re.match(r"^[pst]\d+$", c) or re.match(r"^td\d+$", c):
            return c
    return classes[0] if classes else None


def merge_style(tag: Tag, rules: Dict[str, str]) -> None:
    parts: List[str] = []
    for c in class_list(tag):
        key_p = f"p.{c}" if c.startswith("p") else None
        key_s = f"span.{c}" if c.startswith("s") else None
        key_td = f"td.{c}" if c.startswith("td") else None
        key_t = f"table.{c}" if c.startswith("t") else None
        for key in (key_p, key_s, key_td, key_t):
            if key and key in rules:
                parts.append(rules[key])
    if parts:
        existing = tag.get("style", "")
        merged = ";".join([existing] + parts) if existing else ";".join(parts)
        tag["style"] = merged


def strip_apple_noise(soup: BeautifulSoup) -> None:
    for el in soup.find_all(class_="Apple-converted-space"):
        el.replace_with("\u00a0" * max(1, len(el.get_text())))
    for el in soup.find_all(class_="Apple-tab-span"):
        el.replace_with("\t")


def wrap_risk_redlines(soup: BeautifulSoup, red_classes: Set[str]) -> None:
    risk_seq = 0

    def next_id() -> str:
        nonlocal risk_seq
        risk_seq += 1
        return f"risk-{risk_seq}"

    def is_red_tag(tag: Tag) -> bool:
        pc = primary_class(tag)
        return pc in red_classes if pc else False

    # Block-level red paragraphs: wrap inner content once
    for p in list(soup.find_all("p")):
        if not is_red_tag(p):
            continue
        if p.find_parent(class_=lambda x: x and "ct-risk-redline" in x):
            continue
        inner = list(p.contents)
        if not inner:
            continue
        wrapper = soup.new_tag(
            "span",
            attrs={
                "class": "ct-risk-redline",
                "data-risk-redline": "1",
                "data-risk-id": next_id(),
            },
        )
        for child in inner:
            wrapper.append(child.extract() if isinstance(child, Tag) else child)
        p.clear()
        p.append(wrapper)

    # Inline red spans inside non-red paragraphs
    for span in list(soup.find_all("span")):
        if not is_red_tag(span):
            continue
        if span.find_parent(class_=lambda x: x and "ct-risk-redline" in x):
            continue
        parent_p = span.find_parent("p")
        if parent_p and is_red_tag(parent_p):
            continue
        wrapper = soup.new_tag(
            "span",
            attrs={
                "class": "ct-risk-redline",
                "data-risk-redline": "1",
                "data-risk-id": next_id(),
            },
        )
        span.wrap(wrapper)


def apply_semantic_classes(root: Tag) -> None:
    for tag in root.find_all(True):
        pc = primary_class(tag)
        if pc and pc in SEMANTIC:
            classes = class_list(tag)
            extra = SEMANTIC[pc]
            if extra not in classes:
                tag["class"] = classes + [extra]


def map_tables(root: Tag) -> None:
    for i, table in enumerate(root.find_all("table")):
        classes = class_list(table)
        if "ct-word-table" not in classes:
            table["class"] = classes + ["ct-doc-table", "ct-word-table"]
        if i == 0:
            for td in table.find_all("td"):
                tdc = primary_class(td)
                tr = td.find_parent("tr")
                row_idx = len(list(tr.find_previous_siblings("tr"))) if tr else 0
                if row_idx == 0:
                    if tdc == "td1":
                        td["class"] = class_list(td) + ["ct-word-party-left"]
                    elif tdc == "td2":
                        td["class"] = class_list(td) + ["ct-word-party-right"]
                else:
                    td["class"] = class_list(td) + ["ct-word-td"]


def apply_template_vars(html: str) -> str:
    regex_replacements = [
        (r"合同编号<span[^>]*>：</span>【LNZLHT\s*<span[^>]*>[\s\S]*?</span>\s*】",
         "合同编号：【LNZLHT {{contractCode}} 】"),
        (r"合同编号：【LNZLHT[^】]*】", "合同编号：【LNZLHT {{contractCode}} 】"),
    ]
    literal_replacements = [
        ("甲方（出租方）：羚牛氢能科技（广东）有限公司", "甲方（出租方）：{{lessorName}}"),
        ("甲方(出租方): 羚牛氢能科技（广东）有限公司", "甲方(出租方): {{lessorName}}"),
        ("甲方(出租方):  羚牛氢能科技（广东）有限公司", "甲方(出租方): {{lessorName}}"),
        ("甲方：羚牛氢能科技（广东）有限公司", "甲方：{{lessorName}}"),
        ("致：羚牛氢能科技（广东）有限公司", "致：{{lessorName}}"),
        ("户 名：【羚牛氢能科技（广东）有限公司", "户 名：【{{lessorAccountName}}"),
        ("开户行：【招商银行广州萝岗支行 】", "开户行：【{{lessorBankName}} 】"),
        ("账 号：【120924165110201 】", "账 号：【{{lessorBankAccount}} 】"),
        ("乙方</b><span class=\"s3\"><b>（承租方）：</b></span>",
         "乙方</b><span class=\"s3\"><b>（承租方）：{{customerName}}</b></span>"),
        ("乙方(承租方):</b>", "乙方(承租方): {{customerName}}</b>"),
        ("乙方（承租方）：</b>", "乙方（承租方）：{{customerName}}</b>"),
    ]
    out = html
    for pattern, repl in regex_replacements:
        out = re.sub(pattern, repl, out)
    for old, new in literal_replacements:
        out = out.replace(old, new)
    return out


def scope_css(css_text: str) -> str:
    scoped = []
    for block in re.findall(r"([^{]+)\{([^}]+)\}", css_text):
        selector = block[0].strip()
        props = block[1].strip()
        if selector.startswith("@") or "," in selector:
            continue
        scoped.append(f".ct-word-doc--v266 {selector}{{{props}}}")
    return "\n".join(scoped)


def convert() -> str:
    raw = SRC.read_text(encoding="utf-8")
    css_match = re.search(r"<style[^>]*>([\s\S]*?)</style>", raw)
    body_match = re.search(r"<body>([\s\S]*?)</body>", raw)
    if not css_match or not body_match:
        raise SystemExit("Invalid source HTML")

    rules = parse_css_rules(css_match.group(1))
    red_classes = build_red_classes(rules)
    scoped = scope_css(css_match.group(1))

    soup = BeautifulSoup(body_match.group(1), "lxml")
    # lxml adds html/body wrapper
    root = soup.body or soup
    strip_apple_noise(soup)

    for tag in soup.find_all(True):
        merge_style(tag, rules)

    wrap_risk_redlines(soup, red_classes)
    apply_semantic_classes(soup)
    map_tables(soup)

    body_html = "".join(str(c) for c in (soup.body or soup).contents)
    body_html = apply_template_vars(body_html)
    body_html = re.sub(r"<p class=\"p1\"><br\s*/?></p>\s*", "", body_html, count=1)

    doc = (
        '<div class="ct-word-doc ct-word-doc--v266">'
        f"<style>{scoped}</style>"
        f"{body_html}"
        "</div>"
    )
    return doc


def main() -> None:
    html = convert()
    escaped = json.dumps(html, ensure_ascii=False)
    OUT.write_text(
        "// AUTO-GENERATED V26.6 Word HTML — do not edit by hand\n"
        f"export var V266_LEASE_DOCUMENT_HTML = {escaped};\n",
        encoding="utf-8",
    )
    print(f"Wrote {OUT} ({OUT.stat().st_size} bytes)")
    print(f"Redline markers: {html.count('data-risk-redline')}")


if __name__ == "__main__":
    main()
