# -*- coding: utf-8 -*-
"""
填写四份培训制式表单。

复制模板库里的 TPL-* 文件，把 {{占位符}} 替换成实际内容，原格式完整保留。

用法:
    python fill_forms.py --config config.json --out "输出目录"

config.json 字段见 references/字段说明.md
"""
import argparse
import copy
import json
import re
import shutil
import sys
from pathlib import Path

import docx

SKILL_DIR = Path(__file__).resolve().parent.parent
SKILL_TEMPLATE_DIR = SKILL_DIR / "templates"                    # 专用模板：优先
SHARED_TEMPLATE_DIR = SKILL_DIR.parent / "template" / "03-培训"  # 通用模板库：兜底

FORMS = [
    # (模板基名, 输出前缀)  —— 实际文件名带 -v版本号，由 resolve_template 匹配
    ("TPL-培训-培训通知单", "1-培训通知单"),
    ("TPL-培训-培训记录和效果评估表", "2-培训记录和效果评估表"),
    ("TPL-培训-培训签到表", "3-培训签到表"),
]

PLACEHOLDER = re.compile(r"\{\{([^}]+)\}\}")
VERSION = re.compile(r"-v(\d+)\.(\d+)\.docx$", re.I)


def resolve_template(base_name, skill_dir, shared_dir):
    """先在技能专用 templates/ 找，找不到再去通用模板库。

    同名多版本时取版本号最高的。返回 (路径, 来源标签) 或 (None, None)。
    """
    for d, label in ((skill_dir, "专用"), (shared_dir, "通用")):
        if not d.is_dir():
            continue
        cands = list(d.glob(f"{base_name}-v*.docx"))
        cands += [p for p in d.glob(f"{base_name}.docx")]
        cands = [p for p in cands if not p.name.startswith("~$")]
        if not cands:
            continue

        def ver(p):
            m = VERSION.search(p.name)
            return (int(m.group(1)), int(m.group(2))) if m else (0, 0)

        return max(cands, key=ver), label
    return None, None


def substitute(text, values):
    return PLACEHOLDER.sub(lambda m: str(values.get(m.group(1).strip(), "")), text)


def fill_paragraph(para, values):
    """段落内替换（单行）。占位符可能被 Word 拆到多个 run，所以整段处理。"""
    full = para.text
    if "{{" not in full:
        return
    new = substitute(full, values).replace("\n", " ")
    runs = para.runs
    if not runs:
        para.add_run(new)
        return
    runs[0].text = new
    for r in runs[1:]:
        r.text = ""


def fill_cell(cell, values):
    """单元格内替换，支持多行（议题、培训目的这类要分行的字段）。"""
    full = "\n".join(p.text for p in cell.paragraphs)
    if "{{" not in full:
        return
    new = substitute(full, values)
    lines = new.split("\n")

    first = cell.paragraphs[0]
    if not first.runs:
        first.add_run("")
    first.runs[0].text = lines[0]
    for r in first.runs[1:]:
        r.text = ""

    # 先删掉其余原有段落，再按行追加——顺序反了会把新段落一起删掉
    for p in list(cell.paragraphs)[1:]:
        p._element.getparent().remove(p._element)

    for extra in lines[1:]:
        el = copy.deepcopy(first._element)
        first._element.getparent().append(el)
        p2 = docx.text.paragraph.Paragraph(el, cell)
        if p2.runs:
            p2.runs[0].text = extra
            for r in p2.runs[1:]:
                r.text = ""
        else:
            p2.add_run(extra)


def fill_doc(path, values):
    d = docx.Document(path)
    for para in d.paragraphs:
        fill_paragraph(para, values)
    for tb in d.tables:
        for row in tb.rows:
            for cell in row.cells:
                fill_cell(cell, values)
    for sec in d.sections:
        for container in (sec.header, sec.footer):
            for para in container.paragraphs:
                fill_paragraph(para, values)
    d.save(path)


def all_text(path):
    d = docx.Document(path)
    parts = [p.text for p in d.paragraphs]
    for tb in d.tables:
        for row in tb.rows:
            for cell in row.cells:
                parts.append(cell.text)
    return "\n".join(parts)


def scan_leftovers(path):
    """返回仍未替换的占位符。"""
    return sorted(set(PLACEHOLDER.findall(all_text(path))))


def inspect_template(path):
    """上岗前体检模板，返回问题列表。

    防的是「模板里根本没有 {{}}，脚本啥也没替换却报 OK，最后交出一张空表」。
    这种失败最阴——文件生成了、没报错、打开才发现是空的。
    """
    text = all_text(path)
    problems = []

    ph = set(PLACEHOLDER.findall(text))
    if not ph:
        problems.append("模板里没有任何 {{占位符}}，填出来会是一张空表")

    # 单大括号的说明文字：{我告诉你}、{根据培训名称自己生成} 之类
    stripped = PLACEHOLDER.sub("", text)
    singles = [s for s in re.findall(r"\{[^{}]{2,}\}", stripped) if re.search(r"[一-龥]", s)]
    if singles:
        uniq = sorted(set(singles))[:4]
        problems.append(
            "发现单大括号说明文字（脚本不认，会原样留在成品里）："
            + " ".join(u[:24] for u in uniq)
        )
    return sorted(ph), problems


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--config", required=True, help="配置 JSON 路径")
    ap.add_argument("--out", required=True, help="输出目录")
    ap.add_argument("--skill-templates", default=str(SKILL_TEMPLATE_DIR),
                    help="技能专用模板目录（优先）")
    ap.add_argument("--shared-templates", default=str(SHARED_TEMPLATE_DIR),
                    help="通用模板库目录（兜底）")
    args = ap.parse_args()

    cfg = json.loads(Path(args.config).read_text(encoding="utf-8"))
    skill_dir = Path(args.skill_templates)
    shared_dir = Path(args.shared_templates)
    out_dir = Path(args.out)
    out_dir.mkdir(parents=True, exist_ok=True)

    theme = cfg.get("培训主题", "安全教育培训")
    datestr = cfg.get("文件日期", "")

    # 签到表按用户要求只填地点和内容，时间栏必须留空
    signin_values = dict(cfg)
    signin_values["培训时间"] = ""

    ok = True
    for base_name, prefix in FORMS:
        src, source = resolve_template(base_name, skill_dir, shared_dir)
        if src is None:
            print(f"  [错误] 找不到模板 {base_name}-v*.docx")
            print(f"         专用: {skill_dir}")
            print(f"         通用: {shared_dir}")
            ok = False
            continue
        print(f"  [{source}] {src.name}")

        # 填之前先体检模板本身
        ph, problems = inspect_template(src)
        for p in problems:
            print(f"        ⚠ {p}")
            ok = False
        if problems:
            print(f"        跳过，不生成空表。修好模板再跑。")
            continue

        # 模板里有、配置里没有的字段 → 会被静默填空，必须提醒
        unknown = [k for k in ph if k not in cfg]
        if unknown:
            print(f"        ⚠ 配置缺字段（将填成空白）: {' '.join(unknown)}")
            ok = False

        dst = out_dir / f"{prefix}-{theme}-{datestr}.docx"
        shutil.copy2(src, dst)
        fill_doc(dst, signin_values if "签到表" in base_name else cfg)

        left = scan_leftovers(dst)
        status = f"OK  已填 {len(ph)} 处" if not left else f"⚠ 未替换: {' '.join(left)}"
        print(f"        -> {dst.name}   {status}")
        if left:
            ok = False

    print("\n四份表单生成完成" if ok else "\n有问题，见上面 ⚠ 标记")
    return 0 if ok else 1


if __name__ == "__main__":
    sys.exit(main())
