"""Build canopy JSON config from UI state.

Transforms the options panel state into a JSON config dict consumed by
canopy.run_json(). Handles parsing of comma-separated inputs, slices,
and figure-specific options.
"""
import json
import os


# -----------------------------------------------------------------------------
# Public API
# -----------------------------------------------------------------------------

def build_and_save_config(
    fields: list[str],
    figure: str,
    state: dict,
    temp_dir: str,
) -> tuple[str, str]:
    """Build config, write to temp dir, return (json_path, output_png_path)."""
    json_path = os.path.join(temp_dir, "config.json")
    output_path = os.path.join(temp_dir, "figure.png")

    cfg = build_config(fields, figure, state, output_path)
    # Write JSON for canopy.run_json(); output_path is where the figure will be saved
    with open(json_path, "w") as f:
        json.dump(cfg, f, indent=2)

    return json_path, output_path


def build_config(
    fields: list[str],
    figure: str,
    state: dict,
    output_file: str,
) -> dict:
    """Build canopy JSON config from fields, figure type, and UI state."""
    cfg = {
        "figure": figure,
        "input_file": fields[0] if len(fields) == 1 else fields,
        "output_file": output_file,
    }

    # --- Pre-process / data loading options ---
    if state.get("file_format"):
        cfg["file_format"] = state["file_format"]
    cfg["grid_type"] = state.get("grid_type", "lonlat")
    if state.get("source"):
        cfg["source"] = state["source"]
    if state.get("region"):
        cfg["region"] = state["region"]
        if state.get("region_type"):
            cfg["region_type"] = state["region_type"]
    # Time / lat / lon slices: only add if both bounds are set
    if state.get("time_slice_start") is not None and state.get("time_slice_end") is not None:
        try:
            cfg["time_slice"] = [int(state["time_slice_start"]), int(state["time_slice_end"])]
        except (TypeError, ValueError):
            pass
    if state.get("lat_slice_min") is not None and state.get("lat_slice_max") is not None:
        try:
            cfg["lat_slice"] = [float(state["lat_slice_min"]), float(state["lat_slice_max"])]
        except (TypeError, ValueError):
            pass
    if state.get("lon_slice_min") is not None and state.get("lon_slice_max") is not None:
        try:
            cfg["lon_slice"] = [float(state["lon_slice_min"]), float(state["lon_slice_max"])]
        except (TypeError, ValueError):
            pass
    # Unit conversion: expects "factor, units" (e.g. "1e-12, GtC")
    if state.get("convert_units"):
        parts = str(state["convert_units"]).split(",")
        if len(parts) == 2:
            try:
                cfg["convert_units"] = [float(parts[0].strip()), parts[1].strip()]
            except (TypeError, ValueError):
                pass
    if state.get("filter"):
        cfg["filter"] = state["filter"]
    # Apply: op + operand; layers optional (empty = apply to all)
    apply_op = state.get("apply_op")
    apply_operand = state.get("apply_operand")
    if apply_op and apply_operand is not None:
        apply_cfg: dict = {"op": apply_op, "operand": float(apply_operand)}
        apply_layers = state.get("apply_layers", "").strip()
        if apply_layers:
            apply_cfg["layers"] = apply_layers
        cfg["apply"] = apply_cfg
    if state.get("unite"):
        cfg["unite"] = True

    # --- Common figure options (title, palette, size, etc.) ---
    if state.get("title"):
        cfg["title"] = state["title"]
    if state.get("custom_palette"):
        cfg["custom_palette"] = state["custom_palette"]
    elif state.get("palette") and state["palette"] != "__custom__":
        cfg["palette"] = state["palette"]
    cfg["dark_mode"] = state.get("dark_mode", False)
    cfg["transparent"] = state.get("transparent", False)
    cfg["x_fig"] = state.get("x_fig", 10)
    cfg["y_fig"] = state.get("y_fig", 10)

    # --- Figure-specific options ---
    if figure in ("map_simple", "map_diff"):
        # Layer: variable name in the data
        if state.get("layer"):
            cfg["layer"] = state["layer"]
        cfg["timeop"] = state.get("timeop", "av")
        if state.get("cb_label"):
            cfg["cb_label"] = state["cb_label"]
        if state.get("unit"):
            cfg["unit"] = state["unit"]
        cfg["n_classes"] = state.get("n_classes", 4)
        cfg["classification"] = state.get("classification", "linear")
        cfg["orientation"] = state.get("orientation", "horizontal")
        cfg["extend"] = state.get("extend", "neither")
        cfg["proj"] = state.get("proj", "Robinson")
        cfg["force_zero"] = state.get("force_zero", False)
        cfg["stats_annotation"] = state.get("stats_annotation", False)
        if figure == "map_diff":
            cfg["percentage"] = state.get("percentage", False)
            cfg["nonsig"] = state.get("nonsig", False)

    elif figure == "time_series":
        # Layers: single value or list depending on count
        layers = _parse_list(state.get("layers", ""))
        if layers:
            cfg["layers"] = layers[0] if len(layers) == 1 else layers
        if state.get("gridop"):
            cfg["gridop"] = state["gridop"]
        cfg["make_diff"] = state.get("make_diff", False)
        if state.get("yaxis_label"):
            cfg["yaxis_label"] = state["yaxis_label"]
        field_labels = _parse_list(state.get("field_labels", ""))
        if field_labels:
            cfg["field_labels"] = field_labels
        if state.get("unit"):
            cfg["unit"] = state["unit"]
        cfg["move_legend"] = state.get("move_legend", False)
        cfg["legend_style"] = state.get("legend_style", "default")
        cfg["reverse_hue_style"] = state.get("reverse_hue_style", False)
        cfg["max_labels_per_col"] = state.get("max_labels_per_col", 15)
        cfg["baseline"] = state.get("baseline", False)
        if state.get("rolling_size") is not None:
            cfg["rolling_size"] = int(state["rolling_size"])
        cfg["stacked"] = state.get("stacked", False)
        cfg["relative"] = state.get("relative", False)
        # Y-axis limits: parse "min, max" string to [min, max]
        ylim = _parse_float_list(state)
        if ylim:
            cfg["yaxis_lim"] = ylim

    elif figure == "static":
        # Scatter plot: field A vs field B
        layers = _parse_list(state.get("layers", ""))
        if layers:
            cfg["layers"] = layers[0] if len(layers) == 1 else layers
        if state.get("field_a_label"):
            cfg["field_a_label"] = state["field_a_label"]
        if state.get("field_b_label"):
            cfg["field_b_label"] = state["field_b_label"]
        if state.get("unit_a"):
            cfg["unit_a"] = state["unit_a"]
        if state.get("unit_b"):
            cfg["unit_b"] = state["unit_b"]
        cfg["scatter_size"] = state.get("scatter_size", 6)
        cfg["scatter_alpha"] = state.get("scatter_alpha", 0.1)
        cfg["move_legend"] = state.get("move_legend", False)

    elif figure == "distribution":
        # Plot type: strip, swarm, box, violin, boxen, point, bar
        cfg["plot_type"] = state.get("plot_type", "box")
        layers = _parse_list(state.get("layers", ""))
        if layers:
            cfg["layers"] = layers[0] if len(layers) == 1 else layers
        if state.get("gridop"):
            cfg["gridop"] = state["gridop"]
        if state.get("yaxis_label"):
            cfg["yaxis_label"] = state["yaxis_label"]
        field_labels = _parse_list(state.get("field_labels", ""))
        if field_labels:
            cfg["field_labels"] = field_labels
        if state.get("unit"):
            cfg["unit"] = state["unit"]
        cfg["horizontal"] = state.get("horizontal", False)
        cfg["vertical_xlabels"] = state.get("vertical_xlabels", False)
        cfg["move_legend"] = state.get("move_legend", False)

    elif figure == "latitudinal_plot":
        # Zonal mean plot along latitude
        layers = _parse_list(state.get("layers", ""))
        if layers:
            cfg["layers"] = layers[0] if len(layers) == 1 else layers
        if state.get("yaxis_label"):
            cfg["yaxis_label"] = state["yaxis_label"]
        field_labels = _parse_list(state.get("field_labels", ""))
        if field_labels:
            cfg["field_labels"] = field_labels
        if state.get("unit"):
            cfg["unit"] = state["unit"]
        cfg["move_legend"] = state.get("move_legend", False)
        cfg["legend_style"] = state.get("legend_style", "default")
        cfg["max_labels_per_col"] = state.get("max_labels_per_col", 15)

    return cfg


# -----------------------------------------------------------------------------
# Helpers
# -----------------------------------------------------------------------------

def _parse_list(s: str) -> list[str] | None:
    """Parse comma-separated string to list, or None if empty."""
    if not s or not str(s).strip():
        return None
    return [x.strip() for x in str(s).split(",") if x.strip()]


def _parse_float_list(state: dict) -> list[float] | None:
    """Parse 'min, max' string to [min, max] floats, or None if invalid."""
    parts = _parse_list(state.get("yaxis_lim", ""))
    if not parts or len(parts) != 2:
        return None
    try:
        return [float(parts[0]), float(parts[1])]
    except (TypeError, ValueError):
        return None
