"""
Plot Data Serialization

Functions for converting PlotComponents to JSON-serializable format
for API responses.
"""

from __future__ import annotations
from typing import Dict, List, Optional, Union
import pandas as pd
import numpy as np

from .components import (
    PlotComponent, Line, Scatter, Bar, HBar, HLine, Fill,
    Segment, Marker, SignalPlot, Label, Panel, PlotResult
)


# Type for values that can be serialized to JSON
SerializableValue = Optional[Union[int, float, str, bool]]

# Type for data that can be converted to a list
SeriesInput = Union[pd.Series, np.ndarray, List[SerializableValue], SerializableValue, None]


def _to_list(data: SeriesInput) -> List[SerializableValue]:
    """Convert Series/array to list for JSON serialization."""
    if data is None:
        return []
    if isinstance(data, pd.Series):
        return [None if pd.isna(v) else (float(v) if isinstance(v, (int, float, np.integer, np.floating)) else v) for v in data]
    elif isinstance(data, np.ndarray):
        return [None if pd.isna(v) else (float(v) if isinstance(v, (int, float, np.integer, np.floating)) else v) for v in data]
    elif isinstance(data, list):
        return [None if (isinstance(v, float) and np.isnan(v)) else v for v in data]
    else:
        return [data]  # Single value


# Type alias for render component dict
RenderComponentDict = Dict[str, Union[str, Dict[str, str], Dict[str, SerializableValue], None]]

# Type alias for data row dict
DataRowDict = Dict[str, Union[int, float, str, bool, None]]


def extract_plot_data(
    components: List[PlotComponent],
    timestamps: List[int]
) -> tuple[List[DataRowDict], List[RenderComponentDict]]:
    """
    Extract data and render configs from PlotComponents.

    Args:
        components: List of PlotComponent objects
        timestamps: List of timestamps for each data point

    Returns:
        (data_rows, render_components)
        - data_rows: List of dicts with timestamp and all field values
        - render_components: List of render component configs for frontend
    """
    all_fields: Dict[str, List] = {'timestamp': timestamps}
    render_components = []
    field_counter = 0

    for component in components:
        if isinstance(component, Line):
            field_name = f'line_{field_counter}'
            field_counter += 1
            all_fields[field_name] = _to_list(component.y)

            style = {}
            if component.color is not None:
                if isinstance(component.color, (list, pd.Series)):
                    color_field = f'{field_name}_color'
                    all_fields[color_field] = _to_list(component.color)
                    style['color'] = color_field
                else:
                    style['color'] = component.color
            if component.line_width is not None:
                style['line_width'] = component.line_width
            if component.line_dash is not None:
                style['line_dash'] = component.line_dash
            if component.alpha is not None:
                style['alpha'] = component.alpha

            render_components.append({
                'type': 'line',
                'data_fields': {'y': field_name},
                'style': style,
                'legend': component.legend
            })

        elif isinstance(component, Bar):
            field_name = f'bar_{field_counter}'
            field_counter += 1
            all_fields[field_name] = _to_list(component.y)

            style = {}
            if component.color is not None:
                if isinstance(component.color, (list, pd.Series)):
                    color_field = f'{field_name}_color'
                    all_fields[color_field] = _to_list(component.color)
                    style['color'] = color_field
                else:
                    style['color'] = component.color
            if component.alpha is not None:
                style['alpha'] = component.alpha

            render_components.append({
                'type': 'bar',
                'data_fields': {'y': field_name},
                'style': style,
                'legend': component.legend
            })

        elif isinstance(component, HLine):
            style = {}
            if component.color is not None:
                style['color'] = component.color
            if component.line_width is not None:
                style['line_width'] = component.line_width
            if component.line_dash is not None:
                style['line_dash'] = component.line_dash
            if component.alpha is not None:
                style['alpha'] = component.alpha

            render_components.append({
                'type': 'hline',
                'data_fields': {'y': component.y},
                'style': style
            })

        elif isinstance(component, HBar):
            y_field = f'hbar_y_{field_counter}'
            width_field = f'hbar_width_{field_counter}'
            field_counter += 1
            all_fields[y_field] = _to_list(component.y)
            all_fields[width_field] = _to_list(component.width)

            style = {}
            if component.color is not None:
                if isinstance(component.color, (list, pd.Series)):
                    color_field = f'{y_field}_color'
                    all_fields[color_field] = _to_list(component.color)
                    style['color'] = color_field
                else:
                    style['color'] = component.color
            if component.alpha is not None:
                style['alpha'] = component.alpha
            if component.line_color is not None:
                style['line_color'] = component.line_color
            if component.line_alpha is not None:
                style['line_alpha'] = component.line_alpha

            render_components.append({
                'type': 'hbar',
                'data_fields': {'y': y_field, 'width': width_field},
                'style': style,
                'legend': component.legend,
                'position_mode': component.position_mode
            })

        elif isinstance(component, Fill):
            y1_field = f'fill_y1_{field_counter}'
            y2_field = f'fill_y2_{field_counter}'
            field_counter += 1
            all_fields[y1_field] = _to_list(component.y1)
            all_fields[y2_field] = _to_list(component.y2)

            style = {}
            if component.color is not None:
                if isinstance(component.color, (list, pd.Series)):
                    color_field = f'{y1_field}_color'
                    all_fields[color_field] = _to_list(component.color)
                    style['color'] = color_field
                else:
                    style['color'] = component.color
            if component.alpha is not None:
                style['alpha'] = component.alpha

            render_components.append({
                'type': 'fill',
                'data_fields': {'y1': y1_field, 'y2': y2_field},
                'style': style
            })

        elif isinstance(component, Scatter):
            field_name = f'scatter_{field_counter}'
            field_counter += 1
            all_fields[field_name] = _to_list(component.y)
            if component.x is not None:
                x_field = f'{field_name}_x'
                all_fields[x_field] = _to_list(component.x)
            else:
                x_field = 'timestamp'

            style = {}
            if component.color is not None:
                if isinstance(component.color, (list, pd.Series)):
                    color_field = f'{field_name}_color'
                    all_fields[color_field] = _to_list(component.color)
                    style['color'] = color_field
                else:
                    style['color'] = component.color
            if component.size is not None:
                if isinstance(component.size, (list, pd.Series, np.ndarray)):
                    size_field = f'{field_name}_size'
                    all_fields[size_field] = _to_list(component.size)
                    style['size'] = size_field
                else:
                    style['size'] = component.size
            if component.shape is not None:
                style['shape'] = component.shape
            if component.alpha is not None:
                style['alpha'] = component.alpha

            render_components.append({
                'type': 'scatter',
                'data_fields': {'y': field_name, 'x': x_field},
                'style': style,
                'legend': component.legend
            })

        elif isinstance(component, Segment):
            prefix = f'segment_{field_counter}'
            field_counter += 1
            all_fields[f'{prefix}_x0'] = _to_list(component.x0)
            all_fields[f'{prefix}_y0'] = _to_list(component.y0)
            all_fields[f'{prefix}_x1'] = _to_list(component.x1)
            all_fields[f'{prefix}_y1'] = _to_list(component.y1)

            style = {}
            if component.color is not None:
                if isinstance(component.color, (list, pd.Series)):
                    color_field = f'{prefix}_color'
                    all_fields[color_field] = _to_list(component.color)
                    style['color'] = color_field
                else:
                    style['color'] = component.color
            if component.line_width is not None:
                style['line_width'] = component.line_width
            if component.line_dash is not None:
                style['line_dash'] = component.line_dash
            if component.alpha is not None:
                style['alpha'] = component.alpha

            render_components.append({
                'type': 'segment',
                'data_fields': {
                    'x0': f'{prefix}_x0', 'y0': f'{prefix}_y0',
                    'x1': f'{prefix}_x1', 'y1': f'{prefix}_y1'
                },
                'style': style
            })

        elif isinstance(component, (Marker, SignalPlot)):
            field_name = f'marker_{field_counter}'
            field_counter += 1
            all_fields[field_name] = _to_list(component.y)
            all_fields[f'{field_name}_type'] = _to_list(component.signal_type)

            if component.x is not None:
                x_field = f'{field_name}_x'
                all_fields[x_field] = _to_list(component.x)
            else:
                x_field = 'timestamp'

            style = {}
            if component.buy_color is not None:
                style['buy_color'] = component.buy_color
            if component.sell_color is not None:
                style['sell_color'] = component.sell_color
            if component.size is not None:
                style['size'] = component.size
            if component.alpha is not None:
                style['alpha'] = component.alpha
            if isinstance(component, SignalPlot) and component.line_width is not None:
                style['line_width'] = component.line_width

            comp_type = 'signal' if isinstance(component, SignalPlot) else 'marker'
            render_components.append({
                'type': comp_type,
                'data_fields': {'y': field_name, 'signal_type': f'{field_name}_type', 'x': x_field},
                'style': style
            })

        elif isinstance(component, Label):
            field_name = f'label_{field_counter}'
            field_counter += 1
            all_fields[field_name] = _to_list(component.y)
            all_fields[f'{field_name}_text'] = _to_list(component.text)
            if component.x is not None:
                x_field = f'{field_name}_x'
                all_fields[x_field] = _to_list(component.x)
            else:
                x_field = 'timestamp'

            style = {}
            if component.color is not None:
                if isinstance(component.color, (list, pd.Series)):
                    color_field = f'{field_name}_color'
                    all_fields[color_field] = _to_list(component.color)
                    style['color'] = color_field
                else:
                    style['color'] = component.color
            if component.font_size is not None:
                style['font_size'] = component.font_size
            if component.text_align is not None:
                style['text_align'] = component.text_align
            if component.text_baseline is not None:
                style['text_baseline'] = component.text_baseline
            if component.x_offset is not None:
                style['x_offset'] = component.x_offset
            if component.y_offset is not None:
                style['y_offset'] = component.y_offset
            if component.alpha is not None:
                style['alpha'] = component.alpha

            render_components.append({
                'type': 'label',
                'data_fields': {'y': field_name, 'text': f'{field_name}_text', 'x': x_field},
                'style': style
            })

    # Build data rows
    n_rows = len(timestamps)
    data_rows = []
    for i in range(n_rows):
        row = {}
        for field_name, values in all_fields.items():
            if i < len(values):
                val = values[i]
                if val is not None:
                    row[field_name] = int(val) if field_name == 'timestamp' else val
        if row:
            data_rows.append(row)

    return data_rows, render_components


# Type for panel data dict
PanelDataDict = Dict[str, Union[str, int, List[DataRowDict], List[RenderComponentDict]]]


def extract_plot_data_with_panels(
    plot_result: Union[List[PlotComponent], PlotResult],
    timestamps: List[int]
) -> Dict[str, Union[List[DataRowDict], List[RenderComponentDict], List[PanelDataDict]]]:
    """
    Extract plot data supporting both legacy List[PlotComponent] and new PlotResult.

    Args:
        plot_result: Either List[PlotComponent] (overlay only) or PlotResult (overlay + panels)
        timestamps: List of timestamps for each data point

    Returns:
        Dict with:
        - overlay_data: List of data row dicts for overlay
        - overlay_components: List of render component configs for overlay
        - panels: List of panel dicts, each with name, height, data, components
    """
    # Handle backwards compatibility
    if isinstance(plot_result, list):
        # Legacy: just overlay components
        overlay_data, overlay_components = extract_plot_data(plot_result, timestamps)
        return {
            'overlay_data': overlay_data,
            'overlay_components': overlay_components,
            'panels': []
        }

    # New PlotResult format
    overlay_data, overlay_components = extract_plot_data(plot_result.overlay, timestamps)

    panels = []
    for panel in plot_result.panels:
        panel_data, panel_components = extract_plot_data(panel.components, timestamps)
        panels.append({
            'name': panel.name,
            'height': panel.height,
            'data': panel_data,
            'components': panel_components,
        })

    return {
        'overlay_data': overlay_data,
        'overlay_components': overlay_components,
        'panels': panels
    }


__all__ = [
    'extract_plot_data',
    'extract_plot_data_with_panels',
    '_to_list',
]
