"""Visualizations for AOA geolocation Monte Carlo statistical validation.

Plots the normalized miss distance distribution against the theoretical
chi-squared(3) distribution to verify covariance calibration.

Run with:
    python -m gri_sandbox.plots.aoa_monte_carlo
"""

from __future__ import annotations

import numpy as np
import plotly.graph_objects as go
from gri_geosim.geolocation import aoa_locator
from gri_utils.observables import get_aoa_quat
from plotly.subplots import make_subplots
from scipy.spatial.transform import Rotation
from scipy.stats import chi2, kstest

EARTH_RADIUS = 6378137.0
LEO_ALTITUDE = 550_000.0  # 550 km LEO altitude (typical Starlink-like orbit)

# Dark theme template
DARK_TEMPLATE = "plotly_dark"


def _create_good_geometry() -> tuple[np.ndarray, Rotation, np.ndarray]:
    """Create test geometry with LEO collectors observing an Earth emitter.

    Creates 4 LEO satellites in a non-coplanar arrangement observing an emitter
    on the Earth's surface. The satellites are positioned with good angular
    separation for AOA geolocation:
    - 3 satellites in a triangular pattern at different altitudes
    - 1 satellite offset in the cross-track direction

    Returns:
        Tuple of (collector_xyz, rotations, target_xyz).
    """
    # Emitter on Earth surface (lat ~30N equivalent position)
    target_xyz = np.array(
        [
            EARTH_RADIUS * np.cos(np.radians(30)),
            0.0,
            EARTH_RADIUS * np.sin(np.radians(30)),
        ],
    )

    # LEO collectors in non-coplanar arrangement
    # Base orbital radius
    leo_radius = EARTH_RADIUS + LEO_ALTITUDE

    # Create 4 satellites with good geometry:
    # - Spread around the target with different elevations and azimuths
    # - Non-coplanar to ensure good 3D observability
    collector_xyz = np.array(
        [
            # Satellite 1: Nearly overhead (high elevation)
            [
                leo_radius * np.cos(np.radians(35)) * np.cos(np.radians(5)),
                leo_radius * np.cos(np.radians(35)) * np.sin(np.radians(5)),
                leo_radius * np.sin(np.radians(35)),
            ],
            # Satellite 2: East, lower elevation
            [
                leo_radius * np.cos(np.radians(25)) * np.cos(np.radians(25)),
                leo_radius * np.cos(np.radians(25)) * np.sin(np.radians(25)),
                leo_radius * np.sin(np.radians(25)),
            ],
            # Satellite 3: West-Southwest, lower elevation
            [
                leo_radius * np.cos(np.radians(20)) * np.cos(np.radians(-20)),
                leo_radius * np.cos(np.radians(20)) * np.sin(np.radians(-20)),
                leo_radius * np.sin(np.radians(20)),
            ],
            # Satellite 4: North, different orbital plane (cross-track offset)
            [
                leo_radius * np.cos(np.radians(40)) * np.cos(np.radians(-10)),
                leo_radius * np.cos(np.radians(40)) * np.sin(np.radians(-10)),
                leo_radius * np.sin(np.radians(40)),
            ],
        ],
    )

    # Collectors point toward nadir (toward Earth center)
    # For each collector, create rotation that points -X toward Earth center
    rotations_list = []
    for pos in collector_xyz:
        # Direction from satellite toward Earth center (nadir)
        nadir = -pos / np.linalg.norm(pos)
        # Create a rotation that aligns sensor boresight with nadir
        # Using from_rotvec with axis perpendicular to default -X and nadir
        default_boresight = np.array([-1.0, 0.0, 0.0])
        axis = np.cross(default_boresight, nadir)
        axis_norm = np.linalg.norm(axis)
        if axis_norm > 1e-10:  # noqa: PLR2004
            axis = axis / axis_norm
            angle = np.arccos(np.clip(np.dot(default_boresight, nadir), -1.0, 1.0))
            rotations_list.append(Rotation.from_rotvec(axis * angle))
        else:
            rotations_list.append(Rotation.identity())

    rotations = Rotation.concatenate(rotations_list)

    return collector_xyz, rotations, target_xyz


def run_monte_carlo(
    n_trials: int = 1000,
    aoa_sigma: float = 0.001,
    seed: int = 42,
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Run Monte Carlo simulation for AOA geolocation.

    Args:
        n_trials: Number of Monte Carlo trials.
        aoa_sigma: AOA measurement noise (1-sigma, radians).
        seed: Random seed for reproducibility.

    Returns:
        Tuple of (normalized_miss_sq, position_errors, target_true).
    """
    rng = np.random.default_rng(seed)

    collector_xyz, rotations, target_true = _create_good_geometry()
    n_collectors = len(collector_xyz)

    aoa_cov = np.eye(2) * aoa_sigma**2
    aoa_covariances = np.tile(aoa_cov, (n_collectors, 1, 1))

    true_aoa = get_aoa_quat(collector_xyz, target_true, rotations)

    normalized_miss_sq = []
    position_errors = []

    for _ in range(n_trials):
        noise = rng.standard_normal((n_collectors, 2)) * aoa_sigma
        noisy_aoa = true_aoa + noise

        result = aoa_locator(
            collector_xyz,
            rotations,
            noisy_aoa,
            aoa_covariances,
        )

        delta = result.position - target_true
        cov_inv = np.linalg.inv(result.covariance)
        d_sq = delta @ cov_inv @ delta
        normalized_miss_sq.append(d_sq)
        position_errors.append(delta)

    return np.array(normalized_miss_sq), np.array(position_errors), target_true


def plot_chi2_histogram(normalized_miss_sq: np.ndarray) -> go.Figure:
    """Plot histogram of normalized miss distance vs chi-squared(3) PDF.

    Args:
        normalized_miss_sq: Array of normalized miss distance squared values.

    Returns:
        Plotly figure with histogram and theoretical PDF overlay.
    """
    fig = go.Figure()

    # Histogram of empirical data
    fig.add_trace(
        go.Histogram(
            x=normalized_miss_sq,
            nbinsx=50,
            histnorm="probability density",
            name="Empirical",
            marker_color="rgba(99, 110, 250, 0.7)",
            marker_line_color="rgba(99, 110, 250, 1)",
            marker_line_width=1,
        ),
    )

    # Theoretical chi-squared(3) PDF
    x_theory = np.linspace(0, max(15, np.max(normalized_miss_sq)), 200)
    pdf_theory = chi2.pdf(x_theory, df=3)

    fig.add_trace(
        go.Scatter(
            x=x_theory,
            y=pdf_theory,
            mode="lines",
            name="χ²(3) Theory",
            line={"color": "#EF553B", "width": 3},
        ),
    )

    # Add vertical lines for key percentiles
    p95_theory = chi2.ppf(0.95, df=3)
    p95_empirical = np.percentile(normalized_miss_sq, 95)

    fig.add_vline(
        x=p95_theory,
        line_dash="dash",
        line_color="#00CC96",
        annotation_text=f"95% theory: {p95_theory:.2f}",
        annotation_position="top right",
    )
    fig.add_vline(
        x=p95_empirical,
        line_dash="dot",
        line_color="#AB63FA",
        annotation_text=f"95% empirical: {p95_empirical:.2f}",
        annotation_position="top left",
    )

    # Statistics annotation
    mean_d_sq = np.mean(normalized_miss_sq)
    var_d_sq = np.var(normalized_miss_sq)
    _, p_value = kstest(normalized_miss_sq, chi2(3).cdf)

    stats_text = (
        f"<b>Statistics:</b><br>"
        f"Mean: {mean_d_sq:.3f} (theory: 3.0)<br>"
        f"Var: {var_d_sq:.3f} (theory: 6.0)<br>"
        f"KS p-value: {p_value:.4f}"
    )

    fig.add_annotation(
        x=0.98,
        y=0.98,
        xref="paper",
        yref="paper",
        text=stats_text,
        showarrow=False,
        font={"size": 12},
        align="left",
        bgcolor="rgba(0,0,0,0.5)",
        bordercolor="rgba(255,255,255,0.3)",
        borderwidth=1,
        borderpad=8,
        xanchor="right",
        yanchor="top",
    )

    fig.update_layout(
        template=DARK_TEMPLATE,
        title="Normalized Miss Distance Distribution vs χ²(3)",
        xaxis_title="Normalized Miss Distance Squared (d²)",
        yaxis_title="Probability Density",
        legend={"x": 0.7, "y": 0.5},
        bargap=0.05,
    )

    return fig


def plot_qq(normalized_miss_sq: np.ndarray) -> go.Figure:
    """Plot Q-Q plot of normalized miss distance vs chi-squared(3).

    Args:
        normalized_miss_sq: Array of normalized miss distance squared values.

    Returns:
        Plotly figure with Q-Q plot.
    """
    fig = go.Figure()

    # Sort empirical data
    sorted_data = np.sort(normalized_miss_sq)
    n = len(sorted_data)

    # Theoretical quantiles
    percentiles = (np.arange(1, n + 1) - 0.5) / n
    theoretical_quantiles = chi2.ppf(percentiles, df=3)

    # Q-Q scatter
    fig.add_trace(
        go.Scatter(
            x=theoretical_quantiles,
            y=sorted_data,
            mode="markers",
            name="Q-Q Points",
            marker={"color": "#636EFA", "size": 4, "opacity": 0.6},
        ),
    )

    # Reference line (y=x)
    max_val = max(theoretical_quantiles[-1], sorted_data[-1])
    fig.add_trace(
        go.Scatter(
            x=[0, max_val],
            y=[0, max_val],
            mode="lines",
            name="Perfect Fit (y=x)",
            line={"color": "#EF553B", "dash": "dash", "width": 2},
        ),
    )

    fig.update_layout(
        template=DARK_TEMPLATE,
        title="Q-Q Plot: Empirical vs χ²(3) Distribution",
        xaxis_title="Theoretical Quantiles (χ²(3))",
        yaxis_title="Empirical Quantiles",
        xaxis={"scaleanchor": "y", "scaleratio": 1},
    )

    return fig


def plot_position_errors(position_errors: np.ndarray) -> go.Figure:
    """Plot 3D scatter of position errors.

    Args:
        position_errors: Array of position error vectors (n_trials x 3).

    Returns:
        Plotly figure with 3D scatter plot.
    """
    fig = go.Figure()

    fig.add_trace(
        go.Scatter3d(
            x=position_errors[:, 0],
            y=position_errors[:, 1],
            z=position_errors[:, 2],
            mode="markers",
            marker={
                "size": 2,
                "color": np.sqrt(np.sum(position_errors**2, axis=1)),
                "colorscale": "Viridis",
                "colorbar": {"title": "Error (m)"},
                "opacity": 0.6,
            },
            name="Position Errors",
        ),
    )

    # Add origin marker
    fig.add_trace(
        go.Scatter3d(
            x=[0],
            y=[0],
            z=[0],
            mode="markers",
            marker={"size": 8, "color": "#EF553B", "symbol": "cross"},
            name="True Position",
        ),
    )

    fig.update_layout(
        template=DARK_TEMPLATE,
        title="3D Position Error Distribution",
        scene={
            "xaxis_title": "X Error (m)",
            "yaxis_title": "Y Error (m)",
            "zaxis_title": "Z Error (m)",
            "aspectmode": "cube",
        },
    )

    return fig


def plot_noise_scaling(
    n_trials: int = 500,
    seed: int = 123,
) -> go.Figure:
    """Plot how position error scales with measurement noise.

    Args:
        n_trials: Number of Monte Carlo trials per noise level.
        seed: Random seed for reproducibility.

    Returns:
        Plotly figure showing error vs noise relationship.
    """
    rng = np.random.default_rng(seed)

    collector_xyz, rotations, target_true = _create_good_geometry()
    n_collectors = len(collector_xyz)
    true_aoa = get_aoa_quat(collector_xyz, target_true, rotations)

    noise_levels = np.array([0.0005, 0.001, 0.002, 0.003, 0.004, 0.005])
    mean_errors = []
    std_errors = []

    for aoa_sigma in noise_levels:
        aoa_cov = np.eye(2) * aoa_sigma**2
        aoa_covariances = np.tile(aoa_cov, (n_collectors, 1, 1))

        errors = []
        for _ in range(n_trials):
            noise = rng.standard_normal((n_collectors, 2)) * aoa_sigma
            noisy_aoa = true_aoa + noise

            result = aoa_locator(
                collector_xyz,
                rotations,
                noisy_aoa,
                aoa_covariances,
            )
            error = np.linalg.norm(result.position - target_true)
            errors.append(error)

        mean_errors.append(np.mean(errors))
        std_errors.append(np.std(errors))

    mean_errors = np.array(mean_errors)
    std_errors = np.array(std_errors)

    fig = go.Figure()

    # Error bars
    fig.add_trace(
        go.Scatter(
            x=noise_levels * 1000,  # Convert to mrad
            y=mean_errors,
            error_y={"type": "data", "array": std_errors, "visible": True},
            mode="markers+lines",
            name="Mean Position Error",
            marker={"size": 10, "color": "#636EFA"},
            line={"width": 2},
        ),
    )

    # Theoretical scaling (linear with noise for small angles)
    # Fit a line through origin to first point
    scale = mean_errors[1] / noise_levels[1]
    theoretical = noise_levels * scale

    fig.add_trace(
        go.Scatter(
            x=noise_levels * 1000,
            y=theoretical,
            mode="lines",
            name="Linear Scaling Reference",
            line={"color": "#EF553B", "dash": "dash", "width": 2},
        ),
    )

    fig.update_layout(
        template=DARK_TEMPLATE,
        title="Position Error vs AOA Measurement Noise",
        xaxis_title="AOA Noise (mrad, 1-sigma)",
        yaxis_title="Position Error (m)",
    )

    return fig


def _create_leo_constellation(
    n_collectors: int,
    target_xyz: np.ndarray,
) -> tuple[np.ndarray, Rotation]:
    """Create a LEO constellation around a target on Earth.

    Uses Fibonacci sphere distribution to ensure good 3D spread for any number
    of satellites, avoiding near-coplanar geometries that cause ill-conditioned
    solutions.

    Args:
        n_collectors: Number of satellites in the constellation.
        target_xyz: Target position on Earth surface.

    Returns:
        Tuple of (collector_xyz, rotations).
    """
    leo_radius = EARTH_RADIUS + LEO_ALTITUDE

    # Target direction (unit vector from Earth center to target)
    target_dir = target_xyz / np.linalg.norm(target_xyz)

    # Create orthonormal basis centered on target direction
    if abs(target_dir[2]) < 0.9:  # noqa: PLR2004
        ref = np.array([0.0, 0.0, 1.0])
    else:
        ref = np.array([1.0, 0.0, 0.0])
    east = np.cross(ref, target_dir)
    east = east / np.linalg.norm(east)
    north = np.cross(target_dir, east)

    collector_xyz = np.zeros((n_collectors, 3))
    rotations_list = []

    # Use Fibonacci sphere sampling for uniform distribution on a spherical cap
    # This ensures good 3D geometry for any number of satellites
    golden_ratio = (1 + np.sqrt(5)) / 2

    for i in range(n_collectors):
        # Fibonacci sphere: distribute points uniformly
        # Map index to elevation (30-70 deg from horizon = 20-60 deg zenith angle)
        t = (i + 0.5) / n_collectors
        zenith_angle = np.radians(20 + 40 * t)  # 20-60 degrees from target zenith

        # Golden angle for azimuth ensures no two satellites align
        azimuth = 2 * np.pi * i / golden_ratio

        # Direction to satellite from Earth center
        sat_dir = (
            np.cos(zenith_angle) * target_dir
            + np.sin(zenith_angle) * np.cos(azimuth) * east
            + np.sin(zenith_angle) * np.sin(azimuth) * north
        )

        collector_xyz[i] = leo_radius * sat_dir

        # Create rotation pointing toward nadir
        nadir = -sat_dir
        default_boresight = np.array([-1.0, 0.0, 0.0])
        axis = np.cross(default_boresight, nadir)
        axis_norm = np.linalg.norm(axis)
        if axis_norm > 1e-10:  # noqa: PLR2004
            axis = axis / axis_norm
            angle = np.arccos(np.clip(np.dot(default_boresight, nadir), -1.0, 1.0))
            rotations_list.append(Rotation.from_rotvec(axis * angle))
        else:
            rotations_list.append(Rotation.identity())

    return collector_xyz, Rotation.concatenate(rotations_list)


def plot_collector_count_effect() -> go.Figure:
    """Plot how covariance trace decreases with more LEO collectors.

    Returns:
        Plotly figure showing trace vs collector count.
    """
    aoa_sigma = 0.001
    aoa_cov = np.eye(2) * aoa_sigma**2

    collector_counts = [3, 4, 5, 6, 8, 10, 15, 20]
    traces = []

    # Emitter on Earth surface
    target_true = np.array(
        [
            EARTH_RADIUS * np.cos(np.radians(30)),
            0.0,
            EARTH_RADIUS * np.sin(np.radians(30)),
        ],
    )

    for n_collectors in collector_counts:
        collector_xyz, rotations = _create_leo_constellation(n_collectors, target_true)

        aoa_measurements = get_aoa_quat(collector_xyz, target_true, rotations)
        aoa_covariances = np.tile(aoa_cov, (n_collectors, 1, 1))

        result = aoa_locator(
            collector_xyz,
            rotations,
            aoa_measurements,
            aoa_covariances,
        )
        traces.append(np.trace(result.covariance))

    traces = np.array(traces)

    fig = go.Figure()

    fig.add_trace(
        go.Scatter(
            x=collector_counts,
            y=np.sqrt(traces),  # Convert to RMS position error
            mode="markers+lines",
            name="RMS Position Uncertainty",
            marker={"size": 10, "color": "#636EFA"},
            line={"width": 2},
        ),
    )

    # Theoretical 1/sqrt(n) scaling (normalized to n=3)
    ref_idx = 1  # n=3
    scale = np.sqrt(traces[ref_idx]) * np.sqrt(collector_counts[ref_idx])
    theoretical = scale / np.sqrt(collector_counts)

    fig.add_trace(
        go.Scatter(
            x=collector_counts,
            y=theoretical,
            mode="lines",
            name="1/√n Scaling Reference",
            line={"color": "#EF553B", "dash": "dash", "width": 2},
        ),
    )

    fig.update_layout(
        template=DARK_TEMPLATE,
        title="Position Uncertainty vs Number of Collectors",
        xaxis_title="Number of Collectors",
        yaxis_title="RMS Position Uncertainty (m)",
        xaxis={"type": "log"},
        yaxis={"type": "log"},
    )

    return fig


def plot_dashboard(n_trials: int = 1000) -> go.Figure:
    """Create a comprehensive dashboard with all Monte Carlo visualizations.

    Args:
        n_trials: Number of Monte Carlo trials.

    Returns:
        Plotly figure with subplots.
    """
    # Run Monte Carlo
    normalized_miss_sq, position_errors, _ = run_monte_carlo(n_trials=n_trials)

    # Create subplots
    fig = make_subplots(
        rows=2,
        cols=2,
        subplot_titles=[
            "χ² Distribution Validation",
            "Q-Q Plot",
            "Position Error Cloud (XY)",
            "Error vs Collector Count",
        ],
        specs=[
            [{"type": "xy"}, {"type": "xy"}],
            [{"type": "xy"}, {"type": "xy"}],
        ],
        vertical_spacing=0.12,
        horizontal_spacing=0.1,
    )

    # --- Subplot 1: Histogram ---
    x_theory = np.linspace(0, 15, 200)
    pdf_theory = chi2.pdf(x_theory, df=3)

    fig.add_trace(
        go.Histogram(
            x=normalized_miss_sq,
            nbinsx=40,
            histnorm="probability density",
            name="Empirical",
            marker_color="rgba(99, 110, 250, 0.7)",
            showlegend=True,
        ),
        row=1,
        col=1,
    )
    fig.add_trace(
        go.Scatter(
            x=x_theory,
            y=pdf_theory,
            mode="lines",
            name="χ²(3)",
            line={"color": "#EF553B", "width": 2},
        ),
        row=1,
        col=1,
    )

    # --- Subplot 2: Q-Q Plot ---
    sorted_data = np.sort(normalized_miss_sq)
    n = len(sorted_data)
    percentiles = (np.arange(1, n + 1) - 0.5) / n
    theoretical_quantiles = chi2.ppf(percentiles, df=3)

    fig.add_trace(
        go.Scatter(
            x=theoretical_quantiles,
            y=sorted_data,
            mode="markers",
            name="Q-Q",
            marker={"color": "#636EFA", "size": 3, "opacity": 0.5},
            showlegend=False,
        ),
        row=1,
        col=2,
    )
    max_val = max(theoretical_quantiles[-1], sorted_data[-1])
    fig.add_trace(
        go.Scatter(
            x=[0, max_val],
            y=[0, max_val],
            mode="lines",
            name="y=x",
            line={"color": "#EF553B", "dash": "dash"},
            showlegend=False,
        ),
        row=1,
        col=2,
    )

    # --- Subplot 3: Position errors XY view ---
    fig.add_trace(
        go.Scatter(
            x=position_errors[:, 1],
            y=position_errors[:, 2],
            mode="markers",
            name="Errors",
            marker={
                "color": np.sqrt(np.sum(position_errors**2, axis=1)),
                "colorscale": "Viridis",
                "size": 3,
                "opacity": 0.5,
            },
            showlegend=False,
        ),
        row=2,
        col=1,
    )
    fig.add_trace(
        go.Scatter(
            x=[0],
            y=[0],
            mode="markers",
            marker={"color": "#EF553B", "size": 10, "symbol": "cross"},
            showlegend=False,
        ),
        row=2,
        col=1,
    )

    # --- Subplot 4: Collector count effect (LEO geometry) ---
    aoa_sigma = 0.001
    aoa_cov = np.eye(2) * aoa_sigma**2
    collector_counts = [3, 4, 5, 6, 8, 10, 15, 20]
    traces_cov = []

    # Emitter on Earth surface
    target_true = np.array(
        [
            EARTH_RADIUS * np.cos(np.radians(30)),
            0.0,
            EARTH_RADIUS * np.sin(np.radians(30)),
        ],
    )

    for n_collectors in collector_counts:
        collector_xyz, rotations = _create_leo_constellation(n_collectors, target_true)
        aoa_measurements = get_aoa_quat(collector_xyz, target_true, rotations)
        aoa_covariances = np.tile(aoa_cov, (n_collectors, 1, 1))
        result = aoa_locator(
            collector_xyz,
            rotations,
            aoa_measurements,
            aoa_covariances,
        )
        traces_cov.append(np.sqrt(np.trace(result.covariance)))

    fig.add_trace(
        go.Scatter(
            x=collector_counts,
            y=traces_cov,
            mode="markers+lines",
            name="RMS Uncertainty",
            marker={"size": 8, "color": "#636EFA"},
            showlegend=False,
        ),
        row=2,
        col=2,
    )

    # Statistics text
    mean_d_sq = np.mean(normalized_miss_sq)
    var_d_sq = np.var(normalized_miss_sq)
    _, p_value = kstest(normalized_miss_sq, chi2(3).cdf)

    fig.add_annotation(
        x=0.25,
        y=0.52,
        xref="paper",
        yref="paper",
        text=(
            f"<b>Stats:</b> μ={mean_d_sq:.2f} (3.0), "
            f"σ²={var_d_sq:.2f} (6.0), KS p={p_value:.3f}"
        ),
        showarrow=False,
        font={"size": 11},
        bgcolor="rgba(0,0,0,0.5)",
    )

    # Update axes labels
    fig.update_xaxes(title_text="d²", row=1, col=1)
    fig.update_yaxes(title_text="Density", row=1, col=1)
    fig.update_xaxes(title_text="Theoretical", row=1, col=2)
    fig.update_yaxes(title_text="Empirical", row=1, col=2)
    fig.update_xaxes(title_text="Y Error (m)", row=2, col=1)
    fig.update_yaxes(title_text="Z Error (m)", row=2, col=1)
    fig.update_xaxes(title_text="# Collectors", type="log", row=2, col=2)
    fig.update_yaxes(title_text="RMS (m)", type="log", row=2, col=2)

    fig.update_layout(
        template=DARK_TEMPLATE,
        title=f"AOA Monte Carlo Validation Dashboard (n={n_trials})",
        height=800,
        showlegend=True,
        legend={"x": 0.4, "y": 0.55},
    )

    return fig


def main() -> None:
    """Run all visualizations."""
    normalized_miss_sq, position_errors, _ = run_monte_carlo(n_trials=1000)

    # Individual plots
    fig1 = plot_chi2_histogram(normalized_miss_sq)
    fig1.show()

    fig2 = plot_qq(normalized_miss_sq)
    fig2.show()

    fig3 = plot_position_errors(position_errors)
    fig3.show()

    fig4 = plot_noise_scaling()
    fig4.show()

    fig5 = plot_collector_count_effect()
    fig5.show()

    # Dashboard
    fig_dashboard = plot_dashboard(n_trials=1000)
    fig_dashboard.show()


if __name__ == "__main__":
    main()
