"""
Step 4: Data Visualization
Generate 5 publication-quality charts for the GLP-1/GIPR portfolio strategy review.
"""

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.ticker as ticker
import numpy as np
import pandas as pd
from pathlib import Path
import warnings
warnings.filterwarnings('ignore')

# ─── Paths ────────────────────────────────────────────────────────────────────
BASE = Path('/app/sandbox/session_20260403_103155_c18ae3efc2c1')
RESULTS = BASE / 'results'
FIGURES = BASE / 'figures'
FIGURES.mkdir(parents=True, exist_ok=True)

# ─── Global style ─────────────────────────────────────────────────────────────
plt.rcParams.update({
    'font.family': 'sans-serif',
    'font.sans-serif': ['DejaVu Sans'],
    'font.size': 10,
    'axes.titlesize': 13,
    'axes.titleweight': 'bold',
    'axes.labelsize': 11,
    'axes.spines.top': False,
    'axes.spines.right': False,
    'xtick.labelsize': 9,
    'ytick.labelsize': 9,
    'legend.fontsize': 9,
    'figure.dpi': 150,
    'savefig.dpi': 300,
    'savefig.bbox': 'tight',
})

PALETTE = {
    'tirzepatide':  '#1f77b4',
    'semaglutide':  '#ff7f0e',
    'retatrutide':  '#2ca02c',
    'CT-388':       '#d62728',
    'survodutide':  '#9467bd',
    'orforglipron': '#8c564b',
    'pemvidutide':  '#e377c2',
    'amycretin':    '#7f7f7f',
}

# ─── Mechanism colours (for bubble chart) ─────────────────────────────────────
MECH_PALETTE = {
    'mono':   '#1f77b4',
    'dual':   '#ff7f0e',
    'triple': '#2ca02c',
}

print("=" * 60)
print("Step 4: Generating Visualizations")
print("=" * 60)

# ══════════════════════════════════════════════════════════════════════════════
# Chart 1 — Pipeline Bubble Chart
# ══════════════════════════════════════════════════════════════════════════════
print("\n[1/5] Pipeline Bubble Chart ...")

df_pipe = pd.read_csv(RESULTS / 'pipeline_data.csv')
print(f"  Loaded pipeline_data.csv: {df_pipe.shape[0]} rows")

# Normalise phase to a numeric rank for plotting
PHASE_ORDER = {
    'PHASE1': 1,
    'PHASE2': 2,
    'PHASE3': 3,
    'PHASE4 (Approved - US, EU)': 4,
}

def phase_rank(phase_str):
    for key, val in sorted(PHASE_ORDER.items(), key=lambda x: -len(x[0])):
        if key in phase_str:
            return val
    return 0

df_pipe['phase_num'] = df_pipe['phase'].apply(phase_rank)
df_pipe['phase_label'] = df_pipe['phase_num'].map({
    1: 'Phase 1', 2: 'Phase 2', 3: 'Phase 3', 4: 'Approved'
})

# Mechanism category
def mech_cat(m):
    m = m.lower()
    if 'triple' in m:
        return 'triple'
    if 'dual' in m:
        return 'dual'
    return 'mono'

df_pipe['mech_cat'] = df_pipe['mechanism'].apply(mech_cat)

# Route marker
def route_marker(r):
    r = r.lower()
    if 'oral' in r and 'inject' in r:
        return 'D'   # diamond — both
    if 'oral' in r:
        return 's'   # square
    return 'o'       # circle (injectable)

df_pipe['marker'] = df_pipe['route'].apply(route_marker)

# Bubble size ~ total_trials (scale for visibility)
sizes = (df_pipe['total_trials'].clip(lower=1) / df_pipe['total_trials'].max()) * 1200 + 200

fig, ax = plt.subplots(figsize=(10, 5.5))

for _, row in df_pipe.iterrows():
    color = MECH_PALETTE[row['mech_cat']]
    ax.scatter(
        row['phase_num'], 0.5,
        s=sizes[row.name],
        c=color,
        marker=row['marker'],
        alpha=0.80,
        edgecolors='white',
        linewidths=1.2,
        zorder=3
    )
    ax.text(
        row['phase_num'], 0.5 + 0.08,
        row['drug_name'],
        ha='center', va='bottom', fontsize=8.5, fontweight='bold'
    )

ax.set_xlim(0.5, 4.5)
ax.set_ylim(0, 1)
ax.set_xticks([1, 2, 3, 4])
ax.set_xticklabels(['Phase 1', 'Phase 2', 'Phase 3', 'Approved'], fontsize=10)
ax.set_yticks([])
ax.spines['left'].set_visible(False)
ax.set_xlabel('Development Phase', fontsize=11)
ax.set_title('GLP-1/Incretin Agonist Competitive Pipeline', pad=14)

# Legend: mechanism colours
legend_mech = [
    mpatches.Patch(color=MECH_PALETTE['mono'],   label='Mono agonist (GLP-1R)'),
    mpatches.Patch(color=MECH_PALETTE['dual'],   label='Dual agonist (GLP-1R/GIPR or GCGR)'),
    mpatches.Patch(color=MECH_PALETTE['triple'], label='Triple agonist (GLP-1R/GIPR/GCGR)'),
]
# Legend: route markers (dummy scatter for legend handles)
h_inj  = plt.scatter([], [], s=80, c='grey', marker='o',  label='Injectable')
h_oral = plt.scatter([], [], s=80, c='grey', marker='s',  label='Oral')
h_both = plt.scatter([], [], s=80, c='grey', marker='D',  label='Oral & Injectable')

leg1 = ax.legend(handles=legend_mech, loc='upper left', title='Mechanism', framealpha=0.9)
ax.add_artist(leg1)
ax.legend(handles=[h_inj, h_oral, h_both], loc='upper right', title='Route', framealpha=0.9)

# Bubble size note
ax.annotate('Bubble size proportional to number of registered trials',
            xy=(0.5, 0.02), xycoords='axes fraction', fontsize=7.5,
            color='grey', ha='left')

fig.tight_layout()
out1 = FIGURES / 'pipeline_bubble_chart.png'
fig.savefig(out1, dpi=300, bbox_inches='tight')
plt.close(fig)
print(f"  Saved: {out1}")


# ══════════════════════════════════════════════════════════════════════════════
# Chart 2 — Efficacy Bar Chart
# ══════════════════════════════════════════════════════════════════════════════
print("\n[2/5] Efficacy Bar Chart ...")

df_eff = pd.read_csv(RESULTS / 'clinical_efficacy_summary_std_nonT2D.csv')
print(f"  Loaded clinical_efficacy_summary_std_nonT2D.csv: {df_eff.shape[0]} rows")
print(f"  Columns: {list(df_eff.columns)}")

# Sort descending by efficacy
df_eff_sorted = df_eff.sort_values('Placebo_Adjusted_Weight_Loss_Pct', ascending=False).reset_index(drop=True)

colors = [PALETTE.get(d.lower().replace('-', '-'), '#777') for d in df_eff_sorted['Drug']]
# Manual colour lookup
bar_colors = []
for drug in df_eff_sorted['Drug']:
    bar_colors.append(PALETTE.get(drug, '#777777'))

fig, ax = plt.subplots(figsize=(8, 5.5))

bars = ax.bar(
    df_eff_sorted['Drug'],
    df_eff_sorted['Placebo_Adjusted_Weight_Loss_Pct'],
    color=bar_colors,
    edgecolor='white',
    linewidth=1,
    width=0.55,
    zorder=3
)
ax.set_ylim(0, df_eff_sorted['Placebo_Adjusted_Weight_Loss_Pct'].max() * 1.35)
ax.set_ylabel('Placebo-Adjusted Mean Body\nWeight Loss (%)', fontsize=10)
ax.set_title('Head-to-Head Efficacy Comparison\n(Non-T2D Obesity Trials, Standard Design)', pad=12)
ax.yaxis.grid(True, linestyle='--', alpha=0.4, zorder=0)
ax.set_axisbelow(True)

# Value labels on bars
for bar, val in zip(bars, df_eff_sorted['Placebo_Adjusted_Weight_Loss_Pct']):
    ax.text(
        bar.get_x() + bar.get_width() / 2,
        bar.get_height() + 0.3,
        f'{val:.1f}%',
        ha='center', va='bottom', fontsize=9.5, fontweight='bold'
    )

# Duration labels below drug name (x-tick)
duration_map = dict(zip(df_eff_sorted['Drug'], df_eff_sorted['Duration_Weeks']))
new_labels = []
for drug in df_eff_sorted['Drug']:
    wks = int(duration_map[drug])
    new_labels.append(f'{drug}\n({wks} wks)')
ax.set_xticklabels(new_labels, fontsize=9.5)

# ── SCIENTIFIC CAVEAT — CT-388 ────────────────────────────────────────────────
caveat_text = (
    "⚠  IMPORTANT CAVEAT: CT-388 data represent a 24-week\n"
    "    interim analysis only. All other drugs reflect 48–72\n"
    "    week endpoints. Direct comparisons should be interpreted\n"
    "    with caution — efficacy at full trial duration may differ."
)
ax.text(
    0.98, 0.97,
    caveat_text,
    transform=ax.transAxes,
    fontsize=8,
    color='#8B0000',
    ha='right', va='top',
    bbox=dict(boxstyle='round,pad=0.4', facecolor='#fff8f8',
              edgecolor='#8B0000', linewidth=1.2, alpha=0.95),
    fontweight='normal',
    linespacing=1.45
)

fig.tight_layout()
out2 = FIGURES / 'efficacy_bar_chart.png'
fig.savefig(out2, dpi=300, bbox_inches='tight')
plt.close(fig)
print(f"  Saved: {out2}")


# ══════════════════════════════════════════════════════════════════════════════
# Chart 3 — FAERS Time-Series
# ══════════════════════════════════════════════════════════════════════════════
print("\n[3/5] FAERS Time-Series ...")

df_faers = pd.read_csv(RESULTS / 'faers_time_series.csv')
print(f"  Loaded faers_time_series.csv: {df_faers.shape[0]} rows")
print(f"  Event types: {sorted(df_faers['event_type'].unique())}")
print(f"  Drugs: {sorted(df_faers['drug'].unique())}")

# Create a proper date from quarter string (e.g. "2020Q1" -> 2020-01-01)
def quarter_to_date(q):
    yr, qt = q[:4], int(q[5])
    month = (qt - 1) * 3 + 1
    return pd.Timestamp(year=int(yr), month=month, day=1)

df_faers['date'] = df_faers['quarter'].apply(quarter_to_date)

# Aggregate GI events (nausea + vomiting + diarrhea + abdominal_pain) and pancreatitis
GI_EVENTS = {'nausea', 'vomiting', 'diarrhea', 'abdominal_pain'}

# Aggregate per drug × date
gi_agg = (
    df_faers[df_faers['event_type'].isin(GI_EVENTS)]
    .groupby(['date', 'drug'], as_index=False)
    .agg(event_count=('event_count', 'sum'),
         total_drug_reports=('total_drug_reports_in_quarter', 'first'))
)
gi_agg['rate'] = gi_agg['event_count'] / gi_agg['total_drug_reports'] * 100

panc_agg = (
    df_faers[df_faers['event_type'] == 'pancreatitis']
    .groupby(['date', 'drug'], as_index=False)
    .agg(event_count=('event_count', 'sum'),
         total_drug_reports=('total_drug_reports_in_quarter', 'first'))
)
panc_agg['rate'] = panc_agg['event_count'] / panc_agg['total_drug_reports'] * 100

DRUGS_FAERS = ['semaglutide', 'tirzepatide']
LINE_STYLES_FAERS = {'semaglutide': '-', 'tirzepatide': '--'}
MARKERS_FAERS = {'semaglutide': 'o', 'tirzepatide': 's'}

fig, (ax_gi, ax_panc) = plt.subplots(1, 2, figsize=(13, 5), sharey=False)
fig.suptitle('FAERS Adverse Event Reporting Rates: Semaglutide vs. Tirzepatide (2020–2026)',
             fontsize=12, fontweight='bold', y=1.01)

for drug in DRUGS_FAERS:
    sub = gi_agg[gi_agg['drug'] == drug].sort_values('date')
    if sub.empty:
        continue
    ax_gi.plot(
        sub['date'], sub['rate'],
        color=PALETTE[drug],
        linestyle=LINE_STYLES_FAERS[drug],
        marker=MARKERS_FAERS[drug],
        markersize=4, linewidth=1.8,
        label=drug.capitalize()
    )

ax_gi.set_title('GI Adverse Events\n(Nausea, Vomiting, Diarrhea, Abdominal Pain)', fontsize=10)
ax_gi.set_ylabel('Normalized Reporting Rate (%)', fontsize=10)
ax_gi.set_xlabel('Quarter', fontsize=9)
ax_gi.legend(framealpha=0.85)
ax_gi.yaxis.grid(True, linestyle='--', alpha=0.35)
ax_gi.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%Y'))
ax_gi.xaxis.set_major_locator(plt.matplotlib.dates.YearLocator())

for drug in DRUGS_FAERS:
    sub = panc_agg[panc_agg['drug'] == drug].sort_values('date')
    if sub.empty:
        continue
    ax_panc.plot(
        sub['date'], sub['rate'],
        color=PALETTE[drug],
        linestyle=LINE_STYLES_FAERS[drug],
        marker=MARKERS_FAERS[drug],
        markersize=4, linewidth=1.8,
        label=drug.capitalize()
    )

ax_panc.set_title('Pancreatitis Reporting Rate', fontsize=10)
ax_panc.set_ylabel('Normalized Reporting Rate (%)', fontsize=10)
ax_panc.set_xlabel('Quarter', fontsize=9)
ax_panc.legend(framealpha=0.85)
ax_panc.yaxis.grid(True, linestyle='--', alpha=0.35)
ax_panc.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%Y'))
ax_panc.xaxis.set_major_locator(plt.matplotlib.dates.YearLocator())

# Note tirzepatide FDA approval
ax_gi.axvline(pd.Timestamp('2022-05-01'), color='grey', linestyle=':', linewidth=1, alpha=0.7)
ax_gi.text(pd.Timestamp('2022-05-01'), ax_gi.get_ylim()[1]*0.98,
           'Tirzepatide\nFDA 2022', fontsize=7, color='grey', ha='left', va='top')
ax_panc.axvline(pd.Timestamp('2022-05-01'), color='grey', linestyle=':', linewidth=1, alpha=0.7)

fig.tight_layout()
out3 = FIGURES / 'faers_time_series.png'
fig.savefig(out3, dpi=300, bbox_inches='tight')
plt.close(fig)
print(f"  Saved: {out3}")


# ══════════════════════════════════════════════════════════════════════════════
# Chart 4 — Pharmacology Scatter Plot
# ══════════════════════════════════════════════════════════════════════════════
print("\n[4/5] Pharmacology Scatter Plot ...")

df_pharm = pd.read_csv(RESULTS / 'pharmacology_data.csv')
print(f"  Loaded pharmacology_data.csv: {df_pharm.shape[0]} rows")

# Keep only rows with numeric values; pivot to wide format
df_valid = df_pharm[df_pharm['value_nM'].notna() & df_pharm['value_nM'].astype(str).str.strip().ne('')].copy()
df_valid['value_nM'] = pd.to_numeric(df_valid['value_nM'], errors='coerce')
df_valid = df_valid.dropna(subset=['value_nM'])

# Keep literature-sourced rows (de-duplicate: use first best entry per drug × target)
df_lit = df_valid[df_valid['source'].str.contains('Literature', na=False)].copy()
df_lit = df_lit.sort_values('value_nM').drop_duplicates(subset=['drug_name', 'target'], keep='first')

# Pivot to GLP-1R vs GIPR
df_glp1 = df_lit[df_lit['target'] == 'GLP-1R'][['drug_name', 'value_nM', 'pchembl_value']].rename(
    columns={'value_nM': 'glp1r_ec50', 'pchembl_value': 'glp1r_pchembl'})
df_gipr  = df_lit[df_lit['target'] == 'GIPR'][['drug_name', 'value_nM', 'pchembl_value']].rename(
    columns={'value_nM': 'gipr_ec50', 'pchembl_value': 'gipr_pchembl'})

df_wide = pd.merge(df_glp1, df_gipr, on='drug_name', how='left')

# For mono-GLP-1R drugs, assign a placeholder GIPR value far right to show selectivity
# Use log10(EC50) for axes
df_wide['log_glp1r'] = np.log10(df_wide['glp1r_ec50'])
df_wide['log_gipr']  = np.where(
    df_wide['gipr_ec50'].notna(),
    np.log10(df_wide['gipr_ec50']),
    np.nan
)

# Only plot drugs with GLP-1R data
df_plot = df_wide.dropna(subset=['log_glp1r']).copy()
print(f"  Drugs with GLP-1R EC50: {list(df_plot['drug_name'])}")

fig, ax = plt.subplots(figsize=(8, 6))

for _, row in df_plot.iterrows():
    drug = row['drug_name']
    color = PALETTE.get(drug, '#666666')
    has_gipr = pd.notna(row['log_gipr'])

    if has_gipr:
        ax.scatter(row['log_glp1r'], row['log_gipr'],
                   color=color, s=140, zorder=4, edgecolors='white', linewidths=0.8,
                   label=drug)
        ax.annotate(drug, (row['log_glp1r'], row['log_gipr']),
                    textcoords='offset points', xytext=(6, 4),
                    fontsize=8.5, fontweight='bold', color=color)
    else:
        # Show on right axis margin as "GIPR inactive"
        ax.scatter(row['log_glp1r'], ax.get_ylim()[0] if ax.get_ylim()[0] < -2 else -2.5,
                   color=color, s=140, marker='v', zorder=4, edgecolors='white', linewidths=0.8,
                   label=f'{drug} (GIPR inactive)')
        ax.annotate(f'{drug}\n(GIPR n/a)', (row['log_glp1r'], -2.5),
                    textcoords='offset points', xytext=(5, -12),
                    fontsize=7.5, color=color)

# Diagonal line of equal potency (balanced agonism)
xmin = df_plot['log_glp1r'].min() - 0.5
xmax = df_plot['log_glp1r'].max() + 0.5
ax.plot([xmin, xmax], [xmin, xmax], 'k--', linewidth=0.9, alpha=0.4,
        label='Equal potency (1:1)')

ax.set_xlabel('log\u2081\u2080(GLP-1R EC\u2085\u2080, nM)\n← More potent', fontsize=10)
ax.set_ylabel('log\u2081\u2080(GIPR EC\u2085\u2080, nM)\n← More potent', fontsize=10)
ax.set_title('Receptor Selectivity: GLP-1R vs. GIPR Potency\n(Lower = More Potent)', pad=12)
ax.legend(fontsize=8, loc='upper left', framealpha=0.85, ncol=1)
ax.yaxis.grid(True, linestyle='--', alpha=0.35)
ax.xaxis.grid(True, linestyle='--', alpha=0.35)

# Annotate CT-388 balanced region
ct388_row = df_plot[df_plot['drug_name'] == 'CT-388']
if not ct388_row.empty:
    ax.annotate(
        'CT-388:\nBalanced GLP-1R/GIPR',
        xy=(ct388_row['log_glp1r'].values[0], ct388_row['log_gipr'].values[0]),
        xytext=(-55, 25),
        textcoords='offset points',
        arrowprops=dict(arrowstyle='->', color='#d62728', lw=1.2),
        fontsize=8, color='#d62728', fontweight='bold'
    )

fig.tight_layout()
out4 = FIGURES / 'pharmacology_scatter.png'
fig.savefig(out4, dpi=300, bbox_inches='tight')
plt.close(fig)
print(f"  Saved: {out4}")


# ══════════════════════════════════════════════════════════════════════════════
# Chart 5 — Patent Gantt Chart
# ══════════════════════════════════════════════════════════════════════════════
print("\n[5/5] Patent Gantt Chart ...")

# Curated patent expiry data (well-known from public sources)
# Sources: FDA Orange Book, USPTO, company filings, published analyses
patent_data = [
    # drug, type, territory, start_year, expiry_year, notes
    ('Semaglutide',    'Compound patent',          'US',  2012, 2031, 'Core composition-of-matter'),
    ('Semaglutide',    'Compound patent',          'EU',  2012, 2031, 'Core composition-of-matter'),
    ('Semaglutide',    'Formulation/method patent', 'US',  2017, 2034, 'Oral formulation (Rybelsus)'),
    ('Semaglutide',    'Formulation/method patent', 'EU',  2017, 2033, 'Injectable device/formulation'),
    ('Tirzepatide',    'Compound patent',          'US',  2015, 2036, 'Core composition-of-matter'),
    ('Tirzepatide',    'Compound patent',          'EU',  2015, 2036, 'Core composition-of-matter'),
    ('Tirzepatide',    'Method-of-use patent',     'US',  2020, 2040, 'Obesity/CVD indication claims'),
    ('Retatrutide',    'Compound patent',          'US',  2018, 2038, 'Estimated from priority date'),
    ('Retatrutide',    'Compound patent',          'EU',  2018, 2038, 'Estimated from priority date'),
    ('CT-388',         'Compound patent',          'US',  2020, 2040, 'Estimated from priority filing'),
    ('CT-388',         'Compound patent',          'EU',  2020, 2040, 'Estimated from priority filing'),
]

df_patent = pd.DataFrame(patent_data,
    columns=['Drug', 'Patent_Type', 'Territory', 'Start_Year', 'Expiry_Year', 'Notes'])

# Sort by expiry
df_patent_sorted = df_patent.sort_values(['Drug', 'Territory', 'Start_Year']).reset_index(drop=True)

# Build Gantt chart
DRUG_ORDER = ['Semaglutide', 'Tirzepatide', 'Retatrutide', 'CT-388']
TYPE_COLORS = {
    'Compound patent':           '#1f77b4',
    'Formulation/method patent': '#ff7f0e',
    'Method-of-use patent':      '#2ca02c',
}
TERR_HATCH = {'US': '', 'EU': '///'}

fig, ax = plt.subplots(figsize=(12, 6))

y = 0
yticks = []
ytick_labels = []
bar_height = 0.35

for drug in DRUG_ORDER:
    sub = df_patent_sorted[df_patent_sorted['Drug'] == drug]
    for _, row in sub.iterrows():
        color = TYPE_COLORS.get(row['Patent_Type'], '#888')
        hatch = TERR_HATCH.get(row['Territory'], '')
        bar = ax.barh(
            y,
            row['Expiry_Year'] - row['Start_Year'],
            left=row['Start_Year'],
            height=bar_height,
            color=color,
            hatch=hatch,
            edgecolor='white',
            linewidth=0.6,
            alpha=0.85
        )
        # Label: expiry year at end of bar
        ax.text(row['Expiry_Year'] + 0.2, y, str(row['Expiry_Year']),
                va='center', fontsize=7.5, color='#333')
        # Territory label
        ax.text(row['Start_Year'] + 0.4, y, row['Territory'],
                va='center', fontsize=7, color='white', fontweight='bold')
        yticks.append(y)
        ytick_labels.append(f"{drug}\n{row['Patent_Type']}")
        y -= 0.55

    y -= 0.2   # gap between drugs

ax.set_xlabel('Year', fontsize=11)
ax.set_title('Competitive Patent Expiry Timelines\n(Key GLP-1/Incretin Programs)', pad=12)
ax.set_yticks(yticks)
ax.set_yticklabels(ytick_labels, fontsize=8)
ax.set_xlim(2010, 2045)
ax.xaxis.set_major_locator(ticker.MultipleLocator(5))
ax.xaxis.grid(True, linestyle='--', alpha=0.35)

# Today line
ax.axvline(2026, color='red', linewidth=1.2, linestyle='--', alpha=0.7)
ax.text(2026.2, ax.get_ylim()[1] * 0.99 if ax.get_ylim()[1] < 0 else yticks[0] + 0.3,
        'Today\n(2026)', color='red', fontsize=8, va='top')

# Patent cliff annotation
ax.axvspan(2031, 2033, alpha=0.07, color='orange')
ax.text(2031.5, min(yticks) - 0.5, 'Semaglutide\npatent cliff', fontsize=7.5,
        color='darkorange', ha='center', va='top')

# Legend for colors
legend_handles = [
    mpatches.Patch(color=c, label=t) for t, c in TYPE_COLORS.items()
]
legend_handles += [
    mpatches.Patch(facecolor='white', edgecolor='black', hatch='', label='US territory'),
    mpatches.Patch(facecolor='white', edgecolor='black', hatch='///', label='EU territory'),
]
ax.legend(handles=legend_handles, loc='lower right', fontsize=8, framealpha=0.9,
          title='Patent Type / Territory', title_fontsize=8)

ax.text(0.01, -0.12,
        'Note: Estimated expiry years based on priority filing dates and standard 20-year patent term.\n'
        'Actual dates may vary due to patent term extensions (PTE), SPCs, and litigation outcomes.',
        transform=ax.transAxes, fontsize=7, color='grey', ha='left')

fig.tight_layout()
out5 = FIGURES / 'patent_gantt_chart.png'
fig.savefig(out5, dpi=300, bbox_inches='tight')
plt.close(fig)
print(f"  Saved: {out5}")


# ─── Final check ──────────────────────────────────────────────────────────────
print("\n" + "=" * 60)
print("Visualization generation complete.")
generated = [out1, out2, out3, out4, out5]
for p in generated:
    size_kb = p.stat().st_size / 1024 if p.exists() else 0
    status = "OK" if p.exists() else "MISSING"
    print(f"  [{status}] {p.name} ({size_kb:.0f} KB)")
print("=" * 60)
