#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""am 桥接器 —— 把生产报告 markdown 转成 answer-me-with-html 草稿并渲染

用途（2026-10-19 用户要求：所有 HTML 一律走 am）：
  md (复盘/早盘/LXXG/研究报告) → am draft (frontmatter + ## panel) → am render → qpyjs.com

设计要点：
1. 只做格式转换，不改内容（内容由上游生成器/fill 决定）
2. `## xxx` 原样作 panel；`### / ####` 保留为面板内小标题
3. 状态行 blockquote（`> 行情:...`）→ frontmatter 之外的 intro 或 kv
4. 表格原样保留（am 支持 GFM 表格）
5. STE 检查默认关闭（财经长句无法压到 45 字，且会阻断渲染）

用法：
  python3 am_bridge.py <input.md> <output.html> [--title T] [--subtitle S]
                       [--template doc|sheet] [--theme paper|shadcn|blueprint]
                       [--source S] [--cols N] [--strict]
"""
import argparse
import pathlib
import re
import subprocess
import sys

sys.path.insert(0, str(pathlib.Path(__file__).resolve().parent))
try:                       # 2026-10-21: 复盘 md → am 原生写法（去「模块 N:」/h4→h3/摘要转卡）
    import am_md_norm
except Exception:          # pragma: no cover
    am_md_norm = None

AM = "/root/.hermes/skills/answer-me-with-html/scripts/am.mjs"
NODE = "node"


def split_frontmatter(text):
    """剥离已有 YAML frontmatter, 返回 (meta_dict, body)。"""
    meta = {}
    if text.startswith("---\n"):
        end = text.find("\n---\n", 4)
        if end > 0:
            fm = text[4:end]
            for line in fm.split("\n"):
                m = re.match(r"^(\w[\w-]*):\s*(.*)$", line)
                if m:
                    meta[m.group(1)] = m.group(2).strip().strip('"\'')
            text = text[end + 5:]
    return meta, text


def extract_status_block(body):
    """提取开头的 blockquote 状态行（复盘 KPI），返回 (status_lines, rest)。"""
    lines = body.split("\n")
    out, i = [], 0
    # 跳过空行
    while i < len(lines) and not lines[i].strip():
        i += 1
    while i < len(lines) and lines[i].lstrip().startswith(">"):
        out.append(re.sub(r"^\s*>\s?", "", lines[i]))
        i += 1
    rest = "\n".join(lines[i:])
    return out, rest


def status_to_kv(status_lines, cols="auto"):
    """把 `> 键: 值 | 键: 值` 状态行转成 am kv 块。

    2026-10-20: 改为逐行解析。旧实现把多行 join 成一个字符串再按 `|` 拆，
    早盘每行只有一个键值对（不含 `|`）时全部并成 1 段，整条 KPI 被塞进
    单个 dd（页面上是一个细长单列盒子，8 行字）。
    """
    if not status_lines:
        return ""
    rows = []
    for line in status_lines:
        for p in re.split(r"[|｜]", line):
            p = p.strip()
            if not p:
                continue
            # 冒号键值：`行情:春→夏`；但 `涨跌比 2275:3129` 的值自带冒号，
            # 先认空格分隔的固定字段，否则会被误拆成 key=`涨跌比 2275`。
            _KNOWN = ("涨停", "跌停", "涨跌比", "两市成交")
            m = re.match(r"^([^:：]{1,20})[:：]\s*(.+)$", p)
            _k1 = m.group(1).strip() if m else ""
            if m and (_k1 in _KNOWN or _k1.split()[0] not in _KNOWN):
                rows.append((m.group(1).strip(), m.group(2).strip()))
                continue
            # 合并键 `涨停 / 跌停: 50 / 27`（早盘 KPI 用），必须走冒号分支
            m = re.match(r"^(涨停\s*[/／]\s*跌停)[:：]\s*(.+)$", p)
            if m:
                rows.append((re.sub(r"\s*[/／]\s*", " / ", m.group(1)), m.group(2)))
                continue
            m = re.match(r"^(" + "|".join(_KNOWN) + r")\s+(\S+)$", p)
            if m:
                rows.append((m.group(1), m.group(2)))
                continue
            m = re.match(r"^最高\s*(\d+)\s*板$", p)
            if m:
                rows.append(("最高板", f"{m.group(1)}板"))
                continue
            m = re.match(r"^首板占比\s*([\d.]+%)$", p)
            if m:
                rows.append(("首板占比", m.group(1)))
                continue
            # 其余无冒号段(如 `信号库 SD≥9 池 582 只`)并入上一项，不单开一格
            if rows:
                k0, v0 = rows[-1]
                rows[-1] = (k0, f"{v0} · {p}")
            else:
                rows.append((p, "—"))
    if not rows:
        return ""
    # 列数按条目数自适应：12 项(早盘/复盘) → 3 列 4 行；4 项(LXXG) → 2 列，避免右侧空出一格
    if cols is None or cols == "auto":
        cols = 3 if len(rows) >= 7 else (2 if len(rows) >= 3 else 1)
    out = [f"```kv cols={cols}"]
    out += [f"{k}: {v}" for k, v in rows]
    out.append("```")
    return "\n".join(out) + "\n"


def apply_components(body):
    """把密集段落转成 am 原生组件 (callout/limits/kv)。2026-10-19 用户要求。"""
    try:
        import am_components
        return am_components.transform_sections(body)
    except Exception as e:
        print(f"[am_bridge] components skipped: {e}", file=sys.stderr)
        return body


def clean_body(body):
    """清理不适合 am 的痕迹：
    - 分隔线 `---` 在 body 中会被误认为 frontmatter 边界 → 转成空行
    - 纯 emoji 标题前缀保留（am 支持）
    """
    body = re.sub(r"^\s*---\s*$", "", body, flags=re.M)
    # 折叠 3+ 连续空行
    body = re.sub(r"\n{3,}", "\n\n", body)
    return body.strip()


def md_to_am_draft(md_text, title=None, subtitle=None, template="doc",
                   theme="shadcn", source=None, cols=None):
    """生产 md → am 草稿文本。"""
    if am_md_norm is not None:
        md_text = am_md_norm.normalize_md(md_text)
    meta, body = split_frontmatter(md_text)
    title = title or meta.get("title") or "报告"
    subtitle = subtitle or meta.get("subtitle") or ""
    source = source or meta.get("source") or ""

    # 正文 H1 与页眉 title 重复（页面上两个 h1）；且 `# 标题` 挡在 `> 状态行` 前，
    # extract_status_block 抓不到状态行 → 复盘页整条 KPI 丢失。必须先删 H1。(2026-10-20)
    body = re.sub(r"^#\s+[^\n]*\n?", "", body, count=1, flags=re.M)
    status, body = extract_status_block(body)
    kv_block = status_to_kv(status)
    body = clean_body(body)
    body = apply_components(body)

    # 若无 `## ` 面板但有 `# ` 标题，把 `#` 转 `##`
    if "\n## " not in "\n" + body and "\n# " in "\n" + body:
        body = re.sub(r"^#\s+", "## ", body, flags=re.M)

    head = ["---", f"template: {template}", f"theme: {theme}",
            f"title: {title}"]
    if subtitle:
        head.append(f"subtitle: {subtitle}")
    if cols:
        head.append(f"cols: {cols}")
    if source:
        head.append(f"source: {source}")
    head.append("---")

    parts = ["\n".join(head), ""]
    if kv_block:
        parts.append(kv_block)
    if template == "sheet" and cols:
        body = _sheet_span_hints(body, int(cols))
    parts.append(body)
    return "\n".join(parts)


def _sheet_span_hints(body, cols):
    """sheet 版式下，给「宽表」面板加 {span=cols}，让它整行显示。

    2026-10-27 实测（复盘 7 面板）：sheet cols=2 裸用时 6 列表格被压到 576px，
    比 doc 的 826 更窄；只给「≥5 列表格」的面板整行后，13 张表里 12 张到 1350px，
    其余面板两两并排（消息面 820px + 市场状态 544px），页高 12037→9376。
    长列表**不**触发 span：消息面那种短 bullet 在 820px 一行刚好，撑满反而行太长。
    """
    if not cols or cols < 2:
        return body
    blocks = body.split("\n## ")

    def flush(text):
        head, _, rest = text.partition("\n")
        wide_table = any(
            len([c for c in ln.strip().strip("|").split("|")]) >= 5
            for ln in rest.splitlines() if ln.strip().startswith("|")
        )
        if wide_table and "{span=" not in head:
            head = head.rstrip() + f" {{span={cols}}}"
        return head + "\n" + rest

    return "\n## ".join([flush(blocks[0])] + [flush(b) for b in blocks[1:]])


def render(draft_text, out_path, style="80", draft_path=None):
    """调 am render。返回 (ok, stdout, stderr)。

    2026-10-23 起默认 style=80（开 STE 检查），草稿先过 am_ste_norm 规整：
    长句拆短、编号步骤→列表、词级规整，规整后应为 0 warnings。
    """
    try:
        import am_ste_norm
        draft_text = am_ste_norm.normalize(draft_text)
    except Exception as exc:                      # 规整失败不挡住出页
        print(f"[am_bridge] ste norm skipped: {exc}", file=sys.stderr)
    dpath = draft_path or str(pathlib.Path(out_path).with_suffix(".am.md"))
    pathlib.Path(dpath).write_text(draft_text, encoding="utf-8")
    cmd = [NODE, AM, "render", "--no-open", "--style", style,
           "-o", out_path, dpath]
    p = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
    return p.returncode == 0, p.stdout, p.stderr


def convert(md_path, out_path, **kw):
    """一步到位：md 文件 → am 草稿 → 渲染 HTML。"""
    md_text = pathlib.Path(md_path).read_text(encoding="utf-8")
    style = "strict" if kw.pop("strict", False) else "80"
    draft = md_to_am_draft(md_text, **kw)
    return render(draft, out_path, style=style)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("md")
    ap.add_argument("out")
    ap.add_argument("--title")
    ap.add_argument("--subtitle")
    ap.add_argument("--template", default="doc", choices=["doc", "sheet"])
    ap.add_argument("--theme", default="shadcn",
                    choices=["paper", "shadcn", "blueprint", "auto"])
    ap.add_argument("--source")
    ap.add_argument("--cols", type=int)
    ap.add_argument("--strict", action="store_true")
    ap.add_argument("--draft-only", action="store_true",
                    help="只生成 am 草稿, 不渲染")
    a = ap.parse_args()

    md_text = pathlib.Path(a.md).read_text(encoding="utf-8")
    draft = md_to_am_draft(md_text, title=a.title, subtitle=a.subtitle,
                           template=a.template, theme=a.theme,
                           source=a.source, cols=a.cols)
    if a.draft_only:
        dpath = str(pathlib.Path(a.out).with_suffix(".am.md"))
        pathlib.Path(dpath).write_text(draft, encoding="utf-8")
        print(f"draft: {dpath}")
        return 0
    ok, out, err = render(draft, a.out,
                          style="strict" if a.strict else "80")
    print(out.strip() or err.strip())
    return 0 if ok else 1


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