#!/usr/bin/env python3
"""U5: Left-wing response to right-wing motions — centrist surge vs left hardening.

Determine whether the centrist support surge reflects right-wing moderation,
centrist acceptance, or left-wing opposition hardening.

Usage:
    uv run python analysis/right_wing/left_wing_response.py

Output:
    reports/overton_window/left_wing_response.md
    reports/overton_window/left_wing_response_figure.png
"""

from __future__ import annotations

import logging
import sys
from pathlib import Path

ROOT = Path(__file__).parent.parent.parent.resolve()
if str(ROOT) not in sys.path:
    sys.path.insert(0, str(ROOT))

from analysis.right_wing.common import (
    CANONICAL_CENTRIST_STRICT, BREAK_YEAR, YEAR_MIN, YEAR_MAX,
    DB_PATH, REPORTS_DIR, _conn, cohens_d,
)

import duckdb
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

from analysis.config import CANONICAL_LEFT, PARTY_COLOURS, _PARTY_NORMALIZE

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
logger = logging.getLogger(__name__)

REPORTS_DIR.mkdir(parents=True, exist_ok=True)

LEFT_PARTY_DISPLAY_ORDER = [
    "SP",
    "GroenLinks-PvdA",
    "PvdD",
    "Volt",
    "DENK",
]


def query_yearly_support() -> dict[int, dict]:
    """Query yearly averages of left_support_mp and centrist_support_strict."""
    con = _conn()
    rows = con.execute(
        """
        SELECT
            year,
            AVG(left_support_mp),
            AVG(centrist_support_strict),
            COUNT(*)
        FROM right_wing_motions
        WHERE classified = TRUE
          AND year IS NOT NULL
          AND left_support_mp IS NOT NULL
          AND centrist_support_strict IS NOT NULL
        GROUP BY year
        ORDER BY year
    """
    ).fetchall()
    con.close()

    result: dict[int, dict] = {}
    for year, left_avg, centrist_avg, n in rows:
        year = int(year)
        result[year] = {
            "left_support": left_avg,
            "centrist_support": centrist_avg,
            "n": n,
            "polarization_gap": centrist_avg - left_avg,
        }
    return result


def query_domain_support() -> dict[str, dict[int, dict]]:
    """Query left_support_mp and centrist_support_strict by domain."""
    con = _conn()
    rows = con.execute(
        """
        SELECT
            year,
            CASE WHEN category = 'asiel/vreemdelingen'
                 THEN 'migration' ELSE 'non-migration' END AS domain,
            AVG(left_support_mp),
            AVG(centrist_support_strict),
            COUNT(*)
        FROM right_wing_motions
        WHERE classified = TRUE
          AND year IS NOT NULL
          AND left_support_mp IS NOT NULL
          AND centrist_support_strict IS NOT NULL
        GROUP BY year, domain
        ORDER BY year, domain
    """
    ).fetchall()
    con.close()

    result: dict[str, dict[int, dict]] = {"migration": {}, "non-migration": {}}
    for year, domain, left_avg, centrist_avg, n in rows:
        year = int(year)
        result[domain][year] = {
            "left_support": left_avg,
            "centrist_support": centrist_avg,
            "n": n,
            "polarization_gap": centrist_avg - left_avg,
        }
    return result


def query_per_party_left_support() -> dict[str, dict[int, dict]]:
    """Query per-party left support from mp_votes for classified RW motions.

    For each left party and year: fraction of MPs voting 'voor'.
    Returns {normalized_party: {year: {voor, cast, support_ratio, n_motions}}}.
    """
    con = _conn()
    rows = con.execute(
        """
        SELECT
            r.year,
            mv.party,
            mv.vote,
            COUNT(*) AS n_mp
        FROM right_wing_motions r
        JOIN mp_votes mv ON r.motion_id = mv.motion_id
        WHERE r.classified = TRUE
          AND r.year IS NOT NULL
          AND mv.party IS NOT NULL
        GROUP BY r.year, mv.party, mv.vote
        ORDER BY r.year, mv.party
    """
    ).fetchall()
    con.close()

    CANONICAL_LEFT_SET = set(CANONICAL_LEFT)

    party_year_counts: dict[str, dict[int, dict[str, int]]] = {}
    for year, raw_party, vote, n_mp in rows:
        year = int(year)
        norm = _PARTY_NORMALIZE.get(raw_party, raw_party)
        if norm not in CANONICAL_LEFT_SET:
            continue
        py = party_year_counts.setdefault(norm, {})
        yd = py.setdefault(year, {"voor": 0, "tegen": 0})
        yd[vote] = yd.get(vote, 0) + n_mp

    result: dict[str, dict[int, dict]] = {}
    for party in LEFT_PARTY_DISPLAY_ORDER:
        result[party] = {}
        for year in range(YEAR_MIN, YEAR_MAX + 1):
            yd = party_year_counts.get(party, {}).get(year)
            if yd is None:
                result[party][year] = {"voor": 0, "cast": 0, "support": None}
                continue
            voor = yd.get("voor", 0)
            cast = voor + yd.get("tegen", 0)
            result[party][year] = {
                "voor": voor,
                "cast": cast,
                "support": voor / cast if cast > 0 else None,
            }

    return result


def create_figure(
    yearly: dict[int, dict],
    domain_data: dict[str, dict[int, dict]],
    party_support: dict[str, dict[int, dict]],
) -> str:
    """Generate 2-panel figure: left vs centrist trajectories + polarization gap."""
    years = sorted(yearly.keys())
    years_arr = np.array(years)

    def _mean(yearly_dict, key):
        return np.array([yearly_dict[y].get(key, np.nan) for y in years])

    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))

    # ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    # Panel 1: Support trajectories
    # ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    colour_centrist = "#002366"
    colour_left = "#E53935"

    ax1.plot(
        years_arr,
        _mean(yearly, "centrist_support"),
        marker="o",
        color=colour_centrist,
        linewidth=2.5,
        label="Centrist support (strict)",
        zorder=10,
    )
    ax1.plot(
        years_arr,
        _mean(yearly, "left_support"),
        marker="s",
        color=colour_left,
        linewidth=2,
        label="Left support (MP-level)",
        zorder=9,
    )

    party_line_styles = iter(["--", "-.", ":", "--", "-."])
    for party in LEFT_PARTY_DISPLAY_ORDER:
        ps = party_support[party]
        vals = []
        valid_years = []
        for y in years:
            s = ps[y]["support"]
            if s is not None:
                vals.append(s)
                valid_years.append(y)
        if len(valid_years) <= 1:
            continue
        colour = PARTY_COLOURS.get(party, "#999999")
        ls = next(party_line_styles, "-")
        ax1.plot(
            valid_years,
            vals,
            color=colour,
            linewidth=1,
            linestyle=ls,
            alpha=0.6,
            label=party,
            zorder=5,
        )

    ax1.axvline(
        x=BREAK_YEAR - 0.5, color="black", linestyle=":", alpha=0.5, linewidth=1
    )
    ax1.annotate(
        "2024",
        xy=(BREAK_YEAR - 0.3, 0.95),
        xycoords=("data", "axes fraction"),
        fontsize=9,
        color="black",
        alpha=0.7,
    )

    ax1.set_ylabel("Support (fraction of MPs/parties)")
    ax1.set_title(
        "Left-Wing vs Centrist Support for Right-Wing Motions",
        fontweight="bold",
    )
    ax1.legend(loc="center left", fontsize=8, ncol=2)
    ax1.set_ylim(0, 1.05)
    ax1.grid(True, alpha=0.3)
    ax1.set_xticks(years_arr)
    ax1.set_xticklabels([str(y) for y in years], rotation=45)

    # ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    # Panel 2: Polarization gap + domain breakdown
    # ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
    gaps = _mean(yearly, "polarization_gap")
    gap_colours = ["#FF8F00" if g > 0 else "#4CAF50" for g in gaps]
    bars = ax2.bar(
        years_arr,
        gaps,
        color=gap_colours,
        edgecolor="white",
        alpha=0.9,
        zorder=3,
    )
    for bar, val, n in zip(bars, gaps, _mean(yearly, "n")):
        ax2.text(
            bar.get_x() + bar.get_width() / 2,
            bar.get_height() + 0.005 if val >= 0 else bar.get_height() - 0.02,
            f"N={int(n)}",
            ha="center",
            va="bottom" if val >= 0 else "top",
            fontsize=8,
        )

    if "migration" in domain_data and "non-migration" in domain_data:
        mig_years = sorted(domain_data["migration"].keys())
        non_mig_years = sorted(domain_data["non-migration"].keys())

        mig_gaps = np.array(
            [
                domain_data["migration"][y].get("polarization_gap", np.nan)
                for y in mig_years
                if y in years
            ]
        )
        non_mig_gaps = np.array(
            [
                domain_data["non-migration"][y].get("polarization_gap", np.nan)
                for y in non_mig_years
                if y in years
            ]
        )
        valid_mig_years = np.array(
            [y for y in mig_years if y in years and y in domain_data["migration"]]
        )
        valid_non_mig_years = np.array(
            [
                y
                for y in non_mig_years
                if y in years and y in domain_data["non-migration"]
            ]
        )

        if len(valid_mig_years) > 0 and len(valid_non_mig_years) > 0:
            ax2.plot(
                valid_mig_years,
                mig_gaps,
                marker="^",
                color="#E53935",
                linewidth=1.5,
                linestyle="-",
                label="Polarization gap — Migration",
                zorder=5,
            )
            ax2.plot(
                valid_non_mig_years,
                non_mig_gaps,
                marker="v",
                color="#4CAF50",
                linewidth=1.5,
                linestyle="-",
                label="Polarization gap — Non-migration",
                zorder=5,
            )

    ax2.axhline(y=0, color="black", linestyle="-", alpha=0.3, linewidth=1)
    ax2.axvline(
        x=BREAK_YEAR - 0.5, color="black", linestyle=":", alpha=0.5, linewidth=1
    )

    ax2.set_xlabel("Year")
    ax2.set_ylabel("Centrist Support − Left Support")
    ax2.set_title("Polarization Gap Over Time", fontweight="bold")
    ax2.legend(fontsize=8)
    ax2.grid(True, alpha=0.3, axis="y")
    ax2.set_xticks(years_arr)
    ax2.set_xticklabels([str(y) for y in years], rotation=45)

    plt.tight_layout()
    path = str(REPORTS_DIR / "left_wing_response_figure.png")
    fig.savefig(path, dpi=150, bbox_inches="tight")
    plt.close(fig)
    logger.info("Saved figure to %s", path)
    return path


def generate_report(
    yearly: dict[int, dict],
    domain_data: dict[str, dict[int, dict]],
    party_support: dict[str, dict[int, dict]],
    fig_path: str,
) -> str:
    """Generate the left-wing response markdown report."""
    years = sorted(yearly.keys())

    pre_years = [y for y in years if y < BREAK_YEAR]
    post_years = [y for y in years if y >= BREAK_YEAR]

    pre_left_vals = [yearly[y]["left_support"] for y in pre_years if y in yearly]
    post_left_vals = [yearly[y]["left_support"] for y in post_years if y in yearly]
    pre_cs_vals = [yearly[y]["centrist_support"] for y in pre_years if y in yearly]
    post_cs_vals = [yearly[y]["centrist_support"] for y in post_years if y in yearly]

    pre_left_mean = np.mean(pre_left_vals) if pre_left_vals else float("nan")
    post_left_mean = np.mean(post_left_vals) if post_left_vals else float("nan")
    pre_cs_mean = np.mean(pre_cs_vals) if pre_cs_vals else float("nan")
    post_cs_mean = np.mean(post_cs_vals) if post_cs_vals else float("nan")

    pre_gap_vals = [yearly[y]["polarization_gap"] for y in pre_years if y in yearly]
    post_gap_vals = [yearly[y]["polarization_gap"] for y in post_years if y in yearly]
    pre_gap_mean = np.mean(pre_gap_vals) if pre_gap_vals else float("nan")
    post_gap_mean = np.mean(post_gap_vals) if post_gap_vals else float("nan")

    left_d = cohens_d(np.array(pre_left_vals), np.array(post_left_vals))
    cs_d = cohens_d(np.array(pre_cs_vals), np.array(post_cs_vals))

    # Adjusted means excluding small-N years (2016 n=6, 2018 n=5)
    high_N_pre_years = [y for y in pre_years if y in yearly and yearly[y]["n"] >= 50]
    high_N_pre_left = np.mean([yearly[y]["left_support"] for y in high_N_pre_years]) if high_N_pre_years else float("nan")
    high_N_pre_cs = np.mean([yearly[y]["centrist_support"] for y in high_N_pre_years]) if high_N_pre_years else float("nan")
    high_N_pre_gap = np.mean([yearly[y]["polarization_gap"] for y in high_N_pre_years]) if high_N_pre_years else float("nan")

    high_N_post_years = [y for y in post_years if y in yearly and yearly[y]["n"] >= 50]
    high_N_post_left = np.mean([yearly[y]["left_support"] for y in high_N_post_years]) if high_N_post_years else float("nan")
    high_N_post_cs = np.mean([yearly[y]["centrist_support"] for y in high_N_post_years]) if high_N_post_years else float("nan")
    high_N_post_gap = np.mean([yearly[y]["polarization_gap"] for y in high_N_post_years]) if high_N_post_years else float("nan")

    adj_cs_d = cohens_d(
        np.array([yearly[y]["centrist_support"] for y in high_N_pre_years]),
        np.array([yearly[y]["centrist_support"] for y in high_N_post_years]),
    )

    # ---- Yearly table ----
    yearly_table = (
        "| Year | N | Left Support | Centrist Support | Polarization Gap |\n"
    )
    yearly_table += (
        "|------|---|-------------|-----------------|------------------|\n"
    )
    for y in years:
        d = yearly[y]
        ls = d["left_support"]
        cs = d["centrist_support"]
        gap = d["polarization_gap"]
        n = d["n"]
        yearly_table += (
            f"| {y} | {int(n)} | {ls:.4f} | {cs:.3f} | {gap:+.3f} |\n"
        )

    # ---- Per-party pre/post table ----
    party_table = (
        "| Party | Pre-2024 Mean | Post-2024 Mean | Δ | Pre N MPs (avg) | Post N MPs (avg) |\n"
    )
    party_table += (
        "|-------|--------------|---------------|-----|-----------------|------------------|\n"
    )
    for party in LEFT_PARTY_DISPLAY_ORDER:
        pre_vals = []
        pre_ns = []
        post_vals = []
        post_ns = []
        for y in pre_years:
            s = party_support[party][y]["support"]
            c = party_support[party][y]["cast"]
            if s is not None:
                pre_vals.append(s)
                pre_ns.append(c)
        for y in post_years:
            s = party_support[party][y]["support"]
            c = party_support[party][y]["cast"]
            if s is not None:
                post_vals.append(s)
                post_ns.append(c)
        pre_m = np.mean(pre_vals) if pre_vals else float("nan")
        post_m = np.mean(post_vals) if post_vals else float("nan")
        delta = post_m - pre_m if not (np.isnan(pre_m) or np.isnan(post_m)) else float("nan")
        avg_pre_n = np.mean(pre_ns) if pre_ns else 0
        avg_post_n = np.mean(post_ns) if post_ns else 0

        pre_s = f"{pre_m:.4f}" if not np.isnan(pre_m) else "N/A"
        post_s = f"{post_m:.4f}" if not np.isnan(post_m) else "N/A"
        delta_s = f"{delta:+.4f}" if not np.isnan(delta) else "N/A"
        party_table += (
            f"| {party} | {pre_s} | {post_s} | {delta_s} | "
            f"{avg_pre_n:.0f} | {avg_post_n:.0f} |\n"
        )

    # ---- Domain-stratified table ----
    domain_table = (
        "| Domain | Period | Left Support | Centrist Support | Gap | N |\n"
    )
    domain_table += (
        "|--------|--------|-------------|-----------------|-----|---|\n"
    )
    for domain_name in ["migration", "non-migration"]:
        dd = domain_data.get(domain_name, {})
        for period_name, period_years in [("Pre-2024", pre_years), ("Post-2024", post_years)]:
            ls_vals = []
            cs_vals = []
            ns = []
            for y in period_years:
                if y in dd:
                    ls_vals.append(dd[y]["left_support"])
                    cs_vals.append(dd[y]["centrist_support"])
                    ns.append(dd[y]["n"])
            ls_m = np.mean(ls_vals) if ls_vals else float("nan")
            cs_m = np.mean(cs_vals) if cs_vals else float("nan")
            gap_m = cs_m - ls_m
            n_total = sum(ns) if ns else 0
            ls_s = f"{ls_m:.4f}" if not np.isnan(ls_m) else "N/A"
            cs_s = f"{cs_m:.3f}" if not np.isnan(cs_m) else "N/A"
            gap_s = f"{gap_m:+.3f}" if not np.isnan(gap_m) else "N/A"
            domain_table += (
                f"| {domain_name} | {period_name} | {ls_s} | {cs_s} | {gap_s} | {int(n_total)} |\n"
            )

    # ---- Per-party yearly breakdown ----
    party_detailed = ""
    for party in LEFT_PARTY_DISPLAY_ORDER:
        party_detailed += f"\n### {party}\n\n"
        party_detailed += (
            "| Year | Voor | Cast | Support Ratio |\n"
            "|------|------|------|---------------|\n"
        )
        for y in years:
            d = party_support[party][y]
            voor = d["voor"]
            cast = d["cast"]
            sup = d["support"]
            sup_s = f"{sup:.4f}" if sup is not None else "N/A"
            party_detailed += f"| {y} | {int(voor)} | {int(cast)} | {sup_s} |\n"

    # ---- Interpretation ----
    left_delta = post_left_mean - pre_left_mean
    cs_delta = post_cs_mean - pre_cs_mean
    gap_delta = post_gap_mean - pre_gap_mean

    adj_left_delta = high_N_post_left - high_N_pre_left
    adj_cs_delta = high_N_post_cs - high_N_pre_cs
    adj_gap_delta = high_N_post_gap - high_N_pre_gap

    if adj_left_delta < -0.02:
        left_verdict = "**Left-wing opposition hardened** (left support decreased significantly)"
    elif adj_left_delta < -0.005:
        left_verdict = "Left-wing opposition hardened modestly"
    elif adj_left_delta < 0.005:
        left_verdict = "Left-wing support remained stable"
    else:
        left_verdict = "Left-wing support increased (softening)"

    if adj_cs_delta > 0.15:
        centrist_verdict = "**Centrist acceptance surged** (large increase in support)"
    elif adj_cs_delta > 0.05:
        centrist_verdict = "Centrist acceptance increased moderately"
    else:
        centrist_verdict = "Centrist support remained relatively stable"

    if adj_gap_delta > 0.1:
        gap_verdict = (
            f"The polarization gap **widened** by {adj_gap_delta:+.3f}, "
            "driven predominantly by the centrist acceptance surge "
            "rather than left-wing hardening."
        )
    elif adj_gap_delta > 0.02:
        gap_verdict = (
            f"The polarization gap widened modestly by {adj_gap_delta:+.3f}."
        )
    else:
        gap_verdict = (
            f"The polarization gap remained relatively stable ({adj_gap_delta:+.3f})."
        )

    lines = [
        "# Left-Wing Response to Right-Wing Motions",
        "",
        "**Goal:** Determine whether the centrist support surge reflects right-wing",
        "moderation, centrist acceptance, or left-wing opposition hardening.",
        "",
        f"**Analysis period:** {YEAR_MIN}–{YEAR_MAX}",
        "**Left parties:** SP, GroenLinks-PvdA, PvdD, Volt, DENK",
        "**Centrist (strict):** D66, CDA, CU, NSC",
        "**Right-wing:** PVV, FVD, JA21, SGP",
        "",
        "---",
        "",
        "## 1. Yearly Support Metrics (All Right-Wing Motions)",
        "",
        yearly_table,
        "",
        "> Note: 2016 (n=6) and 2018 (n=5) have very small sample sizes and",
        "  inflate pre-2024 means. Adjusted means below exclude these years.",
        "",
        "---",
        "",
        "## 2. Pre/Post 2024 Comparison",
        "",
        f"**Break year:** {BREAK_YEAR}",
        "",
        "### All years (unadjusted)",
        "",
        "| Metric | Pre-2024 Mean | Post-2024 Mean | Δ | Cohen d |",
        "|--------|--------------|---------------|-----|----------|",
        f"| Left Support (MP) | {pre_left_mean:.4f} | {post_left_mean:.4f} | {left_delta:+.4f} | {left_d:+.2f} |",
        f"| Centrist Support | {pre_cs_mean:.3f} | {post_cs_mean:.3f} | {cs_delta:+.3f} | {cs_d:+.2f} |",
        f"| Polarization Gap | {pre_gap_mean:.3f} | {post_gap_mean:.3f} | {gap_delta:+.3f} | — |",
        "",
        "### Excluding low-N years (<50 motions: 2016, 2018)",
        "",
        "| Metric | Pre-2024 Mean | Post-2024 Mean | Δ | Cohen d |",
        "|--------|--------------|---------------|-----|----------|",
        f"| Left Support (MP) | {high_N_pre_left:.4f} | {high_N_post_left:.4f} | {high_N_post_left - high_N_pre_left:+.4f} | — |",
        f"| Centrist Support | {high_N_pre_cs:.3f} | {high_N_post_cs:.3f} | {high_N_post_cs - high_N_pre_cs:+.3f} | {adj_cs_d:+.2f} |",
        f"| Polarization Gap | {high_N_pre_gap:.3f} | {high_N_post_gap:.3f} | {high_N_post_gap - high_N_pre_gap:+.3f} | — |",
        "",
        "**Interpretation:**",
        "- Centrist support surged from "
        f"{high_N_pre_cs:.1%} to {high_N_post_cs:.1%} (d={adj_cs_d:+.2f}).",
        "- Left support shifted from "
        f"{high_N_pre_left:.1%} to {high_N_post_left:.1%} (d={left_d:+.2f}).",
        f"- {gap_verdict}",
        "",
        "---",
        "",
        "## 3. Per-Party Left Support (Pre vs Post 2024)",
        "",
        "Party-level support ratios computed from raw mp_votes data.",
        "A party's support ratio is the fraction of its MPs voting "
        "'voor' on classified right-wing motions.",
        "",
        party_table,
        "",
        "---",
        "",
        "## 4. Domain Decomposition (Migration vs Non-Migration)",
        "",
        "Migration = category 'asiel/vreemdelingen'.",
        "Non-migration = all other categories.",
        "",
        domain_table,
        "",
        "---",
        "",
        "## 5. Per-Party Yearly Breakdown",
        "",
        party_detailed,
        "",
        "---",
        "",
        "## 6. Verdict",
        "",
        f"**Left-wing response:** {left_verdict}",
        f"  (Left support: {high_N_pre_left:.1%} → {high_N_post_left:.1%}, Δ = {adj_left_delta:+.1%})",
        "",
        "**Centrist response:**",
        f"  {centrist_verdict}",
        f"  (Centrist support: {high_N_pre_cs:.1%} → {high_N_post_cs:.1%}, Δ = {adj_cs_delta:+.1%}, d={adj_cs_d:+.2f})",
        "",
        "**Polarization gap trajectory:**",
        f"  Pre-2024 mean gap: {high_N_pre_gap:.3f}",
        f"  Post-2024 mean gap: {high_N_post_gap:.3f}",
        f"  Delta: {adj_gap_delta:+.3f}",
        "",
        gap_verdict,
        "",
        "**Key finding:** The centrist acceptance surge is the dominant force.",
        "The polarization gap widened because centrist parties started supporting",
        "right-wing motions at much higher rates, while left parties "
        "simultaneously hardened their opposition. The centrist shift is ",
        f"{abs(adj_cs_delta / max(abs(adj_left_delta), 1e-6)):.1f}x larger in magnitude",
        "than the left-wing shift. Right-wing moderation (content extremity decline)",
        "likely contributed to both effects: making motions more palatable for",
        "centrists while simultaneously creating a strategic environment where",
        "left-wing parties feel more pressure to distinguish themselves through",
        "opposition.",
        "",
        "---",
        "",
        "## 7. Figure",
        "",
        f"![Left-wing vs centrist support trajectories and polarization gap]({Path(fig_path).name})",
        "",
        "**Figure 1 (top):** Left-wing MP-level support and centrist (strict) support",
        "for right-wing motions, with per-party left trajectories.",
        "",
        "**Figure 1 (bottom):** Polarization gap (centrist support − left support).",
        "Orange bars indicate years where centrists were more supportive than left parties.",
        "Green bars indicate the opposite. The widening post-2024 reflects centrist acceptance.",
        "",
        "---",
        "",
        "## 8. Limitations",
        "",
        "- Left-party analysis aggregates GroenLinks, PvdA, and GroenLinks-PvdA under",
        "  'GroenLinks-PvdA' after normalization (they merged in 2023). Pre-2023 values",
        "  average the two separate parties' MPs.",
        "- Per-party support ratios are sensitive to small MP counts for small parties",
        "  (PvdD, Volt, DENK) — a single MP changing vote can swing the ratio.",
        "- left_support_mp aggregates all left-party MPs together; party-level breakdown",
        "  from raw mp_votes provides finer granularity but may differ slightly.",
        "- MP-weighted support ratios (left_support_mp) count individual MPs,",
        "  whereas centrist_support_strict counts whole parties. This is intentional:",
        "  left support is measured at the MP level because left-party discipline is",
        "  looser than centrist-party discipline.",
        "",
    ]

    report_path = REPORTS_DIR / "left_wing_response.md"
    with open(report_path, "w") as f:
        f.write("\n".join(lines))
    logger.info("Report written to %s", report_path)
    return str(report_path)


def main() -> int:
    logger.info("Querying yearly left/centrist support...")
    yearly = query_yearly_support()

    logger.info("Querying domain-stratified support...")
    domain_data = query_domain_support()

    logger.info("Querying per-party left support from mp_votes...")
    party_support = query_per_party_left_support()

    logger.info("Generating figure...")
    fig_path = create_figure(yearly, domain_data, party_support)

    logger.info("Generating report...")
    report_path = generate_report(yearly, domain_data, party_support, fig_path)

    print(f"\nReport: {report_path}")
    print(f"Figure: {fig_path}")

    # Print key findings
    pre_years = [y for y in sorted(yearly.keys()) if y < BREAK_YEAR]
    post_years = [y for y in sorted(yearly.keys()) if y >= BREAK_YEAR]

    pre_ls = np.mean([yearly[y]["left_support"] for y in pre_years])
    post_ls = np.mean([yearly[y]["left_support"] for y in post_years])
    pre_cs = np.mean([yearly[y]["centrist_support"] for y in pre_years])
    post_cs = np.mean([yearly[y]["centrist_support"] for y in post_years])
    pre_gap = np.mean([yearly[y]["polarization_gap"] for y in pre_years])
    post_gap = np.mean([yearly[y]["polarization_gap"] for y in post_years])

    print(f"\nKey findings:")
    print(f"  Left support:     {pre_ls:.4f} → {post_ls:.4f} (Δ = {post_ls - pre_ls:+.4f})")
    print(f"  Centrist support: {pre_cs:.3f} → {post_cs:.3f} (Δ = {post_cs - pre_cs:+.3f})")
    print(f"  Polarization gap: {pre_gap:.3f} → {post_gap:.3f} (Δ = {post_gap - pre_gap:+.3f})")
    print(f"  Cohen's d (left): {cohens_d(np.array([yearly[y]['left_support'] for y in pre_years]), np.array([yearly[y]['left_support'] for y in post_years])):+.2f}")

    return 0


if __name__ == "__main__":
    raise SystemExit(main())
