#!/usr/bin/env python3
"""
Tab3内容生成器 - 脚本点（元素列表）
"""
import html as html_module
from typing import Dict, Any, List


def get_intent_support_data(element: Dict[str, Any]) -> Dict[str, Any]:
    """
    获取元素的意图支撑数据（兼容新旧数据结构）
    
    优先使用"意图支撑"字段，如果没有则使用"多维度评分"字段
    这两个字段内部存储的都是意图支撑数据
    
    Args:
        element: 元素数据
        
    Returns:
        意图支撑数据字典，格式：{"灵感点": [...], "目的点": [...], "关键点": [...]}
    """
    # 优先使用"意图支撑"字段
    intent_support = element.get('意图支撑')
    if intent_support and isinstance(intent_support, dict):
        return intent_support
    
    # 如果没有"意图支撑"，则使用"多维度评分"字段（兼容旧数据）
    multi_scores = element.get('多维度评分')
    if multi_scores and isinstance(multi_scores, dict):
        return multi_scores
    
    # 都没有则返回空字典
    return {}


def calculate_intent_support_count(element: Dict[str, Any]) -> int:
    """
    计算元素的意图支撑数量

    统计元素支撑的意图点总数
    支撑的意图点越多，说明该元素与意图的关联越强

    Args:
        element: 元素数据

    Returns:
        支撑的意图点总数
    """
    # 统一使用意图支撑数据统计（实质和形式都使用相同逻辑）
    # 优先使用"意图支撑"字段，如果没有则使用"多维度评分"字段
    intent_support_data = get_intent_support_data(element)
    
    total_support_count = 0
    for point_type in ['灵感点', '目的点', '关键点']:
        support_points = intent_support_data.get(point_type, []) or []
        # 每个项目就是一个支撑点，直接统计数量
        total_support_count += len(support_points)

    return total_support_count


def get_element_coverage(element: Dict[str, Any]) -> float:
    """
    获取元素的段落覆盖率（兼容形式元素和实质元素）
    
    对于实质元素：从"共性分析"字段获取
    对于形式元素：从"权重明细"中的"覆盖率分"反推
    根据script_form_extraction_agent.py的逻辑：
    - coverage_rate_base_score = coverage_rate * 50 (基础分0-50)
    - coverage_rate_score = coverage_rate_base_score * 0.3 (加权后0-15)
    所以：coverage_rate = coverage_rate_score / 15
    
    Args:
        element: 元素数据
        
    Returns:
        段落覆盖率（0.0-1.0）
    """
    dimension = element.get('维度', {})
    is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'
    
    if is_form:
        # 形式元素：从权重明细中反推覆盖率
        weight_details = element.get('权重明细', {})
        if weight_details:
            coverage_score = float(weight_details.get('覆盖率分', 0) or 0)
            # 覆盖率分 = 覆盖率 × 50 × 0.3 = 覆盖率 × 15
            # 所以：覆盖率 = 覆盖率分 / 15
            if coverage_score > 0:
                coverage = coverage_score / 15.0
                return min(1.0, max(0.0, coverage))
        
        # 如果权重明细中没有覆盖率分，返回0
        return 0.0
    else:
        # 实质元素：从共性分析中获取
        commonality = element.get('共性分析') or {}
        coverage = float(commonality.get('段落覆盖率', 0.0) or 0.0)
        return coverage


def get_support_stats(element: Dict[str, Any]) -> Dict[str, int]:
    """
    获取元素的支撑统计信息（灵感点/目的点/关键点数量）

    优先使用元素中已经预计算好的 support_stats 字段；
    若不存在，则根据「意图支撑」或「多维度评分」字段动态统计。
    """
    # 优先使用预计算的支撑统计
    support_stats = element.get('支撑统计')
    if isinstance(support_stats, dict):
        # 做一次安全拷贝并补齐缺失字段
        return {
            '灵感点数量': int(support_stats.get('灵感点数量', 0) or 0),
            '目的点数量': int(support_stats.get('目的点数量', 0) or 0),
            '关键点数量': int(support_stats.get('关键点数量', 0) or 0),
        }

    # 兼容旧字段名
    support_stats_old = element.get('support_stats')
    if isinstance(support_stats_old, dict):
        return {
            '灵感点数量': int(support_stats_old.get('灵感点数量', 0) or 0),
            '目的点数量': int(support_stats_old.get('目的点数量', 0) or 0),
            '关键点数量': int(support_stats_old.get('关键点数量', 0) or 0),
        }

    # 统一使用意图支撑数据统计（实质和形式都使用相同逻辑）
    # 优先使用"意图支撑"字段，如果没有则使用"多维度评分"字段
    intent_support_data = get_intent_support_data(element)
    
    return {
        '灵感点数量': len(intent_support_data.get('灵感点', []) or []),
        '目的点数量': len(intent_support_data.get('目的点', []) or []),
        '关键点数量': len(intent_support_data.get('关键点', []) or []),
    }


def compute_weight_scores(element: Dict[str, Any]) -> Dict[str, Any]:
    """
    计算元素的权重相关得分。

    对于形式元素：优先使用元素中预计算的权重分和权重明细
    对于实质元素：根据共性分析和意图支撑动态计算

    原始总分 = 各子项得分之和
    权重分 = min(100, 原始总分 × 100 / 110)
    """
    # 判断是形式元素还是实质元素
    dimension = element.get('维度') or {}
    is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'

    # 形式元素：优先使用预计算的权重信息
    if is_form:
        weight_score = element.get('权重分')
        weight_details = element.get('权重明细')
        support_stats = get_support_stats(element)

        # 如果存在预计算的权重信息，直接使用
        if weight_score is not None and weight_details is not None:
            # 从权重明细中提取各项得分
            freq_score = float(weight_details.get('频次分', 0) or 0)
            coverage_count_score = float(weight_details.get('覆盖段落数分', 0) or 0)
            coverage_rate_score = float(weight_details.get('覆盖率分', 0) or 0)
            inspiration_score = float(weight_details.get('灵感点支撑分', 0) or 0)
            purpose_score = float(weight_details.get('目的点支撑分', 0) or 0)
            keypoint_score = float(weight_details.get('关键点支撑分', 0) or 0)

            # 计算原始总分（共性总分 + 支撑总分）
            commonality_total = weight_details.get('共性总分', 0) or 0
            support_total = weight_details.get('支撑总分', 0) or 0
            raw_total = float(commonality_total) + float(support_total)

            return {
                'weight_score': float(weight_score),
                'raw_total': raw_total,
                'details': {
                    '频次分': freq_score,
                    '覆盖段落数分': coverage_count_score,
                    '覆盖率分': coverage_rate_score,
                    '灵感点支撑分': inspiration_score,
                    '目的点支撑分': purpose_score,
                    '关键点支撑分': keypoint_score,
                },
                'support_stats': support_stats,
            }

    # 实质元素或形式元素没有预计算权重：使用旧的计算逻辑
    commonality = element.get('共性分析') or {}
    coverage = float(commonality.get('段落覆盖率', 0.0) or 0.0)
    frequency = int(commonality.get('出现频次', 0) or 0)

    support_stats = get_support_stats(element)
    inspiration_count = support_stats.get('灵感点数量', 0) or 0
    purpose_count = support_stats.get('目的点数量', 0) or 0
    keypoint_count = support_stats.get('关键点数量', 0) or 0

    # 1) 频次分（0–30分）
    # 假定「高频」的参考上限为 12 次，超过即视为满分
    # 频次分 = min(30, 出现频次 / 12 * 30)
    if frequency <= 0:
        freq_score = 0.0
    else:
        freq_score = min(30.0, frequency * 30.0 / 12.0)

    # 2) 覆盖率分（0–30分）
    # 覆盖率分 = 段落覆盖率 × 30
    coverage_score = max(0.0, min(30.0, coverage * 30.0))

    # 3) 灵感点支撑分（0–25分）
    # 按你的说明：支撑{灵感点数量}个灵感点 × 25分/个，封顶 25 分
    inspiration_score = min(25.0, float(inspiration_count) * 25.0)

    # 4) 目的点支撑分（0–15分）
    # 说明：支撑{目的点数量}个目的点 × 5分/个，封顶 15 分
    purpose_score = min(15.0, float(purpose_count) * 5.0)

    # 5) 关键点支撑分（0–10分）
    # 说明：支撑{关键点数量}个关键点 × 1分/个，封顶 10 分
    keypoint_score = min(10.0, float(keypoint_count) * 1.0)

    raw_total = freq_score + coverage_score + inspiration_score + purpose_score + keypoint_score

    if raw_total <= 0:
        weight_score = 0.0
    else:
        weight_score = min(100.0, raw_total * 100.0 / 110.0)

    return {
        'weight_score': weight_score,
        'raw_total': raw_total,
        'details': {
            '频次分': freq_score,
            '覆盖率分': coverage_score,
            '灵感点支撑分': inspiration_score,
            '目的点支撑分': purpose_score,
            '关键点支撑分': keypoint_score,
        },
        'support_stats': support_stats,
    }


def determine_dominant_factor(element: Dict[str, Any], all_elements: List[Dict[str, Any]]) -> str:
    """
    判断元素排序的主导因素

    排序规则：覆盖率 > 频次 > 意图支撑数
    主导因素判断：哪个指标在当前元素中相对最显著

    Args:
        element: 当前元素
        all_elements: 同组所有元素

    Returns:
        主导因素: 'coverage' | 'frequency' | 'intent_support'
    """
    if not all_elements:
        return 'coverage'

    # 获取当前元素的指标
    commonality = element.get('共性分析') or {}
    coverage = commonality.get('段落覆盖率', 0.0)
    frequency = commonality.get('出现频次', 0)
    intent_count = calculate_intent_support_count(element)

    # 将所有指标归一化到同一量级，然后比较
    # 覆盖率已经是0-1范围
    # 频次归一化：假设最大频次为10
    normalized_frequency = min(frequency / 10.0, 1.0)
    # 意图支撑数归一化：假设最大支撑数为10
    normalized_intent = min(intent_count / 10.0, 1.0)

    # 比较归一化后的值，取最大的作为主导因素
    scores = {
        'coverage': coverage,
        'frequency': normalized_frequency,
        'intent_support': normalized_intent
    }
    return max(scores, key=scores.get)


def sort_elements_by_coverage_and_frequency(elements: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    按照覆盖率、频次和意图支撑数对元素排序

    排序规则：
    1. 第一优先级：共性（段落覆盖率）- 倒序
    2. 第二优先级：共性（出现频次）- 倒序
    3. 第三优先级：意图支撑数 - 倒序

    Args:
        elements: 元素列表

    Returns:
        排序后的元素列表
    """
    def get_sort_key(elem):
        # 获取共性分析，防止为None
        commonality = elem.get('共性分析') or {}
        # 获取段落覆盖率
        coverage = commonality.get('段落覆盖率', 0.0)
        # 获取出现频次
        frequency = commonality.get('出现频次', 0)
        # 计算意图支撑数
        intent_count = calculate_intent_support_count(elem)
        # 返回排序键（负数用于倒序）
        return (-coverage, -frequency, -intent_count)

    return sorted(elements, key=get_sort_key)


def get_element_category(element: Dict[str, Any]) -> str:
    """
    获取元素的分类名称（支持新旧两种数据结构）

    Args:
        element: 元素数据

    Returns:
        分类名称字符串
    """
    category = element.get('分类', '未分类')
    if isinstance(category, dict):
        # 新结构：分类是对象，包含一级分类和二级分类
        level1 = category.get('一级分类', '')
        level2 = category.get('二级分类', '')
        if level1 and level2:
            return f"{level1} - {level2}"
        elif level1:
            return level1
        else:
            return '未分类'
    else:
        # 旧结构：分类是字符串或列表
        if isinstance(category, list):
            return ' - '.join(category) if category else '未分类'
        return category if category else '未分类'


def group_elements_by_hierarchical_category(elements: List[Dict[str, Any]]) -> Dict[str, Any]:
    """
    按树形分类结构组织元素（一级分类 → 二级分类 → 元素）

    优化规则：同一个父节点下的所有子节点采用统一的分类格式展示
    - 如果一级分类下既有元素又有分类，将元素归入"未分类"二级分类

    Args:
        elements: 元素列表

    Returns:
        树形分类结构字典
    """
    # 1. 按一级分类和二级分类分组
    level1_groups = {}

    for elem in elements:
        category_data = elem.get('分类', {})

        if isinstance(category_data, dict):
            level1 = category_data.get('一级分类', '未分类')
            level2 = category_data.get('二级分类', '')
        elif isinstance(category_data, list):
            # 列表格式：第一个元素作为一级分类，第二个作为二级分类
            level1 = category_data[0] if len(category_data) > 0 else '未分类'
            level2 = category_data[1] if len(category_data) > 1 else ''
        else:
            # 旧结构：分类是字符串
            level1 = str(category_data) if category_data else '未分类'
            level2 = ''

        # 初始化一级分类
        if level1 not in level1_groups:
            level1_groups[level1] = {
                'elements': [],
                'level2_groups': {}
            }

        # 如果有二级分类，放入二级分类组；否则放入一级分类的直接元素列表（临时）
        if level2:
            if level2 not in level1_groups[level1]['level2_groups']:
                level1_groups[level1]['level2_groups'][level2] = []
            level1_groups[level1]['level2_groups'][level2].append(elem)
        else:
            level1_groups[level1]['elements'].append(elem)

    # 1.5 优化：仅当一级分类下既有直接元素又有二级分类时，才将直接元素移到"未分类"二级分类中
    # 如果一级分类下只有直接元素，没有二级分类，则保持原样（不需要"未分类"概念）
    for level1_name, level1_data in level1_groups.items():
        if level1_data['elements'] and level1_data['level2_groups']:
            # 将直接元素移到"未分类"分类
            if '未分类' not in level1_data['level2_groups']:
                level1_data['level2_groups']['未分类'] = []
            level1_data['level2_groups']['未分类'].extend(level1_data['elements'])
            level1_data['elements'] = []
        # 如果只有直接元素，没有二级分类，则保持level1_data['elements']不变

    # 2. 对每个分类内的元素排序
    for level1_data in level1_groups.values():
        # 排序一级分类直接包含的元素
        if level1_data['elements']:
            level1_data['elements'] = sort_elements_by_coverage_and_frequency(level1_data['elements'])

        # 排序每个二级分类的元素
        for level2_name in level1_data['level2_groups']:
            level1_data['level2_groups'][level2_name] = sort_elements_by_coverage_and_frequency(
                level1_data['level2_groups'][level2_name]
            )

    # 3. 计算每个一级分类的统计信息用于排序
    level1_scores = {}
    for level1_name, level1_data in level1_groups.items():
        # 收集该一级分类下的所有元素（包括二级分类下的）
        all_elements = level1_data['elements'][:]
        for level2_elements in level1_data['level2_groups'].values():
            all_elements.extend(level2_elements)

        if not all_elements:
            level1_scores[level1_name] = (0.0, 0, 0.0)
            continue

        # 计算统计指标
        avg_coverage = sum(get_element_coverage(e) for e in all_elements) / len(all_elements)
        avg_frequency = sum((e.get('共性分析') or {}).get('出现频次', 0) for e in all_elements) / len(all_elements)
        avg_intent_count = sum(calculate_intent_support_count(e) for e in all_elements) / len(all_elements)

        level1_scores[level1_name] = (avg_coverage, avg_frequency, avg_intent_count)

    # 4. 对一级分类排序
    sorted_level1 = sorted(
        level1_scores.keys(),
        key=lambda c: (-level1_scores[c][0], -level1_scores[c][1], -level1_scores[c][2])
    )

    # 5. 对每个一级分类内的二级分类排序
    for level1_name in sorted_level1:
        level1_data = level1_groups[level1_name]
        level2_groups = level1_data['level2_groups']

        if not level2_groups:
            continue

        # 计算二级分类的统计信息
        level2_scores = {}
        for level2_name, level2_elements in level2_groups.items():
            if not level2_elements:
                level2_scores[level2_name] = (0.0, 0, 0.0)
                continue

            avg_coverage = sum(get_element_coverage(e) for e in level2_elements) / len(level2_elements)
            avg_frequency = sum((e.get('共性分析') or {}).get('出现频次', 0) for e in level2_elements) / len(level2_elements)
            avg_intent_count = sum(calculate_intent_support_count(e) for e in level2_elements) / len(level2_elements)

            level2_scores[level2_name] = (avg_coverage, avg_frequency, avg_intent_count)

        # 排序二级分类
        sorted_level2_names = sorted(
            level2_scores.keys(),
            key=lambda c: (-level2_scores[c][0], -level2_scores[c][1], -level2_scores[c][2])
        )

        # 重新组织为有序字典
        sorted_level2_groups = {name: level2_groups[name] for name in sorted_level2_names}
        level1_data['level2_groups'] = sorted_level2_groups

    # 6. 返回排序后的结构
    return {level1_name: level1_groups[level1_name] for level1_name in sorted_level1}


def render_element_item(element: Dict[str, Any], all_elements: List[Dict[str, Any]] = None) -> str:
    """渲染单个元素项的HTML（卡片样式，详细信息在弹窗中）

    Args:
        element: 元素数据
        all_elements: 同组所有元素（用于计算主导因素）
    """
    elem_id = element.get('id', '')
    name = element.get('名称') or ''  # 处理None的情况
    description = element.get('描述') or ''  # 处理None的情况

    # 获取类型和维度（兼容新旧结构）
    dimension = element.get('维度', {})
    if isinstance(dimension, dict):
        elem_type = dimension.get('一级', '')
        elem_type_level2 = dimension.get('二级', '')
    else:
        elem_type = element.get('类型', '')
        elem_type_level2 = ''

    # 获取共性分析（防止为None）
    commonality = element.get('共性分析') or {}
    coverage = commonality.get('段落覆盖率', 0.0)
    frequency = commonality.get('出现频次', 0)
    intent_count = calculate_intent_support_count(element)

    # 计算权重得分
    weight_info = compute_weight_scores(element)

    # 计算主导因素
    dominant_factor = 'coverage'  # 默认
    if all_elements:
        dominant_factor = determine_dominant_factor(element, all_elements)

    # 根据主导因素确定边框颜色
    border_color_class = f'dominant-{dominant_factor}'

    # 检查是否有详细信息
    category_def = element.get('分类定义', '')
    paragraphs_list = commonality.get('出现段落列表', [])
    source = element.get('来源', [])
    intent_support = get_intent_support_data(element)
    has_details = bool(category_def or paragraphs_list or source or intent_support or weight_info.get('raw_total', 0) > 0)

    # 判断是否为形式元素
    is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'

    # 卡片样式
    html = f'<li class="element-card {border_color_class}" data-elem-id="{elem_id}">\n'
    html += '<div class="element-card-body">\n'
    
    # 卡片头部
    html += '<div class="element-card-header">\n'
    if elem_id:
        html += f'<span class="element-card-id">#{elem_id}</span>\n'
    html += f'<h4 class="element-card-name">{html_module.escape(name)}</h4>\n'
    html += '</div>\n'

    # 统计指标
    html += '<div class="element-card-stats">\n'
    if weight_info.get('raw_total', 0) > 0:
        html += f'<span class="stat-badge stat-weight">权重分: {weight_info["weight_score"]:.1f}</span>\n'
    
    if is_form:
        html += f'<span class="stat-badge stat-intent">支撑: {intent_count}</span>\n'
    else:
        coverage_highlight = 'stat-highlight' if dominant_factor == 'coverage' else ''
        frequency_highlight = 'stat-highlight' if dominant_factor == 'frequency' else ''
        intent_highlight = 'stat-highlight' if dominant_factor == 'intent_support' else ''
        
        html += f'<span class="stat-badge stat-coverage {coverage_highlight}">覆盖率: {coverage:.2%}</span>\n'
        html += f'<span class="stat-badge stat-frequency {frequency_highlight}">频次: {frequency}</span>\n'
        html += f'<span class="stat-badge stat-intent {intent_highlight}">意图支撑: {intent_count}</span>\n'
    html += '</div>\n'

    # 描述
    if description:
        html += f'<div class="element-card-description">{html_module.escape(description)}</div>\n'

    # 查看详情按钮
    if has_details:
        html += f'<button class="element-detail-btn" onclick="openElementModal(\'{elem_id}\')">查看详情</button>\n'

    html += '</div>\n'  # element-card-body
    html += '</li>\n'
    return html


def render_element_modal(element: Dict[str, Any]) -> str:
    """渲染元素详情的弹窗内容

    Args:
        element: 元素数据
    """
    elem_id = element.get('id', '')
    name = element.get('名称') or ''
    description = element.get('描述') or ''

    # 获取类型和维度
    dimension = element.get('维度', {})
    if isinstance(dimension, dict):
        elem_type = dimension.get('一级', '')
        elem_type_level2 = dimension.get('二级', '')
    else:
        elem_type = element.get('类型', '')
        elem_type_level2 = ''

    # 获取分类
    category_data = element.get('分类', '')
    category_def = element.get('分类定义', '')
    
    # 获取共性分析
    commonality = element.get('共性分析') or {}
    coverage = commonality.get('段落覆盖率', 0.0)
    frequency = commonality.get('出现频次', 0)
    paragraphs_list = commonality.get('出现段落列表', [])
    source = element.get('来源', [])
    intent_support = get_intent_support_data(element)
    weight_info = compute_weight_scores(element)

    html = f'<div class="element-modal-content" data-elem-id="{elem_id}">\n'
    html += '<div class="modal-header">\n'
    if elem_id:
        html += f'<span class="modal-id">#{elem_id}</span>\n'
    html += f'<h3 class="modal-title">{html_module.escape(name)}</h3>\n'
    html += '<button class="modal-close" onclick="closeElementModal()">&times;</button>\n'
    html += '</div>\n'

    html += '<div class="modal-body">\n'

    # 描述
    if description:
        html += '<div class="detail-section">\n'
        html += '<strong>描述：</strong>\n'
        html += f'<div class="detail-text">{html_module.escape(description)}</div>\n'
        html += '</div>\n'

    # 分类定义
    if category_def:
        html += '<div class="detail-section">\n'
        html += '<strong>分类定义：</strong>\n'
        html += f'<div class="detail-text">{html_module.escape(category_def)}</div>\n'
        html += '</div>\n'

    # 针对"形式"维度，显示"支撑"、"推理"和"支撑关系"
    if isinstance(dimension, dict) and dimension.get('一级') == '形式':
        # 支撑
        zhicheng = element.get('支撑')
        if zhicheng:
                html += '<div class="detail-section">\n'
                html += '<strong>支撑：</strong>\n'
                html += '<div class="detail-content">\n'
                if isinstance(zhicheng, list):
                    for item in zhicheng:
                        if isinstance(item, dict):
                            item_id = item.get('id', '')
                            item_name = item.get('名称', '')
                            html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
                        else:
                            html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
                elif isinstance(zhicheng, dict):
                    # 支撑可能是对象（包含具体元素、具象概念等）
                    for key, values in zhicheng.items():
                        if isinstance(values, list):
                            html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
                            for item in values:
                                if isinstance(item, dict):
                                    item_id = item.get('id', '')
                                    item_name = item.get('名称', '')
                                    html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
                                else:
                                    html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
                        else:
                            html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
                else:
                    html += f'<span class="detail-tag">{html_module.escape(str(zhicheng))}</span>\n'
                html += '</div>\n'
                html += '</div>\n'

        # 推理
        tuili = element.get('推理')
        if tuili:
                html += '<div class="detail-section">\n'
                html += '<strong>推理：</strong>\n'
                html += f'<div class="detail-text">{html_module.escape(tuili)}</div>\n'
                html += '</div>\n'

        # 支撑关系（形式元素专用，从意图支撑数据中显示支撑点）
        # 优先使用"意图支撑"字段，如果没有则使用"多维度评分"字段
        intent_support_data = get_intent_support_data(element)
        if intent_support_data:
            html += '<div class="detail-section">\n'
            html += '<strong>支撑关系：</strong>\n'
            for point_type in ['灵感点', '目的点', '关键点']:
                    support_points = intent_support_data.get(point_type) or []
                    if not support_points:
                        continue

                    html += f'<div class="score-type">{point_type}</div>\n'
                    html += '<div class="score-list">\n'

                    # 每个项目就是一个支撑点
                    for support_point in support_points:
                        if not isinstance(support_point, dict):
                            continue

                        point = support_point.get('点', '')
                        point_intention = support_point.get('点的意图', '')
                        support_reason = support_point.get('支撑理由', '')

                        html += '<div class="score-item">\n'
                        html += f'<div class="score-point">{html_module.escape(point)}</div>\n'

                        # 显示点的意图
                        if point_intention:
                            html += '<div class="point-intention">\n'
                            html += f'<strong style="color: #666;">点的意图：</strong>{html_module.escape(point_intention)}\n'
                            html += '</div>\n'

                        # 显示支撑理由
                        if support_reason:
                            html += '<div class="score-reasons">\n'
                            html += f'<strong style="color: #666;">支撑理由：</strong>\n'
                            html += f'<div class="score-reason">{html_module.escape(support_reason)}</div>\n'
                            html += '</div>\n'

                        html += '</div>\n'

                    html += '</div>\n'  # end score-list
            html += '</div>\n'  # end detail-section

        # 针对"隐含概念"，显示"来源"（声音特征、语气语调、BGM、音效等）和"时间范围"
        elem_type = element.get('类型', '')
        if elem_type == '隐含概念':
            # 来源（隐含概念的来源包含声音特征、语气语调、背景音乐、音效等）
            laiyuan = element.get('来源')
            if laiyuan and isinstance(laiyuan, dict):
                html += '<div class="detail-section">\n'
                html += '<strong>来源：</strong>\n'
                html += '<div class="detail-content">\n'
                for key, values in laiyuan.items():
                    if isinstance(values, list) and values:
                        html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
                        for item in values:
                            html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
                    elif values:
                        html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
                html += '</div>\n'
                html += '</div>\n'

            # 时间范围
            time_range = element.get('时间范围')
            if time_range:
                html += '<div class="detail-section">\n'
                html += '<strong>时间范围：</strong>\n'
                html += '<div class="detail-content">\n'
                if isinstance(time_range, list):
                    for tr in time_range:
                        html += f'<span class="detail-tag source-tag">{html_module.escape(str(tr))}</span>\n'
                else:
                    html += f'<span class="detail-tag source-tag">{html_module.escape(str(time_range))}</span>\n'
                html += '</div>\n'
                html += '</div>\n'

        # 针对"抽象概念"（实质-抽象概念），显示"类型"、"来源"和"推理过程"
        # 注意：排除隐含概念（隐含概念有自己的显示逻辑）
        elif isinstance(dimension, dict) and dimension.get('二级') == '抽象概念':
            # 类型
            leixing = element.get('类型')
            if leixing:
                html += '<div class="detail-section">\n'
                html += '<strong>类型：</strong>\n'
                html += f'<div class="detail-text">{html_module.escape(str(leixing))}</div>\n'
                html += '</div>\n'

            # 来源（抽象概念的来源可能是复杂对象）
            laiyuan = element.get('来源')
            if laiyuan and isinstance(laiyuan, dict):
                html += '<div class="detail-section">\n'
                html += '<strong>来源：</strong>\n'
                html += '<div class="detail-content">\n'
                for key, values in laiyuan.items():
                    if isinstance(values, list):
                        html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
                        for item in values:
                            if isinstance(item, dict):
                                item_id = item.get('id', '')
                                item_name = item.get('名称', '')
                                html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
                            else:
                                html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
                    else:
                        html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
                html += '</div>\n'
                html += '</div>\n'

            # 推理过程
            tuili_guocheng = element.get('推理过程')
            if tuili_guocheng:
                html += '<div class="detail-section">\n'
                html += '<strong>推理过程：</strong>\n'
                html += f'<div class="detail-text">{html_module.escape(tuili_guocheng)}</div>\n'
                html += '</div>\n'

        else:
            if source:
                html += '<div class="detail-section">\n'
                html += '<strong>来源：</strong>\n'
                html += '<div class="detail-content">\n'
                for src in source:
                    html += f'<span class="detail-tag source-tag">{html_module.escape(str(src))}</span>\n'
                html += '</div>\n'
                html += '</div>\n'

        # 上下文验证（适用于具象概念）
        context_verification = element.get('上下文验证')
        if context_verification:
            html += '<div class="detail-section">\n'
            html += '<strong>上下文验证：</strong>\n'
            html += '<div class="context-verification" style="margin-top: 8px; padding: 12px; background-color: #f8f9fa; border-radius: 4px; border-left: 3px solid #6c757d;">\n'

            # 原文位置
            original_position = context_verification.get('原文位置', '')
            if original_position:
                html += '<div class="context-item" style="margin-bottom: 8px;">\n'
                html += '<strong style="color: #495057; font-size: 13px;">原文位置：</strong>\n'
                html += f'<div style="margin-top: 4px; padding: 6px 10px; background-color: #fff; border-radius: 3px; color: #212529; font-style: italic;">{html_module.escape(original_position)}</div>\n'
                html += '</div>\n'

            # 语法成分
            grammar_component = context_verification.get('语法成分', '')
            if grammar_component:
                html += '<div class="context-item" style="margin-bottom: 8px;">\n'
                html += '<strong style="color: #495057; font-size: 13px;">语法成分：</strong>\n'
                html += f'<span style="margin-left: 8px; padding: 3px 10px; background-color: #e7f3ff; color: #0066cc; border-radius: 3px; font-size: 12px;">{html_module.escape(grammar_component)}</span>\n'
                html += '</div>\n'

            # 语境判断
            context_judgment = context_verification.get('语境判断', '')
            if context_judgment:
                html += '<div class="context-item" style="margin-bottom: 0;">\n'
                html += '<strong style="color: #495057; font-size: 13px;">语境判断：</strong>\n'
                html += f'<div style="margin-top: 4px; padding: 8px 10px; background-color: #fff; border-radius: 3px; color: #495057; line-height: 1.6;">{html_module.escape(context_judgment)}</div>\n'
                html += '</div>\n'

            html += '</div>\n'
            html += '</div>\n'

        # 出现段落
        if paragraphs_list:
            html += '<div class="detail-section">\n'
            html += '<strong>出现段落：</strong>\n'
            html += '<div class="paragraphs-detail-list">\n'
            for para in paragraphs_list:
                if isinstance(para, dict):
                    # 新结构：对象包含段落ID和如何体现
                    para_id = para.get('段落ID', '')
                    how = para.get('如何体现', '')
                    html += '<div class="paragraph-detail-item">\n'
                    html += f'<span class="detail-tag para-id-tag">{html_module.escape(para_id)}</span>\n'
                    if how:
                        html += f'<div class="para-how">{html_module.escape(how)}</div>\n'
                    html += '</div>\n'
                else:
                    # 旧结构：字符串
                    html += f'<span class="detail-tag">{html_module.escape(str(para))}</span>\n'
            html += '</div>\n'
            html += '</div>\n'

        # 意图支撑
        if intent_support:
            html += '<div class="detail-section">\n'
            html += '<strong>意图支撑：</strong>\n'

            for point_type in ['灵感点', '目的点', '关键点']:
                if point_type in intent_support and intent_support[point_type]:
                    html += f'<div class="score-type">{point_type}</div>\n'
                    html += '<div class="score-list">\n'

                    for item in intent_support[point_type]:
                        point = item.get('点', '')
                        point_intention = item.get('点的意图', '')
                        support_reason = item.get('支撑理由', '')

                        html += '<div class="score-item">\n'
                        html += f'<div class="score-point">{html_module.escape(point)}</div>\n'

                        # 显示点的意图
                        if point_intention:
                            html += '<div class="point-intention">\n'
                            html += f'<strong style="color: #666;">点的意图：</strong>{html_module.escape(point_intention)}\n'
                            html += '</div>\n'

                        # 显示支撑理由
                        if support_reason:
                            html += '<div class="score-reasons">\n'
                            html += f'<strong style="color: #666;">支撑理由：</strong>\n'
                            html += f'<div class="score-reason">{html_module.escape(support_reason)}</div>\n'
                            html += '</div>\n'

                        html += '</div>\n'

                    html += '</div>\n'

            html += '</div>\n'

        # 权重明细（放在展开内容最底部，简化为紧凑样式）
        if weight_info.get('raw_total', 0) > 0:
            wd = weight_info['details']
            ss = weight_info['support_stats']

            # 判断是形式元素还是实质元素
            is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'

            html += '<div class="detail-section weight-detail-section" style="margin-top: 12px; border-top: 1px dashed #e0e0e0; padding-top: 10px;">\n'
            html += '<strong style="font-size: 13px; color: #555;">权重得分明细：</strong>\n'
            html += '<div class="weight-summary-header" style="margin-top: 4px; font-size: 12px; color: #666;">\n'
            if is_form:
                html += f'权重分：{weight_info["weight_score"]:.1f}（形式元素使用新的权重计算逻辑）\n'
            else:
                html += f'原始总分：{weight_info["raw_total"]:.1f}，权重分：{weight_info["weight_score"]:.1f}（min(100, 原始总分 × 100 / 110)）\n'
            html += '</div>\n'

            html += '<div class="weight-summary-grid" style="margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px;">\n'
            
            # 共性维度得分
            if is_form:
                # 形式元素：显示频次分、覆盖段落数分、覆盖率分
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">频次分 {wd.get("频次分", 0):.1f}</span>\n'
                if "覆盖段落数分" in wd:
                    html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">覆盖段落数分 {wd.get("覆盖段落数分", 0):.1f}</span>\n'
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">覆盖率分 {wd.get("覆盖率分", 0):.1f}</span>\n'
            else:
                # 实质元素：显示频次分、覆盖率分
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">频次分 {wd.get("频次分", 0):.1f} / 30</span>\n'
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">覆盖率分 {wd.get("覆盖率分", 0):.1f} / 30</span>\n'

            # 支撑维度得分
            inspiration_count = ss.get('灵感点数量', 0)
            if is_form:
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f}（{inspiration_count} 个）</span>\n'
            else:
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f} / 25（{inspiration_count} 个）</span>\n'

            purpose_count = ss.get('目的点数量', 0)
            if is_form:
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">目的点支撑分 {wd.get("目的点支撑分", 0):.1f}（{purpose_count} 个）</span>\n'
            else:
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">目的点支撑分 {wd.get("目的点支撑分", 0):.1f} / 15（{purpose_count} 个）</span>\n'

            keypoint_count = ss.get('关键点数量', 0)
            if is_form:
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">关键点支撑分 {wd.get("关键点支撑分", 0):.1f}（{keypoint_count} 个）</span>\n'
            else:
                html += f'<span class="weight-chip" style="font-size: 12px; padding: 2px 8px; border-radius: 10px; background: #f5f5f5; color: #333;">关键点支撑分 {wd.get("关键点支撑分", 0):.1f} / 10（{keypoint_count} 个）</span>\n'

            html += '</div>\n'
            html += '</div>\n'

        html += '</div>\n'

    html += '</li>\n'
    return html


def render_element_modal(element: Dict[str, Any]) -> str:
    """渲染元素详情的弹窗内容

    Args:
        element: 元素数据
    """
    elem_id = element.get('id', '')
    name = element.get('名称') or ''
    description = element.get('描述') or ''

    # 获取类型和维度
    dimension = element.get('维度', {})
    if isinstance(dimension, dict):
        elem_type = dimension.get('一级', '')
        elem_type_level2 = dimension.get('二级', '')
    else:
        elem_type = element.get('类型', '')
        elem_type_level2 = ''

    # 获取分类
    category_data = element.get('分类', '')
    category_def = element.get('分类定义', '')
    
    # 获取共性分析
    commonality = element.get('共性分析') or {}
    coverage = commonality.get('段落覆盖率', 0.0)
    frequency = commonality.get('出现频次', 0)
    paragraphs_list = commonality.get('出现段落列表', [])
    source = element.get('来源', [])
    intent_support = get_intent_support_data(element)
    weight_info = compute_weight_scores(element)

    html = f'<div class="element-modal-content" data-elem-id="{elem_id}">\n'
    html += '<div class="modal-header">\n'
    if elem_id:
        html += f'<span class="modal-id">#{elem_id}</span>\n'
    html += f'<h3 class="modal-title">{html_module.escape(name)}</h3>\n'
    html += '<button class="modal-close" onclick="closeElementModal()">&times;</button>\n'
    html += '</div>\n'

    html += '<div class="modal-body">\n'

    # 描述
    if description:
        html += '<div class="detail-section">\n'
        html += '<strong>描述：</strong>\n'
        html += f'<div class="detail-text">{html_module.escape(description)}</div>\n'
        html += '</div>\n'

    # 分类定义
    if category_def:
        html += '<div class="detail-section">\n'
        html += '<strong>分类定义：</strong>\n'
        html += f'<div class="detail-text">{html_module.escape(category_def)}</div>\n'
        html += '</div>\n'

    # 针对"形式"维度，显示"支撑"、"推理"和"支撑关系"
    if isinstance(dimension, dict) and dimension.get('一级') == '形式':
        # 支撑
        zhicheng = element.get('支撑')
        if zhicheng:
            html += '<div class="detail-section">\n'
            html += '<strong>支撑：</strong>\n'
            html += '<div class="detail-content">\n'
            if isinstance(zhicheng, list):
                for item in zhicheng:
                    if isinstance(item, dict):
                        item_id = item.get('id', '')
                        item_name = item.get('名称', '')
                        html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
                    else:
                        html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
            elif isinstance(zhicheng, dict):
                for key, values in zhicheng.items():
                    if isinstance(values, list):
                        html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
                        for item in values:
                            if isinstance(item, dict):
                                item_id = item.get('id', '')
                                item_name = item.get('名称', '')
                                html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
                            else:
                                html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
                    else:
                        html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
            else:
                html += f'<span class="detail-tag">{html_module.escape(str(zhicheng))}</span>\n'
            html += '</div>\n'
            html += '</div>\n'

        # 推理
        tuili = element.get('推理')
        if tuili:
            html += '<div class="detail-section">\n'
            html += '<strong>推理：</strong>\n'
            html += f'<div class="detail-text">{html_module.escape(tuili)}</div>\n'
            html += '</div>\n'

        # 支撑关系
        intent_support_data = get_intent_support_data(element)
        if intent_support_data:
            html += '<div class="detail-section">\n'
            html += '<strong>支撑关系：</strong>\n'
            for point_type in ['灵感点', '目的点', '关键点']:
                support_points = intent_support_data.get(point_type) or []
                if not support_points:
                    continue
                html += f'<div class="score-type">{point_type}</div>\n'
                html += '<div class="score-list">\n'
                for support_point in support_points:
                    if not isinstance(support_point, dict):
                        continue
                    point = support_point.get('点', '')
                    point_intention = support_point.get('点的意图', '')
                    support_reason = support_point.get('支撑理由', '')
                    html += '<div class="score-item">\n'
                    html += f'<div class="score-point">{html_module.escape(point)}</div>\n'
                    if point_intention:
                        html += '<div class="point-intention">\n'
                        html += f'<strong style="color: #666;">点的意图：</strong>{html_module.escape(point_intention)}\n'
                        html += '</div>\n'
                    if support_reason:
                        html += '<div class="score-reasons">\n'
                        html += f'<strong style="color: #666;">支撑理由：</strong>\n'
                        html += f'<div class="score-reason">{html_module.escape(support_reason)}</div>\n'
                        html += '</div>\n'
                    html += '</div>\n'
                html += '</div>\n'
            html += '</div>\n'

    # 针对"隐含概念"
    elem_type = element.get('类型', '')
    if elem_type == '隐含概念':
        laiyuan = element.get('来源')
        if laiyuan and isinstance(laiyuan, dict):
            html += '<div class="detail-section">\n'
            html += '<strong>来源：</strong>\n'
            html += '<div class="detail-content">\n'
            for key, values in laiyuan.items():
                if isinstance(values, list) and values:
                    html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
                    for item in values:
                        html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
                elif values:
                    html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
            html += '</div>\n'
            html += '</div>\n'

        time_range = element.get('时间范围')
        if time_range:
            html += '<div class="detail-section">\n'
            html += '<strong>时间范围：</strong>\n'
            html += '<div class="detail-content">\n'
            if isinstance(time_range, list):
                for tr in time_range:
                    html += f'<span class="detail-tag source-tag">{html_module.escape(str(tr))}</span>\n'
            else:
                html += f'<span class="detail-tag source-tag">{html_module.escape(str(time_range))}</span>\n'
            html += '</div>\n'
            html += '</div>\n'

    # 针对"抽象概念"
    elif isinstance(dimension, dict) and dimension.get('二级') == '抽象概念':
        leixing = element.get('类型')
        if leixing:
            html += '<div class="detail-section">\n'
            html += '<strong>类型：</strong>\n'
            html += f'<div class="detail-text">{html_module.escape(str(leixing))}</div>\n'
            html += '</div>\n'

        laiyuan = element.get('来源')
        if laiyuan and isinstance(laiyuan, dict):
            html += '<div class="detail-section">\n'
            html += '<strong>来源：</strong>\n'
            html += '<div class="detail-content">\n'
            for key, values in laiyuan.items():
                if isinstance(values, list):
                    html += f'<div class="detail-tag category-level1">{html_module.escape(key)}</div>\n'
                    for item in values:
                        if isinstance(item, dict):
                            item_id = item.get('id', '')
                            item_name = item.get('名称', '')
                            html += f'<span class="detail-tag">{html_module.escape(f"{item_id}: {item_name}")}</span>\n'
                        else:
                            html += f'<span class="detail-tag">{html_module.escape(str(item))}</span>\n'
                else:
                    html += f'<span class="detail-tag">{html_module.escape(str(values))}</span>\n'
            html += '</div>\n'
            html += '</div>\n'

        tuili_guocheng = element.get('推理过程')
        if tuili_guocheng:
            html += '<div class="detail-section">\n'
            html += '<strong>推理过程：</strong>\n'
            html += f'<div class="detail-text">{html_module.escape(tuili_guocheng)}</div>\n'
            html += '</div>\n'

    else:
        if source:
            html += '<div class="detail-section">\n'
            html += '<strong>来源：</strong>\n'
            html += '<div class="detail-content">\n'
            for src in source:
                html += f'<span class="detail-tag source-tag">{html_module.escape(str(src))}</span>\n'
            html += '</div>\n'
            html += '</div>\n'

    # 上下文验证
    context_verification = element.get('上下文验证')
    if context_verification:
        html += '<div class="detail-section">\n'
        html += '<strong>上下文验证：</strong>\n'
        html += '<div class="context-verification">\n'
        original_position = context_verification.get('原文位置', '')
        if original_position:
            html += '<div class="context-item">\n'
            html += '<strong>原文位置：</strong>\n'
            html += f'<div class="context-text">{html_module.escape(original_position)}</div>\n'
            html += '</div>\n'
        grammar_component = context_verification.get('语法成分', '')
        if grammar_component:
            html += '<div class="context-item">\n'
            html += '<strong>语法成分：</strong>\n'
            html += f'<span class="grammar-tag">{html_module.escape(grammar_component)}</span>\n'
            html += '</div>\n'
        context_judgment = context_verification.get('语境判断', '')
        if context_judgment:
            html += '<div class="context-item">\n'
            html += '<strong>语境判断：</strong>\n'
            html += f'<div class="context-text">{html_module.escape(context_judgment)}</div>\n'
            html += '</div>\n'
        html += '</div>\n'
        html += '</div>\n'

    # 出现段落
    if paragraphs_list:
        html += '<div class="detail-section">\n'
        html += '<strong>出现段落：</strong>\n'
        html += '<div class="paragraphs-detail-list">\n'
        for para in paragraphs_list:
            if isinstance(para, dict):
                para_id = para.get('段落ID', '')
                how = para.get('如何体现', '')
                html += '<div class="paragraph-detail-item">\n'
                html += f'<span class="detail-tag para-id-tag">{html_module.escape(para_id)}</span>\n'
                if how:
                    html += f'<div class="para-how">{html_module.escape(how)}</div>\n'
                html += '</div>\n'
            else:
                html += f'<span class="detail-tag">{html_module.escape(str(para))}</span>\n'
        html += '</div>\n'
        html += '</div>\n'

    # 意图支撑
    if intent_support:
        html += '<div class="detail-section">\n'
        html += '<strong>意图支撑：</strong>\n'
        for point_type in ['灵感点', '目的点', '关键点']:
            if point_type in intent_support and intent_support[point_type]:
                html += f'<div class="score-type">{point_type}</div>\n'
                html += '<div class="score-list">\n'
                for item in intent_support[point_type]:
                    point = item.get('点', '')
                    point_intention = item.get('点的意图', '')
                    support_reason = item.get('支撑理由', '')
                    html += '<div class="score-item">\n'
                    html += f'<div class="score-point">{html_module.escape(point)}</div>\n'
                    if point_intention:
                        html += '<div class="point-intention">\n'
                        html += f'<strong style="color: #666;">点的意图：</strong>{html_module.escape(point_intention)}\n'
                        html += '</div>\n'
                    if support_reason:
                        html += '<div class="score-reasons">\n'
                        html += f'<strong style="color: #666;">支撑理由：</strong>\n'
                        html += f'<div class="score-reason">{html_module.escape(support_reason)}</div>\n'
                        html += '</div>\n'
                    html += '</div>\n'
                html += '</div>\n'
        html += '</div>\n'

    # 权重明细
    if weight_info.get('raw_total', 0) > 0:
        wd = weight_info['details']
        ss = weight_info['support_stats']
        is_form = isinstance(dimension, dict) and dimension.get('一级') == '形式'

        html += '<div class="detail-section weight-detail-section">\n'
        html += '<strong>权重得分明细：</strong>\n'
        html += '<div class="weight-summary-header">\n'
        if is_form:
            html += f'权重分：{weight_info["weight_score"]:.1f}（形式元素使用新的权重计算逻辑）\n'
        else:
            html += f'原始总分：{weight_info["raw_total"]:.1f}，权重分：{weight_info["weight_score"]:.1f}（min(100, 原始总分 × 100 / 110)）\n'
        html += '</div>\n'
        html += '<div class="weight-summary-grid">\n'
        
        if is_form:
            html += f'<span class="weight-chip">频次分 {wd.get("频次分", 0):.1f}</span>\n'
            if "覆盖段落数分" in wd:
                html += f'<span class="weight-chip">覆盖段落数分 {wd.get("覆盖段落数分", 0):.1f}</span>\n'
            html += f'<span class="weight-chip">覆盖率分 {wd.get("覆盖率分", 0):.1f}</span>\n'
        else:
            html += f'<span class="weight-chip">频次分 {wd.get("频次分", 0):.1f} / 30</span>\n'
            html += f'<span class="weight-chip">覆盖率分 {wd.get("覆盖率分", 0):.1f} / 30</span>\n'

        inspiration_count = ss.get('灵感点数量', 0)
        if is_form:
            html += f'<span class="weight-chip">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f}（{inspiration_count} 个）</span>\n'
        else:
            html += f'<span class="weight-chip">灵感点支撑分 {wd.get("灵感点支撑分", 0):.1f} / 25（{inspiration_count} 个）</span>\n'

        purpose_count = ss.get('目的点数量', 0)
        if is_form:
            html += f'<span class="weight-chip">目的点支撑分 {wd.get("目的点支撑分", 0):.1f}（{purpose_count} 个）</span>\n'
        else:
            html += f'<span class="weight-chip">目的点支撑分 {wd.get("目的点支撑分", 0):.1f} / 15（{purpose_count} 个）</span>\n'

        keypoint_count = ss.get('关键点数量', 0)
        if is_form:
            html += f'<span class="weight-chip">关键点支撑分 {wd.get("关键点支撑分", 0):.1f}（{keypoint_count} 个）</span>\n'
        else:
            html += f'<span class="weight-chip">关键点支撑分 {wd.get("关键点支撑分", 0):.1f} / 10（{keypoint_count} 个）</span>\n'

        html += '</div>\n'
        html += '</div>\n'

    html += '</div>\n'  # modal-body
    html += '</div>\n'  # element-modal-content
    return html


def generate_tab3_content(data: Dict[str, Any]) -> str:
    """生成Tab3内容：按层次展示（实质/形式 → 具体元素/具体概念/抽象概念 → 树形展示）"""
    html = '<div class="tab-content" id="tab3" style="display:none;">\n'

    # 添加全局控制按钮（移除展开/收起按钮）
    html += '<div class="global-controls">\n'
    html += '    <div class="color-legend">\n'
    html += '        <span class="legend-title">颜色图例（主导因素）：</span>\n'
    html += '        <span class="legend-item legend-coverage">覆盖率</span>\n'
    html += '        <span class="legend-item legend-frequency">频次</span>\n'
    html += '        <span class="legend-item legend-intent">意图支撑</span>\n'
    html += '    </div>\n'
    html += '</div>\n'

    # 收集所有元素用于弹窗
    all_elements_dict = {}

    if '脚本理解' in data:
        script = data['脚本理解']
        # 尝试获取元素列表，如果不存在则合并实质列表和形式列表
        elements = script.get('元素列表', [])
        if not elements:
            substance_list = script.get('实质列表', [])
            form_list = script.get('形式列表', [])
            elements = substance_list + form_list

        # 收集所有元素到字典中（用于弹窗）
        for elem in elements:
            elem_id = elem.get('id', '')
            if elem_id:
                all_elements_dict[elem_id] = elem

        # 第一层：按维度.一级分组（实质 vs 形式）
        level1_groups = {}
        for elem in elements:
            dimension = elem.get('维度', {})
            if isinstance(dimension, dict):
                level1 = dimension.get('一级', '实质')
            else:
                # 兼容旧结构
                level1 = elem.get('类型', '实质')

            if level1 not in level1_groups:
                level1_groups[level1] = []
            level1_groups[level1].append(elem)

        # 按顺序渲染：实质、形式
        for level1_name in ['实质', '形式']:
            if level1_name not in level1_groups:
                continue

            level1_elements = level1_groups[level1_name]
            html += '<div class="section level1-section">\n'
            html += f'<div class="level1-header collapsible" onclick="toggleLevel1(this)">\n'
            html += '<span class="level-toggle-icon">▼</span>\n'
            html += f'<h2 class="level1-title">{level1_name} ({len(level1_elements)}个)</h2>\n'
            html += '</div>\n'
            html += '<div class="level1-content">\n'

            # 第二层：按维度.二级分组
            level2_groups = {}
            for elem in level1_elements:
                dimension = elem.get('维度', {})
                elem_type = elem.get('类型', '')
                
                # 隐含概念：优先通过类型判断（因为维度二级可能是"隐含概念"或"抽象概念"）
                if elem_type == '隐含概念':
                    level2 = '隐含概念'
                elif isinstance(dimension, dict):
                    level2 = dimension.get('二级', '具体元素')
                else:
                    # 兼容旧结构
                    elem_type_old = elem.get('类型', '实质')
                    if elem_type_old == '实质':
                        level2 = '具体元素'
                    elif elem_type_old == '具象概念':
                        level2 = '具体概念'
                    else:
                        level2 = '抽象概念'

                if level2 not in level2_groups:
                    level2_groups[level2] = []
                level2_groups[level2].append(elem)

            # 根据一级维度确定二级维度遍历顺序
            if level1_name == '实质':
                level2_order = ['具体元素', '具象概念', '隐含概念', '抽象概念']
            else:  # 形式
                level2_order = ['具体元素形式', '具象概念形式', '整体形式']

            # 按顺序渲染二级维度
            for level2_name in level2_order:
                if level2_name not in level2_groups:
                    continue

                level2_elements = level2_groups[level2_name]
                html += '<div class="level2-section">\n'
                html += f'<div class="level2-header collapsible" onclick="toggleLevel2(this)">\n'
                html += '<span class="level-toggle-icon">▼</span>\n'
                html += f'<h3 class="level2-title">{level2_name} ({len(level2_elements)}个)</h3>\n'
                html += '</div>\n'
                html += '<div class="level2-content">\n'

                # 第三层：按树形分类结构组织
                hierarchical_categories = group_elements_by_hierarchical_category(level2_elements)

                # 渲染树形分类结构
                for cat_level1_name, cat_level1_data in hierarchical_categories.items():
                    # 收集该一级分类下的所有元素
                    all_cat_elements = cat_level1_data['elements'][:]
                    for level2_elems in cat_level1_data['level2_groups'].values():
                        all_cat_elements.extend(level2_elems)

                    if not all_cat_elements:
                        continue

                    # 计算一级分类的统计信息
                    avg_coverage = sum(get_element_coverage(e) for e in all_cat_elements) / len(all_cat_elements)
                    avg_intent_count = sum(calculate_intent_support_count(e) for e in all_cat_elements) / len(all_cat_elements)

                    html += '<div class="category-group collapsible">\n'
                    html += '<div class="category-header" onclick="toggleCategoryGroup(this)">\n'
                    html += '<span class="category-toggle-icon">▼</span>\n'
                    html += f'<h4 class="category-title">{html_module.escape(cat_level1_name)} ({len(all_cat_elements)}个)</h4>\n'
                    html += '<div class="category-stats">\n'
                    html += f'<span class="stat-badge">平均覆盖率: {avg_coverage:.2%}</span>\n'
                    html += f'<span class="stat-badge">平均意图支撑: {avg_intent_count:.1f}</span>\n'
                    html += '</div>\n'
                    html += '</div>\n'

                    html += '<div class="category-content">\n'

                    # 渲染一级分类直接包含的元素
                    if cat_level1_data['elements']:
                        html += '<ul class="element-list">\n'
                        for elem in cat_level1_data['elements']:
                            html += render_element_item(elem, all_cat_elements)
                        html += '</ul>\n'

                    # 渲染二级分类
                    for cat_level2_name, cat_level2_elements in cat_level1_data['level2_groups'].items():
                        if not cat_level2_elements:
                            continue

                        # 计算二级分类的统计信息
                        avg_coverage_l2 = sum(get_element_coverage(e) for e in cat_level2_elements) / len(cat_level2_elements)
                        avg_intent_count_l2 = sum(calculate_intent_support_count(e) for e in cat_level2_elements) / len(cat_level2_elements)

                        html += '<div class="subcategory-group collapsible">\n'
                        html += '<div class="subcategory-header" onclick="toggleSubcategoryGroup(this)">\n'
                        html += '<span class="subcategory-toggle-icon">▼</span>\n'
                        html += f'<h5 class="subcategory-title">{html_module.escape(cat_level2_name)} ({len(cat_level2_elements)}个)</h5>\n'
                        html += '<div class="subcategory-stats">\n'
                        html += f'<span class="stat-badge-small">覆盖率: {avg_coverage_l2:.2%}</span>\n'
                        html += '</div>\n'
                        html += '</div>\n'

                        html += '<div class="subcategory-content">\n'
                        html += '<ul class="element-list">\n'
                        for elem in cat_level2_elements:
                            html += render_element_item(elem, cat_level2_elements)
                        html += '</ul>\n'
                        html += '</div>\n'
                        html += '</div>\n'

                    html += '</div>\n'
                    html += '</div>\n'

                html += '</div>\n'  # level2-content
                html += '</div>\n'  # level2-section

            html += '</div>\n'  # level1-content
            html += '</div>\n'  # level1-section

    # 预生成所有元素的弹窗内容（隐藏）
    html += '<div id="elementModalTemplates" style="display:none;">\n'
    for elem_id, elem in all_elements_dict.items():
        html += render_element_modal(elem)
    html += '</div>\n'

    # 添加弹窗结构
    html += '<div id="elementModal" class="element-modal" style="display:none;">\n'
    html += '<div class="element-modal-overlay" onclick="closeElementModal()"></div>\n'
    html += '<div class="element-modal-dialog">\n'
    html += '<div id="elementModalContent" class="element-modal-content-wrapper">\n'
    html += '<!-- 弹窗内容将通过JavaScript动态填充 -->\n'
    html += '</div>\n'
    html += '</div>\n'
    html += '</div>\n'

    # 添加CSS样式
    html += '<style>\n'
    html += '''
    /* 元素卡片样式 */
    .element-list {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
        gap: 20px;
        list-style: none;
        padding: 0;
        margin: 20px 0;
    }
    
    .element-card {
        background: white;
        border-radius: 8px;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        transition: all 0.3s ease;
        overflow: hidden;
    }
    
    .element-card:hover {
        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
        transform: translateY(-2px);
    }
    
    .element-card.dominant-coverage {
        border-left: 4px solid #667eea;
    }
    
    .element-card.dominant-frequency {
        border-left: 4px solid #f093fb;
    }
    
    .element-card.dominant-intent_support {
        border-left: 4px solid #4facfe;
    }
    
    .element-card-body {
        padding: 16px;
    }
    
    .element-card-header {
        display: flex;
        align-items: center;
        gap: 8px;
        margin-bottom: 12px;
    }
    
    .element-card-id {
        color: #667eea;
        font-weight: 600;
        font-size: 14px;
    }
    
    .element-card-name {
        margin: 0;
        font-size: 18px;
        font-weight: 600;
        color: #333;
        flex: 1;
    }
    
    .element-card-stats {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        margin-bottom: 12px;
    }
    
    .stat-badge {
        display: inline-block;
        padding: 4px 10px;
        border-radius: 12px;
        font-size: 12px;
        font-weight: 500;
        background: #f5f5f5;
        color: #666;
    }
    
    .stat-badge.stat-weight {
        background: #667eea;
        color: white;
    }
    
    .stat-badge.stat-coverage {
        background: #e8f0ff;
        color: #667eea;
    }
    
    .stat-badge.stat-frequency {
        background: #fce4ec;
        color: #f093fb;
    }
    
    .stat-badge.stat-intent {
        background: #e0f7fa;
        color: #4facfe;
    }
    
    .stat-badge.stat-highlight {
        font-weight: 700;
        transform: scale(1.05);
    }
    
    .element-card-description {
        color: #666;
        font-size: 14px;
        line-height: 1.6;
        margin-bottom: 12px;
    }
    
    .element-detail-btn {
        width: 100%;
        padding: 10px;
        background: #667eea;
        color: white;
        border: none;
        border-radius: 6px;
        font-size: 14px;
        font-weight: 500;
        cursor: pointer;
        transition: all 0.2s;
    }
    
    .element-detail-btn:hover {
        background: #5568d3;
        transform: translateY(-1px);
        box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3);
    }
    
    /* 弹窗样式 */
    .element-modal {
        display: none;
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        z-index: 1000;
        align-items: center;
        justify-content: center;
    }
    
    .element-modal-overlay {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, 0.5);
        backdrop-filter: blur(4px);
    }
    
    .element-modal-dialog {
        position: relative;
        width: 800px;
        height: 90vh;
        background: white;
        border-radius: 12px;
        box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
        z-index: 1001;
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
    
    .element-modal-content-wrapper {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        overflow: hidden;
    }
    
    .element-modal-content {
        width: 100%;
        height: 100%;
        display: flex;
        flex-direction: column;
        padding: 0;
        overflow: hidden;
    }
    
    .modal-header {
        width: 100%;
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 20px 24px;
        border-bottom: 1px solid rgba(255, 255, 255, 0.2);
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white;
        border-radius: 12px 12px 0 0;
        position: sticky;
        top: 0;
        z-index: 10;
        flex-shrink: 0;
        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    }
    
    .modal-id {
        font-weight: 600;
        font-size: 14px;
        opacity: 0.9;
    }
    
    .modal-title {
        margin: 0;
        font-size: 20px;
        font-weight: 600;
        flex: 1;
    }
    
    .modal-close {
        background: rgba(255, 255, 255, 0.2);
        border: none;
        color: white;
        font-size: 24px;
        width: 32px;
        height: 32px;
        border-radius: 50%;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all 0.2s;
    }
    
    .modal-close:hover {
        background: rgba(255, 255, 255, 0.3);
        transform: rotate(90deg);
    }
    
    .modal-body {
        width: 100%;
        flex: 1;
        padding: 24px;
        overflow-y: auto;
        overflow-x: hidden;
        min-height: 0;
    }
    
    .detail-section {
        margin-bottom: 20px;
    }
    
    .detail-section strong {
        display: block;
        color: #667eea;
        font-size: 14px;
        font-weight: 600;
        margin-bottom: 8px;
    }
    
    .detail-text {
        color: #555;
        line-height: 1.6;
        padding: 12px;
        background: #f8f9fa;
        border-radius: 6px;
        border-left: 3px solid #667eea;
    }
    
    .detail-content {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        margin-top: 8px;
    }
    
    .detail-tag {
        display: inline-block;
        padding: 6px 12px;
        background: #e8f0ff;
        color: #667eea;
        border-radius: 16px;
        font-size: 13px;
        font-weight: 500;
    }
    
    .detail-tag.category-level1 {
        width: 100%;
        background: #667eea;
        color: white;
        font-weight: 600;
        margin-top: 8px;
    }
    
    .detail-tag.source-tag {
        background: #fce4ec;
        color: #f093fb;
    }
    
    .detail-tag.para-id-tag {
        background: #e0f7fa;
        color: #4facfe;
    }
    
    .weight-chip {
        display: inline-block;
        padding: 6px 12px;
        background: #f5f5f5;
        color: #333;
        border-radius: 16px;
        font-size: 12px;
        margin: 4px;
    }
    
    .weight-summary-grid {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        margin-top: 12px;
    }
    
    .weight-summary-header {
        font-size: 13px;
        color: #666;
        margin-top: 8px;
    }
    
    .weight-detail-section {
        margin-top: 20px;
        padding-top: 20px;
        border-top: 1px dashed #e0e0e0;
    }
    
    .score-type {
        font-weight: 600;
        color: #667eea;
        margin-top: 16px;
        margin-bottom: 8px;
        font-size: 15px;
    }
    
    .score-list {
        margin-left: 16px;
    }
    
    .score-item {
        margin-bottom: 12px;
        padding: 12px;
        background: #f8f9fa;
        border-radius: 6px;
        border-left: 3px solid #667eea;
    }
    
    .score-point {
        font-weight: 600;
        color: #333;
        margin-bottom: 8px;
    }
    
    .point-intention, .score-reasons {
        margin-top: 8px;
        font-size: 13px;
        color: #666;
    }
    
    .score-reason {
        margin-top: 4px;
        padding: 8px;
        background: white;
        border-radius: 4px;
    }
    
    .context-verification {
        margin-top: 8px;
        padding: 12px;
        background: #f8f9fa;
        border-radius: 4px;
        border-left: 3px solid #6c757d;
    }
    
    .context-item {
        margin-bottom: 8px;
    }
    
    .context-text {
        margin-top: 4px;
        padding: 6px 10px;
        background: white;
        border-radius: 3px;
        color: #212529;
        font-style: italic;
    }
    
    .grammar-tag {
        margin-left: 8px;
        padding: 3px 10px;
        background: #e7f3ff;
        color: #0066cc;
        border-radius: 3px;
        font-size: 12px;
    }
    
    .paragraphs-detail-list {
        display: flex;
        flex-direction: column;
        gap: 8px;
        margin-top: 8px;
    }
    
    .paragraph-detail-item {
        display: flex;
        align-items: flex-start;
        gap: 8px;
    }
    
    .para-how {
        flex: 1;
        padding: 8px;
        background: white;
        border-radius: 4px;
        color: #555;
        font-size: 13px;
    }
    
    @media (max-width: 768px) {
        .element-list {
            grid-template-columns: 1fr;
        }
        
        .element-modal-dialog {
            width: 95%;
            height: 95vh;
        }
        
        .modal-body {
            padding: 16px;
        }
    }
    '''
    html += '</style>\n'

    # 添加JavaScript代码
    html += '<script>\n'
    html += '''
    function openElementModal(elemId) {
        const modal = document.getElementById('elementModal');
        const contentWrapper = document.getElementById('elementModalContent');
        const templates = document.getElementById('elementModalTemplates');
        
        // 查找预生成的弹窗内容
        const modalContent = templates.querySelector(`.element-modal-content[data-elem-id="${elemId}"]`);
        if (modalContent) {
            contentWrapper.innerHTML = modalContent.outerHTML;
            modal.style.display = 'flex';
            document.body.style.overflow = 'hidden';
        } else {
            console.error('Modal content not found for element:', elemId);
        }
    }
    
    function closeElementModal() {
        const modal = document.getElementById('elementModal');
        modal.style.display = 'none';
        document.body.style.overflow = '';
    }
    
    // ESC键关闭弹窗
    document.addEventListener('keydown', function(e) {
        if (e.key === 'Escape') {
            closeElementModal();
        }
    });
    '''
    html += '</script>\n'

    html += '</div>\n'
    return html
