#!/usr/bin/env python3
"""Render audited iFinD market-structure charts as dependency-free SVG."""

from __future__ import annotations

import csv
import html
import textwrap
from collections import defaultdict
from pathlib import Path


INDUSTRIES = ["能源", "原材料", "工业", "可选消费", "主要消费", "医药卫生", "金融地产", "信息技术", "通信服务", "公用事业", "未映射"]
COLORS = ["#9d3d36", "#c58439", "#4c6b63", "#4f759b", "#7b9e4d", "#43a38c", "#355070", "#5b5f97", "#6d8fa3", "#8f9b8f", "#c7c9cc"]


def read_rows(path: Path) -> list[dict[str, str]]:
    with path.open(encoding="utf-8-sig") as handle:
        return list(csv.DictReader(handle))


def matrix(rows: list[dict[str, str]], value_field: str) -> tuple[list[int], list[list[float]]]:
    years = sorted({int(row["year"]) for row in rows})
    values = {(int(row["year"]), row["industry"]): float(row[value_field] or 0) for row in rows}
    return years, [[values.get((year, industry), 0) for year in years] for industry in INDUSTRIES]


def svg_text(x: float, y: float, value: str, size: int = 14, fill: str = "#17212b", anchor: str = "start", weight: int = 400) -> str:
    return f'<text x="{x:.1f}" y="{y:.1f}" font-size="{size}" fill="{fill}" text-anchor="{anchor}" font-weight="{weight}">{html.escape(value)}</text>'


def svg_document(width: int, height: int, body: str) -> str:
    return f'''<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}">
<rect width="100%" height="100%" fill="#ffffff"/>
<g font-family="PingFang SC, Microsoft YaHei, Arial, sans-serif">{body}</g>
</svg>'''


def plot_market_structure(market_rows: list[dict[str, str]], metadata_rows: list[dict[str, str]], out: Path) -> None:
    years, shares = matrix(market_rows, "total_market_cap_share")
    width, height = 1400, 780
    left, top, plot_width, plot_height = 82, 105, 1250, 500
    xs = [left + index * plot_width / (len(years) - 1) for index in range(len(years))]
    elements = [svg_text(left, 48, "A股行业总市值结构演变（历史时点股票池）", 25, weight=700)]
    for pct in range(0, 101, 20):
        y = top + plot_height * (1 - pct / 100)
        elements.append(f'<line x1="{left}" y1="{y:.1f}" x2="{left + plot_width}" y2="{y:.1f}" stroke="#d9dde2" stroke-width="1"/>')
        elements.append(svg_text(left - 12, y + 5, f"{pct}%", 12, "#58606b", "end"))
    lower = [0.0] * len(years)
    for industry, color, row in zip(INDUSTRIES, COLORS, shares):
        upper = [a + b for a, b in zip(lower, row)]
        top_points = [(x, top + plot_height * (1 - value)) for x, value in zip(xs, upper)]
        bottom_points = [(x, top + plot_height * (1 - value)) for x, value in zip(reversed(xs), reversed(lower))]
        points = " ".join(f"{x:.1f},{y:.1f}" for x, y in [*top_points, *bottom_points])
        elements.append(f'<polygon points="{points}" fill="{color}" fill-opacity="0.95"><title>{html.escape(industry)}</title></polygon>')
        lower = upper
    for x, year in zip(xs, years):
        elements.append(f'<line x1="{x:.1f}" y1="{top + plot_height}" x2="{x:.1f}" y2="{top + plot_height + 6}" stroke="#66707a"/>')
        elements.append(svg_text(x, top + plot_height + 26, str(year), 13, "#38424c", "middle"))
    for index, (industry, color) in enumerate(zip(INDUSTRIES, COLORS)):
        col, row = index % 6, index // 6
        x, y = left + col * 196, 660 + row * 29
        elements.append(f'<rect x="{x}" y="{y - 13}" width="15" height="15" rx="2" fill="{color}"/>')
        elements.append(svg_text(x + 22, y, industry, 13))
    count_coverage = {int(row["year"]): float(row["industry_mapping_rate"]) for row in metadata_rows}
    cap_coverage = {int(row["year"]): float(row["mapped_total_market_cap_rate"]) for row in metadata_rows}
    count_text = "  ".join(f"{year}: {count_coverage[year]:.1%}" for year in years)
    cap_text = "  ".join(f"{year}: {cap_coverage[year]:.1%}" for year in years)
    elements.append(svg_text(left, 722, f"公司数映射覆盖率  {count_text}", 11, "#58606b"))
    elements.append(svg_text(left, 742, f"总市值映射覆盖率  {cap_text}", 11, "#58606b"))
    elements.append(svg_text(left, 756, "来源：同花顺iFinD HTTP API；年末快照；总市值口径；本图为计算结果。", 11, "#58606b"))
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def hex_to_rgb(value: str) -> tuple[int, int, int]:
    value = value.lstrip("#")
    return tuple(int(value[index:index + 2], 16) for index in (0, 2, 4))


def mix(a: str, b: str, ratio: float) -> str:
    ratio = max(0.0, min(1.0, ratio))
    left, right = hex_to_rgb(a), hex_to_rgb(b)
    return "#" + "".join(f"{round(x + (y - x) * ratio):02x}" for x, y in zip(left, right))


def sequential(value: float, maximum: float) -> str:
    return mix("#edf4f2", "#24586b", value / maximum if maximum else 0)


def diverging(value: float, maximum: float) -> str:
    if value < 0:
        return mix("#f7f7f7", "#b2473e", abs(value) / maximum if maximum else 0)
    return mix("#f7f7f7", "#315f86", value / maximum if maximum else 0)


def heatmap_block(elements: list[str], title: str, data: list[list[float]], years: list[int], top: int, diverge: bool) -> None:
    left, label_width, cell_width, cell_height = 65, 126, 155, 41
    values = [abs(value) for row in data for value in row]
    maximum = max(values) if values else 1
    elements.append(svg_text(left, top, title, 19, weight=700))
    grid_top = top + 33
    for col, year in enumerate(years):
        elements.append(svg_text(left + label_width + col * cell_width + cell_width / 2, grid_top - 10, str(year), 12, "#58606b", "middle"))
    for row_index, (industry, values_row) in enumerate(zip(INDUSTRIES[:-1], data[:-1])):
        y = grid_top + row_index * cell_height
        elements.append(svg_text(left + label_width - 12, y + 26, industry, 13, "#26313b", "end"))
        for col, value in enumerate(values_row):
            x = left + label_width + col * cell_width
            color = diverging and diverging_color(value, maximum) or sequential(value, maximum)
            elements.append(f'<rect x="{x}" y="{y}" width="{cell_width - 3}" height="{cell_height - 3}" rx="2" fill="{color}"/>')
            text_color = "#ffffff" if abs(value) / maximum > 0.55 else "#17212b"
            elements.append(svg_text(x + (cell_width - 3) / 2, y + 25, f"{value * 100:.1f}", 12, text_color, "middle"))


def diverging_color(value: float, maximum: float) -> str:
    return diverging(value, maximum)


def plot_market_profit_comparison(market_rows: list[dict[str, str]], profit_rows: list[dict[str, str]], out: Path) -> None:
    years, market = matrix(market_rows, "total_market_cap_share")
    profit_years, profit = matrix(profit_rows, "net_profit_share")
    if years != profit_years:
        raise RuntimeError("market and profit years do not match")
    elements = [svg_text(65, 48, "行业市值权重与利润占比对照", 25, weight=700)]
    heatmap_block(elements, "总市值占比（%）", market, years, 92, False)
    heatmap_block(elements, "归母净利润占比（%，允许负值）", profit, years, 570, True)
    elements.append(svg_text(65, 1050, "来源：同花顺iFinD HTTP API；利润为对应财年归属于母公司所有者的净利润，行业负利润保留；本图为计算结果。", 11, "#58606b"))
    out.parent.mkdir(parents=True, exist_ok=True)
    out.write_text(svg_document(1400, 1080, "".join(elements)), encoding="utf-8")


def plot_fundamental_valuation(rows: list[dict[str, str]], out: Path) -> None:
    current = [row for row in rows if int(row["year"]) == 2025 and row["industry"] != "未映射"]
    metrics = [
        ("aggregate_roe", "行业ROE", 100, "%"),
        ("median_company_pe_ttm_positive", "PE中位数", 1, "x"),
        ("median_company_pb_mrq_positive", "PB中位数", 1, "x"),
        ("median_company_annual_turnover", "年换手率中位数", 1, "%"),
        ("median_company_fund_held_ratio", "基金持股比例中位数", 1, "%"),
    ]
    width, height = 1320, 690
    left, top, row_height = 74, 128, 47
    label_width, cell_width = 150, 204
    elements = [
        svg_text(left, 48, "2025年A股行业盈利、估值、交易与机构持仓快照", 25, weight=700),
        svg_text(left, 78, "公司中位数口径；PE仅统计正值；ROE为行业归母净利润/行业归母权益", 12, "#58606b"),
    ]
    for col, (_, label, _, _) in enumerate(metrics):
        x = left + label_width + col * cell_width
        elements.append(svg_text(x + (cell_width - 4) / 2, top - 18, label, 13, "#39434d", "middle", 600))
    maxima = {
        key: max(abs(float(row[key] or 0) * scale) for row in current)
        for key, _, scale, _ in metrics
    }
    for row_index, row in enumerate(current):
        y = top + row_index * row_height
        elements.append(svg_text(left + label_width - 16, y + 29, row["industry"], 14, "#26313b", "end", 600))
        for col, (key, _, scale, suffix) in enumerate(metrics):
            value = float(row[key] or 0) * scale
            x = left + label_width + col * cell_width
            color = diverging(value, maxima[key]) if key == "aggregate_roe" else sequential(value, maxima[key])
            elements.append(f'<rect x="{x}" y="{y}" width="{cell_width - 4}" height="{row_height - 4}" rx="2" fill="{color}"/>')
            text_color = "#fff" if maxima[key] and abs(value) / maxima[key] > 0.55 else "#17212b"
            elements.append(svg_text(x + (cell_width - 4) / 2, y + 28, f"{value:.1f}{suffix}", 13, text_color, "middle", 600))
    elements.append(svg_text(left, 630, "来源：同花顺iFinD HTTP API；2025-12-31市场与财报快照；经过本报告计算。基金持仓1995/2000无返回值。", 11, "#58606b"))
    elements.append(svg_text(left, 652, "比较限制：七期快照分位不是连续日频估值分位；行业分类与样本会调整；本图不构成投资建议。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_multi_event_summary(rows: list[dict[str, str]], out: Path) -> None:
    selected = [
        row for row in rows
        if row["政策暴露"] == "全部行业" and row["事件类型"] in {"规划建议", "规划纲要", "专项政策"}
    ]
    types = ["规划建议", "规划纲要", "专项政策"]
    windows = [-12, 12, 24, 36]
    colors = {"规划建议": "#355070", "规划纲要": "#4c8b78", "专项政策": "#b56a32"}
    values = {
        (row["事件类型"], int(row["观察窗口(月)"])): float(row["相对收益中位数"]) * 100
        for row in selected
    }
    width, height = 1320, 700
    left, top, plot_width, plot_height = 92, 120, 1120, 420
    zero_y = top + plot_height * 0.42
    scale = 24
    elements = [
        svg_text(left, 48, "建议稿、纲要与专项政策发布前后行业相对收益中位数", 25, weight=700),
        svg_text(left, 78, "相对沪深300，百分点；负12个月表示政策发布前窗口", 12, "#58606b"),
        f'<line x1="{left}" y1="{zero_y:.1f}" x2="{left + plot_width}" y2="{zero_y:.1f}" stroke="#6c747c" stroke-width="1.2"/>',
    ]
    group_width = plot_width / len(windows)
    bar_width = 62
    for group_index, window in enumerate(windows):
        group_x = left + group_index * group_width
        for type_index, event_type in enumerate(types):
            value = values[(event_type, window)]
            x = group_x + 38 + type_index * 76
            height_value = abs(value) / scale * plot_height
            y = zero_y - height_value if value >= 0 else zero_y
            elements.append(f'<rect x="{x}" y="{y:.1f}" width="{bar_width}" height="{height_value:.1f}" rx="2" fill="{colors[event_type]}"/>')
            label_y = y - 7 if value >= 0 else y + height_value + 16
            elements.append(svg_text(x + bar_width / 2, label_y, f"{value:+.1f}", 12, "#26313b", "middle", 600))
        elements.append(svg_text(group_x + group_width / 2, top + plot_height + 34, "发布前12个月" if window < 0 else f"发布后{window}个月", 13, "#39434d", "middle", 600))
    for index, event_type in enumerate(types):
        x = left + index * 180
        elements.append(f'<rect x="{x}" y="585" width="16" height="16" rx="2" fill="{colors[event_type]}"/>')
        elements.append(svg_text(x + 24, 598, event_type, 13))
    elements.append(svg_text(left, 638, "来源：中央政策原文日期、腾讯证券公开行情接口；3份建议、3份纲要、5项专项政策；本报告计算。", 11, "#58606b"))
    elements.append(svg_text(left, 660, "事件与行业并非独立样本；结果用于描述时序，不能识别单一政策因果。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_financing_structure(
    ipo_rows: list[dict[str, str]],
    refinancing_rows: list[dict[str, str]],
    out: Path,
) -> None:
    periods = ["十二五(2011-2015)", "十三五(2016-2020)", "十四五(2021-2025)"]
    industries = INDUSTRIES
    ipo = {(row["规划周期"], row["industry"]): float(row["IPO募集资金占比"] or 0) for row in ipo_rows}
    refinancing = {
        (row["规划周期"], row["industry"]): float(row["再融资募集资金占比"] or 0)
        for row in refinancing_rows
    }
    width, height = 1320, 680
    left, bar_left, bar_width = 78, 305, 900
    elements = [
        svg_text(left, 48, "十二五至十四五A股IPO与再融资行业结构", 25, weight=700),
        svg_text(left, 78, "募集资金占各规划期总额比例；再融资=增发+配股", 12, "#58606b"),
    ]
    y = 128
    for period in periods:
        elements.append(svg_text(left, y + 25, period, 14, "#26313b", weight=600))
        for offset, (label, values) in enumerate((("IPO", ipo), ("再融资", refinancing))):
            row_y = y + offset * 45
            elements.append(svg_text(bar_left - 14, row_y + 25, label, 12, "#58606b", "end"))
            cursor = bar_left
            for industry, color in zip(industries, COLORS):
                value = values.get((period, industry), 0)
                segment = bar_width * value
                if segment > 0:
                    elements.append(f'<rect x="{cursor:.1f}" y="{row_y}" width="{segment:.1f}" height="31" fill="{color}"><title>{html.escape(industry)} {value:.1%}</title></rect>')
                cursor += segment
        y += 126
    for index, (industry, color) in enumerate(zip(industries, COLORS)):
        col, row = index % 6, index // 6
        x, legend_y = left + col * 192, 532 + row * 30
        elements.append(f'<rect x="{x}" y="{legend_y - 13}" width="15" height="15" rx="2" fill="{color}"/>')
        elements.append(svg_text(x + 22, legend_y, industry, 12))
    elements.append(svg_text(left, 608, "来源：同花顺iFinD HTTP API；七期历史股票池并集5,786只证券；金额单位为元；本报告计算。", 11, "#58606b"))
    elements.append(svg_text(left, 630, "覆盖边界：可能遗漏两个快照之间上市后又退市的公司；早期增发字段返回稀疏，不宜跨期直接比较。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_listing_composition(rows: list[dict[str, str]], out: Path) -> None:
    grouped: dict[tuple[int, int], list[dict[str, str]]] = defaultdict(list)
    for row in rows:
        grouped[(int(row["起始年份"]), int(row["结束年份"]))].append(row)
    periods = sorted(grouped)
    series: list[tuple[str, float, float, float]] = []
    for start, end in periods:
        items = grouped[(start, end)]
        total_start = sum(float(row["起始A股公司收入合计"] or 0) for row in items)
        total_end = sum(float(row["结束A股公司收入合计"] or 0) for row in items)
        continuing_start = sum(float(row["持续公司起始收入"] or 0) for row in items)
        continuing_end = sum(float(row["持续公司结束收入"] or 0) for row in items)
        ipo_revenue = sum(float(row["区间IPO且期末在样本收入"] or 0) for row in items)
        years = end - start
        total_cagr = (total_end / total_start) ** (1 / years) - 1
        continuing_cagr = (continuing_end / continuing_start) ** (1 / years) - 1
        series.append((f"{start}-{end}", total_cagr, continuing_cagr, ipo_revenue / total_end))

    width, height = 1320, 650
    left, top, plot_width, plot_height = 100, 125, 1120, 390
    group_width = plot_width / len(series)
    maximum = 0.8
    elements = [
        svg_text(left, 48, "A股上市扩容与持续公司收入增长拆分", 25, weight=700),
        svg_text(left, 78, "已映射稳定十行业；CAGR与区间IPO期末收入占比；不代表全产业增长", 12, "#58606b"),
    ]
    for pct in range(0, 81, 20):
        y = top + plot_height * (1 - pct / 80)
        elements.append(f'<line x1="{left}" y1="{y:.1f}" x2="{left + plot_width}" y2="{y:.1f}" stroke="#d9dde2"/>')
        elements.append(svg_text(left - 12, y + 5, f"{pct}%", 12, "#58606b", "end"))
    colors = ("#315f86", "#4c8b78", "#b56a32")
    for index, (period, total, continuing, entrant) in enumerate(series):
        group_x = left + index * group_width
        for offset, value in enumerate((total, continuing, entrant)):
            x = group_x + 28 + offset * 47
            bar_height = min(value, maximum) / maximum * plot_height
            y = top + plot_height - bar_height
            elements.append(f'<rect x="{x:.1f}" y="{y:.1f}" width="36" height="{bar_height:.1f}" rx="2" fill="{colors[offset]}"/>')
            elements.append(svg_text(x + 18, y - 7, f"{value:.1%}", 11, "#26313b", "middle", 600))
        elements.append(svg_text(group_x + group_width / 2, top + plot_height + 29, period, 12, "#39434d", "middle", 600))
    labels = ("A股公司收入合计CAGR", "持续公司收入CAGR", "区间IPO期末收入占比")
    for index, (label, color) in enumerate(zip(labels, colors)):
        x = left + index * 290
        elements.append(f'<rect x="{x}" y="565" width="16" height="16" rx="2" fill="{color}"/>')
        elements.append(svg_text(x + 24, 578, label, 12))
    elements.append(svg_text(left, 616, "来源：同花顺iFinD HTTP API；七期年末快照；持续公司要求起止两期均位于同一稳定行业；本报告计算。", 11, "#58606b"))
    elements.append(svg_text(left, 636, "限制：收入桥另列覆盖变化、行业迁移、未映射转入转出和观察宇宙退出；全产业增长见官方连续面板。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_policy_strength(rows: list[dict[str, str]], out: Path) -> None:
    plans = [
        "八五（1991-1995）", "九五（1996-2000）", "十五（2001-2005）",
        "十一五（2006-2010）", "十二五（2011-2015）", "十三五（2016-2020）",
        "十四五（2021-2025）", "十五五（2026-2030）",
    ]
    counts = {(plan, level): 0 for plan in plans for level in (2, 3, 4)}
    for row in rows:
        counts[(row["规划周期"], int(row["强度等级"]))] += 1
    width, height = 1320, 650
    left, top, plot_width, plot_height = 115, 125, 1080, 370
    group_width = plot_width / len(plans)
    colors = {2: "#9eb8c6", 3: "#4c8b78", 4: "#b2473e"}
    elements = [
        svg_text(left, 48, "八期五年规划政策强度人工编码", 25, weight=700),
        svg_text(left, 78, "28条方向；等级描述政策承诺形式，不是关键词频次，也不是政策效果评分", 12, "#58606b"),
    ]
    for count in range(0, 6):
        y = top + plot_height * (1 - count / 5)
        elements.append(f'<line x1="{left}" y1="{y:.1f}" x2="{left + plot_width}" y2="{y:.1f}" stroke="#d9dde2"/>')
        elements.append(svg_text(left - 14, y + 5, str(count), 12, "#58606b", "end"))
    for index, plan in enumerate(plans):
        x = left + index * group_width + 30
        cursor_y = top + plot_height
        for level in (2, 3, 4):
            count = counts[(plan, level)]
            height_value = count / 5 * plot_height
            cursor_y -= height_value
            if count:
                elements.append(f'<rect x="{x:.1f}" y="{cursor_y:.1f}" width="72" height="{height_value:.1f}" fill="{colors[level]}"/>')
                elements.append(svg_text(x + 36, cursor_y + height_value / 2 + 5, str(count), 13, "#ffffff" if level >= 3 else "#17212b", "middle", 700))
        short = plan.split("（")[0]
        elements.append(svg_text(x + 36, top + plot_height + 28, short, 13, "#39434d", "middle", 600))
    labels = {2: "2：明确方向或单类支持", 3: "3：重大工程、多工具或明确监管", 4: "4：约束性指标、硬任务或系统工程"}
    for index, level in enumerate((2, 3, 4)):
        x = left + index * 340
        elements.append(f'<rect x="{x}" y="550" width="16" height="16" rx="2" fill="{colors[level]}"/>')
        elements.append(svg_text(x + 24, 563, labels[level], 12))
    elements.append(svg_text(left, 612, "来源：八五至十五五规划纲要原文；研究者逐条人工编码；完整章节、摘要和链接见派生表。", 11, "#58606b"))
    elements.append(svg_text(left, 632, "限制：各期选取条数与粒度并不完全相同，柱高不可解释为政策力度或政策成效的跨期排名。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_deep_timelines(rows: list[dict[str, str]], out: Path) -> None:
    order = ["房地产与基础设施", "煤炭钢铁与原材料", "移动互联网与信息产业", "新能源汽车与新能源体系"]
    grouped: dict[str, list[dict[str, str]]] = defaultdict(list)
    for row in rows:
        grouped[row["案例"]].append(row)
    width, height = 1400, 900
    left, top, lane_height = 64, 110, 185
    colors = ["#b2473e", "#b56a32", "#315f86", "#4c8b78"]
    elements = [
        svg_text(left, 48, "四个深度产业的政策—产业—A股结构时间轴", 25, weight=700),
        svg_text(left, 78, "阶段框用于定位历史进程；完整关键变化、证据来源与等级见CSV和正文", 12, "#58606b"),
    ]
    for lane, case in enumerate(order):
        items = grouped[case]
        y = top + lane * lane_height
        elements.append(svg_text(left, y + 24, case, 18, "#26313b", weight=700))
        box_top = y + 45
        box_width = 1250 / len(items)
        for index, item in enumerate(items):
            x = left + index * box_width
            elements.append(f'<rect x="{x:.1f}" y="{box_top}" width="{box_width - 8:.1f}" height="106" rx="4" fill="{mix("#ffffff", colors[lane], 0.17)}" stroke="{colors[lane]}"/>')
            elements.append(svg_text(x + 10, box_top + 22, item["时期"], 12, colors[lane], weight=700))
            lines = textwrap.wrap(item["产业阶段"], width=11)[:2]
            for line_index, line in enumerate(lines):
                elements.append(svg_text(x + 10, box_top + 48 + line_index * 20, line, 13, "#17212b", weight=600))
            grade = item["证据等级"]
            elements.append(svg_text(x + 10, box_top + 94, grade[:16], 10, "#58606b"))
    elements.append(svg_text(left, 866, "来源：中央规划与配套政策原文、同花顺iFinD结构快照、腾讯证券公开行情；政策事实与合理推断分级标注。", 11, "#58606b"))
    elements.append(svg_text(left, 886, "限制：时间轴不是因果估计；产业连续指标已补入独立面板，公司主营收入暴露仍只有候选层。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_four_industry_continuous_trends(rows: list[dict[str, str]], out: Path) -> None:
    selected = {
        "房地产与基础设施": ["房地产开发投资额", "商品房销售面积", "房地产施工面积"],
        "煤炭钢铁与原材料": ["原煤产量", "粗钢产量", "煤炭开采和洗选业利润总额"],
        "移动互联网与信息产业": ["移动电话年末用户", "互联网宽带接入端口", "软件业务收入", "集成电路产量"],
        "新能源汽车与新能源体系": ["新能源汽车产量", "太阳能发电量", "风力发电量"],
    }
    colors = ["#315f86", "#b2473e", "#4c8b78", "#b56a32"]
    width, height = 1420, 900
    panel_width, panel_height = 650, 325
    panel_positions = [(70, 125), (750, 125), (70, 505), (750, 505)]
    elements = [
        svg_text(70, 48, "四产业连续年度趋势面板", 25, weight=700),
        svg_text(70, 78, "各指标首个有效年度=100；只比较自身历史方向，不比较不同指标绝对规模", 12, "#58606b"),
    ]
    for panel_index, (case, metrics) in enumerate(selected.items()):
        x0, y0 = panel_positions[panel_index]
        elements.append(svg_text(x0, y0 - 20, case, 18, "#26313b", weight=700))
        series: list[tuple[str, list[tuple[int, float]]]] = []
        for metric in metrics:
            points = []
            for row in rows:
                if row["案例"] != case or row["指标"] != metric or not row["数值"]:
                    continue
                points.append((int(row["年份"]), float(row["数值"])))
            points.sort()
            if not points or points[0][1] == 0:
                continue
            base = points[0][1]
            series.append((metric, [(year, value / base * 100) for year, value in points]))
        all_values = [value for _, points in series for _, value in points]
        maximum = max(all_values) if all_values else 100
        minimum = min(0, min(all_values) if all_values else 0)
        plot_left, plot_top = x0, y0
        plot_width, plot_height = panel_width, panel_height - 40
        for tick in range(5):
            value = minimum + (maximum - minimum) * tick / 4
            y = plot_top + plot_height * (1 - tick / 4)
            elements.append(f'<line x1="{plot_left}" y1="{y:.1f}" x2="{plot_left + plot_width}" y2="{y:.1f}" stroke="#e1e4e8"/>')
            elements.append(svg_text(plot_left - 8, y + 4, f"{value:.0f}", 10, "#58606b", "end"))
        for metric_index, (metric, points) in enumerate(series):
            path_parts = []
            for point_index, (year, value) in enumerate(points):
                x = plot_left + (year - 1990) / 35 * plot_width
                y = plot_top + (maximum - value) / (maximum - minimum or 1) * plot_height
                path_parts.append(("M" if point_index == 0 else "L") + f"{x:.1f},{y:.1f}")
            color = colors[metric_index]
            elements.append(f'<path d="{" ".join(path_parts)}" fill="none" stroke="{color}" stroke-width="2.4"/>')
            legend_x = plot_left + (metric_index % 2) * 315
            legend_y = plot_top + plot_height + 22 + (metric_index // 2) * 18
            elements.append(f'<line x1="{legend_x}" y1="{legend_y - 4}" x2="{legend_x + 18}" y2="{legend_y - 4}" stroke="{color}" stroke-width="3"/>')
            elements.append(svg_text(legend_x + 24, legend_y, metric, 10, "#38424c"))
        for year in (1990, 2000, 2010, 2020, 2025):
            x = plot_left + (year - 1990) / 35 * plot_width
            elements.append(svg_text(x, plot_top + plot_height + 5, str(year), 9, "#58606b", "middle"))
    elements.append(svg_text(70, 872, "来源：国家统计局数据发布库；年度原值或月度累计12月值；缺失年份未插值；本图为标准化计算结果。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_policy_causal_test(rows: list[dict[str, str]], out: Path) -> None:
    width, height = 1400, 620
    left, top, plot_width, plot_height = 115, 125, 1180, 320
    values = [float(row["月均差分估计"]) * 100 for row in rows]
    labels = [row["检验"].replace("发布后", "") for row in rows]
    limit = max(2.5, max(abs(value) for value in values) * 1.25)
    zero_y = top + plot_height / 2
    elements = [
        svg_text(left, 48, "专项政策相关行业：堆叠式行业差分与前趋势压力测试", 25, weight=700),
        svg_text(left, 78, "月均相对收益差，百分点；误差区间与p值见CSV，图形不解释为政策因果效应", 12, "#58606b"),
    ]
    for tick in (-limit, 0, limit):
        y = zero_y - tick / (2 * limit) * plot_height
        elements.append(f'<line x1="{left}" y1="{y:.1f}" x2="{left + plot_width}" y2="{y:.1f}" stroke="#d9ddd8"/>')
        elements.append(svg_text(left - 12, y + 5, f"{tick:+.1f}", 11, "#58606b", "end"))
    group_width = plot_width / len(values)
    for index, (label, value, row) in enumerate(zip(labels, values, rows)):
        x = left + index * group_width + group_width * 0.2
        bar_width = group_width * 0.6
        y = zero_y - value / (2 * limit) * plot_height
        bar_y = min(y, zero_y)
        bar_height = abs(zero_y - y)
        color = "#b3272d" if value >= 0 else "#355070"
        elements.append(f'<rect x="{x:.1f}" y="{bar_y:.1f}" width="{bar_width:.1f}" height="{bar_height:.1f}" fill="{color}"/>')
        elements.append(svg_text(x + bar_width / 2, bar_y - 9 if value >= 0 else bar_y + bar_height + 20, f"{value:+.2f}", 13, "#26313b", "middle", 700))
        elements.append(svg_text(x + bar_width / 2, top + plot_height + 30, label, 12, "#39434d", "middle", 600))
        elements.append(svg_text(x + bar_width / 2, top + plot_height + 51, f"BH p={float(row['BH校正p值']):.3f}", 10, "#58606b", "middle"))
    elements.append(svg_text(left, 535, "关键诊断：发布前趋势为 +0.306 个百分点/月，BH校正 p=0.011；平行趋势假设被拒绝。", 14, "#b3272d", weight=700))
    elements.append(svg_text(left, 562, "来源：5项专项政策、19个处理行业事件、中证十行业与沪深300月度价格指数；20,000次置换、10,000次事件bootstrap；本报告计算。", 11, "#58606b"))
    elements.append(svg_text(left, 584, "结论边界：13-24月窗口虽显著为负，也不能解释为政策导致行业下跌。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_macro_control_coverage(rows: list[dict[str, str]], out: Path) -> None:
    width, height = 1400, 520
    left, top, plot_width = 230, 105, 1040
    metrics = []
    for metric in dict.fromkeys(row["指标"] for row in rows):
        valid = [row["月份"][:7] for row in rows if row["指标"] == metric and row["缺失标记"] == "否"]
        if valid:
            metrics.append((metric, min(valid), max(valid), next(row["底层提供方"] for row in rows if row["指标"] == metric)))
    start_year, end_year = 1990, 2025
    elements = [
        svg_text(left, 48, "月度宏观共同变量：有效数据区间", 25, weight=700),
        svg_text(left, 78, "横条只表示可得区间，不表示已经消除宏观混杂；缺失月份不插值", 12, "#58606b"),
    ]
    for tick in range(1990, 2026, 5):
        x = left + (tick - start_year) / (end_year - start_year) * plot_width
        elements.append(f'<line x1="{x:.1f}" y1="{top - 18}" x2="{x:.1f}" y2="{top + len(metrics) * 55}" stroke="#e4e7e3"/>')
        elements.append(svg_text(x, top - 28, str(tick), 11, "#58606b", "middle"))
    colors = ["#b3272d", "#087f73", "#c58439", "#355070", "#5b5f97", "#7b9e4d"]
    for index, (metric, start, end, provider) in enumerate(metrics):
        y = top + index * 55
        start_value = int(start[:4]) + (int(start[5:7]) - 1) / 12
        end_value = int(end[:4]) + (int(end[5:7]) - 1) / 12
        x1 = left + (start_value - start_year) / (end_year - start_year) * plot_width
        x2 = left + (end_value - start_year) / (end_year - start_year) * plot_width
        elements.append(svg_text(left - 16, y + 7, metric, 13, "#26313b", "end", 600))
        elements.append(f'<rect x="{x1:.1f}" y="{y - 7}" width="{max(3, x2 - x1):.1f}" height="18" fill="{colors[index % len(colors)]}"/>')
        elements.append(svg_text(x1 + 5, y + 7, start, 10, "#ffffff", weight=600))
        elements.append(svg_text(x2 + 6, y + 7, end, 10, "#58606b"))
    elements.append(svg_text(left, 465, "来源：FRED分发；底层提供方包括IMF、OECD、纳斯达克。M2截至2019-08，PPI截至2022-12，其余最长至2025-12。", 11, "#58606b"))
    elements.append(svg_text(left, 486, "代理边界：NASDAQ综合指数是全球科技周期代理；全球商品指数不等于中国单一行业价格。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def plot_business_exposure_coverage(rows: list[dict[str, str]], out: Path) -> None:
    width, height = 1400, 660
    left, top, plot_width, row_height = 270, 105, 960, 53
    values = [int(row["2025匹配暴露公司数"]) for row in rows]
    maximum = max(values) if values else 1
    elements = [
        svg_text(left, 48, "2025全A公司池：细分产业主营收入暴露匹配覆盖", 25, weight=700),
        svg_text(left, 78, "按产品分类优先、行业分类次之；匹配的是结构化主营构成，不是宽行业代理", 12, "#58606b"),
    ]
    for index, row in enumerate(rows):
        y = top + index * row_height
        value = int(row["2025匹配暴露公司数"])
        valid = int(row["2025收入比例有效公司数"])
        width_value = value / maximum * plot_width
        elements.append(svg_text(left - 18, y + 17, row["细分产业"], 13, "#26313b", "end", 600))
        elements.append(f'<rect x="{left}" y="{y}" width="{width_value:.1f}" height="24" fill="#087f73"/>')
        elements.append(svg_text(left + width_value + 10, y + 17, f"{value}家 / 比例有效{valid}家", 12, "#39434d", weight=600))
    total = rows[0]["2025全A公司数"] if rows else "0"
    successful = rows[0]["接口成功公司数"] if rows else "0"
    elements.append(svg_text(left, 600, f"全A公司池 {total} 家；接口成功 {successful} 家。公司可多标签，产业条数不能相加为公司总数。", 11, "#58606b"))
    elements.append(svg_text(left, 622, "来源：东方财富公开主营构成接口；按2025全A公司池回溯历史；未覆盖此前已退市公司，未逐页核对年报原文。", 11, "#58606b"))
    out.write_text(svg_document(width, height, "".join(elements)), encoding="utf-8")


def main() -> None:
    root = Path(__file__).resolve().parents[1]
    derived = root / "data/research/derived"
    figures = root / "docs/research/figures"
    market_rows = read_rows(derived / "a_share_industry_market_cap_structure.csv")
    profit_rows = read_rows(derived / "a_share_industry_profit_structure.csv")
    metadata_rows = read_rows(derived / "ifind_market_structure_metadata.csv")
    fundamental_rows = read_rows(derived / "a_share_industry_fundamentals_valuation.csv")
    multi_event_rows = read_rows(derived / "policy_multi_event_summary.csv")
    ipo_rows = read_rows(derived / "a_share_ipo_industry_structure_by_plan.csv")
    refinancing_rows = read_rows(derived / "a_share_refinancing_industry_structure_by_plan.csv")
    listing_rows = read_rows(derived / "listing_composition_revenue_decomposition.csv")
    policy_strength_rows = read_rows(derived / "five_year_plan_policy_strength_coding.csv")
    timeline_rows = read_rows(derived / "deep_industry_timelines.csv")
    continuous_rows = read_rows(derived / "four_industry_continuous_panel.csv")
    causal_rows = read_rows(derived / "policy_stacked_did_results.csv")
    macro_monthly_rows = read_rows(derived / "macro_controls_monthly.csv")
    exposure_coverage_rows = read_rows(derived / "company_subindustry_revenue_exposure_coverage.csv")
    plot_market_structure(market_rows, metadata_rows, figures / "a_share_industry_market_cap_structure.svg")
    plot_market_profit_comparison(market_rows, profit_rows, figures / "industry_market_cap_profit_comparison.svg")
    plot_fundamental_valuation(fundamental_rows, figures / "industry_fundamental_valuation_2025.svg")
    plot_multi_event_summary(multi_event_rows, figures / "policy_multi_event_summary.svg")
    plot_financing_structure(ipo_rows, refinancing_rows, figures / "ipo_refinancing_structure.svg")
    plot_listing_composition(listing_rows, figures / "listing_composition_decomposition.svg")
    plot_policy_strength(policy_strength_rows, figures / "policy_strength_coding.svg")
    plot_deep_timelines(timeline_rows, figures / "deep_industry_timelines.svg")
    plot_four_industry_continuous_trends(continuous_rows, figures / "four_industry_continuous_trends.svg")
    plot_policy_causal_test(causal_rows, figures / "policy_stacked_did_results.svg")
    plot_macro_control_coverage(macro_monthly_rows, figures / "macro_controls_monthly_coverage.svg")
    plot_business_exposure_coverage(exposure_coverage_rows, figures / "company_revenue_exposure_coverage.svg")
    print("wrote iFinD market-structure, policy-coding and industry-timeline SVG figures")


if __name__ == "__main__":
    main()
