# -*- coding: utf-8 -*-
"""语义分段渲染器 (2026-10-19) — 复盘报告长散文分层
用户反馈: 归因判断/因果判断核心矛盾/行业分布/资金矛盾核心矛盾/持续方向判定口径/双轨判断
          段落"太密、没序列、分不清信息密度主次"。

设计: 把一段长散文按语义标记切成 4 类层, 每层独立视觉块:
  1) 论点层 (claim)  — 冒号前的标签 + 首句判断  → 加粗大字块
  2) 证据层 (evidence) — 数字/亿/只/% 密集段     → 缩进灰块, 数字高亮
  3) 矛盾层 (clash)  — 「A」vs「B」/ 矛盾 / 背离 → 双栏对比块
  4) 验证层 (verify) — 决定变量 / 验证信号 / 阈值 → 边框强调块
"""
import re

# ── 语义边界: 这些连接词处优先断句 ──
BOUNDARY = [
    r'；\s*', r';\s+', r'。\s*', r'→', r'——', r'—(?=[^—])',
]
# 层标记词
# 2026-10-19: "背离"单独出现常是证据描述(如"存储芯片资金背离"), 只有与 vs/矛盾/分歧 共现才算矛盾层
CLASH_MARK = re.compile(r'(核心矛盾|矛盾\s*=|vs|VS|分歧点|冲突|背离.{0,40}(vs|而|但))')
VERIFY_MARK = re.compile(r'(决定变量|验证信号|明日验证|最早验证|证真|证伪|阈值|观察点)')
CLAIM_MARK = re.compile(r'^(今日定性|主线状态|明日验证|指数分化|情绪信号|涨停趋势|量能|净流入集中方向|涨停与资金匹配|跌停方向资金|判定|结论|定性)')


def _split_sentences(txt):
    """按语义边界断句, 返回句子列表(保留标记)。
    注意: 中文里的 "→" 大量用于 "65→15" 这类数字区间, 不得当作分句符;
          只在 "→" 前是汉字/括号/句读时才切(即真正的推理箭头)。
    """
    # 主分隔: 中文分号/句号 (不切 ";" 后紧跟数字的情况)
    parts = re.split(r'(?<=[；。])\s*', txt)
    out = []
    for p in parts:
        p = p.strip()
        if not p:
            continue
        if len(p) > 120:
            # 只在 "→" 前是汉字、')'、'」' 或空格时切; 前面是数字/字母则不切
            sub = re.split(r'(?<=[\u4e00-\u9fff\)\」\"\s])\s*→\s*(?=[\u4e00-\u9fff「])', p)
            out.extend([s.strip() for s in sub if s.strip()])
        else:
            out.append(p)
    return out


def _classify(sent):
    if CLASH_MARK.search(sent):
        return 'clash'
    if VERIFY_MARK.search(sent):
        return 'verify'
    return 'evidence'


def seg_block(txt, inline, para_html, style='default'):
    """把长散文渲染成分层块。

    style: 'default' | 'quote' | 'dual'
    返回 HTML 字符串。
    """
    txt = (txt or '').strip()
    if not txt:
        return ''

    # 1) 提取冒号标签 (如 "今日定性: xxx" / "核心矛盾: xxx")
    label = None
    m = re.match(r'^(\*{0,2})([^：:]{2,18})\1?\s*[:：]\s*(.*)$', txt, re.S)
    if m and len(m.group(2)) <= 18:
        label = m.group(2).strip()
        txt = m.group(3).strip()
        # 去掉重复标签 ("今日定性: 今日定性: ...")
        txt = re.sub(r'^' + re.escape(label) + r'\s*[:：]\s*', '', txt)

    sents = _split_sentences(txt)
    if len(sents) <= 1 and len(txt) < 100:
        # 短句: 原样输出
        body = f'<div class="sg-claim">{para_html(txt)}</div>'
        return f'<div class="sg-block">{_label_html(label)}{body}</div>'

    # 论点层 = 首句 + "xxx=" 型判定句(如 "判定=..." / "定性=..." / "结论=...")
    # 修复(2026-10-19): 原按顺序把首句当 claim, 但 "A轨(大盘): 外围..." 分号切开后
    #   首句只剩标签, 真正论点落到 evidence, 导致标签文字混进证据块中间。
    groups = {'claim': [], 'clash': [], 'verify': [], 'evidence': []}
    JUDGE = re.compile(r'(判定\s*=|定性\s*=|结论\s*=|即\s|属[于]?)')
    for idx, s in enumerate(sents):
        kind = _classify(s)
        if idx == 0 and kind == 'evidence':
            kind = 'claim'
        elif kind == 'evidence' and JUDGE.search(s) and len(groups['claim']) < 2:
            # 判定句归入论点层(最多 2 句), 让主判断成组呈现
            kind = 'claim'
        groups[kind].append(s)

    def _split_long2(s):
        if len(s) <= 150:
            return [s]
        _subs = re.split(r'(?<=[;；])\s*|\s+—\s+', s)
        _subs = [x.strip(' ;；') for x in _subs if x and x.strip(' ;；')]
        _flat = []
        for _x in _subs:
            if len(_x) > 160:
                _y = [z.strip(' 、') for z in re.split(r'(?<=[、])\s*', _x) if z and z.strip(' 、')]
                for _k in range(0, len(_y), 4):
                    _flat.append("、".join(_y[_k:_k+4]))
            else:
                _flat.append(_x)
        return _flat if _flat else [s]

    parts = []
    if groups['claim']:
        # 2026-10-19: 论点层也可能是长段(如 A轨 首句 440字), 同样拆行; 首行保留强调
        _ls = []
        for s in groups['claim']:
            for _i2, _x in enumerate(_split_long2(s)):
                _cls = "sg-line sg-lead" if (_i2 == 0 and len(groups['claim']) == 1) else "sg-line"
                _ls.append(f'<div class="{_cls}">{para_html(_x)}</div>')
        parts.append(f'<div class="sg-claim">{"".join(_ls)}</div>')
    if groups['evidence']:
        _lines = []
        for s in groups['evidence']:
            # 2026-10-19: 单行>150字的证据再按语义标点拆(、" / " — " / "= "),
            # 保持每行一句可扫读(用户反馈"太密")
            if len(s) > 150:
                # 主拆: 分号(语义并列项) + " — "(论点/数据分界)
                _subs = re.split(r'(?<=[;；])\s*|\s+—\s+', s)
                _subs = [x.strip(' ;；') for x in _subs if x and x.strip(' ;；')]
                # 二级拆: 仍 >160 字的段按 "、" 列举项拆(指数/资金清单)
                _flat = []
                for _x in _subs:
                    if len(_x) > 160:
                        _y = re.split(r'(?<=[、])\s*', _x)
                        _y = [z.strip(' 、') for z in _y if z and z.strip(' 、')]
                        # 每 4 项合并成一行, 避免行过碎
                        for _k in range(0, len(_y), 4):
                            _flat.append("、".join(_y[_k:_k+4]))
                    else:
                        _flat.append(_x)
                if len(_flat) > 1:
                    for _x in _flat:
                        _lines.append(f'<div class="sg-line">{para_html(_x)}</div>')
                    continue
            _lines.append(f'<div class="sg-line">{para_html(s)}</div>')
        parts.append(f'<div class="sg-evidence"><div class="sg-tag">证据</div>{"".join(_lines)}</div>')
    def _split_long(s):
        """长句拆成可扫读的多行(2026-10-19)"""
        if len(s) <= 150:
            return [s]
        _subs = re.split(r'(?<=[;；])\s*|\s+—\s+', s)
        _subs = [x.strip(' ;；') for x in _subs if x and x.strip(' ;；')]
        _flat = []
        for _x in _subs:
            if len(_x) > 160:
                _y = [z.strip(' 、') for z in re.split(r'(?<=[、])\s*', _x) if z and z.strip(' 、')]
                for _k in range(0, len(_y), 4):
                    _flat.append("、".join(_y[_k:_k+4]))
            else:
                _flat.append(_x)
        return _flat if _flat else [s]

    if groups['clash']:
        _ls = []
        for s in groups['clash']:
            for _x in _split_long(s):
                _ls.append(f'<div class="sg-line">{para_html(_x)}</div>')
        parts.append(f'<div class="sg-clash"><div class="sg-tag">矛盾</div>{"".join(_ls)}</div>')
    if groups['verify']:
        _ls = []
        for s in groups['verify']:
            for _x in _split_long(s):
                _ls.append(f'<div class="sg-line">{para_html(_x)}</div>')
        parts.append(f'<div class="sg-verify"><div class="sg-tag">验证</div>{"".join(_ls)}</div>')

    return f'<div class="sg-block">{_label_html(label)}{"".join(parts)}</div>'


def _label_html(label):
    if not label:
        return ''
    return f'<div class="sg-label">{label}</div>'


# ── 三层归因专用: 今日定性/主线状态/明日验证 ──
def seg_attribution(items, inline, para_html):
    """items: [(标签, 文本), ...] → 三张竖排卡片"""
    cards = []
    for lab, body in items:
        cls = 'att-verify' if '验证' in lab else ('att-state' if '主线' in lab else 'att-claim')
        cards.append(
            f'<div class="sg-att {cls}"><div class="sg-att-h">{lab}</div>'
            f'<div class="sg-att-b">{para_html(body)}</div></div>')
    return f'<div class="sg-att-wrap">{"".join(cards)}</div>'


# ── 双轨: A轨/B轨/结论 三块 ──
def seg_dual(items, inline, para_html):
    cards = []
    for lab, body in items:
        if 'A轨' in lab or lab.startswith('A'):
            cls = 'dual-a'
        elif 'B轨' in lab or lab.startswith('B'):
            cls = 'dual-b'
        else:
            cls = 'dual-c'
        cards.append(
            f'<div class="sg-dual {cls}"><div class="sg-dual-h">{lab}</div>'
            f'<div class="sg-dual-b">{para_html(body)}</div></div>')
    return f'<div class="sg-dual-wrap">{"".join(cards)}</div>'


# ── 行业分布: 拆成 行业/数量/代表股 的密集列表 ──
def seg_industry(txt, inline, para_html):
    """把 '汽车零部件14只(股1/股2...)、通用设备6只(...)' 拆成结构化行。"""
    txt = (txt or '').strip()
    # 头部说明 (第一个 ":" 前); 过长则拆成 短标题 + 说明行
    head, rest, subhead = '', txt, ''
    m = re.match(r'^([^：:]{4,80})[:：]\s*(.*)$', txt, re.S)
    if m:
        head, rest = m.group(1), m.group(2)
        # 2026-10-19: 标题里塞了整句统计说明 → 拆出括号内容作为副标题
        mh = re.match(r'^([^（(]{2,20})([（(].*)$', head)
        if mh and len(head) > 20:
            head, subhead = mh.group(1).strip(), mh.group(2).strip()
    # 尾部结构特征 (— 之后)
    tail = ''
    if '—' in rest:
        rest, tail = rest.split('—', 1)
    # 逐行业拆
    rows = []
    rest_row = None      # "其余N个行业各M只" 单独记录
    for seg in re.split(r'[、,，]\s*(?=[^\s/、,，()（）]{2,10}\d+只)|(?=其余\d+个行业)', rest):
        seg = seg.strip()
        if not seg:
            continue
        mr = re.match(r'^其余\s*(\d+)\s*个行业\s*各\s*(\d+)\s*只$', seg)
        if mr:
            rest_row = (int(mr.group(1)), int(mr.group(2)))
            continue
        mm = re.match(r'^([^\d(（]{2,12})\s*(\d+)\s*只\s*(?:[（(](.*)[）)])?$', seg, re.S)
        if mm:
            ind, cnt, names = mm.group(1).strip(), mm.group(2), (mm.group(3) or '').strip()
            rows.append((ind, cnt, names))
        elif rows:
            # 续行(不含 "N只" 的尾巴) 只并入上一行的 names, 不新增行
            ind, cnt, names = rows[-1]
            rows[-1] = (ind, cnt, (names + '、' + seg).strip('、'))
    if not rows:
        return f'<div class="sg-block"><div class="sg-claim">{para_html(txt)}</div></div>'
    # 渲染
    out = []
    if head:
        out.append(f'<div class="sg-label">{head}</div>')
    if subhead:
        out.append(f'<div class="sg-subhead">{subhead}</div>')
    maxc = max(int(r[1]) for r in rows) if rows else 1
    items = []
    for ind, cnt, names in rows:
        c = int(cnt)
        w = max(4, int(c / maxc * 100))
        if names:
            _parts = [x for x in re.split(r'[/、]', names) if x.strip()]
            if len(_parts) > 5:
                _shown = "、".join(_parts[:4])
                _more = f'<span class="sg-ind-more">等 {len(_parts)} 只</span>'
                nm = f'<span class="sg-ind-names">{inline(_shown)}{_more}</span>'
            else:
                nm = f'<span class="sg-ind-names">{inline(names)}</span>'
        else:
            nm = ''
        items.append(
            f'<li class="sg-ind-row"><span class="sg-ind-n">{inline(ind)}</span>'
            f'<span class="sg-ind-c">{c}只</span>'
            f'<span class="sg-ind-bar" style="width:{w}%"></span>{nm}</li>')
    if rest_row:
        n_ind, n_each = rest_row
        items.append(
            f'<li class="sg-ind-row sg-ind-rest"><span class="sg-ind-n">其余 {n_ind} 个行业</span>'
            f'<span class="sg-ind-c">各 {n_each} 只</span></li>')
    out.append(f'<ul class="sg-ind">{"".join(items)}</ul>')
    if tail:
        out.append(f'<div class="sg-verify"><div class="sg-tag">结构</div>'
                   f'<div class="sg-line">{para_html(tail.strip())}</div></div>')
    return f'<div class="sg-block">{"".join(out)}</div>'
