import plotly.graph_objects as go
import plotly.express as px
import numpy as np
from src.analysis.geospatial import create_circle_points, haversine_distance
from src.config import config
import pandas as pd

def plot_turbine_map(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame) -> go.Figure:
    turbine_lat = turbine_row['lat']
    turbine_lon = turbine_row['lng']
    fig = go.Figure()
    # Plot all lightnings
    lightning_colors = []
    for _, lightning in lightning_df.iterrows():
        d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
        color = 'gray'
        for ring, ring_color in zip(config.distance_rings, config.ring_colors):
            if d <= ring:
                color = ring_color
                break
        lightning_colors.append(color)
    lightning_sizes = np.clip(lightning_df['current_abs'] / 1500, 3, 12)
    fig.add_trace(go.Scattermapbox(
        lat=lightning_df['lat'],
        lon=lightning_df['lng'],
        mode='markers',
        marker=dict(size=lightning_sizes, color=lightning_colors, opacity=0.8, symbol='circle', sizemin=3),
        name='Lightning Strikes',
        showlegend=True
    ))

    # Add distance circles
    for radius, color in zip(config.distance_rings, config.ring_colors):
        circle_lats, circle_lons = create_circle_points(turbine_lat, turbine_lon, radius)
        fig.add_trace(go.Scattermapbox(
            lat=circle_lats,
            lon=circle_lons,
            mode='lines',
            line=dict(color=color, width=2),
            opacity=0.6,
            name=f'{radius/1000:.1f}km Distance Ring',
            showlegend=True
        ))
    # Add turbines colored by risk using fixed intervals
    if 'risk_log' in turbine_df.columns:
        from src.utils import get_turbine_colors_by_fixed_intervals
        turbine_colors = get_turbine_colors_by_fixed_intervals(turbine_df['risk_log'].tolist())
    else:
        turbine_colors = ['red'] * len(turbine_df)
    fig.add_trace(go.Scattermapbox(
        lat=turbine_df['lat'],
        lon=turbine_df['lng'],
        mode='markers+text',
        marker=dict(size=16, color=turbine_colors, symbol='circle', opacity=1),
        text=['T'] * len(turbine_df),
        textfont=dict(size=10, color='black'),
        textposition='middle center',
        name='Wind Turbines',
        showlegend=True
    ))
    # Calculate zoom level based on the largest distance ring
    max_radius_m = max(config.distance_rings)
    
    # Convert meters to degrees (approximate)
    # 1 degree latitude ≈ 111,000 meters
    # 1 degree longitude ≈ 111,000 * cos(latitude) meters
    lat_degrees = max_radius_m / 111000
    lon_degrees = max_radius_m / (111000 * np.cos(np.radians(turbine_lat)))
    
    # Calculate the required map span to show the largest circle
    # We want the circle to almost touch the top and bottom edges
    # Map aspect ratio is roughly 4:3 (width:height)
    # We need to account for the circle diameter (2 * radius) plus minimal padding
    
    # Calculate the required span in degrees
    required_lat_span = 2 * lat_degrees * 1.1  # 10% padding
    required_lon_span = 2 * lon_degrees * 1.1  # 10% padding
    
    # Use the larger span to ensure the circle fits
    required_span = max(required_lat_span, required_lon_span)
    
    # Calculate zoom level based on required span
    # Mapbox zoom levels: each level doubles the scale
    # At zoom 0: 360 degrees longitude spans the map
    # At zoom 1: 180 degrees longitude spans the map
    # At zoom 2: 90 degrees longitude spans the map
    # Formula: zoom = log2(360 / required_span)
    
    import math
    zoom_level = math.log2(360 / required_span)
    
    # Clamp zoom level to reasonable bounds
    zoom_level = max(8, min(15, zoom_level))
    
    fig.update_layout(
        mapbox=dict(
            style='carto-positron',
            center=dict(lat=turbine_lat, lon=turbine_lon),
            zoom=zoom_level
        ),
        margin=dict(l=0, r=0, t=0, b=0),
        showlegend=True,
        legend=dict(
            x=0.02,
            y=0.98,
            bgcolor='rgba(255, 255, 255, 0.8)',
            bordercolor='black',
            borderwidth=1
        )
    )
    return fig

def plot_intercloud_map(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame) -> go.Figure:
    """Create a map showing only intercloud lightning strikes."""
    turbine_lat = turbine_row['lat']
    turbine_lon = turbine_row['lng']
    fig = go.Figure()
    
    # Filter for intercloud lightning only (p_type != '0')
    ic_mask = (lightning_df['p_type'].astype(str) != '0')
    ic_lightning_df = lightning_df[ic_mask]
    
    if len(ic_lightning_df) > 0:
        # Plot intercloud lightnings
        lightning_colors = []
        for _, lightning in ic_lightning_df.iterrows():
            d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
            color = 'gray'
            for ring, ring_color in zip(config.distance_rings, config.ring_colors):
                if d <= ring:
                    color = ring_color
                    break
            lightning_colors.append(color)
        
        lightning_sizes = np.clip(ic_lightning_df['current_abs'] / 1500, 3, 12)
        fig.add_trace(go.Scattermapbox(
            lat=ic_lightning_df['lat'],
            lon=ic_lightning_df['lng'],
            mode='markers',
            marker=dict(size=lightning_sizes, color=lightning_colors, opacity=0.8, symbol='circle', sizemin=3),
            name='Intercloud Lightning',
            showlegend=True
        ))
    
    # Add distance circles
    for radius, color in zip(config.distance_rings, config.ring_colors):
        circle_lats, circle_lons = create_circle_points(turbine_lat, turbine_lon, radius)
        fig.add_trace(go.Scattermapbox(
            lat=circle_lats,
            lon=circle_lons,
            mode='lines',
            line=dict(color=color, width=2),
            opacity=0.6,
            name=f'{radius/1000:.1f}km Distance Ring',
            showlegend=True
        ))
    
    # Add turbines colored by risk
    if 'risk_log' in turbine_df.columns:
        from src.utils import get_turbine_colors_by_fixed_intervals
        turbine_colors = get_turbine_colors_by_fixed_intervals(turbine_df["risk_log"].tolist())
        norm_risk = (turbine_df['risk_log'] - turbine_df['risk_log'].min()) / (turbine_df['risk_log'].max() - turbine_df['risk_log'].min() + 1e-9)
    else:
        turbine_colors = ['red'] * len(turbine_df)
    
    fig.add_trace(go.Scattermapbox(
        lat=turbine_df['lat'],
        lon=turbine_df['lng'],
        mode='markers+text',
        marker=dict(size=16, color=turbine_colors, symbol='circle', opacity=1),
        text=['T'] * len(turbine_df),
        textfont=dict(size=10, color='black'),
        textposition='middle center',
        name='Wind Turbines',
        showlegend=True
    ))
    
    # Calculate zoom level based on the largest distance ring
    max_radius_m = max(config.distance_rings)
    
    # Convert meters to degrees (approximate)
    lat_degrees = max_radius_m / 111000
    lon_degrees = max_radius_m / (111000 * np.cos(np.radians(turbine_lat)))
    
    # Calculate the required map span to show the largest circle
    required_lat_span = 2 * lat_degrees * 1.1
    required_lon_span = 2 * lon_degrees * 1.1
    required_span = max(required_lat_span, required_lon_span)
    
    # Calculate zoom level based on required span
    import math
    zoom_level = math.log2(360 / required_span)
    
    # Clamp zoom level to reasonable bounds
    zoom_level = max(8, min(15, zoom_level))
    
    fig.update_layout(
        mapbox=dict(
            style='carto-positron',
            center=dict(lat=turbine_lat, lon=turbine_lon),
            zoom=zoom_level
        ),
        margin=dict(l=0, r=0, t=0, b=0),
        showlegend=True,
        legend=dict(
            x=0.02,
            y=0.98,
            bgcolor='rgba(255, 255, 255, 0.8)',
            bordercolor='black',
            borderwidth=1
        )
    )
    return fig

def plot_cloud_to_ground_map(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame) -> go.Figure:
    """Create a map showing only cloud-to-ground lightning strikes."""
    turbine_lat = turbine_row['lat']
    turbine_lon = turbine_row['lng']
    fig = go.Figure()
    
    # Filter for cloud-to-ground lightning only (p_type == '0')
    cg_mask = (lightning_df['p_type'].astype(str) == '0')
    cg_lightning_df = lightning_df[cg_mask]
    
    if len(cg_lightning_df) > 0:
        # Plot cloud-to-ground lightnings
        lightning_colors = []
        for _, lightning in cg_lightning_df.iterrows():
            d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
            color = 'gray'
            for ring, ring_color in zip(config.distance_rings, config.ring_colors):
                if d <= ring:
                    color = ring_color
                    break
            lightning_colors.append(color)
        
        lightning_sizes = np.clip(cg_lightning_df['current_abs'] / 1500, 3, 12)
        fig.add_trace(go.Scattermapbox(
            lat=cg_lightning_df['lat'],
            lon=cg_lightning_df['lng'],
            mode='markers',
            marker=dict(size=lightning_sizes, color=lightning_colors, opacity=0.8, symbol='circle', sizemin=3),
            name='Cloud-to-Ground Lightning',
            showlegend=True
        ))
    
    # Add distance circles
    for radius, color in zip(config.distance_rings, config.ring_colors):
        circle_lats, circle_lons = create_circle_points(turbine_lat, turbine_lon, radius)
        fig.add_trace(go.Scattermapbox(
            lat=circle_lats,
            lon=circle_lons,
            mode='lines',
            line=dict(color=color, width=2),
            opacity=0.6,
            name=f'{radius/1000:.1f}km Distance Ring',
            showlegend=True
        ))
    
    # Add turbines colored by risk
    if 'risk_log' in turbine_df.columns:
        from src.utils import get_turbine_colors_by_fixed_intervals
        turbine_colors = get_turbine_colors_by_fixed_intervals(turbine_df["risk_log"].tolist())
        norm_risk = (turbine_df['risk_log'] - turbine_df['risk_log'].min()) / (turbine_df['risk_log'].max() - turbine_df['risk_log'].min() + 1e-9)
    else:
        turbine_colors = ['red'] * len(turbine_df)
    
    fig.add_trace(go.Scattermapbox(
        lat=turbine_df['lat'],
        lon=turbine_df['lng'],
        mode='markers+text',
        marker=dict(size=16, color=turbine_colors, symbol='circle', opacity=1),
        text=['T'] * len(turbine_df),
        textfont=dict(size=10, color='black'),
        textposition='middle center',
        name='Wind Turbines',
        showlegend=True
    ))
    
    # Calculate zoom level based on the largest distance ring
    max_radius_m = max(config.distance_rings)
    
    # Convert meters to degrees (approximate)
    lat_degrees = max_radius_m / 111000
    lon_degrees = max_radius_m / (111000 * np.cos(np.radians(turbine_lat)))
    
    # Calculate the required map span to show the largest circle
    required_lat_span = 2 * lat_degrees * 1.1
    required_lon_span = 2 * lon_degrees * 1.1
    required_span = max(required_lat_span, required_lon_span)
    
    # Calculate zoom level based on required span
    import math
    zoom_level = math.log2(360 / required_span)
    
    # Clamp zoom level to reasonable bounds
    zoom_level = max(8, min(15, zoom_level))
    
    fig.update_layout(
        mapbox=dict(
            style='carto-positron',
            center=dict(lat=turbine_lat, lon=turbine_lon),
            zoom=zoom_level
        ),
        margin=dict(l=0, r=0, t=0, b=0),
        showlegend=True,
        legend=dict(
            x=0.02,
            y=0.98,
            bgcolor='rgba(255, 255, 255, 0.8)',
            bordercolor='black',
            borderwidth=1
        )
    )
    return fig

def plot_coordinate_plane(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame, storm_data: list = None) -> go.Figure:
    """
    Create a coordinate plane visualization where:
    - X-axis = Longitude values
    - Y-axis = Latitude values
    - Turbines, lightnings, and storms are plotted as points
    """
    turbine_lat = turbine_row['lat']
    turbine_lon = turbine_row['lng']
    
    fig = go.Figure()
    
    # Add distance rings as circles on the coordinate plane (background layer)
    for radius, color in zip(config.distance_rings, config.ring_colors):
        # Convert radius from meters to degrees (approximate)
        radius_deg = radius / 111000  # 1 degree ≈ 111,000 meters
        
        # Create circle points in lat/lon space
        circle_lats, circle_lons = create_circle_points(turbine_lat, turbine_lon, radius)
        
        fig.add_trace(go.Scatter(
            x=circle_lons,  # X-axis = Longitude
            y=circle_lats,  # Y-axis = Latitude
            mode='lines',
            line=dict(color=color, width=2),
            opacity=0.6,
            name=f'{radius/1000:.1f}km Distance Ring',
            showlegend=True
        ))
    
    # Add turbines (middle layer)
    if 'risk_log' in turbine_df.columns:
        from src.utils import get_turbine_colors_by_fixed_intervals
        turbine_colors = get_turbine_colors_by_fixed_intervals(turbine_df["risk_log"].tolist())
        norm_risk = (turbine_df['risk_log'] - turbine_df['risk_log'].min()) / (turbine_df['risk_log'].max() - turbine_df['risk_log'].min() + 1e-9)
    else:
        turbine_colors = ['red'] * len(turbine_df)
    
    fig.add_trace(go.Scatter(
        x=turbine_df['lng'],  # X-axis = Longitude
        y=turbine_df['lat'],  # Y-axis = Latitude
        mode='markers+text',
        marker=dict(
            size=30, 
            color=turbine_colors, 
            symbol='triangle-down', 
            opacity=1,
            line=dict(color='black', width=1)
        ),
        text=turbine_df['name'].tolist(),
        textfont=dict(size=18, color='black'),  # turbine name label font size
        textposition='middle center',
        name='Wind Turbines',
        showlegend=True
    ))
    
    # Add storms if available (middle layer)
    if storm_data is not None and len(storm_data) > 0:
        # Convert storm data to DataFrame for easier handling
        storm_df = pd.DataFrame(storm_data)
        if 'lat' in storm_df.columns and 'lng' in storm_df.columns:
            fig.add_trace(go.Scatter(
                x=storm_df['lng'],  # X-axis = Longitude
                y=storm_df['lat'],  # Y-axis = Latitude
                mode='markers+text',
                marker=dict(
                    size=20, 
                    color='purple', 
                    symbol='star', 
                    opacity=0.8,
                    line=dict(color='black', width=1)
                ),
                text=['S'] * len(storm_df),
                textfont=dict(size=18, color='white'),  # storm "S" label font size
                textposition='middle center',
                name='Storm Cells',
                showlegend=True
            ))
    
    # Plot lightning strikes (foreground layer - always on top)
    lightning_colors = []
    for _, lightning in lightning_df.iterrows():
        d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
        color = 'gray'
        for ring, ring_color in zip(config.distance_rings, config.ring_colors):
            if d <= ring:
                color = ring_color
                break
        lightning_colors.append(color)
    
    lightning_sizes = np.clip(lightning_df['current_abs'] / 1500, 3, 12)
    
    fig.add_trace(go.Scatter(
        x=lightning_df['lng'],  # X-axis = Longitude
        y=lightning_df['lat'],  # Y-axis = Latitude
        mode='markers',
        marker=dict(
            size=lightning_sizes, 
            color=lightning_colors, 
            opacity=0.8, 
            symbol='circle', 
            sizemin=3
        ),
        name='Lightning Strikes',
        showlegend=True
    ))
    
    # Calculate axis limits based on data and distance rings
    max_radius_deg = max(config.distance_rings) / 111000
    
    lat_min = turbine_lat - max_radius_deg * 1.5
    lat_max = turbine_lat + max_radius_deg * 1.5
    lon_min = turbine_lon - max_radius_deg * 1.5
    lon_max = turbine_lon + max_radius_deg * 1.5
    
    fig.update_layout(
        title=f'Coordinate Plane View - Central Turbine: {turbine_row["name"]}',
        xaxis=dict(
            title=dict(text='Longitude', font=dict(size=18)),
            tickfont=dict(size=16),
            range=[lon_min, lon_max],
            showgrid=True,
            gridwidth=1,
            gridcolor='lightgray',
            zeroline=False
        ),
        yaxis=dict(
            title=dict(text='Latitude', font=dict(size=18)),
            tickfont=dict(size=16),
            range=[lat_min, lat_max],
            showgrid=True,
            gridwidth=1,
            gridcolor='lightgray',
            zeroline=False
        ),
        plot_bgcolor='white',
        paper_bgcolor='white',
        showlegend=True,
        legend=dict(
            title=dict(text='Legend', font=dict(size=20)),
            font=dict(size=17),
            orientation='h',
            x=0.5,
            xanchor='center',
            y=-0.15,
            yanchor='top',
            bgcolor='rgba(255, 255, 255, 0.8)',
            bordercolor='black',
            borderwidth=1,
            itemsizing='constant',
            itemwidth=30,
        ),
        width=800,
        height=900
    )
    
    return fig

def plot_intercloud_coordinate_plane(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame, storm_data: list = None, precomputed: dict | None = None) -> go.Figure:
    """Create a coordinate plane showing only intercloud lightning strikes."""
    turbine_lat = turbine_row['lat']
    turbine_lon = turbine_row['lng']
    
    fig = go.Figure()
    
    # Add distance rings (background layer)
    for radius, color in zip(config.distance_rings, config.ring_colors):
        circle_lats, circle_lons = create_circle_points(turbine_lat, turbine_lon, radius)
        
        fig.add_trace(go.Scatter(
            x=circle_lons,  # X-axis = Longitude
            y=circle_lats,  # Y-axis = Latitude
            mode='lines',
            line=dict(color=color, width=2),
            opacity=0.6,
            name=f'{radius/1000:.1f}km Distance Ring',
            showlegend=True
        ))
    
    # Add turbines (middle layer)
    if 'risk_log' in turbine_df.columns:
        from src.utils import get_turbine_colors_by_fixed_intervals
        turbine_colors = get_turbine_colors_by_fixed_intervals(turbine_df["risk_log"].tolist())
        norm_risk = (turbine_df['risk_log'] - turbine_df['risk_log'].min()) / (turbine_df['risk_log'].max() - turbine_df['risk_log'].min() + 1e-9)
    else:
        turbine_colors = ['red'] * len(turbine_df)
    
    fig.add_trace(go.Scatter(
        x=turbine_df['lng'],  # X-axis = Longitude
        y=turbine_df['lat'],  # Y-axis = Latitude
        mode='markers+text',
        marker=dict(
            size=30, 
            color=turbine_colors, 
            symbol='triangle-down', 
            opacity=1,
            line=dict(color='black', width=1)
        ),
        text=turbine_df['name'].tolist(),
        textfont=dict(size=24, color='black'),
        textposition='middle center',
        name='Wind Turbines',
        showlegend=True
    ))
    
    # Add storms if available (middle layer)
    if storm_data is not None and len(storm_data) > 0:
        # Convert storm data to DataFrame for easier handling
        storm_df = pd.DataFrame(storm_data)
        if 'lat' in storm_df.columns and 'lng' in storm_df.columns:
            fig.add_trace(go.Scatter(
                x=storm_df['lng'],  # X-axis = Longitude
                y=storm_df['lat'],  # Y-axis = Latitude
                mode='markers+text',
                marker=dict(
                    size=20, 
                    color='purple', 
                    symbol='star', 
                    opacity=0.8,
                    line=dict(color='black', width=1)
                ),
                text=['S'] * len(storm_df),
                textfont=dict(size=24, color='white'),
                textposition='middle center',
                name='Storm Cells',
                showlegend=True
            ))
    
    # Filter for intercloud lightning only (p_type != '0')
    ic_mask = (lightning_df['p_type'].astype(str) != '0')
    ic_lightning_df = lightning_df[ic_mask]
    
    if len(ic_lightning_df) > 0:
        # Plot intercloud lightnings (foreground layer - always on top)
        lightning_colors = []
        if precomputed is not None and 'ring_idx' in precomputed and len(precomputed['ring_idx']) == len(lightning_df):
            mask_ic = (lightning_df['p_type'].astype(str) != '0').values
            ring_idx = precomputed['ring_idx'][mask_ic]
            for ri in ring_idx:
                if 0 <= int(ri) < len(config.ring_colors):
                    lightning_colors.append(config.ring_colors[int(ri)])
                else:
                    lightning_colors.append('gray')
        else:
            for _, lightning in ic_lightning_df.iterrows():
                d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
                color = 'gray'
                for ring, ring_color in zip(config.distance_rings, config.ring_colors):
                    if d <= ring:
                        color = ring_color
                        break
                lightning_colors.append(color)
        
        lightning_sizes = np.clip(ic_lightning_df['current_abs'] / 1500, 3, 12)
        
        fig.add_trace(go.Scatter(
            x=ic_lightning_df['lng'],  # X-axis = Longitude
            y=ic_lightning_df['lat'],  # Y-axis = Latitude
            mode='markers',
            marker=dict(
                size=lightning_sizes, 
                color=lightning_colors, 
                opacity=0.8, 
                symbol='circle', 
                sizemin=3
            ),
            name='Intercloud Lightning',
            showlegend=True
        ))
    
    # Calculate axis limits
    max_radius_deg = max(config.distance_rings) / 111000
    
    lat_min = turbine_lat - max_radius_deg * 1.5
    lat_max = turbine_lat + max_radius_deg * 1.5
    lon_min = turbine_lon - max_radius_deg * 1.5
    lon_max = turbine_lon + max_radius_deg * 1.5
    
    fig.update_layout(
        title=f'Intercloud Lightning - Coordinate Plane View - Central Turbine: {turbine_row["name"]}',
        xaxis=dict(
            title=dict(text='Longitude', font=dict(size=28)),  # x-axis title font size
            tickfont=dict(size=22),  # x-axis tick label font size
            range=[lon_min, lon_max],
            showgrid=True,
            gridwidth=1,
            gridcolor='lightgray',
            zeroline=False
        ),
        yaxis=dict(
            title=dict(text='Latitude', font=dict(size=28)),  # y-axis title font size
            tickfont=dict(size=22),  # y-axis tick label font size
            range=[lat_min, lat_max],
            showgrid=True,
            gridwidth=1,
            gridcolor='lightgray',
            zeroline=False
        ),
        plot_bgcolor='white',
        paper_bgcolor='white',
        showlegend=True,
        legend=dict(
            title=dict(text='Legend', font=dict(size=24)),  # legend title font size
            font=dict(size=20),  # legend item font size
            orientation='h',
            x=0.5,
            xanchor='center',
            y=-0.22,
            yanchor='top',
            bgcolor='rgba(255, 255, 255, 0.8)',
            bordercolor='black',
            borderwidth=1,
            itemsizing='constant',
            itemwidth=30,
        ),
        width=950,
        height=950,
        margin=dict(l=40, r=40, t=80, b=220),
        font=dict(size=18),  # global chart font size
    )
    
    return fig

def plot_cloud_to_ground_coordinate_plane(turbine_row: pd.Series, lightning_df: pd.DataFrame, turbine_df: pd.DataFrame, storm_data: list = None, precomputed: dict | None = None) -> go.Figure:
    """Create a coordinate plane showing only cloud-to-ground lightning strikes."""
    turbine_lat = turbine_row['lat']
    turbine_lon = turbine_row['lng']
    
    fig = go.Figure()
    
    # Add distance rings (background layer)
    for radius, color in zip(config.distance_rings, config.ring_colors):
        circle_lats, circle_lons = create_circle_points(turbine_lat, turbine_lon, radius)
        
        fig.add_trace(go.Scatter(
            x=circle_lons,  # X-axis = Longitude
            y=circle_lats,  # Y-axis = Latitude
            mode='lines',
            line=dict(color=color, width=2),
            opacity=0.6,
            name=f'{radius/1000:.1f}km Distance Ring',
            showlegend=True
        ))
    
    # Add turbines (middle layer)
    if 'risk_log' in turbine_df.columns:
        from src.utils import get_turbine_colors_by_fixed_intervals
        turbine_colors = get_turbine_colors_by_fixed_intervals(turbine_df["risk_log"].tolist())
        norm_risk = (turbine_df['risk_log'] - turbine_df['risk_log'].min()) / (turbine_df['risk_log'].max() - turbine_df['risk_log'].min() + 1e-9)
    else:
        turbine_colors = ['red'] * len(turbine_df)
    
    fig.add_trace(go.Scatter(
        x=turbine_df['lng'],  # X-axis = Longitude
        y=turbine_df['lat'],  # Y-axis = Latitude
        mode='markers+text',
        marker=dict(
            size=30, 
            color=turbine_colors, 
            symbol='triangle-down', 
            opacity=1,
            line=dict(color='black', width=1)
        ),
        text=turbine_df['name'].tolist(),
        textfont=dict(size=18, color='black'),  # turbine name label font size
        textposition='middle center',
        name='Wind Turbines',
        showlegend=True
    ))
    
    # Add storms if available (middle layer)
    if storm_data is not None and len(storm_data) > 0:
        # Convert storm data to DataFrame for easier handling
        storm_df = pd.DataFrame(storm_data)
        if 'lat' in storm_df.columns and 'lng' in storm_df.columns:
            fig.add_trace(go.Scatter(
                x=storm_df['lng'],  # X-axis = Longitude
                y=storm_df['lat'],  # Y-axis = Latitude
                mode='markers+text',
                marker=dict(
                    size=20, 
                    color='purple', 
                    symbol='star', 
                    opacity=0.8,
                    line=dict(color='black', width=1)
                ),
                text=['S'] * len(storm_df),
                textfont=dict(size=18, color='white'),  # storm "S" label font size
                textposition='middle center',
                name='Storm Cells',
                showlegend=True
            ))
    
    # Filter for cloud-to-ground lightning only (p_type == '0')
    cg_mask = (lightning_df['p_type'].astype(str) == '0')
    cg_lightning_df = lightning_df[cg_mask]
    
    if len(cg_lightning_df) > 0:
        # Plot cloud-to-ground lightnings (foreground layer - always on top)
        lightning_colors = []
        if precomputed is not None and 'ring_idx' in precomputed and len(precomputed['ring_idx']) == len(lightning_df):
            mask_cg = (lightning_df['p_type'].astype(str) == '0').values
            ring_idx = precomputed['ring_idx'][mask_cg]
            for ri in ring_idx:
                if 0 <= int(ri) < len(config.ring_colors):
                    lightning_colors.append(config.ring_colors[int(ri)])
                else:
                    lightning_colors.append('gray')
        else:
            for _, lightning in cg_lightning_df.iterrows():
                d = haversine_distance(turbine_lat, turbine_lon, lightning['lat'], lightning['lng'])
                color = 'gray'
                for ring, ring_color in zip(config.distance_rings, config.ring_colors):
                    if d <= ring:
                        color = ring_color
                        break
                lightning_colors.append(color)
        
        lightning_sizes = np.clip(cg_lightning_df['current_abs'] / 1500, 3, 12)
        
        fig.add_trace(go.Scatter(
            x=cg_lightning_df['lng'],  # X-axis = Longitude
            y=cg_lightning_df['lat'],  # Y-axis = Latitude
            mode='markers',
            marker=dict(
                size=lightning_sizes, 
                color=lightning_colors, 
                opacity=0.8, 
                symbol='circle', 
                sizemin=3
            ),
            name='Cloud-to-Ground Lightning',
            showlegend=True
        ))
    
    # Calculate axis limits
    max_radius_deg = max(config.distance_rings) / 111000
    
    lat_min = turbine_lat - max_radius_deg * 1.5
    lat_max = turbine_lat + max_radius_deg * 1.5
    lon_min = turbine_lon - max_radius_deg * 1.5
    lon_max = turbine_lon + max_radius_deg * 1.5
    
    fig.update_layout(
        title=f'Cloud-to-Ground Lightning - Coordinate Plane View - Central Turbine: {turbine_row["name"]}',
        xaxis=dict(
            title=dict(text='Longitude', font=dict(size=28)),  # x-axis title font size
            tickfont=dict(size=22),  # x-axis tick label font size
            range=[lon_min, lon_max],
            showgrid=True,
            gridwidth=1,
            gridcolor='lightgray',
            zeroline=False
        ),
        yaxis=dict(
            title=dict(text='Latitude', font=dict(size=28)),  # y-axis title font size
            tickfont=dict(size=22),  # y-axis tick label font size
            range=[lat_min, lat_max],
            showgrid=True,
            gridwidth=1,
            gridcolor='lightgray',
            zeroline=False
        ),
        plot_bgcolor='white',
        paper_bgcolor='white',
        showlegend=True,
        legend=dict(
            title=dict(text='Legend', font=dict(size=24)),  # legend title font size
            font=dict(size=20),  # legend item font size
            orientation='h',
            x=0.5,
            xanchor='center',
            y=-0.22,
            yanchor='top',
            bgcolor='rgba(255, 255, 255, 0.8)',
            bordercolor='black',
            borderwidth=1,
            itemsizing='constant',
            itemwidth=30,
        ),
        width=950,
        height=950,
        margin=dict(l=40, r=40, t=80, b=220),
        font=dict(size=18),  # global chart font size
    )
    
    return fig

def save_map_image(fig, filename):
    fig.write_image(filename, format='png', scale=2, engine='kaleido')

def create_risk_score_chart() -> go.Figure:
    """
    Create a risk score chart showing the relationship between distance, current magnitude, and risk scores.
    This helps readers understand how risk scores are calculated and what the min/max values could be.
    """
    import numpy as np
    from src.config import config
    
    # Get risk parameters
    P_0 = config.risk_params['P_0']
    alpha = config.risk_params['alpha']
    current_weight = config.risk_params['current_weight']
    
    # Create distance and current ranges
    distances_km = np.linspace(0.1, 5.0, 50)  # 0.1 to 5 km
    currents_amp = np.linspace(1000, 50000, 50)  # 1kA to 50kA
    
    # Create meshgrid for 3D plotting
    D, C = np.meshgrid(distances_km, currents_amp)
    
    # Calculate risk scores using the same formula as in risk calculation
    current_factor = 1 + current_weight * C / 10000
    distance_factor = np.exp(-alpha * D)
    risk_scores = P_0 * current_factor * distance_factor
    
    # Create the 3D surface plot
    fig = go.Figure(data=[go.Surface(
        x=D,
        y=C,
        z=risk_scores,
        colorscale='Viridis',
        colorbar=dict(
            title="Risk Score",
            tickformat=".3f"
        ),
        hovertemplate=(
            "<b>Risk Score: %{z:.3f}</b><br>" +
            "Distance: %{x:.2f} km<br>" +
            "Current: %{y:.0f} A<br>" +
            "<extra></extra>"
        )
    )])
    
    # Update layout
    fig.update_layout(
        title={
            'text': 'Risk Score Calculation Chart<br><sub>Shows how distance and current magnitude affect risk scores</sub>',
            'x': 0.5,
            'xanchor': 'center',
            'font': {'size': 16}
        },
        scene=dict(
            xaxis_title='Distance (km)',
            yaxis_title='Current Magnitude (A)',
            zaxis_title='Risk Score',
            xaxis=dict(
                range=[0, 5],
                tickmode='linear',
                tick0=0,
                dtick=1,
                gridcolor='lightgray',
                zerolinecolor='lightgray'
            ),
            yaxis=dict(
                range=[0, 50000],
                tickmode='linear',
                tick0=0,
                dtick=10000,
                gridcolor='lightgray',
                zerolinecolor='lightgray'
            ),
            zaxis=dict(
                gridcolor='lightgray',
                zerolinecolor='lightgray'
            ),
            camera=dict(
                eye=dict(x=1.5, y=1.5, z=1.2)
            )
        ),
        width=900,
        height=700,
        margin=dict(l=0, r=0, t=80, b=0)
    )
    
    return fig

def create_risk_score_heatmap() -> go.Figure:
    """
    Create a 2D heatmap showing risk scores for different distance and current combinations.
    This provides a clearer view of the risk calculation relationship.
    """
    import numpy as np
    from src.config import config
    
    # Get risk parameters
    P_0 = config.risk_params['P_0']
    alpha = config.risk_params['alpha']
    current_weight = config.risk_params['current_weight']
    
    # Create distance and current ranges
    # Use distance rings from config, convert from meters to kilometers
    max_distance_km = max(config.distance_rings) / 1000
    distances_km = np.linspace(0.1, max_distance_km, 50)
    currents_amp = np.linspace(1000, 300000, 50)  # Extended to 300k amps
    
    # Create meshgrid (swapped: current on x-axis, distance on y-axis)
    C, D = np.meshgrid(currents_amp, distances_km)
    
    # Calculate risk scores
    current_factor = 1 + current_weight * C / 10000
    distance_factor = np.exp(-alpha * D)
    risk_scores = P_0 * current_factor * distance_factor
    
    # Create custom colorscale using the new color palette (normalized to 0-1)
    custom_colorscale = [
        [0.0, '#577590'],    # Blue - Very Low Risk
        [0.067, '#43AA8B'],  # Teal - Low Risk (0.1/1.5)
        [0.133, '#90BE6D'],  # Green - Med-Low Risk (0.2/1.5)
        [0.267, '#F9C74F'],  # Yellow - Medium Risk (0.4/1.5)
        [0.4, '#F8961E'],    # Orange - Med-High Risk (0.6/1.5)
        [0.533, '#F3722C'],  # Dark Orange - High Risk (0.8/1.5)
        [0.667, '#F94144'],  # Red - Very High Risk (1.0/1.5)
        [0.8, '#D32F2F'],    # Darker Red - Critical Risk (1.2/1.5)
        [0.933, '#B71C1C'],  # Deepest Red - Maximum Risk (1.4/1.5)
        [1.0, '#8B0000']     # Dark Red - Maximum Risk (1.5/1.5)
    ]
    
    # Normalize risk scores to 0-1 range for colorscale (matching our fixed intervals)
    normalized_risk = np.clip(risk_scores, 0, 1.5) / 1.5
    
    # Create heatmap (swapped axes: current on x-axis, distance on y-axis)
    fig = go.Figure(data=go.Heatmap(
        z=normalized_risk,
        x=currents_amp,
        y=distances_km,
        colorscale=custom_colorscale,
        colorbar=dict(
            title="Risk Level",
            tickmode='array',
            tickvals=[0.01, 0.13, 0.26, 0.4, 0.53, 0.66, 0.8, 0.93],
            ticktext=['Very Low: <0.1', 'Low: 0.1-0.2', 'Medium-Low: 0.2-0.4', 'Medium: 0.4-0.6', 'Medium-High: 0.6-0.8', 'High: 0.8-1.0', 'Very High: 1.0-1.2', 'Critical: >1.2'],
            len=1.0,
            y=0.5,
            yanchor='middle',
            thickness=22,
            tickfont=dict(size=18),  # colorbar tick label font size
            xpad=8
        ),
        hovertemplate=(
            "<b>Risk Score: %{customdata:.3f}</b><br>" +
            "Current: %{x:.0f} A<br>" +
            "Distance: %{y:.2f} km<br>" +
            "<extra></extra>"
        ),
        customdata=risk_scores
    ))

    # Overlay risk level contour curves at fixed risk score levels (over normalized z)
    risk_levels = [0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.2, 1.4, 1.5]
    normalized_levels = [min(level / 1.5, 1.0) for level in risk_levels]
    for norm_level in normalized_levels:
        fig.add_trace(go.Contour(
            z=normalized_risk,
            x=currents_amp,
            y=distances_km,
            contours=dict(
                start=norm_level,
                end=norm_level,
                size=0,
                coloring='none',
                showlines=True
            ),
            line=dict(color='rgba(0,0,0,0.5)', width=1),
            showscale=False,
            hoverinfo='skip',
            name='Risk level',
            showlegend=False
        ))
    
    # Update layout
    fig.update_layout(
        title={
            'text': f'Risk Score Heatmap<br><sub>Current Magnitude vs Distance (0.1-{max_distance_km:.1f}km) - Higher values (red) = Higher risk</sub>',
            'x': 0.5,
            'xanchor': 'center',
            'font': {'size': 24}  # title font size
        },
        xaxis_title='Lightning Current Magnitude (A)',
        yaxis_title='Distance from Turbine (km)',
        xaxis=dict(
            tickmode='linear',
            tick0=0,
            dtick=50000,
            gridcolor='lightgray',
            zerolinecolor='lightgray',
            tickfont=dict(size=18),  # x-axis tick label font size
            title_font=dict(size=20),  # x-axis title font size
        ),
        yaxis=dict(
            tickmode='array',
            tickvals=[0] + [ring/1000 for ring in config.distance_rings],
            ticktext=['0'] + [f'{ring/1000:.1f}' for ring in config.distance_rings],
            gridcolor='lightgray',
            zerolinecolor='lightgray',
            tickfont=dict(size=18),  # y-axis tick label font size
            title_font=dict(size=20),  # y-axis title font size
        ),
        width=1150,
        height=800,
        plot_bgcolor='white',
        paper_bgcolor='white',
        margin=dict(l=40, r=220, t=80, b=40),
        font=dict(size=18),  # global chart font size
    )
    
    return fig

def create_risk_score_examples() -> go.Figure:
    """
    Create a chart showing example risk scores for specific distance and current combinations.
    This helps readers understand typical risk score ranges.
    """
    import numpy as np
    from src.config import config
    
    # Get risk parameters
    P_0 = config.risk_params['P_0']
    alpha = config.risk_params['alpha']
    current_weight = config.risk_params['current_weight']
    
    # Define example scenarios
    scenarios = [
        {"distance": 0.5, "current": 5000, "label": "Close, Low Current"},
        {"distance": 0.5, "current": 25000, "label": "Close, High Current"},
        {"distance": 2.0, "current": 5000, "label": "Medium Distance, Low Current"},
        {"distance": 2.0, "current": 25000, "label": "Medium Distance, High Current"},
        {"distance": 4.0, "current": 5000, "label": "Far, Low Current"},
        {"distance": 4.0, "current": 25000, "label": "Far, High Current"},
    ]
    
    # Calculate risk scores for each scenario
    distances = [s["distance"] for s in scenarios]
    currents = [s["current"] for s in scenarios]
    labels = [s["label"] for s in scenarios]
    
    current_factors = 1 + current_weight * np.array(currents) / 10000
    distance_factors = np.exp(-alpha * np.array(distances))
    risk_scores = P_0 * current_factors * distance_factors
    
    # Create bar chart
    fig = go.Figure(data=go.Bar(
        x=labels,
        y=risk_scores,
        text=[f'{score:.3f}' for score in risk_scores],
        textposition='auto',
        marker_color='lightcoral',
        hovertemplate=(
            "<b>%{x}</b><br>" +
            "Risk Score: %{y:.3f}<br>" +
            "<extra></extra>"
        )
    ))
    
    # Update layout
    fig.update_layout(
        title={
            'text': 'Example Risk Scores for Different Scenarios<br><sub>Shows how distance and current affect risk calculation</sub>',
            'x': 0.5,
            'xanchor': 'center',
            'font': {'size': 16}
        },
        xaxis_title='Scenario (Distance, Current)',
        yaxis_title='Risk Score',
        xaxis=dict(
            tickangle=45,
            gridcolor='lightgray',
            zerolinecolor='lightgray'
        ),
        yaxis=dict(
            gridcolor='lightgray',
            zerolinecolor='lightgray'
        ),
        width=900,
        height=600,
        plot_bgcolor='white',
        paper_bgcolor='white',
        showlegend=False
    )
    
    # Add annotation with formula
    fig.add_annotation(
        x=0.5,
        y=1.02,
        xref='paper',
        yref='paper',
        text=f'Formula: Risk = {P_0} × (1 + {current_weight}×Current/10000) × e^(-{alpha}×Distance)',
        showarrow=False,
        font=dict(size=12),
        bgcolor='lightblue',
        bordercolor='black',
        borderwidth=1
    )
    
    return fig
