#!/usr/bin/env python3
"""
个股深度分析 HTML 报告生成器
参照 yanbao-center 版式风格 —— 深色赛博科技风、玻璃态卡片、霓虹灯效
"""

import json
import argparse
import os
from datetime import datetime
from html import escape


def load_data(data_file):
    with open(data_file, 'r', encoding='utf-8') as f:
        return json.load(f)


def fmt_num(n, unit=''):
    """格式化数字"""
    if n is None or n == '':
        return '--'
    try:
        n = float(n)
        if abs(n) >= 1e8:
            return f'{n/1e8:.2f}亿{unit}'
        elif abs(n) >= 1e4:
            return f'{n/1e4:.2f}万{unit}'
        else:
            return f'{n:.2f}{unit}'
    except (ValueError, TypeError):
        return str(n) if n else '--'


def color_val(val, type='涨跌'):
    """红涨绿跌配色"""
    if val is None or val == '' or val == '--':
        return ('var(--text-dim)', '--')
    try:
        v = float(val)
        if type == '涨跌':
            if v > 0:
                return ('var(--up)', f'+{v:.2f}%')
            elif v < 0:
                return ('var(--down)', f'{v:.2f}%')
            else:
                return ('var(--text-dim)', '0.00%')
        return ('var(--text)', str(val))
    except (ValueError, TypeError):
        return ('var(--text)', str(val))


def render_stock_info(data):
    """渲染公司基本情况"""
    s = data.get('stock', {})
    fin = data.get('finance', {})
    val_data = data.get('valuation', {})

    rows = []
    rows.append(f'''<div class="info-grid">
        <div class="info-item"><span class="info-label">股票名称</span><span class="info-value">{escape(s.get('name','--'))}</span></div>
        <div class="info-item"><span class="info-label">股票代码</span><span class="info-value mono">{escape(s.get('code','--'))}</span></div>
        <div class="info-item"><span class="info-label">所属行业</span><span class="info-value">{escape(s.get('industry_l1','--'))} - {escape(s.get('industry_l2','--'))}</span></div>
        <div class="info-item"><span class="info-label">总市值</span><span class="info-value">{fmt_num(s.get('market_cap',''),'元')}</span></div>
        <div class="info-item"><span class="info-label">流通市值</span><span class="info-value">{fmt_num(s.get('float_cap',''),'元')}</span></div>
        <div class="info-item"><span class="info-label">市盈率(PE-TTM)</span><span class="info-value">{val_data.get('pe_ttm','--')}</span></div>
        <div class="info-item"><span class="info-label">市净率(PB)</span><span class="info-value">{val_data.get('pb','--')}</span></div>
        <div class="info-item"><span class="info-label">换手率</span><span class="info-value">{s.get('turnover_rate','--')}%</span></div>
    </div>''')
    return '\n'.join(rows)


def render_conclusion(data):
    """渲染核心结论"""
    summary = data.get('summary', {})
    if not summary:
        return ''
    
    highlights_html = ''.join(f'<li>{escape(h)}</li>' for h in summary.get('highlights', [])[:5])
    
    return f'''<div class="conclusion-card">
        <div class="conc-header">
            <div class="conc-icon">🎯</div>
            <div>
                <h3>核心结论</h3>
                <p class="conc-statement">{escape(summary.get('conclusion',''))}</p>
            </div>
        </div>
        <div class="conc-ratings">
            <div class="conc-rating">
                <span class="r-label">短期</span>
                <span class="r-value r-neutral">{escape(summary.get('rating_short','--'))}</span>
            </div>
            <div class="conc-rating">
                <span class="r-label">中期</span>
                <span class="r-value r-positive">{escape(summary.get('rating_mid','--'))}</span>
            </div>
            <div class="conc-rating">
                <span class="r-label">长期</span>
                <span class="r-value r-strong">{escape(summary.get('rating_long','--'))}</span>
            </div>
        </div>
        <div class="conc-detail">
            <div class="conc-col">
                <div class="conc-subtitle">📈 主要看点</div>
                <ul class="conc-list">{highlights_html}</ul>
            </div>
            <div class="conc-col">
                <div class="conc-subtitle">⚠️ 最大风险</div>
                <p class="conc-risk">{escape(summary.get('main_risk',''))}</p>
            </div>
        </div>
    </div>'''


def render_peer_table(data):
    """渲染同行对比表"""
    peers = data.get('peers', [])
    if not peers:
        return ''
    
    s = data.get('stock', {})
    all_peers = [{'name': s.get('name','?'), 'code': s.get('code','?'), **data.get('valuation', {}), 'revenue_growth': '--'}] + peers
    
    rows = ''
    for i, p in enumerate(all_peers):
        cls = 'highlight-row' if i == 0 else ''
        rows += f'''<tr class="{cls}">
            <td>{escape(p.get('name',''))}</td>
            <td class="mono">{escape(p.get('code',''))}</td>
            <td>{p.get('pe','--')}</td>
            <td>{p.get('pb','--')}</td>
            <td>{p.get('roe','--')}{'%' if p.get('roe') != '--' else ''}</td>
            <td>{p.get('revenue_growth','--')}{'%' if p.get('revenue_growth','--') != '--' and p.get('revenue_growth') != '--' else ''}</td>
        </tr>'''
    
    return f'''<table class="data-table">
        <thead><tr>
            <th>公司</th><th>代码</th><th>PE</th><th>PB</th><th>ROE</th><th>营收增速</th>
        </tr></thead>
        <tbody>{rows}</tbody>
    </table>'''


def render_finance_table(data):
    """渲染财务数据表"""
    fin = data.get('finance', {})
    if not fin:
        return ''
    
    revenue = fin.get('revenue', [])
    net_profit = fin.get('net_profit', [])
    gross_margin = fin.get('gross_margin', [])
    roe = fin.get('roe', [])
    
    if not revenue:
        return '<p style="color:var(--text-dim)">暂无财务数据</p>'
    
    rows = ''
    for i, r in enumerate(revenue):
        np = net_profit[i] if i < len(net_profit) else {}
        gm = gross_margin[i] if i < len(gross_margin) else {}
        rv = roe[i] if i < len(roe) else {}
        rows += f'''<tr>
            <td>{r.get('period','--')}</td>
            <td>{fmt_num(r.get('value',''))}</td>
            <td>{fmt_num(np.get('value',''))}</td>
            <td>{gm.get('value','--')}{'%' if gm.get('value','--') != '--' else ''}</td>
            <td>{rv.get('value','--')}{'%' if rv.get('value','--') != '--' else ''}</td>
        </tr>'''
    
    rating = fin.get('rating','--')
    rating_colors = {'优秀': 'var(--up)', '良好': 'var(--add)', '一般': 'var(--hold)', '偏弱': 'var(--down)'}
    rc = rating_colors.get(rating, 'var(--text)')
    
    return f'''<table class="data-table">
        <thead><tr>
            <th>报告期</th><th>营业收入</th><th>净利润</th><th>毛利率</th><th>ROE</th>
        </tr></thead>
        <tbody>{rows}</tbody>
    </table>
    <div class="finance-rating">财务质量评级：<span style="color:{rc};font-weight:700">{rating}</span></div>'''


def render_section(title, icon, content, badge=''):
    """渲染一个分析板块"""
    badge_html = f'<span class="badge">{escape(badge)}</span>' if badge else ''
    return f'''<section class="analysis-section">
        <div class="section-header">
            <div class="icon" style="background:rgba(0,212,255,0.1)">{icon}</div>
            <h2>{escape(title)}</h2>
            {badge_html}
        </div>
        <div class="section-body">{content}</div>
    </section>'''


def render_text_card(text):
    """渲染文本内容卡片，支持 dict/str/list"""
    if not text:
        return ''
    if isinstance(text, dict):
        # 如果是 dict（如 industry_analysis 误传为 dict），格式化为段落
        parts = []
        label_map = {
            'market_size': '市场规模', 'growth_rate': '增速', 'localization_rate': '国产化率',
            'sentiment': '行业景气度', 'policy': '政策环境', 'competition': '竞争格局',
            'sector_position': '赛道定位', 'space': '行业空间', 'trend': '行业趋势',
            'drivers': '增长驱动', 'barriers': '进入壁垒', 'cycle': '行业周期'
        }
        for k, v in text.items():
            if isinstance(v, str) and v:
                label = label_map.get(k, k)
                parts.append(f'<p><strong>{escape(label)}：</strong>{escape(v)}</p>')
        if parts:
            return '<div class="text-block">' + '\n'.join(parts) + '</div>'
        return ''
    if isinstance(text, list):
        items = ''.join(f'<li>{escape(str(t))}</li>' for t in text)
        return f'<ul class="text-list">{items}</ul>'
    return f'<p class="text-block">{escape(str(text))}</p>'


def render_grading_card(data):
    """渲染六维评分总览卡"""
    grading = data.get('grading', {})
    if not grading:
        return ''
    
    score = grading.get('overall_score', 0)
    grade = grading.get('overall_grade', '')
    grade_desc = grading.get('grade_desc', '')
    dims = grading.get('dimensions', {})
    veto = grading.get('veto_flags', [])
    adjustments = grading.get('adjustments', [])
    
    # 评级样式
    grade_cls = f'grade-{grade}' if grade else 'grade-B'
    
    # 各维度渲染
    dim_html = ''
    dim_keys = ['business_model', 'financial_quality', 'moat', 'valuation', 'catalyst', 'risk']
    dim_labels = {'business_model': '商业模式', 'financial_quality': '财务质量', 'moat': '护城河', 'valuation': '估值', 'catalyst': '催化剂', 'risk': '风险'}
    for k in dim_keys:
        d = dims.get(k, {})
        sval = d.get('score', 0)
        w = d.get('weight', 0)
        pct = sval * 10
        bar_cls = 'bar-high' if sval >= 7.5 else ('bar-mid' if sval >= 5 else 'bar-low')
        dim_html += f'''<div class="grading-dim">
            <span class="dim-name">{dim_labels.get(k,k)}</span>
            <span class="dim-weight">{w}%</span>
            <div class="dim-bar-bg"><div class="dim-bar-fill {bar_cls}" style="width:{pct}%"></div></div>
            <span class="dim-score">{sval:.1f}</span>
        </div>'''
    
    # 一票否决
    veto_html = ''
    if veto:
        veto_html = f'<div class="grade-veto"><span class="veto-title">🚫 一票否决触发：</span>{" | ".join(veto)}</div>'
    
    # 调整因子
    adj_html = ''
    if adjustments:
        adj_strs = []
        for a in adjustments:
            if isinstance(a, dict):
                adj_strs.append(f"{a.get('type','')}{a.get('value','')}分: {a.get('reason','')}")
            else:
                adj_strs.append(str(a))
        adj_html = f'<div style="margin-top:8px;font-size:11px;color:var(--text-dim);padding:6px 12px;">📌 调整因子：{" | ".join(adj_strs)}</div>'
    
    return f'''<div class="grading-card" style="margin-top:20px;padding-top:20px;border-top:1px solid var(--border);">
        <div class="grading-main">
            <div class="grade-badge {grade_cls}">{grade}</div>
            <span class="grade-score">{score:.1f}<small>/10</small></span>
            <div class="grade-desc">{escape(grade_desc)}</div>
            {veto_html}
            {adj_html}
        </div>
        <div class="grading-dims">
            {dim_html}
        </div>
    </div>'''


def render_moat_section(data):
    """渲染护城河分析"""
    moat = data.get('moat', {})
    if not moat:
        return ''
    
    categories = moat.get('categories', [])
    depth = moat.get('depth', '')
    depth_colors = {'deep': 'var(--up)', 'moderate': 'var(--add)', 'shallow': 'var(--down)', 'none': 'var(--text-dim)'}
    sustainability = moat.get('sustainability', '')
    desc = moat.get('description', '')
    
    tags_html = ''.join(f'<span class="moat-tag">{escape(c)}</span>' for c in categories)
    
    return f'''<div class="moat-section">
        {f'<div class="moat-categories">{tags_html}</div>' if tags_html else ''}
        {f'<div class="moat-depth"><span class="moat-depth-label">护城河深度：</span><span class="moat-depth-value" style="color:{depth_colors.get(depth,"var(--text)")}">{escape(depth)}</span><span style="margin-left:16px;font-size:12px;color:var(--text-dim);">持续性：{escape(sustainability)}</span></div>' if depth else ''}
        {f'<div class="moat-desc">{escape(desc)}</div>' if desc else ''}
    </div>'''


def render_quant_factors(data):
    """渲染量化因子仪表盘"""
    qf = data.get('quant_factors', {})
    factors = qf.get('factors', [])
    if not factors:
        return '<p style="color:var(--text-dim)">暂无量化因子数据</p>'
    
    cards = ''
    for f in factors:
        name = f.get('name', '')
        val = f.get('value', 0)
        rank_pct = f.get('rank_pct', 0)
        ind_rank = f.get('industry_rank_pct', 0)
        note = f.get('note', '')
        
        # Skip factors with null/None values
        if rank_pct is None or val is None:
            val_str = 'N/A' if val is None else f'{val:.3f}'
            cards += f'''<div class="factor-card na-card">
                <div class="factor-name">{escape(name)}</div>
                <div class="factor-value">{val_str}</div>
                <div class="factor-rank">全市场排名：<strong>N/A</strong></div>
                <div class="factor-note">{escape(note)}</div>
            </div>'''
            continue
        
        # 判断高亮/警示
        card_cls = ''
        if rank_pct >= 80:
            card_cls = 'highlight'
        elif rank_pct <= 20:
            card_cls = 'warn'
        
        rank_pct_str = f'{rank_pct:.0f}%'
        rank_cls = 'rank-high' if rank_pct >= 80 else ('rank-low' if rank_pct <= 20 else '')
        
        ind_rank_str = f'优于 {ind_rank:.0f}% 标的' if ind_rank is not None else 'N/A'
        cards += f'''<div class="factor-card {card_cls}">
            <div class="factor-name">{escape(name)}</div>
            <div class="factor-value">{val:.3f}</div>
            <div class="factor-rank">
                全市场排名：<strong class="{rank_cls}">优于 {rank_pct:.0f}% 标的</strong>
                &nbsp;|&nbsp; 行业排名：<strong>{ind_rank_str}</strong>
            </div>
            <div class="factor-bar"><div class="factor-bar-fill" style="width:{rank_pct}%;background:{'var(--down)' if rank_pct<=20 else ('var(--up)' if rank_pct>=80 else 'var(--accent)')}"></div></div>
        </div>'''
    
    # 高亮和警示摘要
    highlights = qf.get('highlights', [])
    warnings = qf.get('warnings', [])
    tags_html = ''
    for h in highlights:
        tags_html += f'<span class="ff-tag good">{escape(h)}</span>'
    for w in warnings:
        tags_html += f'<span class="ff-tag bad">{escape(w)}</span>'
    
    return f'''<div class="factor-grid">{cards}</div>
        {f'<div class="factor-highlights">{tags_html}</div>' if tags_html else ''}
        <p style="font-size:11px;color:var(--text-dim);margin-top:10px;">数据来源：华泰智研金工因子 — 综合排名越高，因子信号越强</p>'''


def render_catalyst_section(data):
    """渲染增强版催化剂"""
    catalysts = data.get('catalysts', [])
    if not catalysts:
        return '<p style="color:var(--text-dim)">暂无明确催化因素</p>'
    
    # 检查是否为新的结构化格式
    if isinstance(catalysts, list) and len(catalysts) > 0 and isinstance(catalysts[0], dict):
        items = ''
        for c in catalysts:
            ctype = c.get('type', '')
            type_cls = 'done' if ctype == '已发生' else 'pending'
            impact = c.get('impact', '')
            impact_cls = 'strong' if '强烈' in impact else ('positive' if '正面' in impact else 'negative')
            event = c.get('event', '')
            duration = c.get('duration', '')
            items += f'''<li>
                <span class="catalyst-type {type_cls}">{escape(ctype)}</span>
                <span class="catalyst-event">{escape(event)}</span>
                <span class="catalyst-impact {impact_cls}">{escape(impact)}</span>
                <span class="catalyst-duration">{escape(duration)}</span>
            </li>'''
        return f'<ul class="catalyst-list">{items}</ul>'
    
    # 旧格式兼容
    return render_text_card(catalysts)


def render_risk_matrix(data):
    """渲染三层风险矩阵"""
    risk = data.get('risk', {})
    risk_matrix = risk.get('risk_matrix', {})
    
    # 如果是新的三层风险矩阵格式
    if risk_matrix:
        html = ''
        tiers = [
            ('fatal', '🔴 致命风险（一票否决级）', 'fatal'),
            ('major', '🟠 重大风险（需折价补偿）', 'major'),
            ('manageable', '🟢 可管理风险（正常监控）', 'manageable'),
        ]
        for key, title, tier_cls in tiers:
            items = risk_matrix.get(key, [])
            if not items:
                if key == 'fatal':
                    html += f'<div class="risk-tier"><span class="risk-tier-title {tier_cls}">{title}</span><p style="font-size:13px;color:var(--up);padding:4px 14px;">✅ 未检测到致命风险</p></div>'
            else:
                items_html = ''
                for r in items:
                    prob_cls = 'high' if r.get('probability') == '高' else ('mid' if r.get('probability') == '中' else 'low')
                    imp_cls = 'high' if r.get('impact') == '高' else ('mid' if r.get('impact') == '中' else 'low')
                    items_html += f'''<li>
                        <strong>{escape(r.get('name',''))}</strong> — {escape(r.get('desc',''))}
                        <span class="risk-prob {prob_cls}">概率:{r.get('probability','?')}</span>
                        <span class="risk-impact {imp_cls}">影响:{r.get('impact','?')}</span>
                    </li>'''
                html += f'''<div class="risk-tier">
                    <span class="risk-tier-title {tier_cls}">{title}</span>
                    <ul class="risk-items">{items_html}</ul>
                </div>'''
        
        # 预警指标
        thresholds = risk.get('warning_thresholds', {})
        if thresholds:
            warn_items = ''
            for k, v in thresholds.items():
                v_cls = 'alert' if ('警告' in str(v) or '异常' in str(v) or '触' in str(v)) else 'normal'
                warn_items += f'<div class="warning-item"><span class="warn-label">{escape(k)}：</span><span class="warn-val {v_cls}">{escape(str(v))}</span></div>'
            html += f'<div style="margin-top:16px;"><div style="font-size:13px;font-weight:700;color:var(--accent);margin-bottom:8px;">📊 财务预警指标</div><div class="warning-grid">{warn_items}</div></div>'
        
        return html
    
    # 旧格式兼容
    risks = data.get('risks', [])
    return render_text_card(risks) if risks else '<p style="color:var(--text-dim)">暂无特别风险提示</p>'


def generate_html(data):
    """主 HTML 生成函数"""
    s = data.get('stock', {})
    summary = data.get('summary', {})
    today = datetime.now().strftime('%Y-%m-%d')
    source = data.get('source', '多数据源综合')
    
    # 构建各板块内容
    sections = []
    
    # 1. 核心结论 + 六维评分
    conc_html = render_conclusion(data)
    grading_html = render_grading_card(data)
    section1_body = conc_html
    if grading_html:
        section1_body += grading_html
    if conc_html or grading_html:
        sections.append(render_section('核心结论与评级', '🎯', section1_body))
    
    # 2. 公司基本情况
    sections.append(render_section('公司基本情况', '🏢', render_stock_info(data)))
    
    # 3. 行业与赛道分析
    industry_text = data.get('industry_analysis', '')
    if not industry_text:
        industry_text = f'所属行业：{s.get("industry_l1","")} - {s.get("industry_l2","")}。详细分析待补充。'
    sections.append(render_section('行业与赛道分析', '🏭', render_text_card(industry_text)))
    
    # 4. 公司竞争优势 (含护城河)
    advantages = data.get('advantages', '')
    moat_html = render_moat_section(data)
    adv_body = moat_html if moat_html else (render_text_card(advantages) if advantages else '<p style="color:var(--text-dim)">待分析</p>')
    sections.append(render_section('公司竞争优势与护城河', '💎', adv_body))
    
    # 5. 财务质量分析
    sections.append(render_section('财务质量分析', '📊', render_finance_table(data)))
    
    # 6. 成长逻辑
    growth = data.get('growth_analysis', '')
    growth_body = render_text_card(growth) if growth else '<p style="color:var(--text-dim);padding:8px 0;">成长逻辑分析需 AI 填充 growth_analysis 字段，请确认 JSON 数据中已包含此内容</p>'
    sections.append(render_section('成长逻辑分析', '🚀', growth_body))
    
    # 7. 估值分析 (增强：DCF + 安全边际)
    val_data = data.get('valuation', {})
    val_html = f'''<div class="val-grid">
        <div class="val-item"><span>PE(TTM)</span><strong>{val_data.get('pe_ttm','--')}</strong></div>
        <div class="val-item"><span>PB</span><strong>{val_data.get('pb','--')}</strong></div>
        <div class="val-item"><span>PS(TTM)</span><strong>{val_data.get('ps_ttm','--')}</strong></div>
        <div class="val-item"><span>5年PE分位</span><strong>{val_data.get('pe_percentile_5y','--')}{'%' if val_data.get('pe_percentile_5y','--') != '--' else ''}</strong></div>
        <div class="val-item"><span>PEG</span><strong>{val_data.get('peg','--')}</strong></div>
        <div class="val-item"><span>合理估值区间</span><strong style="color:var(--add)">{escape(val_data.get('fair_value_range','--'))}</strong></div>
    </div>'''
    # DCF 参考
    dcf = val_data.get('dcf_reference', {})
    if dcf and dcf.get('fair_value'):
        wacc_pct = f"{dcf.get('wacc',0)*100:.1f}%" if dcf.get('wacc') else '--'
        growth_pct = f"{dcf.get('growth_rate',0)*100:.1f}%" if dcf.get('growth_rate') else '--'
        val_html += f'''<div class="dcf-extra">
            <div class="dcf-item"><label>推荐估值方法</label><span class="val">{escape(val_data.get('preferred_method','PE'))}</span></div>
            <div class="dcf-item"><label>WACC参考</label><span class="val">{wacc_pct}</span></div>
            <div class="dcf-item"><label>永续增长率</label><span class="val">{growth_pct}</span></div>
            <div class="dcf-item"><label>DCF合理价</label><span class="val" style="color:var(--add)">{dcf.get('fair_value','--')}</span></div>
        </div>'''
    # 安全边际
    margin = val_data.get('safety_margin', '')
    if margin:
        margin_cls = 'warn' if '不足' in margin else 'ok'
        val_html += f'<div class="safety-margin {margin_cls}">🛡️ 安全边际评估：{escape(margin)}</div>'
    if val_data.get('analysis'):
        val_html += f'<p class="text-block">{escape(val_data.get("analysis",""))}</p>'
    sections.append(render_section('估值分析', '💰', val_html))
    
    # 8. 技术走势
    tech = data.get('technical', {})
    tech_html = f'''<div class="val-grid">
        <div class="val-item"><span>趋势</span><strong>{tech.get('trend','--')}</strong></div>
        <div class="val-item"><span>支撑位</span><strong style="color:var(--up)">{tech.get('support','--')}</strong></div>
        <div class="val-item"><span>压力位</span><strong style="color:var(--down)">{tech.get('resistance','--')}</strong></div>
        <div class="val-item"><span>MA5</span><strong>{tech.get('ma5','--')}</strong></div>
        <div class="val-item"><span>MA20</span><strong>{tech.get('ma20','--')}</strong></div>
        <div class="val-item"><span>成交量</span><strong>{tech.get('volume_trend','--')}</strong></div>
    </div>'''
    sections.append(render_section('技术走势分析', '📈', tech_html))
    
    # 9. 资金与筹码 (含大股东信号)
    cap = data.get('capital', {})
    major = cap.get('major_holder_signals', {})
    signal_parts = []
    if major.get('减持'): signal_parts.append('⚠️ 大股东减持')
    if major.get('增持'): signal_parts.append('✅ 大股东增持')
    if major.get('回购'): signal_parts.append('✅ 公司正在回购')
    if major.get('质押比例'): signal_parts.append(f'📌 质押{major.get("质押比例")}')
    signal_html = ' | '.join(signal_parts) if signal_parts else '暂无显著信号'
    cap_html = f'''<div class="info-grid">
        <div class="info-item"><span class="info-label">主力资金</span><span class="info-value">{escape(cap.get('main_flow','--'))}</span></div>
        <div class="info-item"><span class="info-label">北向资金</span><span class="info-value">{escape(cap.get('north_flow','--'))}</span></div>
        <div class="info-item"><span class="info-label">融资余额</span><span class="info-value">{escape(cap.get('margin_balance','--'))}</span></div>
        <div class="info-item"><span class="info-label">股东户数变化</span><span class="info-value">{escape(cap.get('shareholders_change','--'))}</span></div>
        <div class="info-item"><span class="info-label">机构持仓</span><span class="info-value">{escape(cap.get('institution_holding','--'))}</span></div>
        <div class="info-item"><span class="info-label">大股东信号</span><span class="info-value">{signal_html}</span></div>
    </div>'''
    sections.append(render_section('资金与筹码分析', '💵', cap_html))
    
    # 10. 量化因子面板
    qf_html = render_quant_factors(data)
    sections.append(render_section('量化因子面板', '🧬', qf_html))
    
    # 11. 近期催化 (增强版)
    cat_html = render_catalyst_section(data)
    sections.append(render_section('近期催化因素', '⚡', cat_html))
    
    # 12. 风险因素 (三层风险矩阵)
    risk_html = render_risk_matrix(data)
    sections.append(render_section('风险因素', '⚠️', risk_html))
    
    # 13. 同行对比
    peer_html = render_peer_table(data) or '<p style="color:var(--text-dim)">暂无同行对比数据</p>'
    sections.append(render_section('同行对比', '🤝', peer_html))
    
    # 14. 投资观点 (含华泰首席观点)
    opinion = data.get('opinion', {})
    principal_view = opinion.get('principal_asset_view', '')
    op_html = f'''<div class="opinion-grid">
        <div class="opinion-card"><div class="op-period">短期 (1周~1月)</div><div class="op-text">{escape(opinion.get('short','待分析'))}</div></div>
        <div class="opinion-card"><div class="op-period">中期 (1~6月)</div><div class="op-text">{escape(opinion.get('mid','待分析'))}</div></div>
        <div class="opinion-card"><div class="op-period">长期 (6月+)</div><div class="op-text">{escape(opinion.get('long','待分析'))}</div></div>
    </div>'''
    if principal_view:
        op_html += f'''<div style="margin-top:14px;padding:14px 18px;background:rgba(123,47,255,0.05);border:1px solid rgba(123,47,255,0.15);border-radius:10px;">
            <div style="font-size:12px;color:#a78bfa;margin-bottom:4px;">📡 华泰首席 · 资产配置观点</div>
            <div style="font-size:13px;line-height:1.7;color:var(--text);">{escape(principal_view)}</div>
        </div>'''
    sections.append(render_section('投资观点', '🎯', op_html))
    
    # 15. 操作参考 (含一票否决)
    op_data = data.get('operation', {})
    op_html = f'''<div class="info-grid">
        <div class="info-item"><span class="info-label">买入区间</span><span class="info-value" style="color:var(--up)">{escape(op_data.get('buy_zone','--'))}</span></div>
        <div class="info-item"><span class="info-label">加仓条件</span><span class="info-value">{escape(op_data.get('add_condition','--'))}</span></div>
        <div class="info-item"><span class="info-label">止损位</span><span class="info-value" style="color:var(--down)">{escape(op_data.get('stop_loss','--'))}</span></div>
        <div class="info-item"><span class="info-label">目标价</span><span class="info-value">{escape(op_data.get('target','--'))}</span></div>
    </div>'''
    veto_triggers = op_data.get('veto_triggers', [])
    grading_vetos = data.get('grading', {}).get('veto_flags', [])
    if veto_triggers and grading_vetos:
        # 有实际触发的一票否决条件
        veto_str = ' | '.join(grading_vetos)
        op_html += f'<div class="grade-veto"><span class="veto-title">🚫 一票否决已触发：</span>{escape(veto_str)}</div>'
    elif veto_triggers and not grading_vetos:
        # 有条件列表但未触发 → 标注未触发
        op_html += f'<div style="margin-top:12px;padding:10px 14px;background:rgba(0,255,136,0.06);border:1px solid rgba(0,255,136,0.2);border-radius:10px;font-size:13px;">✅ <strong>一票否决条件：未触发</strong></div>'
    else:
        # 没有一票否决条件列表
        op_html += f'<div style="margin-top:12px;padding:10px 14px;background:rgba(0,255,136,0.06);border:1px solid rgba(0,255,136,0.2);border-radius:10px;font-size:13px;">✅ <strong>一票否决条件：未触发</strong></div>'
    sections.append(render_section('操作参考', '📋', op_html))
    
    # 16. 跟踪指标
    tracking = data.get('tracking', [])
    track_html = render_text_card(tracking) if tracking else '<p style="color:var(--text-dim)">关注季度财报和行业景气变化</p>'
    sections.append(render_section('后续跟踪指标', '🔍', track_html))
    
    # 17. 总结 + 框架洞察
    sum_parts = []
    sum_parts.append(f'<p class="text-block"><strong>核心观点：</strong>{escape(summary.get("conclusion",""))}</p>')
    
    # 框架洞察
    insights = data.get('framework_insights', {})
    if insights:
        insight_items = [
            ('行业轮动环境', insights.get('rotation_context', '')),
            ('行业景气位置', insights.get('industry_position', '')),
            ('A股市场情绪', insights.get('market_emotion', '')),
            ('资金面环境', insights.get('capital_flow_context', '')),
        ]
        insight_html = ''
        for label, val in insight_items:
            if val:
                insight_html += f'<div class="insight-item"><div class="ins-label">{label}</div><div class="ins-val">{escape(val)}</div></div>'
        if insight_html:
            sum_parts.append(f'<div style="margin-top:14px;"><div style="font-size:13px;font-weight:700;color:var(--accent);margin-bottom:8px;">📡 华泰智研框架分析洞见</div><div class="insight-grid">{insight_html}</div></div>')
    
    sum_parts.append(f'''<div class="disclaimer">
        <strong>免责声明：</strong><br>
        数据来源：{escape(source)}<br><br>
        <strong>华泰智研声明：</strong>以上内容由 AI 基于华泰证券研究报告及其附属成果总结生成，可能省略完整报告中的重要信息、市场风险、业绩不及预期等风险，请务必阅读完整原始报告。有关数据、观点、评级等具有时效性，请自行核实最新信息。<br><br>
        <strong>通用声明：</strong>以上内容仅作为研究分析和投资参考，不构成任何保证收益或直接买卖建议。市场有风险，投资需谨慎，具体操作应结合个人风险承受能力、仓位情况和实时市场变化。
    </div>''')
    sections.append(render_section('总结与声明', '📝', '\n'.join(sum_parts)))
    
    # 导航
    nav_items = [
        ('conc', '核心结论'), ('info', '基本情况'), ('industry', '行业赛道'),
        ('adv', '护城河'), ('fin', '财务质量'), ('growth', '成长逻辑'),
        ('val', '估值分析'), ('tech', '技术走势'), ('capital', '资金筹码'),
        ('quant', '量化因子'), ('catalyst', '催化因素'), ('risk', '风险因素'),
        ('peer', '同行对比'), ('opinion', '投资观点'), ('oper', '操作参考'),
        ('track', '跟踪指标'), ('sum', '总结')
    ]
    nav_html = ''.join(f'<a href="#section-conc" onclick="event.preventDefault();document.getElementById(\'section-{k}\').scrollIntoView({{behavior:\'smooth\'}})">{v}</a>' for k, v in nav_items)
    
    # 组装完整 HTML
    html = f'''<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>个股深度分析 - {escape(s.get('name',''))} ({escape(s.get('code',''))})</title>
<style>
:root {{
  --bg: #06080d;
  --surface: rgba(15, 22, 45, 0.85);
  --surface-hover: rgba(20, 32, 60, 0.95);
  --border: rgba(0, 212, 255, 0.12);
  --border-glow: rgba(0, 212, 255, 0.35);
  --accent: #00d4ff;
  --accent2: #7b2fff;
  --up: #00ff88;
  --add: #ffd700;
  --hold: #00d4ff;
  --down: #ff6b6b;
  --text: #e0e8f4;
  --text-dim: #7a8ba8;
  --font: 'PingFang SC', 'Microsoft YaHei', 'Helvetica Neue', sans-serif;
}}
* {{ margin:0; padding:0; box-sizing:border-box; }}
body {{
  font-family: var(--font);
  background: var(--bg);
  color: var(--text);
  min-height: 100vh;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}}
.bg-stars {{
  position: fixed; inset: 0; z-index: 0; pointer-events: none;
  background-image:
    radial-gradient(1px 1px at 15% 25%, rgba(0,212,255,0.35), transparent),
    radial-gradient(1.5px 1.5px at 45% 60%, rgba(123,47,255,0.25), transparent),
    radial-gradient(1px 1px at 75% 20%, rgba(0,212,255,0.3), transparent),
    radial-gradient(1.5px 1.5px at 85% 75%, rgba(0,255,136,0.2), transparent);
}}
.app {{ position: relative; z-index: 1; max-width: 1280px; margin: 0 auto; padding: 0 20px; }}

/* Header */
.header {{
  text-align: center; padding: 48px 20px 24px; position: relative;
}}
.header::after {{
  content: ''; position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
  width: 120px; height: 2px;
  background: linear-gradient(90deg, transparent, var(--accent), var(--accent2), transparent);
}}
.header-title {{
  font-size: 38px; font-weight: 900; letter-spacing: 4px;
  background: linear-gradient(135deg, var(--accent) 0%, #5ee7ff 30%, var(--accent2) 70%, #a78bfa 100%);
  -webkit-background-clip: text; -webkit-text-fill-color: transparent;
  background-clip: text; margin-bottom: 6px;
}}
.header-sub {{
  font-size: 16px; color: var(--text-dim); letter-spacing: 2px; margin-top: 6px;
}}
.header-sub .code {{ color: var(--accent); font-family: 'Courier New', monospace; }}
.header-sub .dot {{ color: var(--accent); }}

/* Nav */
.nav-bar {{
  display: flex; gap: 6px; flex-wrap: wrap; justify-content: center;
  padding: 16px 0 28px; position: sticky; top: 0; z-index: 10;
  background: rgba(6,8,13,0.95); margin: 0 -20px; padding: 16px 20px 28px;
}}
.nav-bar a {{
  color: var(--text-dim); text-decoration: none; font-size: 12px; font-weight: 600;
  padding: 6px 14px; border-radius: 20px;
  border: 1px solid rgba(0,212,255,0.1); transition: all 0.3s;
  white-space: nowrap;
}}
.nav-bar a:hover {{
  color: var(--accent); border-color: var(--accent);
  background: rgba(0,212,255,0.08);
}}

/* Section */
.analysis-section {{
  margin-bottom: 28px;
}}
.section-header {{
  display: flex; align-items: center; gap: 12px; margin-bottom: 14px; padding: 0 4px;
}}
.section-header .icon {{
  width: 36px; height: 36px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center; font-size: 18px;
}}
.section-header h2 {{ font-size: 19px; font-weight: 700; letter-spacing: 1px; }}
.section-header .badge {{
  font-size: 11px; padding: 2px 10px; border-radius: 10px;
  background: rgba(0,212,255,0.1); color: var(--accent);
}}
.section-body {{
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 14px; padding: 20px 24px;
}}

/* Conclusion Card */
.conclusion-card {{
  background: linear-gradient(135deg, rgba(0,212,255,0.05), rgba(123,47,255,0.06));
  border: 1px solid rgba(0,212,255,0.2); border-radius: 16px; padding: 24px;
}}
.conc-header {{ display: flex; align-items: flex-start; gap: 14px; margin-bottom: 18px; }}
.conc-icon {{ font-size: 32px; }}
.conc-header h3 {{ font-size: 18px; color: var(--accent); margin-bottom: 4px; }}
.conc-statement {{ font-size: 15px; line-height: 1.7; color: var(--text); }}
.conc-ratings {{ display: flex; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }}
.conc-rating {{
  flex: 1; min-width: 100px; background: var(--surface);
  border: 1px solid var(--border); border-radius: 12px; padding: 14px; text-align: center;
}}
.r-label {{ display: block; font-size: 11px; color: var(--text-dim); margin-bottom: 6px; letter-spacing: 1px; }}
.r-value {{ font-size: 16px; font-weight: 700; }}
.r-neutral {{ color: var(--hold); }}
.r-positive {{ color: var(--add); }}
.r-strong {{ color: var(--up); }}
.conc-detail {{ display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }}
@media (max-width: 600px) {{ .conc-detail {{ grid-template-columns: 1fr; }} }}
.conc-subtitle {{ font-size: 13px; font-weight: 700; color: var(--accent); margin-bottom: 8px; }}
.conc-list {{ list-style: none; padding: 0; }}
.conc-list li {{
  padding: 4px 0; font-size: 13px; color: var(--text);
  border-bottom: 1px solid rgba(255,255,255,0.04);
}}
.conc-list li::before {{ content: '▸ '; color: var(--accent); }}
.conc-risk {{ font-size: 14px; color: var(--down); }}

/* Info Grid */
.info-grid {{
  display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 12px;
}}
.info-item {{
  padding: 10px 14px; border-radius: 10px;
  background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04);
}}
.info-label {{ display: block; font-size: 11px; color: var(--text-dim); margin-bottom: 4px; }}
.info-value {{ font-size: 14px; font-weight: 600; color: var(--text); word-break: break-all; }}
.info-value.mono {{ font-family: 'Courier New', monospace; color: var(--accent); }}

/* Valuation Grid */
.val-grid {{
  display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px;
  margin-bottom: 14px;
}}
.val-item {{
  padding: 12px 16px; border-radius: 10px; text-align: center;
  background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.04);
}}
.val-item span {{ display: block; font-size: 11px; color: var(--text-dim); margin-bottom: 4px; }}
.val-item strong {{ font-size: 18px; font-weight: 700; color: var(--text); }}

/* Opinion Grid */
.opinion-grid {{
  display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 14px;
}}
.opinion-card {{
  background: rgba(255,255,255,0.02); border: 1px solid var(--border);
  border-radius: 12px; padding: 16px;
}}
.op-period {{ font-size: 12px; font-weight: 700; color: var(--accent); margin-bottom: 8px; letter-spacing: 1px; }}
.op-text {{ font-size: 14px; line-height: 1.7; color: var(--text); }}

/* Data Table */
.data-table {{
  width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 12px;
}}
.data-table th {{
  text-align: left; padding: 10px 12px; color: var(--text-dim); font-weight: 600;
  font-size: 11px; text-transform: uppercase; letter-spacing: 1px;
  border-bottom: 1px solid var(--border);
}}
.data-table td {{
  padding: 10px 12px; border-bottom: 1px solid rgba(255,255,255,0.03);
}}
.data-table tr:hover td {{ background: rgba(0,212,255,0.03); }}
.highlight-row td {{ background: rgba(0,212,255,0.06); font-weight: 600; }}
.mono {{ font-family: 'Courier New', monospace; }}
.data-table .up {{ color: var(--up); }}
.data-table .down {{ color: var(--down); }}

/* Finance Rating */
.finance-rating {{
  font-size: 14px; padding: 8px 0; color: var(--text);
}}

/* Text */
.text-block {{
  font-size: 14px; line-height: 1.8; color: var(--text);
}}
.text-list {{
  list-style: none; padding: 0;
}}
.text-list li {{
  padding: 8px 0; font-size: 14px; line-height: 1.6;
  border-bottom: 1px solid rgba(255,255,255,0.04);
}}
.text-list li::before {{
  content: '▸ '; color: var(--accent);
}}

/* Disclaimer */
.disclaimer {{
  margin-top: 16px; padding: 14px 18px;
  background: rgba(255,214,0,0.05); border: 1px solid rgba(255,214,0,0.15);
  border-radius: 10px; font-size: 12px; color: var(--text-dim); line-height: 1.7;
}}
.disclaimer strong {{ color: var(--add); }}

/* Footer */
.footer {{
  text-align: center; padding: 32px 20px; margin-top: 36px;
  border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);
  letter-spacing: 1px;
}}

/* Back to top */
.back-top {{
  position: fixed; bottom: 32px; right: 32px; z-index: 99;
  width: 44px; height: 44px; border-radius: 50%; cursor: pointer;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--accent); font-size: 18px;
  display: flex; align-items: center; justify-content: center;
  transition: all 0.3s; opacity: 0; transform: translateY(20px);
}}
.back-top.show {{ opacity: 1; transform: translateY(0); }}
.back-top:hover {{ border-color: var(--accent); box-shadow: 0 0 24px rgba(0,212,255,0.2); }}

/* Grading Card (六维评分) */
.grading-card {{
  display: grid; grid-template-columns: 300px 1fr; gap: 24px;
}}
@media (max-width: 768px) {{ .grading-card {{ grid-template-columns: 1fr; }} }}
.grading-main {{
  text-align: center; padding: 20px;
  background: rgba(255,255,255,0.02); border-radius: 16px; border: 1px solid var(--border);
}}
.grade-badge {{
  display: inline-block; width: 80px; height: 80px; line-height: 80px; border-radius: 50%;
  font-size: 36px; font-weight: 900; text-align: center; margin-bottom: 8px;
  border: 3px solid; letter-spacing: 2px;
}}
.grade-S {{ color: #ffd700; border-color: #ffd700; box-shadow: 0 0 40px rgba(255,215,0,0.3); background: rgba(255,215,0,0.08); }}
.grade-A {{ color: #00ff88; border-color: #00ff88; box-shadow: 0 0 30px rgba(0,255,136,0.25); background: rgba(0,255,136,0.06); }}
.grade-B {{ color: #00d4ff; border-color: #00d4ff; box-shadow: 0 0 25px rgba(0,212,255,0.2); background: rgba(0,212,255,0.06); }}
.grade-C {{ color: #ff9500; border-color: #ff9500; box-shadow: 0 0 25px rgba(255,149,0,0.2); background: rgba(255,149,0,0.06); }}
.grade-D {{ color: #ff6b6b; border-color: #ff6b6b; box-shadow: 0 0 25px rgba(255,107,107,0.2); background: rgba(255,107,107,0.06); }}
.grade-score {{ font-size: 28px; font-weight: 700; color: var(--text); display: block; }}
.grade-score small {{ font-size: 14px; color: var(--text-dim); font-weight: 400; }}
.grade-desc {{ font-size: 15px; font-weight: 600; margin-top: 4px; }}
.grade-veto {{ margin-top: 12px; padding: 10px 14px; background: rgba(255,107,107,0.1); border: 1px solid rgba(255,107,107,0.3); border-radius: 10px; font-size: 12px; color: var(--down); }}
.grade-veto .veto-title {{ font-weight: 700; font-size: 13px; }}
.grading-dims {{ display: flex; flex-direction: column; gap: 8px; }}
.grading-dim {{
  display: flex; align-items: center; gap: 10px; padding: 8px 12px;
  background: rgba(255,255,255,0.02); border-radius: 8px; border: 1px solid rgba(255,255,255,0.03);
}}
.grading-dim .dim-name {{ flex: 1; font-size: 13px; color: var(--text); min-width: 80px; }}
.grading-dim .dim-weight {{ font-size: 10px; color: var(--text-dim); margin-right: 8px; }}
.grading-dim .dim-bar-bg {{ flex: 2; height: 6px; border-radius: 3px; background: rgba(255,255,255,0.05); overflow: hidden; }}
.grading-dim .dim-bar-fill {{ height: 100%; border-radius: 3px; transition: width 0.6s ease; }}
.grading-dim .dim-score {{ font-size: 13px; font-weight: 700; color: var(--accent); min-width: 30px; text-align: right; }}
.bar-high {{ background: linear-gradient(90deg, var(--up), #00ff88); }}
.bar-mid {{ background: linear-gradient(90deg, var(--accent), #5ee7ff); }}
.bar-low {{ background: linear-gradient(90deg, var(--add), var(--down)); }}

/* Factor Dashboard (量化因子) */
.factor-grid {{
  display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px;
}}
.factor-card {{
  background: rgba(255,255,255,0.02); border: 1px solid var(--border);
  border-radius: 12px; padding: 16px;
}}
.factor-card.highlight {{ border-color: rgba(0,255,136,0.3); background: rgba(0,255,136,0.03); }}
.factor-card.warn {{ border-color: rgba(255,107,107,0.3); background: rgba(255,107,107,0.03); }}
.factor-name {{ font-size: 14px; font-weight: 700; color: var(--text); margin-bottom: 8px; }}
.factor-value {{ font-size: 28px; font-weight: 900; color: var(--accent); margin-bottom: 10px; }}
.factor-card.highlight .factor-value {{ color: var(--up); }}
.factor-card.warn .factor-value {{ color: var(--down); }}
.factor-rank {{ font-size: 12px; color: var(--text-dim); }}
.factor-rank strong {{ color: var(--text); }}
.factor-rank .rank-high {{ color: var(--up); }}
.factor-rank .rank-low {{ color: var(--down); }}
.na-card {{ opacity: 0.5; border-style: dashed; }}
.factor-note {{ font-size: 11px; color: var(--text-dim); margin-top: 6px; font-style: italic; }}
.factor-bar {{ width: 100%; height: 4px; border-radius: 2px; background: rgba(255,255,255,0.06); margin-top: 8px; overflow: hidden; }}
.factor-bar-fill {{ height: 100%; border-radius: 2px; }}
.factor-highlights {{ margin-top: 16px; display: flex; gap: 8px; flex-wrap: wrap; }}
.ff-tag {{ font-size: 11px; padding: 4px 10px; border-radius: 12px; }}
.ff-tag.good {{ background: rgba(0,255,136,0.1); color: var(--up); border: 1px solid rgba(0,255,136,0.2); }}
.ff-tag.bad {{ background: rgba(255,107,107,0.1); color: var(--down); border: 1px solid rgba(255,107,107,0.2); }}

/* Risk Matrix (三层风险) */
.risk-tier {{ margin-bottom: 16px; }}
.risk-tier-title {{ font-size: 14px; font-weight: 700; margin-bottom: 8px; padding: 6px 12px; display: inline-block; border-radius: 6px; }}
.risk-tier-title.fatal {{ background: rgba(255,107,107,0.15); color: var(--down); border: 1px solid rgba(255,107,107,0.3); }}
.risk-tier-title.major {{ background: rgba(255,149,0,0.1); color: #ff9500; border: 1px solid rgba(255,149,0,0.25); }}
.risk-tier-title.manageable {{ background: rgba(0,212,255,0.1); color: var(--accent); border: 1px solid rgba(0,212,255,0.2); }}
.risk-items {{ list-style: none; padding: 0; }}
.risk-items li {{ padding: 8px 14px; font-size: 13px; border-bottom: 1px solid rgba(255,255,255,0.03); line-height: 1.6; }}
.risk-prob {{ font-size: 10px; padding: 2px 8px; border-radius: 8px; margin-left: 8px; }}
.risk-prob.high {{ color: var(--down); background: rgba(255,107,107,0.1); }}
.risk-prob.mid {{ color: #ff9500; background: rgba(255,149,0,0.1); }}
.risk-prob.low {{ color: var(--accent); background: rgba(0,212,255,0.1); }}
.risk-impact {{ font-size: 10px; padding: 2px 8px; border-radius: 8px; margin-left: 4px; }}
.risk-impact.high {{ color: var(--down); background: rgba(255,107,107,0.1); }}
.risk-impact.mid {{ color: #ff9500; background: rgba(255,149,0,0.1); }}
.risk-impact.low {{ color: var(--up); background: rgba(0,255,136,0.1); }}
.warning-grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 8px; margin-top: 14px; }}
.warning-item {{ padding: 10px 14px; border-radius: 8px; background: rgba(255,255,255,0.02); font-size: 12px; }}
.warning-item .warn-label {{ color: var(--text-dim); }}
.warning-item .warn-val {{ font-weight: 700; margin-left: 6px; }}
.warn-val.normal {{ color: var(--up); }}
.warn-val.alert {{ color: var(--down); }}

/* Moat Analysis */
.moat-categories {{ display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 14px; }}
.moat-tag {{ font-size: 12px; padding: 6px 14px; border-radius: 16px; background: rgba(123,47,255,0.1); border: 1px solid rgba(123,47,255,0.2); color: #a78bfa; }}
.moat-depth {{ display: flex; gap: 12px; align-items: center; margin-bottom: 12px; }}
.moat-depth-label {{ font-size: 12px; color: var(--text-dim); }}
.moat-depth-value {{ font-size: 14px; font-weight: 700; }}
.moat-desc {{ font-size: 14px; line-height: 1.8; color: var(--text); }}

/* DCF Extra */
.dcf-extra {{ display: flex; gap: 20px; flex-wrap: wrap; margin-top: 14px; padding: 14px 18px; background: rgba(0,212,255,0.04); border-radius: 10px; border: 1px solid rgba(0,212,255,0.08); }}
.dcf-item {{ flex: 1; min-width: 100px; }}
.dcf-item label {{ display: block; font-size: 11px; color: var(--text-dim); margin-bottom: 4px; }}
.dcf-item .val {{ font-size: 16px; font-weight: 700; color: var(--accent); }}
.safety-margin {{ margin-top: 14px; padding: 10px 14px; border-radius: 8px; font-size: 13px; }}
.safety-margin.ok {{ background: rgba(0,255,136,0.06); color: var(--up); border: 1px solid rgba(0,255,136,0.15); }}
.safety-margin.warn {{ background: rgba(255,149,0,0.06); color: #ff9500; border: 1px solid rgba(255,149,0,0.15); }}

/* Catalyst Enhanced */
.catalyst-list {{ list-style: none; padding: 0; }}
.catalyst-list li {{ padding: 10px 14px; border-bottom: 1px solid rgba(255,255,255,0.03); display: flex; align-items: center; gap: 10px; }}
.catalyst-type {{ font-size: 10px; padding: 2px 8px; border-radius: 8px; white-space: nowrap; }}
.catalyst-type.done {{ background: rgba(0,212,255,0.1); color: var(--accent); }}
.catalyst-type.pending {{ background: rgba(123,47,255,0.1); color: #a78bfa; }}
.catalyst-event {{ flex: 1; font-size: 13px; }}
.catalyst-impact {{ font-size: 10px; padding: 2px 8px; border-radius: 8px; }}
.catalyst-impact.strong {{ background: rgba(0,255,136,0.1); color: var(--up); }}
.catalyst-impact.positive {{ background: rgba(0,212,255,0.1); color: var(--accent); }}
.catalyst-impact.negative {{ background: rgba(255,107,107,0.1); color: var(--down); }}
.catalyst-duration {{ font-size: 10px; color: var(--text-dim); margin-left: auto; }}

/* Framework Insights */
.insight-grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 10px; }}
.insight-item {{ padding: 12px 16px; border-radius: 10px; background: rgba(255,255,255,0.02); border: 1px solid rgba(255,255,255,0.03); }}
.insight-item .ins-label {{ font-size: 11px; color: var(--text-dim); margin-bottom: 4px; }}
.insight-item .ins-val {{ font-size: 13px; color: var(--text); line-height: 1.6; }}

@media (max-width: 768px) {{
  .header-title {{ font-size: 26px; }}
  .conc-ratings {{ flex-direction: column; }}
  .info-grid {{ grid-template-columns: 1fr 1fr; }}
  .app {{ padding: 0 12px; }}
  .nav-bar a {{ font-size: 11px; padding: 4px 10px; }}
}}
</style>
</head>
<body>
<div class="bg-stars"></div>

<div class="app">
  <header class="header">
    <div class="header-title">个股深度分析</div>
    <div class="header-sub">
      <span>{escape(s.get('name',''))}</span>
      <span class="dot">·</span>
      <span class="code">{escape(s.get('code',''))}</span>
      <span class="dot">·</span>
      <span>{escape(s.get('industry_l1',''))} - {escape(s.get('industry_l2',''))}</span>
    </div>
  </header>

  <nav class="nav-bar">
    {nav_html}
  </nav>

  {''.join(sections)}

  <footer class="footer">
    <p>数据来源：{escape(source)} | 生成时间：{today} | 仅供参考，不构成投资建议</p>
  </footer>
</div>

<div class="back-top" id="backTop" onclick="window.scrollTo({{top:0,behavior:'smooth'}})">▲</div>

<script>
window.addEventListener('scroll', function() {{
  var btn = document.getElementById('backTop');
  if (window.scrollY > 400) btn.classList.add('show');
  else btn.classList.remove('show');
}});
</script>
</body>
</html>'''
    
    return html


def main():
    parser = argparse.ArgumentParser(description='个股深度分析 HTML 报告生成器')
    parser.add_argument('--data-file', required=True, help='JSON 数据文件路径')
    parser.add_argument('--name', default='', help='股票名称（输出文件名用）')
    parser.add_argument('--code', default='', help='股票代码（输出文件名用）')
    parser.add_argument('--industry', default='', help='行业')
    parser.add_argument('--output', '-o', default=None, help='输出文件路径')
    
    args = parser.parse_args()
    
    data = load_data(args.data_file)
    
    # 用参数覆盖数据中的股票信息
    if args.name:
        data.setdefault('stock', {})['name'] = args.name
    if args.code:
        data.setdefault('stock', {})['code'] = args.code
    if args.industry:
        parts = args.industry.split('-', 1)
        data.setdefault('stock', {})['industry_l1'] = parts[0].strip()
        data.setdefault('stock', {})['industry_l2'] = parts[1].strip() if len(parts) > 1 else ''
    
    html = generate_html(data)
    
    # 确定输出路径
    if args.output:
        out_path = args.output
    else:
        stock_name = args.name or data.get('stock', {}).get('name', 'stock')
        stock_code = args.code or data.get('stock', {}).get('code', '000000')
        today = datetime.now().strftime('%Y-%m-%d')
        script_dir = os.path.dirname(os.path.abspath(__file__))
        skill_dir = os.path.dirname(script_dir)
        out_path = os.path.join(skill_dir, f'stock_analysis_{stock_code}_{today}.html')
    
    with open(out_path, 'w', encoding='utf-8') as f:
        f.write(html)
    
    print(f'FILE:{os.path.abspath(out_path)}')


if __name__ == '__main__':
    main()
