"""
Charts Router - Chart.js-compatible Data Endpoints

Provides structured data for frontend charts/diagrams.

All endpoints return Chart.js-compatible JSON format:
{
    "chart_type": "line" | "bar" | "scatter" | "pie",
    "data": {
        "labels": [...],
        "datasets": [...]
    },
    "metadata": {
        "confidence": "high" | "medium" | "low" | "insufficient",
        "data_points": int,
        ...
    }
}

Phase 0c: Multi-Layer Architecture
Version: 1.0
"""

from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Dict, List, Optional
from datetime import datetime, timedelta

from auth import require_auth
from data_layer.body_metrics import (
    get_weight_trend_data,
    get_body_composition_data,
    get_circumference_summary_data
)
from data_layer.nutrition_metrics import (
    get_nutrition_average_data,
    get_protein_targets_data,
    get_protein_adequacy_data,
    get_macro_consistency_data,
    get_energy_balance_data,
)
from data_layer.activity_metrics import (
    get_activity_summary_data,
    get_training_type_distribution_data,
    calculate_training_minutes_week,
    calculate_quality_sessions_pct,
    calculate_proxy_internal_load_7d,
    calculate_monotony_score,
    calculate_strain_score,
    calculate_ability_balance
)
from data_layer.recovery_metrics import (
    get_sleep_duration_data,
    get_sleep_quality_data,
    calculate_recovery_score_v2,
    calculate_hrv_vs_baseline_pct,
    calculate_rhr_vs_baseline_pct,
    calculate_sleep_debt_hours
)
from data_layer.correlations import (
    calculate_lag_correlation,
    calculate_correlation_sleep_recovery,
    calculate_top_drivers
)
from data_layer.utils import serialize_dates, safe_float, calculate_confidence

router = APIRouter(prefix="/api/charts", tags=["charts"])


# ── Body Charts ─────────────────────────────────────────────────────────────


@router.get("/weight-trend")
def get_weight_trend_chart(
    days: int = Query(default=90, ge=7, le=365, description="Analysis window in days"),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Weight trend chart data.

    Returns Chart.js-compatible line chart with:
    - Raw weight values
    - Trend line (if enough data)
    - Confidence indicator

    Args:
        days: Analysis window (7-365 days, default 90)
        session: Auth session (injected)

    Returns:
        {
            "chart_type": "line",
            "data": {
                "labels": ["2026-01-01", ...],
                "datasets": [{
                    "label": "Gewicht",
                    "data": [85.0, 84.5, ...],
                    "borderColor": "#1D9E75",
                    "backgroundColor": "rgba(29, 158, 117, 0.1)",
                    "tension": 0.4
                }]
            },
            "metadata": {
                "confidence": "high",
                "data_points": 60,
                "first_value": 92.0,
                "last_value": 85.0,
                "delta": -7.0,
                "direction": "decreasing"
            }
        }

    Metadata fields:
        - confidence: Data quality ("high", "medium", "low", "insufficient")
        - data_points: Number of weight entries in period
        - first_value: First weight in period (kg)
        - last_value: Latest weight (kg)
        - delta: Weight change (kg, negative = loss)
        - direction: "increasing" | "decreasing" | "stable"
    """
    profile_id = session['profile_id']

    # Get structured data from data layer (includes series — no second weight_log query)
    trend_data = get_weight_trend_data(profile_id, days)

    # Early return if insufficient data
    if trend_data['confidence'] == 'insufficient':
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Nicht genug Daten für Trend-Analyse"
            }
        }

    series = trend_data.get("series") or []
    labels = [
        pt["date"].isoformat() if hasattr(pt["date"], "isoformat") else str(pt["date"])
        for pt in series
    ]
    values = [pt["weight"] for pt in series]

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Gewicht",
                    "data": values,
                    "borderColor": "#1D9E75",
                    "backgroundColor": "rgba(29, 158, 117, 0.1)",
                    "borderWidth": 2,
                    "tension": 0.4,
                    "fill": True,
                    "pointRadius": 3,
                    "pointHoverRadius": 5
                }
            ]
        },
        "metadata": serialize_dates({
            "confidence": trend_data['confidence'],
            "data_points": trend_data['data_points'],
            "first_value": trend_data['first_value'],
            "last_value": trend_data['last_value'],
            "delta": trend_data['delta'],
            "direction": trend_data['direction'],
            "first_date": trend_data['first_date'],
            "last_date": trend_data['last_date'],
            "days_analyzed": trend_data['days_analyzed']
        })
    }


@router.get("/body-composition")
def get_body_composition_chart(
    days: int = Query(default=90, ge=7, le=365),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Body composition chart (body fat percentage, lean mass trend).

    Returns Chart.js-compatible multi-line chart.

    Args:
        days: Analysis window (7-365 days, default 90)
        session: Auth session (injected)

    Returns:
        Chart.js format with datasets for:
        - Body fat percentage (%)
        - Lean mass (kg, if weight available)
    """
    profile_id = session['profile_id']

    # Get latest body composition
    comp_data = get_body_composition_data(profile_id, days)

    if comp_data['confidence'] == 'insufficient':
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Körperfett-Messungen vorhanden"
            }
        }

    # For PoC: Return single data point
    # TODO in bulk migration: Fetch historical caliper entries
    return {
        "chart_type": "line",
        "data": {
            "labels": [comp_data['date'].isoformat() if comp_data['date'] else ""],
            "datasets": [
                {
                    "label": "Körperfett %",
                    "data": [comp_data['body_fat_pct']],
                    "borderColor": "#D85A30",
                    "backgroundColor": "rgba(216, 90, 48, 0.1)",
                    "borderWidth": 2
                }
            ]
        },
        "metadata": serialize_dates({
            "confidence": comp_data['confidence'],
            "data_points": comp_data['data_points'],
            "body_fat_pct": comp_data['body_fat_pct'],
            "method": comp_data['method'],
            "date": comp_data['date']
        })
    }


@router.get("/circumferences")
def get_circumferences_chart(
    max_age_days: int = Query(default=90, ge=7, le=365),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Latest circumference measurements as bar chart.

    Shows most recent measurement for each body point.

    Args:
        max_age_days: Maximum age of measurements (default 90)
        session: Auth session (injected)

    Returns:
        Chart.js bar chart with all circumference points
    """
    profile_id = session['profile_id']

    circ_data = get_circumference_summary_data(profile_id, max_age_days)

    if circ_data['confidence'] == 'insufficient':
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Umfangsmessungen vorhanden"
            }
        }

    # Sort by value (descending) for better visualization
    measurements = sorted(
        circ_data['measurements'],
        key=lambda m: m['value'],
        reverse=True
    )

    labels = [m['point'] for m in measurements]
    values = [m['value'] for m in measurements]

    return {
        "chart_type": "bar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Umfang (cm)",
                    "data": values,
                    "backgroundColor": "#1D9E75",
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": serialize_dates({
            "confidence": circ_data['confidence'],
            "data_points": circ_data['data_points'],
            "newest_date": circ_data['newest_date'],
            "oldest_date": circ_data['oldest_date'],
            "measurements": circ_data['measurements']  # Full details
        })
    }


# ── Nutrition Charts ────────────────────────────────────────────────────────


@router.get("/energy-balance")
def get_energy_balance_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Energy balance timeline (E1) - Konzept-konform.

    Shows:
    - Daily calorie intake
    - 7d rolling average
    - 14d rolling average
    - TDEE reference line
    - Energy deficit/surplus
    - Lagged comparison to weight trend

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js line chart with multiple datasets
    """
    profile_id = session['profile_id']

    balance_meta = get_energy_balance_data(profile_id, days)

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT date, SUM(kcal)::float AS kcal
               FROM nutrition_log
               WHERE profile_id=%s AND date >= %s AND kcal IS NOT NULL
               GROUP BY date
               ORDER BY date""",
            (profile_id, cutoff),
        )
        rows = cur.fetchall()

    if not rows or len(rows) < 3:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": len(rows) if rows else 0,
                "message": "Nicht genug Ernährungsdaten (min. 3 Tage)"
            }
        }

    estimated_tdee = balance_meta.get("estimated_tdee") or 0
    if estimated_tdee <= 0:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": len(rows),
                "message": "Kein Gewicht für TDEE-Schätzung (weight_log erforderlich)"
            }
        }

    labels = []
    daily_values = []
    avg_7d = []
    avg_14d = []

    for i, row in enumerate(rows):
        labels.append(row['date'].isoformat())
        daily_values.append(safe_float(row['kcal']))

        start_7d = max(0, i - 6)
        window_7d = [safe_float(rows[j]['kcal']) for j in range(start_7d, i + 1)]
        avg_7d.append(round(sum(window_7d) / len(window_7d), 1) if window_7d else None)

        start_14d = max(0, i - 13)
        window_14d = [safe_float(rows[j]['kcal']) for j in range(start_14d, i + 1)]
        avg_14d.append(round(sum(window_14d) / len(window_14d), 1) if window_14d else None)

    avg_intake = float(balance_meta.get("avg_intake") or (sum(daily_values) / len(daily_values) if daily_values else 0))
    energy_balance = float(balance_meta.get("energy_balance") or (avg_intake - estimated_tdee))
    balance_status = balance_meta.get("status") or (
        "deficit" if energy_balance < -200 else "surplus" if energy_balance > 200 else "maintenance"
    )

    datasets = [
        {
            "label": "Kalorien (täglich)",
            "data": daily_values,
            "borderColor": "#1D9E7599",
            "backgroundColor": "rgba(29, 158, 117, 0.1)",
            "borderWidth": 1.5,
            "tension": 0.2,
            "fill": False,
            "pointRadius": 2
        },
        {
            "label": "Ø 7 Tage",
            "data": avg_7d,
            "borderColor": "#1D9E75",
            "borderWidth": 2.5,
            "tension": 0.3,
            "fill": False,
            "pointRadius": 0
        },
        {
            "label": "Ø 14 Tage",
            "data": avg_14d,
            "borderColor": "#085041",
            "borderWidth": 2,
            "tension": 0.3,
            "fill": False,
            "pointRadius": 0,
            "borderDash": [6, 3]
        },
        {
            "label": "TDEE (geschätzt)",
            "data": [estimated_tdee] * len(labels),
            "borderColor": "#888",
            "borderWidth": 1,
            "borderDash": [5, 5],
            "fill": False,
            "pointRadius": 0
        }
    ]

    confidence = balance_meta.get("confidence") or "low"

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": datasets
        },
        "metadata": serialize_dates({
            "confidence": confidence,
            "data_points": len(rows),
            "avg_kcal": round(avg_intake, 1),
            "estimated_tdee": estimated_tdee,
            "energy_balance": round(energy_balance, 1),
            "balance_status": balance_status,
            "first_date": rows[0]['date'],
            "last_date": rows[-1]['date']
        })
    }


@router.get("/macro-distribution")
def get_macro_distribution_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Macronutrient distribution pie chart (E2).

    Shows average protein/carbs/fat distribution over period.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js pie chart with macro percentages
    """
    profile_id = session['profile_id']

    # Get average macros
    macro_data = get_nutrition_average_data(profile_id, days)

    if macro_data['confidence'] == 'insufficient':
        return {
            "chart_type": "pie",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Ernährungsdaten vorhanden"
            }
        }

    # Calculate calories from macros (protein/carbs = 4 kcal/g, fat = 9 kcal/g)
    protein_kcal = macro_data['protein_avg'] * 4
    carbs_kcal = macro_data['carbs_avg'] * 4
    fat_kcal = macro_data['fat_avg'] * 9
    total_kcal = protein_kcal + carbs_kcal + fat_kcal

    if total_kcal == 0:
        return {
            "chart_type": "pie",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Makronährstoff-Daten"
            }
        }

    protein_pct = (protein_kcal / total_kcal * 100)
    carbs_pct = (carbs_kcal / total_kcal * 100)
    fat_pct = (fat_kcal / total_kcal * 100)

    return {
        "chart_type": "pie",
        "data": {
            "labels": ["Protein", "Kohlenhydrate", "Fett"],
            "datasets": [
                {
                    "data": [round(protein_pct, 1), round(carbs_pct, 1), round(fat_pct, 1)],
                    "backgroundColor": [
                        "#1D9E75",  # Protein (green)
                        "#F59E0B",  # Carbs (amber)
                        "#EF4444"   # Fat (red)
                    ],
                    "borderWidth": 2,
                    "borderColor": "#fff"
                }
            ]
        },
        "metadata": {
            "confidence": macro_data['confidence'],
            "data_points": macro_data['data_points'],
            "protein_g": round(macro_data['protein_avg'], 1),
            "carbs_g": round(macro_data['carbs_avg'], 1),
            "fat_g": round(macro_data['fat_avg'], 1),
            "protein_pct": round(protein_pct, 1),
            "carbs_pct": round(carbs_pct, 1),
            "fat_pct": round(fat_pct, 1)
        }
    }


@router.get("/protein-adequacy")
def get_protein_adequacy_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Protein adequacy timeline (E2) - Konzept-konform.

    Shows:
    - Daily protein intake
    - 7d rolling average
    - 28d rolling average
    - Target range bands

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js line chart with protein intake + averages + target bands
    """
    profile_id = session['profile_id']

    # Get protein targets
    targets = get_protein_targets_data(profile_id)

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT date, SUM(protein_g)::float AS protein_g
               FROM nutrition_log
               WHERE profile_id=%s AND date >= %s AND protein_g IS NOT NULL
               GROUP BY date
               ORDER BY date""",
            (profile_id, cutoff),
        )
        rows = cur.fetchall()

    if not rows or len(rows) < 3:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": len(rows) if rows else 0,
                "message": "Nicht genug Protein-Daten (min. 3 Tage)"
            }
        }

    # Prepare data
    labels = []
    daily_values = []
    avg_7d = []
    avg_28d = []

    for i, row in enumerate(rows):
        labels.append(row['date'].isoformat())
        daily_values.append(safe_float(row['protein_g']))

        # 7d rolling average
        start_7d = max(0, i - 6)
        window_7d = [safe_float(rows[j]['protein_g']) for j in range(start_7d, i + 1)]
        avg_7d.append(round(sum(window_7d) / len(window_7d), 1) if window_7d else None)

        # 28d rolling average
        start_28d = max(0, i - 27)
        window_28d = [safe_float(rows[j]['protein_g']) for j in range(start_28d, i + 1)]
        avg_28d.append(round(sum(window_28d) / len(window_28d), 1) if window_28d else None)

    # Add target range bands
    target_low = targets['protein_target_low']
    target_high = targets['protein_target_high']

    datasets = [
        {
            "label": "Protein (täglich)",
            "data": daily_values,
            "borderColor": "#1D9E7599",
            "backgroundColor": "rgba(29, 158, 117, 0.1)",
            "borderWidth": 1.5,
            "tension": 0.2,
            "fill": False,
            "pointRadius": 2
        },
        {
            "label": "Ø 7 Tage",
            "data": avg_7d,
            "borderColor": "#1D9E75",
            "borderWidth": 2.5,
            "tension": 0.3,
            "fill": False,
            "pointRadius": 0
        },
        {
            "label": "Ø 28 Tage",
            "data": avg_28d,
            "borderColor": "#085041",
            "borderWidth": 2,
            "tension": 0.3,
            "fill": False,
            "pointRadius": 0,
            "borderDash": [6, 3]
        },
        {
            "label": "Ziel Min",
            "data": [target_low] * len(labels),
            "borderColor": "#888",
            "borderWidth": 1,
            "borderDash": [5, 5],
            "fill": False,
            "pointRadius": 0
        }
    ]

    datasets.append({
        "label": "Ziel Max",
        "data": [target_high] * len(labels),
        "borderColor": "#888",
        "borderWidth": 1,
        "borderDash": [5, 5],
        "fill": False,
        "pointRadius": 0
    })

    from data_layer.utils import calculate_confidence
    confidence = calculate_confidence(len(rows), days, "general")

    days_in_target = sum(1 for v in daily_values if target_low <= v <= target_high)

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": datasets
        },
        "metadata": serialize_dates({
            "confidence": confidence,
            "data_points": len(rows),
            "target_low": round(target_low, 1),
            "target_high": round(target_high, 1),
            "days_in_target": days_in_target,
            "target_compliance_pct": round(days_in_target / len(daily_values) * 100, 1) if daily_values else 0,
            "first_date": rows[0]['date'],
            "last_date": rows[-1]['date']
        })
    }


@router.get("/nutrition-consistency")
def get_nutrition_consistency_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Nutrition consistency score (E5).

    Shows macro consistency score as bar chart.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js bar chart with consistency metrics
    """
    profile_id = session['profile_id']

    consistency_data = get_macro_consistency_data(profile_id, days)

    if consistency_data['confidence'] == 'insufficient':
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Nicht genug Daten für Konsistenz-Analyse"
            }
        }

    # Show consistency score + macro averages
    labels = [
        "Gesamt-Score",
        f"Protein ({consistency_data['avg_protein_pct']:.0f}%)",
        f"Kohlenhydrate ({consistency_data['avg_carbs_pct']:.0f}%)",
        f"Fett ({consistency_data['avg_fat_pct']:.0f}%)"
    ]

    # Score = 100 - std_dev (inverted for display)
    # Higher bar = more consistent
    protein_consistency = max(0, 100 - consistency_data['std_dev_protein'] * 10)
    carbs_consistency = max(0, 100 - consistency_data['std_dev_carbs'] * 10)
    fat_consistency = max(0, 100 - consistency_data['std_dev_fat'] * 10)

    values = [
        consistency_data['consistency_score'],
        protein_consistency,
        carbs_consistency,
        fat_consistency
    ]

    return {
        "chart_type": "bar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Konsistenz-Score",
                    "data": values,
                    "backgroundColor": ["#1D9E75", "#1D9E75", "#F59E0B", "#EF4444"],
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": {
            "confidence": consistency_data['confidence'],
            "data_points": consistency_data['data_points'],
            "consistency_score": consistency_data['consistency_score'],
            "std_dev_protein": round(consistency_data['std_dev_protein'], 2),
            "std_dev_carbs": round(consistency_data['std_dev_carbs'], 2),
            "std_dev_fat": round(consistency_data['std_dev_fat'], 2)
        }
    }


# ── NEW: Konzept-konforme Nutrition Endpoints (E3, E4, E5) ──────────────────


@router.get("/weekly-macro-distribution")
def get_weekly_macro_distribution_chart(
    weeks: int = Query(default=12, ge=4, le=52),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Weekly macro distribution (E3) - Konzept-konform.

    100%-gestapelter Wochenbalken statt Pie Chart.
    Shows macro consistency across weeks, not just overall average.

    Args:
        weeks: Number of weeks to analyze (4-52, default 12)
        session: Auth session (injected)

    Returns:
        Chart.js stacked bar chart with weekly macro percentages
    """
    profile_id = session['profile_id']

    from db import get_db, get_cursor
    import statistics

    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(weeks=weeks)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT date, protein_g, carbs_g, fat_g, kcal
               FROM nutrition_log
               WHERE profile_id=%s AND date >= %s
                 AND protein_g IS NOT NULL AND carbs_g IS NOT NULL
                 AND fat_g IS NOT NULL AND kcal > 0
               ORDER BY date""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows or len(rows) < 7:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": len(rows) if rows else 0,
                "message": "Nicht genug Daten für Wochen-Analyse (min. 7 Tage)"
            }
        }

    # Group by ISO week
    weekly_data = {}
    for row in rows:
        date_obj = row['date'] if isinstance(row['date'], datetime) else datetime.fromisoformat(str(row['date']))
        iso_week = date_obj.strftime('%Y-W%V')

        if iso_week not in weekly_data:
            weekly_data[iso_week] = {
                'protein': [],
                'carbs': [],
                'fat': [],
                'kcal': []
            }

        weekly_data[iso_week]['protein'].append(safe_float(row['protein_g']))
        weekly_data[iso_week]['carbs'].append(safe_float(row['carbs_g']))
        weekly_data[iso_week]['fat'].append(safe_float(row['fat_g']))
        weekly_data[iso_week]['kcal'].append(safe_float(row['kcal']))

    # Calculate weekly averages and percentages
    labels = []
    protein_pcts = []
    carbs_pcts = []
    fat_pcts = []

    for iso_week in sorted(weekly_data.keys())[-weeks:]:
        data = weekly_data[iso_week]

        avg_protein = sum(data['protein']) / len(data['protein']) if data['protein'] else 0
        avg_carbs = sum(data['carbs']) / len(data['carbs']) if data['carbs'] else 0
        avg_fat = sum(data['fat']) / len(data['fat']) if data['fat'] else 0

        # Convert to kcal
        protein_kcal = avg_protein * 4
        carbs_kcal = avg_carbs * 4
        fat_kcal = avg_fat * 9

        total_kcal = protein_kcal + carbs_kcal + fat_kcal

        if total_kcal > 0:
            labels.append(f"KW {iso_week[-2:]}")
            protein_pcts.append(round((protein_kcal / total_kcal) * 100, 1))
            carbs_pcts.append(round((carbs_kcal / total_kcal) * 100, 1))
            fat_pcts.append(round((fat_kcal / total_kcal) * 100, 1))

    # Calculate variation coefficient (Variationskoeffizient)
    protein_cv = statistics.stdev(protein_pcts) / statistics.mean(protein_pcts) * 100 if len(protein_pcts) > 1 and statistics.mean(protein_pcts) > 0 else 0
    carbs_cv = statistics.stdev(carbs_pcts) / statistics.mean(carbs_pcts) * 100 if len(carbs_pcts) > 1 and statistics.mean(carbs_pcts) > 0 else 0
    fat_cv = statistics.stdev(fat_pcts) / statistics.mean(fat_pcts) * 100 if len(fat_pcts) > 1 and statistics.mean(fat_pcts) > 0 else 0

    return {
        "chart_type": "bar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Protein (%)",
                    "data": protein_pcts,
                    "backgroundColor": "#1D9E75",
                    "stack": "macro"
                },
                {
                    "label": "Kohlenhydrate (%)",
                    "data": carbs_pcts,
                    "backgroundColor": "#F59E0B",
                    "stack": "macro"
                },
                {
                    "label": "Fett (%)",
                    "data": fat_pcts,
                    "backgroundColor": "#EF4444",
                    "stack": "macro"
                }
            ]
        },
        "metadata": {
            "confidence": calculate_confidence(len(rows), weeks * 7, "general"),
            "data_points": len(rows),
            "weeks_analyzed": len(labels),
            "avg_protein_pct": round(statistics.mean(protein_pcts), 1) if protein_pcts else 0,
            "avg_carbs_pct": round(statistics.mean(carbs_pcts), 1) if carbs_pcts else 0,
            "avg_fat_pct": round(statistics.mean(fat_pcts), 1) if fat_pcts else 0,
            "protein_cv": round(protein_cv, 1),
            "carbs_cv": round(carbs_cv, 1),
            "fat_cv": round(fat_cv, 1)
        }
    }


@router.get("/nutrition-adherence-score")
def get_nutrition_adherence_score(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Nutrition Adherence Score (E4) - Konzept-konform.

    Score 0-100 based on goal-specific criteria:
    - Calorie target adherence
    - Protein target adherence
    - Intake consistency
    - Food quality indicators (fiber, sugar)

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        {
            "score": 0-100,
            "components": {...},
            "recommendation": "..."
        }
    """
    profile_id = session['profile_id']

    from db import get_db, get_cursor
    from data_layer.nutrition_metrics import (
        get_protein_adequacy_data,
        calculate_macro_consistency_score
    )

    # Get user's goal mode (weight_loss, strength, endurance, etc.)
    with get_db() as conn:
        cur = get_cursor(conn)
        cur.execute("SELECT goal_mode FROM profiles WHERE id = %s", (profile_id,))
        profile_row = cur.fetchone()
        goal_mode = profile_row['goal_mode'] if profile_row and profile_row['goal_mode'] else 'health'

        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        # Get nutrition data
        cur.execute(
            """WITH daily AS (
                SELECT date,
                       COALESCE(SUM(kcal), 0)::float AS dk,
                       COALESCE(SUM(protein_g), 0)::float AS dp,
                       COALESCE(SUM(carbs_g), 0)::float AS dc,
                       COALESCE(SUM(fat_g), 0)::float AS df FROM nutrition_log
                WHERE profile_id=%s AND date >= %s AND kcal IS NOT NULL
                GROUP BY date
            )
            SELECT COUNT(*)::int AS cnt,
                   AVG(dk) AS avg_kcal,
                   STDDEV(dk) AS std_kcal,
                   AVG(dp) AS avg_protein,
                   AVG(dc) AS avg_carbs,
                   AVG(df) AS avg_fat
            FROM daily""",
            (profile_id, cutoff),
        )
        stats = cur.fetchone()

    if not stats or stats['cnt'] < 7:
        return {
            "score": 0,
            "components": {},
            "metadata": {
                "confidence": "insufficient",
                "message": "Nicht genug Daten (min. 7 Tage)"
            }
        }

    # Get protein adequacy
    protein_data = get_protein_adequacy_data(profile_id, days)

    # Calculate components based on goal mode
    components = {}

    # 1. Calorie adherence (placeholder, needs goal-specific logic)
    calorie_adherence = 70.0  # TODO: Calculate based on TDEE target

    # 2. Protein adherence
    protein_adequacy_pct = protein_data.get('adequacy_score', 0)
    protein_adherence = min(100, protein_adequacy_pct)

    # 3. Intake consistency (low volatility = good)
    kcal_cv = (safe_float(stats['std_kcal']) / safe_float(stats['avg_kcal']) * 100) if safe_float(stats['avg_kcal']) > 0 else 100
    intake_consistency = max(0, 100 - kcal_cv)  # Invert: low CV = high score

    # 4. Food quality (placeholder for fiber/sugar analysis)
    food_quality = 60.0  # TODO: Calculate from fiber/sugar data

    # Goal-specific weighting (from concept E4)
    if goal_mode == 'weight_loss':
        weights = {
            'calorie': 0.35,
            'protein': 0.25,
            'consistency': 0.20,
            'quality': 0.20
        }
    elif goal_mode == 'strength':
        weights = {
            'calorie': 0.25,
            'protein': 0.35,
            'consistency': 0.20,
            'quality': 0.20
        }
    elif goal_mode == 'endurance':
        weights = {
            'calorie': 0.30,
            'protein': 0.20,
            'consistency': 0.20,
            'quality': 0.30
        }
    else:  # health, recomposition
        weights = {
            'calorie': 0.25,
            'protein': 0.25,
            'consistency': 0.25,
            'quality': 0.25
        }

    # Calculate weighted score
    final_score = (
        calorie_adherence * weights['calorie'] +
        protein_adherence * weights['protein'] +
        intake_consistency * weights['consistency'] +
        food_quality * weights['quality']
    )

    components = {
        'calorie_adherence': round(calorie_adherence, 1),
        'protein_adherence': round(protein_adherence, 1),
        'intake_consistency': round(intake_consistency, 1),
        'food_quality': round(food_quality, 1)
    }

    # Generate recommendation
    weak_areas = [k for k, v in components.items() if v < 60]
    if weak_areas:
        recommendation = f"Verbesserungspotenzial: {', '.join(weak_areas)}"
    else:
        recommendation = "Gute Adhärenz, weiter so!"

    return {
        "score": round(final_score, 1),
        "components": components,
        "goal_mode": goal_mode,
        "weights": weights,
        "recommendation": recommendation,
        "metadata": {
            "confidence": calculate_confidence(stats['cnt'], days, "general"),
            "data_points": stats['cnt'],
            "days_analyzed": days
        }
    }


@router.get("/energy-availability-warning")
def get_energy_availability_warning(
    days: int = Query(default=14, ge=7, le=28),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Energy Availability Warning (E5) - Konzept-konform.

    Heuristic warning for potential undernutrition/overtraining.

    Checks:
    - Persistent large deficit
    - Recovery score declining
    - Sleep quality declining
    - LBM declining

    Args:
        days: Analysis window (7-28 days, default 14)
        session: Auth session (injected)

    Returns:
        {
            "warning_level": "none" | "caution" | "warning",
            "triggers": [...],
            "message": "..."
        }
    """
    profile_id = session['profile_id']

    from db import get_db, get_cursor
    from data_layer.nutrition_metrics import get_energy_balance_data
    from data_layer.recovery_metrics import calculate_recovery_score_v2, calculate_sleep_quality_7d
    from data_layer.body_metrics import calculate_lbm_28d_change

    triggers = []
    warning_level = "none"

    # Check 1: Large energy deficit
    energy_data = get_energy_balance_data(profile_id, days)
    if energy_data.get('energy_balance', 0) < -500:
        triggers.append("Großes Energiedefizit (>500 kcal/Tag)")

    # Check 2: Recovery declining
    try:
        recovery_score = calculate_recovery_score_v2(profile_id)
        if recovery_score and recovery_score < 50:
            triggers.append("Recovery Score niedrig (<50)")
    except:
        pass

    # Check 3: Sleep quality
    try:
        sleep_quality = calculate_sleep_quality_7d(profile_id)
        if sleep_quality and sleep_quality < 60:
            triggers.append("Schlafqualität reduziert (<60%)")
    except:
        pass

    # Check 4: LBM declining
    try:
        lbm_change = calculate_lbm_28d_change(profile_id)
        if lbm_change and lbm_change < -1.0:
            triggers.append("Magermasse sinkt (-{:.1f} kg)".format(abs(lbm_change)))
    except:
        pass

    # Determine warning level
    if len(triggers) >= 3:
        warning_level = "warning"
        message = "⚠️ Hinweis auf mögliche Unterversorgung. Mehrere Indikatoren auffällig. Erwäge Defizit-Anpassung oder Regenerationswoche."
    elif len(triggers) >= 2:
        warning_level = "caution"
        message = "⚡ Beobachte folgende Signale genau. Aktuell noch kein Handlungsbedarf, aber Trend beachten."
    elif len(triggers) >= 1:
        warning_level = "caution"
        message = "💡 Ein Indikator auffällig. Weiter beobachten."
    else:
        message = "✅ Energieverfügbarkeit unauffällig."

    return {
        "warning_level": warning_level,
        "triggers": triggers,
        "message": message,
        "metadata": {
            "days_analyzed": days,
            "trigger_count": len(triggers),
            "note": "Heuristische Einschätzung, keine medizinische Diagnose"
        }
    }


@router.get("/training-volume")
def get_training_volume_chart(
    weeks: int = Query(default=12, ge=4, le=52),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Training volume week-over-week (A1).

    Shows weekly training minutes over time.

    Args:
        weeks: Number of weeks to analyze (4-52, default 12)
        session: Auth session (injected)

    Returns:
        Chart.js bar chart with weekly training minutes
    """
    profile_id = session['profile_id']

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(weeks=weeks)).strftime('%Y-%m-%d')

        # Get weekly aggregates
        cur.execute(
            """SELECT
                DATE_TRUNC('week', date) as week_start,
                SUM(duration_min) as total_minutes,
                COUNT(*) as session_count
               FROM activity_log
               WHERE profile_id=%s AND date >= %s
               GROUP BY week_start
               ORDER BY week_start""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Aktivitätsdaten vorhanden"
            }
        }

    labels = [row['week_start'].strftime('KW %V') for row in rows]
    values = [safe_float(row['total_minutes']) for row in rows]

    confidence = calculate_confidence(len(rows), weeks * 7, "general")

    return {
        "chart_type": "bar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Trainingsminuten",
                    "data": values,
                    "backgroundColor": "#1D9E75",
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": serialize_dates({
            "confidence": confidence,
            "data_points": len(rows),
            "avg_minutes_week": round(sum(values) / len(values), 1) if values else 0,
            "total_sessions": sum(row['session_count'] for row in rows)
        })
    }


@router.get("/training-type-distribution")
def get_training_type_distribution_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Training type distribution (A2).

    Shows distribution of training categories as pie chart.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js pie chart with training categories
    """
    profile_id = session['profile_id']

    dist_data = get_training_type_distribution_data(profile_id, days)

    if dist_data['confidence'] == 'insufficient':
        return {
            "chart_type": "pie",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Trainingstypen-Daten"
            }
        }

    labels = [item['category'] for item in dist_data['distribution']]
    values = [item['count'] for item in dist_data['distribution']]

    # Color palette for training categories
    colors = [
        "#1D9E75", "#3B82F6", "#F59E0B", "#EF4444",
        "#8B5CF6", "#10B981", "#F97316", "#06B6D4"
    ]

    return {
        "chart_type": "pie",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "data": values,
                    "backgroundColor": colors[:len(values)],
                    "borderWidth": 2,
                    "borderColor": "#fff"
                }
            ]
        },
        "metadata": {
            "confidence": dist_data['confidence'],
            "total_sessions": dist_data['total_sessions'],
            "categorized_sessions": dist_data['categorized_sessions'],
            "uncategorized_sessions": dist_data['uncategorized_sessions']
        }
    }


@router.get("/quality-sessions")
def get_quality_sessions_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Quality session rate (A3).

    Shows percentage of quality sessions (RPE >= 7 or duration >= 60min).

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js bar chart with quality metrics
    """
    profile_id = session['profile_id']

    # Calculate quality session percentage
    quality_pct = calculate_quality_sessions_pct(profile_id, days)

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT COUNT(*) as total
               FROM activity_log
               WHERE profile_id=%s AND date >= %s""",
            (profile_id, cutoff)
        )
        row = cur.fetchone()
        total_sessions = row['total'] if row else 0

    if total_sessions == 0:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Aktivitätsdaten"
            }
        }

    quality_count = int(quality_pct / 100 * total_sessions)
    regular_count = total_sessions - quality_count

    return {
        "chart_type": "bar",
        "data": {
            "labels": ["Qualitäts-Sessions", "Reguläre Sessions"],
            "datasets": [
                {
                    "label": "Anzahl",
                    "data": [quality_count, regular_count],
                    "backgroundColor": ["#1D9E75", "#888"],
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": {
            "confidence": calculate_confidence(total_sessions, days, "general"),
            "data_points": total_sessions,
            "quality_pct": round(quality_pct, 1),
            "quality_count": quality_count,
            "regular_count": regular_count
        }
    }


@router.get("/load-monitoring")
def get_load_monitoring_chart(
    days: int = Query(default=28, ge=14, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Load monitoring (A4).

    Shows acute load (7d) vs chronic load (28d) and ACWR.

    Args:
        days: Analysis window (14-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js line chart with load metrics
    """
    profile_id = session['profile_id']

    # Calculate loads
    acute_load = calculate_proxy_internal_load_7d(profile_id)
    chronic_load = calculate_proxy_internal_load_7d(profile_id, days=28)

    # ACWR (Acute:Chronic Workload Ratio)
    acwr = acute_load / chronic_load if chronic_load > 0 else 0

    # Fetch daily loads for timeline
    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT
                date,
                SUM(duration_min * COALESCE(rpe, 5)) as daily_load
               FROM activity_log
               WHERE profile_id=%s AND date >= %s
               GROUP BY date
               ORDER BY date""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Load-Daten"
            }
        }

    labels = [row['date'].isoformat() for row in rows]
    values = [safe_float(row['daily_load']) for row in rows]

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Tages-Load",
                    "data": values,
                    "borderColor": "#1D9E75",
                    "backgroundColor": "rgba(29, 158, 117, 0.1)",
                    "borderWidth": 2,
                    "tension": 0.3,
                    "fill": True
                }
            ]
        },
        "metadata": serialize_dates({
            "confidence": calculate_confidence(len(rows), days, "general"),
            "data_points": len(rows),
            "acute_load_7d": round(acute_load, 1),
            "chronic_load_28d": round(chronic_load, 1),
            "acwr": round(acwr, 2),
            "acwr_status": "optimal" if 0.8 <= acwr <= 1.3 else "suboptimal"
        })
    }


@router.get("/monotony-strain")
def get_monotony_strain_chart(
    days: int = Query(default=7, ge=7, le=28),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Monotony & Strain (A5).

    Shows training monotony and strain scores.

    Args:
        days: Analysis window (7-28 days, default 7)
        session: Auth session (injected)

    Returns:
        Chart.js bar chart with monotony and strain
    """
    profile_id = session['profile_id']

    monotony = calculate_monotony_score(profile_id, days)
    strain = calculate_strain_score(profile_id, days)

    if monotony == 0 and strain == 0:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Nicht genug Daten für Monotonie-Analyse"
            }
        }

    return {
        "chart_type": "bar",
        "data": {
            "labels": ["Monotonie", "Strain"],
            "datasets": [
                {
                    "label": "Score",
                    "data": [round(monotony, 2), round(strain, 1)],
                    "backgroundColor": ["#F59E0B", "#EF4444"],
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": {
            "confidence": "medium",  # Fixed for monotony calculations
            "monotony_score": round(monotony, 2),
            "strain_score": round(strain, 1),
            "monotony_status": "high" if monotony > 2.0 else "normal",
            "strain_status": "high" if strain > 10000 else "normal"
        }
    }


@router.get("/ability-balance")
def get_ability_balance_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Ability balance radar chart (A6).

    Shows training distribution across 5 abilities.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js radar chart with ability balance
    """
    profile_id = session['profile_id']

    balance_data = calculate_ability_balance(profile_id, days)

    if balance_data['total_minutes'] == 0:
        return {
            "chart_type": "radar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Aktivitätsdaten"
            }
        }

    labels = ["Kraft", "Ausdauer", "Beweglichkeit", "Gleichgewicht", "Geist"]
    values = [
        balance_data['strength_pct'],
        balance_data['endurance_pct'],
        balance_data['flexibility_pct'],
        balance_data['balance_pct'],
        balance_data['mind_pct']
    ]

    return {
        "chart_type": "radar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Fähigkeiten-Balance (%)",
                    "data": values,
                    "borderColor": "#1D9E75",
                    "backgroundColor": "rgba(29, 158, 117, 0.2)",
                    "borderWidth": 2,
                    "pointBackgroundColor": "#1D9E75",
                    "pointBorderColor": "#fff",
                    "pointHoverBackgroundColor": "#fff",
                    "pointHoverBorderColor": "#1D9E75"
                }
            ]
        },
        "metadata": {
            "confidence": balance_data['confidence'],
            "total_minutes": balance_data['total_minutes'],
            "strength_pct": round(balance_data['strength_pct'], 1),
            "endurance_pct": round(balance_data['endurance_pct'], 1),
            "flexibility_pct": round(balance_data['flexibility_pct'], 1),
            "balance_pct": round(balance_data['balance_pct'], 1),
            "mind_pct": round(balance_data['mind_pct'], 1)
        }
    }


@router.get("/volume-by-ability")
def get_volume_by_ability_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Training volume by ability (A8).

    Shows absolute minutes per ability category.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js bar chart with volume per ability
    """
    profile_id = session['profile_id']

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT
                COALESCE(ability, 'unknown') as ability,
                SUM(duration_min) as total_minutes
               FROM activity_log
               WHERE profile_id=%s AND date >= %s
               GROUP BY ability
               ORDER BY total_minutes DESC""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Ability-Daten"
            }
        }

    # Map ability names to German
    ability_map = {
        "strength": "Kraft",
        "endurance": "Ausdauer",
        "flexibility": "Beweglichkeit",
        "balance": "Gleichgewicht",
        "mind": "Geist",
        "unknown": "Nicht zugeordnet"
    }

    labels = [ability_map.get(row['ability'], row['ability']) for row in rows]
    values = [safe_float(row['total_minutes']) for row in rows]

    total_minutes = sum(values)

    return {
        "chart_type": "bar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Trainingsminuten",
                    "data": values,
                    "backgroundColor": "#1D9E75",
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": {
            "confidence": calculate_confidence(len(rows), days, "general"),
            "data_points": len(rows),
            "total_minutes": total_minutes
        }
    }


# ── Recovery Charts ─────────────────────────────────────────────────────────


@router.get("/recovery-score")
def get_recovery_score_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Recovery score timeline (R1).

    Shows daily recovery scores over time.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js line chart with recovery scores
    """
    profile_id = session['profile_id']

    # For PoC: Use current recovery score and create synthetic timeline
    # TODO: Store historical recovery scores for true timeline
    current_score = calculate_recovery_score_v2(profile_id)

    if current_score is None:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Recovery-Daten vorhanden"
            }
        }

    # Fetch vitals for timeline approximation
    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT date, resting_hr, hrv_ms
               FROM vitals_baseline
               WHERE profile_id=%s AND date >= %s
               ORDER BY date""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows:
        return {
            "chart_type": "line",
            "data": {
                "labels": [datetime.now().strftime('%Y-%m-%d')],
                "datasets": [
                    {
                        "label": "Recovery Score",
                        "data": [current_score],
                        "borderColor": "#1D9E75",
                        "backgroundColor": "rgba(29, 158, 117, 0.1)",
                        "borderWidth": 2,
                        "tension": 0.3,
                        "fill": True
                    }
                ]
            },
            "metadata": {
                "confidence": "low",
                "data_points": 1,
                "current_score": current_score
            }
        }

    # Simple proxy: Use HRV as recovery indicator (higher HRV = better recovery)
    # This is a placeholder until we store actual recovery scores
    labels = [row['date'].isoformat() for row in rows]
    # Normalize HRV to 0-100 scale (assume typical range 20-100ms)
    values = [min(100, max(0, safe_float(row['hrv_ms']) if row['hrv_ms'] else 50)) for row in rows]

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Recovery Score (proxy)",
                    "data": values,
                    "borderColor": "#1D9E75",
                    "backgroundColor": "rgba(29, 158, 117, 0.1)",
                    "borderWidth": 2,
                    "tension": 0.3,
                    "fill": True
                }
            ]
        },
        "metadata": serialize_dates({
            "confidence": calculate_confidence(len(rows), days, "general"),
            "data_points": len(rows),
            "current_score": current_score,
            "note": "Score based on HRV proxy; true recovery score calculation in development"
        })
    }


@router.get("/hrv-rhr-baseline")
def get_hrv_rhr_baseline_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    HRV/RHR vs baseline (R2).

    Shows HRV and RHR trends vs. baseline values.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js multi-line chart with HRV and RHR
    """
    profile_id = session['profile_id']

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT date, resting_hr, hrv_ms
               FROM vitals_baseline
               WHERE profile_id=%s AND date >= %s
               ORDER BY date""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Vitalwerte vorhanden"
            }
        }

    labels = [row['date'].isoformat() for row in rows]
    hrv_values = [safe_float(row['hrv_ms']) if row['hrv_ms'] else None for row in rows]
    rhr_values = [safe_float(row['resting_hr']) if row['resting_hr'] else None for row in rows]

    # Calculate baselines (28d median)
    hrv_baseline = calculate_hrv_vs_baseline_pct(profile_id)  # This returns % deviation
    rhr_baseline = calculate_rhr_vs_baseline_pct(profile_id)  # This returns % deviation

    # For chart, we need actual baseline values (approximation)
    hrv_filtered = [v for v in hrv_values if v is not None]
    rhr_filtered = [v for v in rhr_values if v is not None]

    avg_hrv = sum(hrv_filtered) / len(hrv_filtered) if hrv_filtered else 50
    avg_rhr = sum(rhr_filtered) / len(rhr_filtered) if rhr_filtered else 60

    datasets = [
        {
            "label": "HRV (ms)",
            "data": hrv_values,
            "borderColor": "#1D9E75",
            "backgroundColor": "rgba(29, 158, 117, 0.1)",
            "borderWidth": 2,
            "tension": 0.3,
            "yAxisID": "y1",
            "fill": False
        },
        {
            "label": "RHR (bpm)",
            "data": rhr_values,
            "borderColor": "#3B82F6",
            "backgroundColor": "rgba(59, 130, 246, 0.1)",
            "borderWidth": 2,
            "tension": 0.3,
            "yAxisID": "y2",
            "fill": False
        }
    ]

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": datasets
        },
        "metadata": serialize_dates({
            "confidence": calculate_confidence(len(rows), days, "general"),
            "data_points": len(rows),
            "avg_hrv": round(avg_hrv, 1),
            "avg_rhr": round(avg_rhr, 1),
            "hrv_vs_baseline_pct": hrv_baseline,
            "rhr_vs_baseline_pct": rhr_baseline
        })
    }


@router.get("/sleep-duration-quality")
def get_sleep_duration_quality_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Sleep duration + quality (R3).

    Shows sleep duration and quality score over time.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js multi-line chart with sleep metrics
    """
    profile_id = session['profile_id']

    duration_data = get_sleep_duration_data(profile_id, days)
    quality_data = get_sleep_quality_data(profile_id, days)

    if duration_data['confidence'] == 'insufficient':
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Schlafdaten vorhanden"
            }
        }

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT date, total_sleep_min
               FROM sleep_log
               WHERE profile_id=%s AND date >= %s
               ORDER BY date""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Schlafdaten"
            }
        }

    labels = [row['date'].isoformat() for row in rows]
    duration_hours = [safe_float(row['total_sleep_min']) / 60 if row['total_sleep_min'] else None for row in rows]

    # Quality score (simple proxy: % of 8 hours)
    quality_scores = [(d / 8 * 100) if d else None for d in duration_hours]

    datasets = [
        {
            "label": "Schlafdauer (h)",
            "data": duration_hours,
            "borderColor": "#3B82F6",
            "backgroundColor": "rgba(59, 130, 246, 0.1)",
            "borderWidth": 2,
            "tension": 0.3,
            "yAxisID": "y1",
            "fill": True
        },
        {
            "label": "Qualität (%)",
            "data": quality_scores,
            "borderColor": "#1D9E75",
            "backgroundColor": "rgba(29, 158, 117, 0.1)",
            "borderWidth": 2,
            "tension": 0.3,
            "yAxisID": "y2",
            "fill": False
        }
    ]

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": datasets
        },
        "metadata": serialize_dates({
            "confidence": duration_data['confidence'],
            "data_points": len(rows),
            "avg_duration_hours": round(duration_data['avg_duration_hours'], 1),
            "sleep_quality_score": quality_data.get('sleep_quality_score', 0)
        })
    }


@router.get("/sleep-debt")
def get_sleep_debt_chart(
    days: int = Query(default=28, ge=7, le=90),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Sleep debt accumulation (R4).

    Shows cumulative sleep debt over time.

    Args:
        days: Analysis window (7-90 days, default 28)
        session: Auth session (injected)

    Returns:
        Chart.js line chart with sleep debt
    """
    profile_id = session['profile_id']

    current_debt = calculate_sleep_debt_hours(profile_id)

    if current_debt is None:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Schlafdaten für Schulden-Berechnung"
            }
        }

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        cur.execute(
            """SELECT date, total_sleep_min
               FROM sleep_log
               WHERE profile_id=%s AND date >= %s
               ORDER BY date""",
            (profile_id, cutoff)
        )
        rows = cur.fetchall()

    if not rows:
        return {
            "chart_type": "line",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Schlafdaten"
            }
        }

    labels = [row['date'].isoformat() for row in rows]

    # Calculate cumulative debt (target 8h/night)
    target_hours = 8.0
    cumulative_debt = 0
    debt_values = []

    for row in rows:
        actual_hours = safe_float(row['total_sleep_min']) / 60 if row['total_sleep_min'] else 0
        daily_deficit = target_hours - actual_hours
        cumulative_debt += daily_deficit
        debt_values.append(cumulative_debt)

    return {
        "chart_type": "line",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Schlafschuld (Stunden)",
                    "data": debt_values,
                    "borderColor": "#EF4444",
                    "backgroundColor": "rgba(239, 68, 68, 0.1)",
                    "borderWidth": 2,
                    "tension": 0.3,
                    "fill": True
                }
            ]
        },
        "metadata": serialize_dates({
            "confidence": calculate_confidence(len(rows), days, "general"),
            "data_points": len(rows),
            "current_debt_hours": round(current_debt, 1),
            "final_debt_hours": round(cumulative_debt, 1)
        })
    }


@router.get("/vital-signs-matrix")
def get_vital_signs_matrix_chart(
    days: int = Query(default=7, ge=7, le=30),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Vital signs matrix (R5).

    Shows latest vital signs as horizontal bar chart.

    Args:
        days: Max age of measurements (7-30 days, default 7)
        session: Auth session (injected)

    Returns:
        Chart.js horizontal bar chart with vital signs
    """
    profile_id = session['profile_id']

    from db import get_db, get_cursor
    with get_db() as conn:
        cur = get_cursor(conn)
        cutoff = (datetime.now() - timedelta(days=days)).strftime('%Y-%m-%d')

        # Get latest vitals
        cur.execute(
            """SELECT resting_hr, hrv_ms, vo2_max, spo2, respiratory_rate
               FROM vitals_baseline
               WHERE profile_id=%s AND date >= %s
               ORDER BY date DESC
               LIMIT 1""",
            (profile_id, cutoff)
        )
        vitals_row = cur.fetchone()

        # Get latest blood pressure
        cur.execute(
            """SELECT systolic, diastolic
               FROM blood_pressure_log
               WHERE profile_id=%s AND date >= %s
               ORDER BY date DESC, time DESC
               LIMIT 1""",
            (profile_id, cutoff)
        )
        bp_row = cur.fetchone()

    if not vitals_row and not bp_row:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine aktuellen Vitalwerte"
            }
        }

    labels = []
    values = []

    if vitals_row:
        if vitals_row['resting_hr']:
            labels.append("Ruhepuls (bpm)")
            values.append(safe_float(vitals_row['resting_hr']))
        if vitals_row['hrv_ms']:
            labels.append("HRV (ms)")
            values.append(safe_float(vitals_row['hrv_ms']))
        if vitals_row['vo2_max']:
            labels.append("VO2 Max")
            values.append(safe_float(vitals_row['vo2_max']))
        if vitals_row['spo2']:
            labels.append("SpO2 (%)")
            values.append(safe_float(vitals_row['spo2']))
        if vitals_row['respiratory_rate']:
            labels.append("Atemfrequenz")
            values.append(safe_float(vitals_row['respiratory_rate']))

    if bp_row:
        if bp_row['systolic']:
            labels.append("Blutdruck sys (mmHg)")
            values.append(safe_float(bp_row['systolic']))
        if bp_row['diastolic']:
            labels.append("Blutdruck dia (mmHg)")
            values.append(safe_float(bp_row['diastolic']))

    if not labels:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Keine Vitalwerte verfügbar"
            }
        }

    return {
        "chart_type": "bar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Wert",
                    "data": values,
                    "backgroundColor": "#1D9E75",
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": {
            "confidence": "medium",
            "data_points": len(values),
            "note": "Latest measurements within last " + str(days) + " days"
        }
    }


# ── Correlation Charts ──────────────────────────────────────────────────────


@router.get("/weight-energy-correlation")
def get_weight_energy_correlation_chart(
    max_lag: int = Query(default=14, ge=7, le=28),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Weight vs energy balance correlation (C1).

    Shows lag correlation between energy intake and weight change.

    Args:
        max_lag: Maximum lag days to analyze (7-28, default 14)
        session: Auth session (injected)

    Returns:
        Chart.js scatter chart with correlation data
    """
    profile_id = session['profile_id']

    corr_data = calculate_lag_correlation(profile_id, "energy_balance", "weight", max_lag)

    if not corr_data or corr_data.get('correlation') is None:
        return {
            "chart_type": "scatter",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Nicht genug Daten für Korrelationsanalyse"
            }
        }

    # Create lag vs correlation data for chart
    # For simplicity, show best lag point as single data point
    best_lag = corr_data.get('best_lag_days', 0)
    correlation = corr_data.get('correlation', 0)

    return {
        "chart_type": "scatter",
        "data": {
            "labels": [f"Lag {best_lag} Tage"],
            "datasets": [
                {
                    "label": "Korrelation",
                    "data": [{"x": best_lag, "y": correlation}],
                    "backgroundColor": "#1D9E75",
                    "borderColor": "#085041",
                    "borderWidth": 2,
                    "pointRadius": 8
                }
            ]
        },
        "metadata": {
            "confidence": corr_data.get('confidence', 'low'),
            "correlation": round(correlation, 3),
            "best_lag_days": best_lag,
            "interpretation": corr_data.get('interpretation', ''),
            "data_points": corr_data.get('data_points', 0)
        }
    }


@router.get("/lbm-protein-correlation")
def get_lbm_protein_correlation_chart(
    max_lag: int = Query(default=14, ge=7, le=28),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Lean mass vs protein intake correlation (C2).

    Shows lag correlation between protein intake and lean mass change.

    Args:
        max_lag: Maximum lag days to analyze (7-28, default 14)
        session: Auth session (injected)

    Returns:
        Chart.js scatter chart with correlation data
    """
    profile_id = session['profile_id']

    corr_data = calculate_lag_correlation(profile_id, "protein", "lbm", max_lag)

    if not corr_data or corr_data.get('correlation') is None:
        return {
            "chart_type": "scatter",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Nicht genug Daten für LBM-Protein Korrelation"
            }
        }

    best_lag = corr_data.get('best_lag_days', 0)
    correlation = corr_data.get('correlation', 0)

    return {
        "chart_type": "scatter",
        "data": {
            "labels": [f"Lag {best_lag} Tage"],
            "datasets": [
                {
                    "label": "Korrelation",
                    "data": [{"x": best_lag, "y": correlation}],
                    "backgroundColor": "#3B82F6",
                    "borderColor": "#1E40AF",
                    "borderWidth": 2,
                    "pointRadius": 8
                }
            ]
        },
        "metadata": {
            "confidence": corr_data.get('confidence', 'low'),
            "correlation": round(correlation, 3),
            "best_lag_days": best_lag,
            "interpretation": corr_data.get('interpretation', ''),
            "data_points": corr_data.get('data_points', 0)
        }
    }


@router.get("/load-vitals-correlation")
def get_load_vitals_correlation_chart(
    max_lag: int = Query(default=14, ge=7, le=28),
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Training load vs vitals correlation (C3).

    Shows lag correlation between training load and HRV/RHR.

    Args:
        max_lag: Maximum lag days to analyze (7-28, default 14)
        session: Auth session (injected)

    Returns:
        Chart.js scatter chart with correlation data
    """
    profile_id = session['profile_id']

    # Try HRV first
    corr_hrv = calculate_lag_correlation(profile_id, "load", "hrv", max_lag)
    corr_rhr = calculate_lag_correlation(profile_id, "load", "rhr", max_lag)

    # Use whichever has stronger correlation
    if corr_hrv and corr_rhr:
        corr_data = corr_hrv if abs(corr_hrv.get('correlation', 0)) > abs(corr_rhr.get('correlation', 0)) else corr_rhr
        metric_name = "HRV" if corr_data == corr_hrv else "RHR"
    elif corr_hrv:
        corr_data = corr_hrv
        metric_name = "HRV"
    elif corr_rhr:
        corr_data = corr_rhr
        metric_name = "RHR"
    else:
        return {
            "chart_type": "scatter",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Nicht genug Daten für Load-Vitals Korrelation"
            }
        }

    best_lag = corr_data.get('best_lag_days', 0)
    correlation = corr_data.get('correlation', 0)

    return {
        "chart_type": "scatter",
        "data": {
            "labels": [f"Load → {metric_name} (Lag {best_lag}d)"],
            "datasets": [
                {
                    "label": "Korrelation",
                    "data": [{"x": best_lag, "y": correlation}],
                    "backgroundColor": "#F59E0B",
                    "borderColor": "#D97706",
                    "borderWidth": 2,
                    "pointRadius": 8
                }
            ]
        },
        "metadata": {
            "confidence": corr_data.get('confidence', 'low'),
            "correlation": round(correlation, 3),
            "best_lag_days": best_lag,
            "metric": metric_name,
            "interpretation": corr_data.get('interpretation', ''),
            "data_points": corr_data.get('data_points', 0)
        }
    }


@router.get("/recovery-performance")
def get_recovery_performance_chart(
    session: dict = Depends(require_auth)
) -> Dict:
    """
    Recovery vs performance correlation (C4).

    Shows relationship between recovery metrics and training quality.

    Args:
        session: Auth session (injected)

    Returns:
        Chart.js bar chart with top drivers
    """
    profile_id = session['profile_id']

    # Get top drivers (hindering/helpful factors)
    drivers = calculate_top_drivers(profile_id)

    if not drivers or len(drivers) == 0:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "insufficient",
                "data_points": 0,
                "message": "Nicht genug Daten für Driver-Analyse"
            }
        }

    # Separate hindering and helpful
    hindering = [d for d in drivers if d.get('impact', '') == 'hindering']
    helpful = [d for d in drivers if d.get('impact', '') == 'helpful']

    # Take top 3 of each
    top_hindering = hindering[:3]
    top_helpful = helpful[:3]

    labels = []
    values = []
    colors = []

    for d in top_hindering:
        labels.append(f"❌ {d.get('factor', '')}")
        values.append(-abs(d.get('score', 0)))  # Negative for hindering
        colors.append("#EF4444")

    for d in top_helpful:
        labels.append(f"✅ {d.get('factor', '')}")
        values.append(abs(d.get('score', 0)))  # Positive for helpful
        colors.append("#1D9E75")

    if not labels:
        return {
            "chart_type": "bar",
            "data": {
                "labels": [],
                "datasets": []
            },
            "metadata": {
                "confidence": "low",
                "data_points": 0,
                "message": "Keine signifikanten Treiber gefunden"
            }
        }

    return {
        "chart_type": "bar",
        "data": {
            "labels": labels,
            "datasets": [
                {
                    "label": "Impact Score",
                    "data": values,
                    "backgroundColor": colors,
                    "borderColor": "#085041",
                    "borderWidth": 1
                }
            ]
        },
        "metadata": {
            "confidence": "medium",
            "hindering_count": len(top_hindering),
            "helpful_count": len(top_helpful),
            "total_factors": len(drivers)
        }
    }


# ── Health Endpoint ──────────────────────────────────────────────────────────


@router.get("/health")
def health_check() -> Dict:
    """
    Health check endpoint for charts API.

    Returns:
        {
            "status": "ok",
            "version": "1.0",
            "available_charts": [...]
        }
    """
    return {
        "status": "ok",
        "version": "1.0",
        "phase": "0c",
        "available_charts": [
            {
                "category": "body",
                "endpoint": "/charts/weight-trend",
                "type": "line",
                "description": "Weight trend over time"
            },
            {
                "category": "body",
                "endpoint": "/charts/body-composition",
                "type": "line",
                "description": "Body fat % and lean mass"
            },
            {
                "category": "body",
                "endpoint": "/charts/circumferences",
                "type": "bar",
                "description": "Latest circumference measurements"
            },
            {
                "category": "nutrition",
                "endpoint": "/charts/energy-balance",
                "type": "line",
                "description": "Daily calorie intake vs. TDEE"
            },
            {
                "category": "nutrition",
                "endpoint": "/charts/macro-distribution",
                "type": "pie",
                "description": "Protein/Carbs/Fat distribution"
            },
            {
                "category": "nutrition",
                "endpoint": "/charts/protein-adequacy",
                "type": "line",
                "description": "Protein intake vs. target range"
            },
            {
                "category": "nutrition",
                "endpoint": "/charts/nutrition-consistency",
                "type": "bar",
                "description": "Macro consistency score"
            },
            {
                "category": "activity",
                "endpoint": "/charts/training-volume",
                "type": "bar",
                "description": "Weekly training minutes"
            },
            {
                "category": "activity",
                "endpoint": "/charts/training-type-distribution",
                "type": "pie",
                "description": "Training category distribution"
            },
            {
                "category": "activity",
                "endpoint": "/charts/quality-sessions",
                "type": "bar",
                "description": "Quality session rate"
            },
            {
                "category": "activity",
                "endpoint": "/charts/load-monitoring",
                "type": "line",
                "description": "Acute vs chronic load + ACWR"
            },
            {
                "category": "activity",
                "endpoint": "/charts/monotony-strain",
                "type": "bar",
                "description": "Training monotony and strain"
            },
            {
                "category": "activity",
                "endpoint": "/charts/ability-balance",
                "type": "radar",
                "description": "Training balance across 5 abilities"
            },
            {
                "category": "activity",
                "endpoint": "/charts/volume-by-ability",
                "type": "bar",
                "description": "Training volume per ability"
            },
            {
                "category": "recovery",
                "endpoint": "/charts/recovery-score",
                "type": "line",
                "description": "Recovery score timeline"
            },
            {
                "category": "recovery",
                "endpoint": "/charts/hrv-rhr-baseline",
                "type": "line",
                "description": "HRV and RHR vs baseline"
            },
            {
                "category": "recovery",
                "endpoint": "/charts/sleep-duration-quality",
                "type": "line",
                "description": "Sleep duration and quality"
            },
            {
                "category": "recovery",
                "endpoint": "/charts/sleep-debt",
                "type": "line",
                "description": "Cumulative sleep debt"
            },
            {
                "category": "recovery",
                "endpoint": "/charts/vital-signs-matrix",
                "type": "bar",
                "description": "Latest vital signs overview"
            },
            {
                "category": "correlations",
                "endpoint": "/charts/weight-energy-correlation",
                "type": "scatter",
                "description": "Weight vs energy balance (lag correlation)"
            },
            {
                "category": "correlations",
                "endpoint": "/charts/lbm-protein-correlation",
                "type": "scatter",
                "description": "Lean mass vs protein intake (lag correlation)"
            },
            {
                "category": "correlations",
                "endpoint": "/charts/load-vitals-correlation",
                "type": "scatter",
                "description": "Training load vs HRV/RHR (lag correlation)"
            },
            {
                "category": "correlations",
                "endpoint": "/charts/recovery-performance",
                "type": "bar",
                "description": "Top drivers (hindering/helpful factors)"
            }
        ]
    }
