"""
Callbacks for the layout designer.
"""

import logging

import pandas as pd
from dash import Input, Output, State, html, no_update
from dash.exceptions import PreventUpdate

from .constants import DEFAULT_PLACEHOLDER, DEFAULT_SEPARATOR
from .designer import (
    DESIGNER_FIELDS,
    PLATE_TYPES,
    create_plate_grid,
    get_row_labels,
    infer_plate_from_raw,
)
from .utils import parse_upload, validate_separator_placeholder

logger = logging.getLogger("instawell.dash_app.designer")


def register_designer_callbacks(app):
    """Register all designer callbacks."""

    @app.callback(
        Output("designer-collapse", "is_open"),
        Output("designer-toggle-btn", "children"),
        Output("designer-toggle-btn", "color"),
        Output("designer-toggle-btn", "outline"),
        Input("designer-toggle-btn", "n_clicks"),
        State("designer-collapse", "is_open"),
        prevent_initial_call=True,
    )
    def toggle_designer(n_clicks, is_open):
        """Toggle designer visibility."""
        if n_clicks is None:
            raise PreventUpdate

        new_state = not is_open

        if new_state:  # Opening designer
            button_text = "Hide Designer"
            button_color = "secondary"
            button_outline = True
        else:  # Closing designer
            button_text = "Show Designer"
            button_color = "primary"
            button_outline = False

        return new_state, button_text, button_color, button_outline

    @app.callback(
        Output("designer-state", "data", allow_duplicate=True),
        Input("designer-plate-type", "value"),
        State("designer-state", "data"),
        prevent_initial_call=True,
    )
    def change_plate_type(plate_type, state):
        """Update plate type in state."""
        new_state = dict(state)
        new_state["plate_type"] = plate_type
        return new_state

    @app.callback(
        Output("designer-state", "data", allow_duplicate=True),
        Input("designer-raw-upload", "contents"),
        State("designer-raw-upload", "filename"),
        State("designer-state", "data"),
        prevent_initial_call=True,
    )
    def import_from_raw(contents, filename, state):
        """Import available wells from raw CSV."""
        if contents is None:
            raise PreventUpdate

        try:
            raw_df = parse_upload(contents, filename)
            plate_type, _, _, available_wells = infer_plate_from_raw(raw_df)

            new_state = dict(state)
            new_state["plate_type"] = plate_type
            new_state["available_wells"] = available_wells
            return new_state
        except Exception:
            logger.warning("Failed to import wells from raw CSV '%s'", filename)
            return no_update

    @app.callback(
        Output("designer-grid-container", "children"),
        Input("designer-state", "data"),
    )
    def render_grid(state):
        """Render the plate grid."""
        return create_plate_grid(
            state["plate_type"],
            state.get("cells", {}),
            state.get("available_wells", []),
        )

    @app.callback(
        Output("designer-selected-wells", "data"),
        Input("designer-plate-grid", "selected_cells"),
        State("designer-state", "data"),
    )
    def update_selected_wells(selected_cells, state):
        """Convert table selection to well names."""
        if not selected_cells:
            return []

        plate_type = state["plate_type"]
        n_rows, _cols = PLATE_TYPES[plate_type]
        row_labels = get_row_labels(n_rows)

        wells = []
        for cell in selected_cells:
            row_idx = cell.get("row")
            col_id = cell.get("column_id")

            # Skip if Well column or invalid
            if (
                col_id is None
                or col_id == "Well"
                or row_idx is None
                or row_idx >= len(row_labels)
            ):
                continue

            try:
                col_num = int(col_id)
                row_label = row_labels[row_idx]
                well_name = f"{row_label}{col_num}"
                wells.append(well_name)
            except (ValueError, IndexError):
                continue

        return sorted(set(wells))

    @app.callback(
        Output("designer-selected-wells-display", "children"),
        Output("designer-assign-btn", "disabled"),
        Output("designer-clear-btn", "disabled"),
        Output("designer-copy-btn", "disabled"),
        Input("designer-selected-wells", "data"),
        State("designer-state", "data"),
    )
    def update_selection_display(selected_wells, state):
        """Display selected wells and enable/disable buttons."""
        if not selected_wells:
            return "No wells selected", True, True, True

        wells_str = ", ".join(selected_wells)
        display = html.Span(
            [
                html.Strong(f"{len(selected_wells)} wells selected: "),
                html.Span(wells_str, className="font-monospace"),
            ]
        )

        # Enable copy button only if exactly one filled well is selected
        cells = state.get("cells", {})
        copy_enabled = len(selected_wells) == 1 and selected_wells[0] in cells

        return display, False, False, not copy_enabled

    @app.callback(
        Output("designer-concentration", "value"),
        Output("designer-unit", "value"),
        Output("designer-ligand", "value"),
        Output("designer-protein", "value"),
        Output("designer-buffer", "value"),
        Input("designer-copy-btn", "n_clicks"),
        State("designer-selected-wells", "data"),
        State("designer-state", "data"),
        prevent_initial_call=True,
    )
    def copy_condition_to_form(n_clicks, selected_wells, state):
        """Copy condition from selected well to form fields."""
        if not n_clicks or not selected_wells or len(selected_wells) != 1:
            raise PreventUpdate

        well = selected_wells[0]
        cells = state.get("cells", {})

        if well not in cells:
            raise PreventUpdate

        cond = cells[well]
        return (
            cond.get("concentration"),
            cond.get("unit", "uM"),
            cond.get("ligand", ""),
            cond.get("protein", ""),
            cond.get("buffer", ""),
        )

    @app.callback(
        Output("designer-state", "data", allow_duplicate=True),
        Output("designer-concentration", "value", allow_duplicate=True),
        Output("designer-ligand", "value", allow_duplicate=True),
        Output("designer-protein", "value", allow_duplicate=True),
        Output("designer-buffer", "value", allow_duplicate=True),
        Input("designer-assign-btn", "n_clicks"),
        State("designer-selected-wells", "data"),
        State("designer-concentration", "value"),
        State("designer-unit", "value"),
        State("designer-ligand", "value"),
        State("designer-protein", "value"),
        State("designer-buffer", "value"),
        State("designer-state", "data"),
        prevent_initial_call=True,
    )
    def assign_condition(
        n_clicks, selected_wells, concentration, unit, ligand, protein, buffer, state
    ):
        """Assign condition to selected wells."""
        if not n_clicks or not selected_wells:
            raise PreventUpdate

        ligand_clean = (ligand or "").strip()
        protein_clean = (protein or "").strip()
        buffer_clean = (buffer or "").strip()

        if concentration is None or not ligand_clean or not protein_clean or not buffer_clean:
            raise PreventUpdate

        # Update state
        new_state = dict(state)
        cells = dict(new_state.get("cells", {}))

        condition = {
            "concentration": concentration,
            "unit": unit or "uM",
            "ligand": ligand_clean,
            "protein": protein_clean,
            "buffer": buffer_clean,
        }

        for well in selected_wells:
            cells[well] = condition

        new_state["cells"] = cells

        # Clear form
        return new_state, None, "", "", ""

    @app.callback(
        Output("designer-state", "data", allow_duplicate=True),
        Input("designer-clear-btn", "n_clicks"),
        State("designer-selected-wells", "data"),
        State("designer-state", "data"),
        prevent_initial_call=True,
    )
    def clear_selected_wells(n_clicks, selected_wells, state):
        """Clear conditions from selected wells."""
        if not n_clicks or not selected_wells:
            raise PreventUpdate

        new_state = dict(state)
        cells = dict(new_state.get("cells", {}))

        for well in selected_wells:
            cells.pop(well, None)

        new_state["cells"] = cells
        return new_state

    @app.callback(
        Output("designer-state", "data", allow_duplicate=True),
        Input("designer-reset-btn", "n_clicks"),
        State("designer-state", "data"),
        prevent_initial_call=True,
    )
    def reset_all(n_clicks, state):
        """Reset all cell assignments."""
        if not n_clicks:
            raise PreventUpdate

        new_state = dict(state)
        new_state["cells"] = {}
        return new_state

    @app.callback(
        Output("designer-download", "data"),
        Input("designer-export-btn", "n_clicks"),
        State("designer-state", "data"),
        State("designer-separator-input", "value"),
        State("designer-placeholder-input", "value"),
        prevent_initial_call=True,
    )
    def export_layout(n_clicks, state, separator, placeholder):
        """Export layout to CSV."""
        if not n_clicks:
            raise PreventUpdate

        cells = state.get("cells", {})
        if not cells:
            raise PreventUpdate

        sep_raw = (separator or DEFAULT_SEPARATOR).strip() or DEFAULT_SEPARATOR
        placeholder_raw = (placeholder or DEFAULT_PLACEHOLDER).strip() or DEFAULT_PLACEHOLDER
        try:
            sep, placeholder_char = validate_separator_placeholder(sep_raw, placeholder_raw)
        except ValueError:
            sep = DEFAULT_SEPARATOR
            placeholder_char = DEFAULT_PLACEHOLDER

        empty_mask = sep.join(placeholder_char for _ in DESIGNER_FIELDS)

        plate_type = state["plate_type"]
        rows, cols = PLATE_TYPES[plate_type]
        row_labels = get_row_labels(rows)

        # Build layout dataframe
        data = []
        for row_label in row_labels:
            row_data = {"Well": row_label}
            for col_num in range(1, cols + 1):
                well_name = f"{row_label}{col_num}"
                if well_name in cells:
                    cond = cells[well_name]
                    first_field = f"{cond.get('concentration', '')}{cond.get('unit', '')}"
                    condition_str = sep.join(
                        [
                            str(first_field),
                            str(cond.get("ligand", "")),
                            str(cond.get("protein", "")),
                            str(cond.get("buffer", "")),
                        ]
                    )
                    row_data[str(col_num)] = condition_str
                else:
                    row_data[str(col_num)] = empty_mask
            data.append(row_data)

        df = pd.DataFrame(data)
        csv_string = df.to_csv(index=False)

        logger.info("Exported layout CSV with %d conditions", len(cells))
        return dict(content=csv_string, filename="layout.csv")
