import csv
import io
import json
import math
import yaml
import zipfile
from datetime import datetime, timezone
from fpdf import FPDF
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side


# ─── CSV EXPORT ───────────────────────────────────────────────
def export_csv(data, columns):
    output = io.StringIO()
    writer = csv.DictWriter(output, fieldnames=columns, extrasaction='ignore')
    writer.writeheader()
    for row in data:
        writer.writerow({k: row.get(k, "") for k in columns})
    return output.getvalue()


def ligands_csv(ligands):
    cols = ["name", "fano_line", "theta_crit", "r_cdr", "partners", "plddt", "rmsd", "kd_pm", "synthesis_cost", "sequence", "notes", "created_at"]
    return export_csv(ligands, cols)


def targets_csv(targets):
    cols = ["name", "cath_class", "fold_type", "pdb_id", "organism", "function", "helix_count", "notes", "created_at"]
    return export_csv(targets, cols)


def experiments_csv(experiments):
    cols = ["title", "ligand_name", "target_name", "experiment_type", "status", "plddt_result", "rmsd_result", "kd_result", "notes", "created_at"]
    return export_csv(experiments, cols)


# ─── JSON EXPORT ──────────────────────────────────────────────
def full_json_export(ligands, targets, experiments, layers):
    return json.dumps({
        "export_meta": {
            "platform": "LIGAND Connections",
            "version": "1.0.0",
            "exported_at": datetime.now(timezone.utc).isoformat(),
            "format": "LIGAND-JSON-v1"
        },
        "layers": layers,
        "ligands": ligands,
        "targets": targets,
        "experiments": experiments,
        "statistics": {
            "total_ligands": len(ligands),
            "total_targets": len(targets),
            "total_experiments": len(experiments),
            "total_layers": len(layers)
        }
    }, indent=2, default=str)


# ─── FASTA EXPORT ─────────────────────────────────────────────
def ligands_fasta(ligands):
    lines = []
    for lig in ligands:
        name = lig.get("name", "Unknown")
        seq = lig.get("sequence", "")
        if seq:
            fano = lig.get("fano_line", "")
            theta = lig.get("theta_crit", "")
            lines.append(f">{name} | Fano={fano} | theta_crit={theta}")
            clean_seq = seq.replace("Ac-", "").replace("-NH2", "").replace("(", "").replace(")", "")
            for i in range(0, len(clean_seq), 80):
                lines.append(clean_seq[i:i+80])
            lines.append("")
    if not lines:
        lines.append("; No ligand sequences available")
    return "\n".join(lines)


# ─── LATEX EXPORT ─────────────────────────────────────────────
def latex_formulas_export(layers):
    doc = r"""\documentclass[11pt,a4paper]{article}
\usepackage{amsmath,amssymb,amsfonts}
\usepackage[margin=2.5cm]{geometry}
\usepackage{booktabs}
\usepackage{hyperref}

\title{LIGAND Connections --- Mathematical Framework\\L0--L7 Formula Reference}
\author{Generated by LIGAND Connections Platform}
\date{""" + datetime.now(timezone.utc).strftime("%B %d, %Y") + r"""}

\begin{document}
\maketitle
\tableofcontents
\newpage

"""
    for layer in layers:
        lid = layer.get("id", "")
        name = layer.get("name", "").replace("—", "---").replace("_", r"\_")
        desc = layer.get("description", "").replace("_", r"\_").replace("%", r"\%").replace("&", r"\&")

        doc += f"\\section{{{lid}: {name}}}\n\n"
        doc += f"{desc}\n\n"

        concepts = layer.get("key_concepts", [])
        if concepts:
            doc += "\\subsection{Key Concepts}\n\\begin{itemize}\n"
            for c in concepts:
                c_clean = c.replace("_", r"\_").replace("&", r"\&").replace("%", r"\%")
                doc += f"  \\item {c_clean}\n"
            doc += "\\end{itemize}\n\n"

        formulas = layer.get("formulas", [])
        if formulas:
            doc += "\\subsection{Formulas}\n\n"
            for f in formulas:
                label = f.get("label", "").replace("_", r"\_")
                tex = f.get("tex", "")
                doc += f"\\paragraph{{{label}}}\n"
                doc += f"\\begin{{equation}}\n{tex}\n\\end{{equation}}\n\n"

        metrics = layer.get("metrics", {})
        if metrics:
            doc += "\\subsection{Metrics}\n\\begin{tabular}{ll}\n\\toprule\n"
            doc += "Parameter & Value \\\\\n\\midrule\n"
            for k, v in metrics.items():
                k_clean = k.replace("_", " ").title()
                doc += f"{k_clean} & {v} \\\\\n"
            doc += "\\bottomrule\n\\end{tabular}\n\n"

        doc += "\\newpage\n\n"

    doc += r"\end{document}" + "\n"
    return doc


# ─── YAML CONFIGURATION EXPORT ────────────────────────────────
def generate_yaml_config(ligands, targets, layers):
    phi = (1 + math.sqrt(5)) / 2

    config = {
        "ligand_connections": {
            "version": "1.0.0",
            "generated": datetime.now(timezone.utc).isoformat(),
            "framework": "Fano-Driven Protein Recognition"
        },
        "fano_plane": {
            "notation": "PG(2, F_2)",
            "points": 7,
            "lines": 7,
            "flags": 42,
            "incidence": [
                [1, 1, 1, 0, 0, 0, 0],
                [1, 0, 0, 1, 1, 0, 0],
                [0, 1, 0, 1, 0, 1, 0],
                [0, 0, 1, 1, 0, 0, 1],
                [0, 1, 0, 0, 1, 0, 1],
                [0, 0, 1, 0, 1, 1, 0],
                [1, 0, 0, 0, 0, 1, 1],
            ]
        },
        "spectral_parameters": {
            "golden_ratio_phi": round(phi, 10),
            "phi_squared": round(phi**2, 10),
            "phi_fifth": round(phi**5, 6),
            "harmonic_spectrum": [
                {"n": 1, "fold": "4-Helix Bundle", "sigma": 0.5150, "phi_pi_n": round(phi / math.pi, 6)},
                {"n": 2, "fold": "TIM Barrel", "sigma": 0.1640, "phi_pi_n": round(phi / math.pi**2, 6)},
                {"n": 3, "fold": "Rossmann", "sigma": 0.0522, "phi_pi_n": round(phi / math.pi**3, 6)},
                {"n": 4, "fold": "Greek Key", "sigma": 0.0166, "phi_pi_n": round(phi / math.pi**4, 6)},
                {"n": 5, "fold": "OB-Fold", "sigma": 0.0053, "phi_pi_n": round(phi / math.pi**5, 6)},
            ]
        },
        "reference_ligands": {
            "L1": {
                "name": "Greek Key Universal Binder",
                "fano_line": "17->7",
                "theta_crit_deg": 77.14,
                "r_cdr_angstrom": 28.0,
                "sequence": "Ac-D(EKRE)8 GA14 K (GA)14 K (DEKR)8 -NH2",
                "mw_da": 4892,
                "partners": 127,
                "plddt_pct": 97.2,
                "rmsd_angstrom": 0.84,
                "kd_pm": 5.3,
                "sigma_sigma": 0.00079,
                "cath_topology": "3.30.70.330",
                "chirality": "D-amino acids only",
                "geometry": {
                    "r_shape": 17,
                    "r_gauge": 7,
                    "volume_angstrom3": 3332,
                    "arm_extension_angstrom": 28.0,
                }
            },
            "L6": {
                "name": "E20 Quaternary Binder",
                "fano_line": "28->17",
                "theta_crit_deg": 58.91,
                "r_cdr_angstrom": 34.0,
                "sequence": "Ac-D(DEKR)17 GA28 K (GA)28 K (DEKR)17 -NH2 (x2 dimer)",
                "mw_da": 18494,
                "partners": 254,
                "plddt_pct": 98.5,
                "rmsd_angstrom": 0.42,
                "kd_pm": 2.1,
                "sigma_sigma": 0.00040,
                "cath_topology": "3.30.450.20",
                "chirality": "D-amino acids, symmetric dimer",
                "geometry": {
                    "r_shape": 28,
                    "r_gauge": 17,
                    "volume_angstrom3": 6664,
                    "dual_arm_angstrom": 34.0,
                    "beta_strands": 56,
                    "symmetry": ["mirror", "C3", "D3", "S6"],
                }
            },
            "L7": {
                "name": "Pentadic 5-Helix",
                "pentadic_norm": "||x||_5 = sum_{a=0}^{4} |<x, e_a>| phi^(5a)",
                "phi_fifth_stability": 11.090,
                "channels": 5,
                "total_keys": "8.95M",
                "enrichment": "5x",
                "yield_pct": 97.1,
                "compression_ratio": "2000:1",
                "hit_rate": 1247,
                "pentadic_channels": [
                    {"alpha": 0, "mode": "association", "action": "N-H...H-N helix docking", "r17_alpha": round(math.sqrt(18) * math.cos(0), 4)},
                    {"alpha": 1, "mode": "rearrangement", "action": "helix sliding", "r17_alpha": round(math.sqrt(18) * math.cos(2*math.pi/5), 4)},
                    {"alpha": 2, "mode": "dissociation", "action": "hinge bending", "r17_alpha": round(math.sqrt(18) * math.cos(4*math.pi/5), 4)},
                    {"alpha": 3, "mode": "non-crystalline", "action": "shear modes", "r17_alpha": round(math.sqrt(18) * math.cos(6*math.pi/5), 4)},
                    {"alpha": 4, "mode": "phi-catalysis", "action": "pentameric active site", "r17_alpha": round(math.sqrt(18) * math.cos(8*math.pi/5), 4)},
                ],
                "spps_scaling": {
                    "r17_formula": "sqrt(18) * cos(2*pi*alpha/5)",
                    "equivs_formula": "3.6 * phi^(alpha-2)",
                    "rt_formula": "0.1 * theta_crit + 2.0 + 0.5 * alpha",
                },
                "cartan_decomposition": {
                    "parent": "E19",
                    "block_count": 5,
                    "block_type": "D5",
                    "symmetry": "D5 pentadic (5-fold dihedral)",
                },
                "targets": ["IL2RG", "EGFR", "FGFR"],
            }
        },
        "database_ligands": [],
        "database_targets": [],
    }

    for lig in ligands:
        config["database_ligands"].append({
            "name": lig.get("name", ""),
            "fano_line": lig.get("fano_line", ""),
            "theta_crit": lig.get("theta_crit"),
            "r_cdr": lig.get("r_cdr"),
            "partners": lig.get("partners"),
            "plddt": lig.get("plddt"),
            "rmsd": lig.get("rmsd"),
            "kd_pm": lig.get("kd_pm"),
            "sequence": lig.get("sequence", ""),
        })

    for tgt in targets:
        config["database_targets"].append({
            "name": tgt.get("name", ""),
            "cath_class": tgt.get("cath_class", ""),
            "fold_type": tgt.get("fold_type", ""),
            "pdb_id": tgt.get("pdb_id", ""),
            "organism": tgt.get("organism", ""),
        })

    layer_configs = []
    for layer in layers:
        layer_configs.append({
            "id": layer.get("id", ""),
            "name": layer.get("name", ""),
            "metrics": layer.get("metrics", {}),
        })
    config["layers"] = layer_configs

    return yaml.dump(config, default_flow_style=False, sort_keys=False, allow_unicode=True, width=120)


# ─── SPPS PROTOCOL EXPORT ─────────────────────────────────────
def generate_spps_protocols(ligands):
    phi = (1 + math.sqrt(5)) / 2
    now = datetime.now(timezone.utc).isoformat()

    doc = f"""================================================================================
  LIGAND CONNECTIONS — SOLID PHASE PEPTIDE SYNTHESIS (SPPS) PROTOCOLS
  Generated: {now}
  Framework: Fano-Driven Protein Recognition
================================================================================


================================================================================
  L1 LIGAND SYNTHESIS: GREEK KEY UNIVERSAL BINDER
  Fano Line: 17->7 | theta_crit = 77.14 deg
================================================================================

TARGET STRUCTURE
  Sequence:    Ac-D(EKRE)8 GA14 K1 (GA)14 K2 (DEKR)8 -NH2 (41-mer)
  MW:          4,892 Da
  Geometry:    theta_arm = 77.14 deg, r_CDR = 28 A, V = 3332 A^3
  Chirality:   D-amino acids only (w2 = 0)
  Targets:     847 Greek Key receptors (92% PDB coverage)
  Yield:       150 mg target (85% overall)

STAGE 1: LINEAR PEPTIDE (Fmoc SPPS)
  Resin:       Fmoc-D-Lys(Boc)-Wang (0.1 mmol scale)
  Cycle:       D-Asp(OtBu), D-Glu(OtBu), D-Lys(Boc), D-Arg(Pbf)
  Protocol:
    1. DMF swell (3 x 5 mL, 30 min)
    2. 20% Piperidine/DMF (2 x 10 min) — Fmoc deprotection
    3. DMF wash (6 x 5 mL)
    4. Fmoc-AA-OH (4 eq, 0.8 mmol): Gly/Ala/Ala/Leu cycle
    5. HBTU/HOBt/DIEA (4/4/8 eq, DMF/DCM 1:1)
    6. Couple 90 min, monitor Kaiser test
    7. Cap: Ac2O/DIEA/DMF (10 min)
    8. DCM wash (5 x 5 mL)
  Cleavage:    TFA/TIS/H2O (95/2.5/2.5, 3h)
  Yield:       78 mg (92%), >98% purity (RP-HPLC)

STAGE 2: ARM EXTENSION CONSTRAINTS
  1. Ac2O cap N-terminus (DIEA, DCM, 30 min)
  2. TFA cleavage (95/2.5/2.5, 3h)
  3. Head-to-tail disulfide: I2 oxidation (pH 7.4)
  4. RP-HPLC: C18, 15-45% MeCN/0.1% TFA
  Final:       62 mg (89% from linear, 75% overall)

STAGE 3: FOLDING & L1 GEOMETRY LOCK
  Buffer:      5 mM NaPi pH 7.2 + 2 mM TCEP, 4 C -> 25 C
  CD:          222 nm peak (77 deg left-handed D-alpha-helix)
               208 nm shoulder (beta-turn at GA14 pivot)
               Tm = 92 C (cooperative unfolding)
  NMR:         HN-HN NOES: 28 A end-to-end distance
               X1 dihedrals: theta = 77.14 +/- 0.2 deg

ARM EXTENSION MECHANISM (Binding Cascade)
  t = 0 ns:    Curled (theta = 0 deg) ligand docks beta-core
  t = 25 ns:   Partial (theta = 45 deg) CDR arms initiate
  t = 75 ns:   L1 critical (theta = 77.14 deg) — Spin unwinding
  t = 100 ns:  Locked — KD = 5 pM, 127 partners

GUARANTEED METRICS
  pLDDT:       >= 97.2% (top 0.1% confidence)
  Ca-RMSD:     <= 0.84 A (atomic precision)
  theta_arm:   77.14 +/- 0.1 deg
  Rg:          17.0 A (E19 gauge)
  Sigma_sigma: 0.00079 (spectral minimum)


================================================================================
  L6 LIGAND SYNTHESIS: E20 QUATERNARY BINDER (DUAL COVER)
  Fano Line: 28->17 | theta_crit = 58.91 deg
================================================================================

TARGET STRUCTURE
  Sequence:    Ac-D(DEKR)17 GA28 K (GA)28 K (DEKR)17 -NH2 (x2 symmetric dimer)
  MW:          18,494 Da (82-mer symmetric dimer)
  Geometry:    theta_dual = 58.91 +/- 0.1 deg (paired arms)
               r_CDR1 = r_CDR2 = 34.0 A
               V_fold = 6664 A^3 (2 x E19 double-cover)
               Beta-core: 56 strands
  Symmetry:    Mirror, C3, D3, S6
  Targets:     254 quaternary partners

STAGE 1: CENTRAL BETA-CORE (GA28 K GA28)
  Resin:       Fmoc-Lys(Boc)-Wang
  Protocol:    (GAAL)28 -> Fmoc-Lys(Boc) -> (GAAL)28
  Yield:       245 mg (93%)

STAGE 2: DUAL D-CHIRAL ARMS
  Arms:        D-Glu(OtBu)-D-Lys(Dde)-D-Glu(OtBu)-D-Arg(Pbf) x 4.25 x 2
  Ligation:    Selective NCL at each Lys(N-epsilon)
  Chirality:   Chiral capillary electrophoresis verified (>99% ee)
  Yield:       312 mg (87%)

STAGE 3: E20 ASSEMBLY
  1. Ac-cap N-terminus
  2. TFA cleavage (90/2.5/5/2.5, 4h)
  3. Dual disulfide formation (I2/DMSO)
  4. Prep-HPLC (C18, 20-50% MeCN/0.1% TFA)
  Final:       218 mg (78% overall, $31/mg)

GUARANTEED METRICS
  pLDDT:       >= 98.5%
  Ca-RMSD:     <= 0.42 A
  theta_dual:  58.91 +/- 0.1 deg
  KD:          2.1 pM
  CATH:        3.30.450.20
  Sigma_sigma: < 0.00040 (zero obstruction)


================================================================================
  L7 PENTADIC 5-HELIX SPPS RECIPES
  Pentadic Norm: ||x||_5 | phi^5 = 11.090
================================================================================

PENTADIC NORM
  ||x||_5 = sum_{{a=0}}^{{4}} |<x, e_a>| * phi^(5*alpha)
  where e_a = (cos(2*pi*alpha/5), sin(2*pi*alpha/5))
  phi^5 = {round(phi**5, 6)}

PENTADIC SPPS SCALING FORMULAS
  r17^alpha   = sqrt(18) * cos(2*pi*alpha/5)
  equivs_a    = 3.6 * phi^(alpha - 2)
  RT_a        = 0.1 * theta_crit + 2.0 + 0.5 * alpha

5-CHANNEL RECIPES (D5 Pentadic Symmetry)
"""

    for a in range(5):
        r17 = math.sqrt(18) * math.cos(2 * math.pi * a / 5)
        equivs = 3.6 * phi**(a - 2)
        rt = 0.1 * 72 + 2.0 + 0.5 * a
        modes = [
            ("association", "N-H...H-N helix docking"),
            ("rearrangement", "helix sliding"),
            ("dissociation", "hinge bending"),
            ("non-crystalline", "shear modes"),
            ("phi-catalysis", "pentameric active site"),
        ]
        mode_name, action = modes[a]
        doc += f"""
  CHANNEL alpha = {a}: {mode_name.upper()}
    Action:    {action}
    r17^{a}:      {r17:.4f} A
    equivs_{a}:   {equivs:.4f} eq Fmoc
    RT_{a}:       {rt:.1f} min HPLC
"""

    doc += f"""
CARTAN DECOMPOSITION
  Parent:      E19 (28 x 28 Cartan matrix)
  Blocks:      5 identical D5 submatrices (5 x 5)
  Symmetry:    D5 pentadic (5-fold dihedral)
  Partition:   Pure Dynkin node partitioning

PRODUCTION METRICS
  L7 Atlas:    42 flags x 20,456 proteome x 42 partners x 5 pentads
  Bindings:    180,429,760 total
  L7 Keys:     1.79M x 5 = 8.95M ligands
  Hit rate:    1,247 picomolar hits (5x enrichment over L6)
  Yield:       97.1% C5 renaturation
  Compression: 120 GB PDB -> 60 MB pentadic keys (2000:1)
  Targets:     IL2RG, EGFR, FGFR (5-helix quaternary)

"""

    # Add custom ligand protocols
    for lig in ligands:
        name = lig.get("name", "")
        theta = lig.get("theta_crit")
        seq = lig.get("sequence", "")
        if name and theta:
            r_cdr = round(17 * math.tan(math.radians(theta)), 2)
            doc += f"""================================================================================
  CUSTOM LIGAND: {name.upper()}
  theta_crit = {theta} deg | r_CDR = {r_cdr} A
================================================================================

  Sequence:    {seq if seq else 'Not specified'}
  Geometry:    theta_crit = {theta} deg, r_CDR = {r_cdr} A
  SPPS:        Fmoc/tBu strategy, D-amino acid chirality enforcement
  Resin:       Fmoc-D-Lys(Boc)-Wang (0.1 mmol scale)
  Cleavage:    TFA/TIS/H2O (95/2.5/2.5, 3h)
  Purification: RP-HPLC (C18, gradient MeCN/0.1% TFA)

"""

    doc += """================================================================================
  END OF SPPS PROTOCOLS
================================================================================
"""
    return doc


# ─── EXCEL EXPORT ─────────────────────────────────────────────
def generate_excel(ligands, targets, experiments, layers):
    wb = Workbook()

    header_font = Font(name='Consolas', bold=True, color='FFFFFF', size=10)
    header_fill = PatternFill(start_color='1A1A2E', end_color='1A1A2E', fill_type='solid')
    data_font = Font(name='Consolas', size=9, color='333333')
    border = Border(
        left=Side(style='thin', color='CCCCCC'),
        right=Side(style='thin', color='CCCCCC'),
        top=Side(style='thin', color='CCCCCC'),
        bottom=Side(style='thin', color='CCCCCC')
    )

    def style_header(ws, row=1):
        for cell in ws[row]:
            cell.font = header_font
            cell.fill = header_fill
            cell.alignment = Alignment(horizontal='center')
            cell.border = border

    def style_data(ws, start_row=2):
        for row in ws.iter_rows(min_row=start_row):
            for cell in row:
                cell.font = data_font
                cell.border = border
                cell.alignment = Alignment(horizontal='left')

    ws_lig = wb.active
    ws_lig.title = "Ligands"
    lig_headers = ["Name", "Fano Line", "Theta Crit (deg)", "r_CDR (A)", "Partners", "pLDDT (%)", "RMSD (A)", "KD (pM)", "Cost ($/mg)", "Sequence", "Notes"]
    ws_lig.append(lig_headers)
    for l in ligands:
        ws_lig.append([l.get("name", ""), l.get("fano_line", ""), l.get("theta_crit"), l.get("r_cdr"),
                        l.get("partners"), l.get("plddt"), l.get("rmsd"), l.get("kd_pm"),
                        l.get("synthesis_cost"), l.get("sequence", ""), l.get("notes", "")])
    style_header(ws_lig)
    style_data(ws_lig)
    for col in ws_lig.columns:
        ws_lig.column_dimensions[col[0].column_letter].width = 16

    ws_tgt = wb.create_sheet("Targets")
    tgt_headers = ["Name", "CATH Class", "Fold Type", "PDB ID", "Organism", "Function", "Helix Count", "Notes"]
    ws_tgt.append(tgt_headers)
    for t in targets:
        ws_tgt.append([t.get("name", ""), t.get("cath_class", ""), t.get("fold_type", ""), t.get("pdb_id", ""),
                        t.get("organism", ""), t.get("function", ""), t.get("helix_count"), t.get("notes", "")])
    style_header(ws_tgt)
    style_data(ws_tgt)
    for col in ws_tgt.columns:
        ws_tgt.column_dimensions[col[0].column_letter].width = 16

    ws_exp = wb.create_sheet("Experiments")
    exp_headers = ["Title", "Ligand", "Target", "Type", "Status", "pLDDT (%)", "RMSD (A)", "KD (pM)", "Notes"]
    ws_exp.append(exp_headers)
    for e in experiments:
        ws_exp.append([e.get("title", ""), e.get("ligand_name", ""), e.get("target_name", ""),
                        e.get("experiment_type", ""), e.get("status", ""), e.get("plddt_result"),
                        e.get("rmsd_result"), e.get("kd_result"), e.get("notes", "")])
    style_header(ws_exp)
    style_data(ws_exp)
    for col in ws_exp.columns:
        ws_exp.column_dimensions[col[0].column_letter].width = 16

    ws_layers = wb.create_sheet("L0-L7 Layers")
    layer_headers = ["ID", "Name", "Description", "Key Concepts", "Metrics"]
    ws_layers.append(layer_headers)
    for layer in layers:
        concepts = "; ".join(layer.get("key_concepts", []))
        metrics = "; ".join([f"{k}={v}" for k, v in layer.get("metrics", {}).items()])
        ws_layers.append([layer.get("id", ""), layer.get("name", ""), layer.get("description", ""), concepts, metrics])
    style_header(ws_layers)
    style_data(ws_layers)
    ws_layers.column_dimensions['A'].width = 8
    ws_layers.column_dimensions['B'].width = 40
    ws_layers.column_dimensions['C'].width = 60
    ws_layers.column_dimensions['D'].width = 60
    ws_layers.column_dimensions['E'].width = 40

    buf = io.BytesIO()
    wb.save(buf)
    buf.seek(0)
    return buf.getvalue()


# ─── PDF REPORT ───────────────────────────────────────────────
def _sanitize_pdf(text):
    replacements = {
        '\u2082': '2', '\u2083': '3', '\u2084': '4', '\u2085': '5', '\u2086': '6', '\u2087': '7', '\u2088': '8', '\u2089': '9', '\u2080': '0', '\u2081': '1',
        '\u00b2': '2', '\u00b3': '3', '\u2074': '4', '\u2075': '5',
        '\u2192': '->', '\u2190': '<-', '\u2194': '<->', '\u2265': '>=', '\u2264': '<=', '\u2248': '~',
        '\u03b1': 'alpha', '\u03b2': 'beta', '\u03b3': 'gamma', '\u03b4': 'delta', '\u03b8': 'theta', '\u03c6': 'phi', '\u03c0': 'pi', '\u03c3': 'sigma', '\u03c8': 'psi',
        '\u00b0': ' deg', '\u00c5': 'A', '\u00d7': 'x', '\u00b1': '+/-', '\u221e': 'inf',
        '\u2014': '--', '\u2013': '-', '\u2019': "'", '\u201c': '"', '\u201d': '"',
        '\u03a3': 'Sigma', '\u0394': 'Delta',
    }
    for k, v in replacements.items():
        text = text.replace(k, v)
    return text.encode('latin-1', errors='replace').decode('latin-1')


class LigandPDF(FPDF):
    def header(self):
        self.set_font('Helvetica', 'B', 10)
        self.set_text_color(60, 130, 246)
        self.cell(0, 8, 'LIGAND CONNECTIONS', new_x="LMARGIN", new_y="NEXT", align='L')
        self.set_font('Helvetica', '', 7)
        self.set_text_color(120, 120, 120)
        self.cell(0, 4, 'Fano-Driven Protein Recognition Platform', new_x="LMARGIN", new_y="NEXT", align='L')
        self.line(10, self.get_y() + 2, 200, self.get_y() + 2)
        self.ln(6)

    def footer(self):
        self.set_y(-15)
        self.set_font('Helvetica', '', 7)
        self.set_text_color(120, 120, 120)
        self.cell(0, 10, f'Page {self.page_no()}/{{nb}} | Generated {datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")}', align='C')

    def section_title(self, title):
        self.set_font('Helvetica', 'B', 12)
        self.set_text_color(240, 240, 240)
        self.set_fill_color(26, 26, 46)
        self.cell(0, 8, f'  {_sanitize_pdf(title)}', new_x="LMARGIN", new_y="NEXT", fill=True)
        self.ln(3)

    def subsection(self, title):
        self.set_font('Helvetica', 'B', 9)
        self.set_text_color(6, 182, 212)
        self.cell(0, 6, _sanitize_pdf(title), new_x="LMARGIN", new_y="NEXT")
        self.ln(1)

    def body_text(self, text):
        self.set_font('Helvetica', '', 8)
        self.set_text_color(200, 200, 200)
        self.multi_cell(0, 4, _sanitize_pdf(text))
        self.ln(2)

    def mono_text(self, text):
        self.set_font('Courier', '', 7)
        self.set_text_color(16, 185, 129)
        self.multi_cell(0, 3.5, _sanitize_pdf(text))
        self.ln(2)

    def data_row(self, label, value, color=(200, 200, 200)):
        self.set_font('Helvetica', '', 8)
        self.set_text_color(120, 120, 120)
        self.cell(50, 5, _sanitize_pdf(label))
        self.set_font('Courier', 'B', 8)
        self.set_text_color(*color)
        self.cell(0, 5, _sanitize_pdf(str(value)), new_x="LMARGIN", new_y="NEXT")


def generate_pdf_report(ligands, targets, experiments, layers):
    pdf = LigandPDF()
    pdf.set_auto_page_break(auto=True, margin=20)
    pdf.alias_nb_pages()
    pdf.add_page()
    pdf.set_fill_color(5, 5, 5)

    pdf.set_font('Helvetica', 'B', 22)
    pdf.set_text_color(240, 240, 240)
    pdf.ln(20)
    pdf.cell(0, 12, 'LIGAND CONNECTIONS', new_x="LMARGIN", new_y="NEXT", align='C')
    pdf.set_font('Helvetica', '', 11)
    pdf.set_text_color(100, 100, 100)
    pdf.cell(0, 8, 'Research Platform Report', new_x="LMARGIN", new_y="NEXT", align='C')
    pdf.ln(5)
    pdf.set_font('Helvetica', '', 8)
    pdf.cell(0, 5, f'Generated: {datetime.now(timezone.utc).strftime("%B %d, %Y at %H:%M UTC")}', new_x="LMARGIN", new_y="NEXT", align='C')
    pdf.ln(10)

    pdf.section_title('PLATFORM SUMMARY')
    pdf.data_row('Total Layers', '8 (L0-L7)', (59, 130, 246))
    pdf.data_row('Fano Flags', '42', (6, 182, 212))
    pdf.data_row('Registered Ligands', str(len(ligands)), (217, 70, 239))
    pdf.data_row('Protein Targets', str(len(targets)), (132, 204, 22))
    pdf.data_row('Experiments', str(len(experiments)), (249, 115, 22))
    pdf.ln(5)

    pdf.section_title('L0-L7 LAYER REFERENCE')
    for layer in layers:
        pdf.subsection(f'{layer["id"]} - {layer.get("short", "")}')
        pdf.body_text(layer.get("description", ""))
        metrics = layer.get("metrics", {})
        if metrics:
            for k, v in metrics.items():
                pdf.data_row(f'  {k.replace("_", " ").title()}', str(v), (6, 182, 212))
        pdf.ln(2)

    if ligands:
        pdf.add_page()
        pdf.section_title('LIGAND DATABASE')
        for lig in ligands:
            pdf.subsection(lig.get("name", "Unknown"))
            if lig.get("fano_line"):
                pdf.data_row('Fano Line', lig["fano_line"], (59, 130, 246))
            if lig.get("theta_crit"):
                pdf.data_row('Theta Crit', f'{lig["theta_crit"]} deg', (6, 182, 212))
            if lig.get("r_cdr"):
                pdf.data_row('r_CDR', f'{lig["r_cdr"]} A')
            if lig.get("partners"):
                pdf.data_row('Partners', str(lig["partners"]))
            if lig.get("plddt"):
                pdf.data_row('pLDDT', f'{lig["plddt"]}%', (16, 185, 129))
            if lig.get("rmsd"):
                pdf.data_row('RMSD', f'{lig["rmsd"]} A')
            if lig.get("kd_pm"):
                pdf.data_row('KD', f'{lig["kd_pm"]} pM', (217, 70, 239))
            if lig.get("sequence"):
                pdf.data_row('Sequence', lig["sequence"][:60])
            pdf.ln(3)

    if targets:
        pdf.add_page()
        pdf.section_title('TARGET DATABASE')
        for tgt in targets:
            pdf.subsection(tgt.get("name", "Unknown"))
            if tgt.get("cath_class"):
                pdf.data_row('CATH', tgt["cath_class"], (132, 204, 22))
            if tgt.get("fold_type"):
                pdf.data_row('Fold Type', tgt["fold_type"])
            if tgt.get("pdb_id"):
                pdf.data_row('PDB ID', tgt["pdb_id"], (6, 182, 212))
            if tgt.get("organism"):
                pdf.data_row('Organism', tgt["organism"])
            if tgt.get("function"):
                pdf.data_row('Function', tgt["function"])
            pdf.ln(3)

    if experiments:
        pdf.add_page()
        pdf.section_title('EXPERIMENT LOG')
        for exp in experiments:
            status = exp.get("status", "planned")
            color = (16, 185, 129) if status == "completed" else (245, 158, 11) if status == "running" else (59, 130, 246)
            pdf.subsection(exp.get("title", "Untitled"))
            pdf.data_row('Ligand', exp.get("ligand_name", "-"))
            pdf.data_row('Target', exp.get("target_name", "-"))
            pdf.data_row('Status', status, color)
            if exp.get("plddt_result"):
                pdf.data_row('pLDDT Result', f'{exp["plddt_result"]}%', (16, 185, 129))
            if exp.get("kd_result"):
                pdf.data_row('KD Result', f'{exp["kd_result"]} pM', (217, 70, 239))
            pdf.ln(3)

    buf = io.BytesIO()
    pdf.output(buf)
    buf.seek(0)
    return buf.getvalue()
