# -*- coding: utf-8 -*-
import tkinter as tk
from tkinter import ttk
import time
import logging
from typing import Any, Dict, Optional

from .base_widgets import BaseCommandFrame, ToolTip
from pymsc.utils.converters import (
    RAD_TO_DEG, DEG_TO_RAD, NM_TO_METERS, METERS_TO_FEET,
    FEET_TO_METERS, MS_TO_KNOTS, KNOTS_TO_MS
)

# Registry for finding widgets by their label (used for automation/scripts)
WIDGET_MAP: Dict[str, Any] = {}

def get_correct_value(info: Dict, idx: int, value: float) -> float:
    """
    Applies LSB and Scale factors to convert a raw value to a readable value.
    Returns None if value is None (field not found).
    """
    if value is None:
        return None
    suffix = str(idx) if idx != -1 else ""
    lsb = info.get(f"lsb{suffix}", 1.0)
    scale = info.get(f"scale{suffix}", 1.0)
    result = value * (scale * lsb)
    return result

def set_correct_value(info: Dict, idx: int, value: float) -> int:
    """
    Applies LSB and Scale factors to convert a readable value to a raw integer.
    """
    suffix = str(idx) if idx != -1 else ""
    lsb = info.get(f"lsb{suffix}", 1.0)
    scale = info.get(f"scale{suffix}", 1.0)
    raw_val = value / (scale * lsb)
    return int(raw_val)

class CommandFrameCheckBox(BaseCommandFrame):
    """
    Widget containing a Label, a Command Checkbox, and a Tellback Checkbox.
    """
    def __init__(self, parent: tk.Widget, command_info: Dict, **kwargs):
        super().__init__(parent, command_info, **kwargs)
        WIDGET_MAP[self.info['label']] = self
        self._create_ui()

    def _create_ui(self):
        self.label = tk.Label(
            self, text=self.info['label'], width=self.column_widths[0],
            anchor="w", font=("Helvetica", 8)
        )
        self.label.grid(row=0, column=0, sticky="w", padx=5)
        self.tooltip = ToolTip(self.label, self.info['description'])

        if self.info.get('message'):
            self.command_var = tk.BooleanVar()
            self.command_check = tk.Checkbutton(
                self, variable=self.command_var, command=self.on_toggle,
                width=self.column_widths[1]
            )
            self.command_check.grid(row=0, column=1)
            initial_val = self.info['message'].get_value_for_field(self.info['field'])
            self.command_var.set(bool(initial_val))

        if self.info.get('message_tb'):
            self.tellback_var = tk.BooleanVar()
            self.tellback_check = tk.Checkbutton(
                self, variable=self.tellback_var, state='disabled',
                width=self.column_widths[2]
            )
            self.tellback_check.grid(row=0, column=2)

    def on_toggle(self):
        # Skip if message is None (disabled field)
        if not self.info.get('message'):
            return
        self.start_time = time.time()
        val = 1 if self.command_var.get() else 0
        self.info['message'].set_value_for_field(self.info['field'], val)
        if self.script_manager.is_recording:
            action_val = "on" if val == 1 else "off"
            self.script_manager.write_command(self.info['label'], "toggle", action_val)
        if self.info.get('message_tb'):
            self.monitor_tellback()

    def monitor_tellback(self):
        msg_tb = self.info['message_tb']
        field_tb = self.info['field_tb']
        current_tb = bool(msg_tb.get_value_for_field(field_tb))
        self.tellback_var.set(current_tb)
        elapsed = (time.time() - self.start_time) * 1000
        if current_tb == self.command_var.get():
            self.label.config(bg="green" if current_tb else "yellow")
            return
        if elapsed > self.threshold_ms:
            self.label.config(bg="red")
            return
        self.after(self.update_interval_ms, self.monitor_tellback)

    def check_updated_value(self):
        if self.info.get('message'):
            current_val = bool(self.info['message'].get_value_for_field(self.info['field']))
            self.command_var.set(current_val)
        if self.info.get('message_tb'):
            current_tb = bool(self.info['message_tb'].get_value_for_field(self.info['field_tb']))
            self.tellback_var.set(current_tb)

class CommandFrameComboBox(BaseCommandFrame):
    """
    Widget containing a Label, a Command ComboBox, and a Tellback Label.
    """
    def __init__(self, parent: tk.Widget, command_info: Dict, **kwargs):
        super().__init__(parent, command_info, **kwargs)
        WIDGET_MAP[self.info['label']] = self
        self._create_ui()

    def _create_ui(self):
        self.label = tk.Label(
            self, text=self.info['label'], width=self.column_widths[0],
            anchor="w", font=("Helvetica", 8)
        )
        self.label.grid(row=0, column=0, sticky="w", padx=5)
        self.tooltip = ToolTip(self.label, self.info['description'])

        self.cmd_var = tk.StringVar()
        self.combo = ttk.Combobox(
            self, textvariable=self.cmd_var, values=self.info["values"],
            width=self.column_widths[1], state="readonly"
        )
        self.combo.grid(row=0, column=1)
        self.combo.bind("<<ComboboxSelected>>", self.on_select)

        if self.info.get('message_tb'):
            self.tb_var = tk.StringVar(value="---")
            self.tb_label = tk.Label(
                self, textvariable=self.tb_var, bg="white",
                width=self.column_widths[2], relief="sunken"
            )
            self.tb_label.grid(row=0, column=2)

    def on_select(self, event=None):
        self.start_time = time.time()
        idx = self.combo.current()
        self.info['message'].set_value_for_field(self.info['field'], idx)
        if self.script_manager.is_recording:
            self.script_manager.write_command(self.info['label'], "set_value", self.combo.get())
        if self.info.get('message_tb'):
            self.monitor_tellback()

    def monitor_tellback(self):
        msg_tb = self.info['message_tb']
        idx_tb = msg_tb.get_value_for_field(self.info['field_tb'])
        # Handle None or invalid index
        if idx_tb is None:
            self.tb_var.set("---")
            return
        try:
            val_tb_text = self.info["values_tb"][idx_tb]
        except (IndexError, KeyError, TypeError):
            val_tb_text = "ERR"
        self.tb_var.set(val_tb_text)
        elapsed = (time.time() - self.start_time) * 1000
        if idx_tb == self.combo.current():
            self.label.config(bg="green")
            return
        if elapsed > self.threshold_ms:
            self.label.config(bg="red")
            return
        self.after(self.update_interval_ms, self.monitor_tellback)

    def check_updated_value(self):
        if self.info.get('message'):
            current_idx = self.info['message'].get_value_for_field(self.info['field'])
            if current_idx is None:
                self.cmd_var.set("---")
            else:
                try:
                    self.cmd_var.set(self.info['values'][current_idx])
                except (IndexError, TypeError):
                    self.cmd_var.set("ERR")
        if self.info.get('message_tb'):
            current_tb_idx = self.info['message_tb'].get_value_for_field(self.info['field_tb'])
            if current_tb_idx is None:
                self.tb_var.set("---")
            else:
                try:
                    self.tb_var.set(self.info['values_tb'][current_tb_idx])
                except (IndexError, TypeError):
                    self.tb_var.set("ERR")

class CommandFrameSpinBox(BaseCommandFrame):
    """
    Widget containing a Label, a Command SpinBox, and a Tellback Label.
    Supports scaling (LSB/Scale) and numeric validation.
    """
    def __init__(self, parent: tk.Widget, command_info: Dict, **kwargs):
        super().__init__(parent, command_info, **kwargs)
        WIDGET_MAP[self.info['label']] = self
        self.is_programmatic_change = False
        self.user_editing = False  # Track if user is actively editing
        self._create_ui()

    def _create_ui(self):
        self.label = tk.Label(
            self, text=self.info['label'], width=self.column_widths[0],
            anchor="w", font=("Helvetica", 8)
        )
        self.label.grid(row=0, column=0, sticky="w", padx=5)
        self.tooltip = ToolTip(self.label, self.info['description'])

        is_int = self.info.get("number_format", "float") == "integer"
        self.cmd_var = tk.IntVar() if is_int else tk.DoubleVar()
        self.cmd_var.set(self.info.get('default_value', 0))

        v_cmd = (self.register(self._validate), '%P')
        self.spin = tk.Spinbox(
            self, textvariable=self.cmd_var, from_=self.info["min_value"],
            to=self.info["max_value"], increment=self.info.get("increment", 1),
            width=self.column_widths[1], validate="key", validatecommand=v_cmd
        )
        self.spin.grid(row=0, column=1)
        self.spin.bind("<Return>", self.on_change)
        self.spin.bind("<FocusOut>", self.on_change)
        # Track when user starts editing
        self.spin.bind("<FocusIn>", self._on_focus_in)
        self.spin.bind("<KeyPress>", self._on_key_press)
        # Detect arrow button clicks - send value immediately after arrow click
        self.spin.bind("<ButtonRelease-1>", self._on_arrow_click)
        # Also use variable trace to detect arrow changes
        self.cmd_var.trace_add("write", self._on_var_change)

        if self.info.get('message_tb'):
            self.tb_var = tk.StringVar(value="0")
            self.tb_label = tk.Label(
                self, textvariable=self.tb_var, bg="white",
                width=self.column_widths[2], relief="sunken"
            )
            self.tb_label.grid(row=0, column=2)

    def _validate(self, p):
        if p == "" or p == "-":
            return True
        try:
            float(p)
            return True
        except ValueError:
            return False
    
    def _on_focus_in(self, event=None):
        """Called when spinbox receives focus - user is about to edit"""
        self.user_editing = True
    
    def _on_key_press(self, event=None):
        """Called when user types - mark as editing"""
        self.user_editing = True
    
    def _on_arrow_click(self, event=None):
        """Called when user clicks spinbox arrows - send value immediately"""
        # Small delay to ensure the value has been updated by the spinbox
        self.after(50, self._send_arrow_value)
    
    def _on_var_change(self, *args):
        """Called when spinbox value changes (from arrows or typing)"""
        # If we're not programmatically changing and not already sending
        if not self.is_programmatic_change and not hasattr(self, '_sending_arrow_value'):
            # User changed value, mark as editing
            self.user_editing = True
    
    def _send_arrow_value(self):
        """Send the value after arrow click"""
        if self.is_programmatic_change:
            return
        self._sending_arrow_value = True
        try:
            val = float(self.cmd_var.get())
            raw_val = set_correct_value(self.info, -1, val)
            self.info['message'].set_value_for_field(self.info['field'], raw_val)
            # Clear editing flag after sending
            self.user_editing = False
        except (tk.TclError, ValueError):
            pass
        finally:
            delattr(self, '_sending_arrow_value')

    def on_change(self, event=None):
        if self.is_programmatic_change:
            return
        # Skip if message is None (disabled field)
        if not self.info.get('message'):
            return
        self.start_time = time.time()
        try:
            val = float(self.cmd_var.get())
        except tk.TclError:
            self.user_editing = False  # Clear flag even on error
            return
        raw_val = set_correct_value(self.info, -1, val)
        self.info['message'].set_value_for_field(self.info['field'], raw_val)
        if self.script_manager.is_recording:
            self.script_manager.write_command(self.info['label'], "set_value", val)
        # Clear editing flag after successful change
        self.user_editing = False
        if self.info.get('message_tb'):
            self.monitor_tellback()

    def monitor_tellback(self):
        msg_tb = self.info['message_tb']
        raw_tb = msg_tb.get_value_for_field(self.info['field_tb'])
        readable_tb = get_correct_value(self.info, -1, raw_tb)
        fmt = "%.0f" if self.info.get("number_format") == "integer" else "%.4f"
        self.tb_var.set(fmt % readable_tb)
        elapsed = (time.time() - self.start_time) * 1000
        target = float(self.cmd_var.get())
        if abs(readable_tb - target) < 0.0001:
            self.label.config(bg="green")
            return
        if elapsed > self.threshold_ms:
            self.label.config(bg="red")
            return
        self.after(self.update_interval_ms, self.monitor_tellback)

    def check_updated_value(self):
        # Skip if user is actively editing this field
        if self.user_editing:
            return
        
        # Skip if message is None (disabled field)
        if not self.info.get('message'):
            return
            
        self.is_programmatic_change = True
        raw_val = self.info['message'].get_value_for_field(self.info['field'])
        readable = get_correct_value(self.info, -1, raw_val)
        if readable is not None:
            self.cmd_var.set(readable)
        else:
            self.cmd_var.set(0)  # Default value for None
        self.is_programmatic_change = False
        if self.info.get('message_tb'):
            raw_tb = self.info['message_tb'].get_value_for_field(self.info['field_tb'])
            readable_tb = get_correct_value(self.info, -1, raw_tb)
            if readable_tb is not None:
                fmt = "%.0f" if self.info.get("number_format") == "integer" else "%.4f"
                self.tb_var.set(fmt % readable_tb)
            else:
                self.tb_var.set("---")

class CommandFrameLabels(tk.Frame):
    """
    Widget for displaying up to 3 read-only values from the bus.
    """
    def __init__(self, parent: tk.Widget, command_info: Dict, **kwargs):
        super().__init__(parent)
        self.info = command_info
        self.vars = {}
        self.column_widths = [20, 14, 14, 14]
        WIDGET_MAP[self.info['label']] = self
        self._create_ui()

    def _create_ui(self):
        self.label = tk.Label(
            self, text=self.info['label'], width=self.column_widths[0],
            anchor="w", font=("Helvetica", 8)
        )
        self.label.grid(row=0, column=0, sticky="w", padx=5)
        ToolTip(self.label, self.info['description'])

        for i in range(1, 4):
            msg = self.info.get(f"message{i}")
            if msg:
                field = self.info[f"field{i}"]
                self.vars[field] = tk.StringVar(value="0")
                lbl = tk.Label(
                    self, textvariable=self.vars[field], bg="#f0f0f0",
                    width=self.column_widths[i], relief="groove", font=("Helvetica", 8)
                )
                lbl.grid(row=0, column=i, padx=2)
                ToolTip(lbl, self.info.get(f"tooltip{i}", ""))

    def check_updated_value(self):
        for i in range(1, 4):
            msg = self.info.get(f"message{i}")
            if msg:
                field = self.info[f"field{i}"]
                raw = msg.get_value_for_field(field)
                val = get_correct_value(self.info, i, raw)
                if val is not None:
                    self.vars[field].set("%.4f" % val)
                else:
                    self.vars[field].set("N/A")

class CommandFrameControls(tk.Frame):
    """
    Highly flexible widget that can combine up to 3 sub-controls.
    """
    def __init__(self, parent: tk.Widget, command_info: Dict, **kwargs):
        super().__init__(parent)
        self.info = command_info
        self.column_widths = [20, 14, 14, 14]
        self.vars = {}
        self.is_programmatic_change = False
        self.editing_fields = {}  # Track which fields are being edited
        WIDGET_MAP[self.info['label']] = self
        self._create_ui()

    def _create_ui(self):
        self.title_label = tk.Label(
            self, text=self.info['label'], width=self.column_widths[0],
            anchor="w", font=("Helvetica", 8)
        )
        self.title_label.grid(row=0, column=0, sticky="w", padx=5)
        ToolTip(self.title_label, self.info['description'])

        for i in range(1, 4):
            ctrl_type = self.info.get(f"type{i}")
            if not ctrl_type:
                continue
            self._add_sub_control(i, ctrl_type)

    def _add_sub_control(self, i: int, ctrl_type: str):
        field = self.info[f"field{i}"]
        msg = self.info.get(f"message{i}")
        
        if ctrl_type == "checkbox":
            self.vars[field] = tk.BooleanVar()
            lbl_text = self.info.get(f"label{i}", "")
            cb = tk.Checkbutton(
                self, text=lbl_text, variable=self.vars[field], width=self.column_widths[i],
                command=lambda f=field, m=msg: self._on_check(f, m)
            )
            cb.grid(row=0, column=i)
            WIDGET_MAP[field] = {"widget": cb, "var": self.vars[field]}

        elif ctrl_type == "combobox":
            self.vars[field] = tk.StringVar()
            vals = self.info.get(f"values{i}", [])
            cmb = ttk.Combobox(
                self, textvariable=self.vars[field], values=vals,
                width=self.column_widths[i], state="readonly"
            )
            cmb.grid(row=0, column=i)
            cmb.bind("<<ComboboxSelected>>", lambda e, f=field, m=msg, idx=i: self._on_combo(f, m, idx))
            WIDGET_MAP[field] = {"widget": cmb, "var": self.vars[field]}

        elif ctrl_type == "spinbox":
            number_format = self.info.get(f"number_format{i}", "float")
            is_int = number_format == "integer"
            self.vars[field] = tk.IntVar() if is_int else tk.DoubleVar()
            self.editing_fields[field] = False  # Initialize editing state
            fmt = '%.0f' if is_int else '%.4f'
            min_v = self.info.get(f"min_value{i}", -999999)
            max_v = self.info.get(f"max_value{i}", 999999)
            inc_v = self.info.get(f"increment{i}", 1)
            
            sp = tk.Spinbox(
                self, textvariable=self.vars[field], width=self.column_widths[i],
                from_=min_v, to=max_v, increment=inc_v, format=fmt
            )
            sp.grid(row=0, column=i)
            sp.bind("<Return>", lambda e, f=field, m=msg, idx=i: self._on_spin(f, m, idx))
            sp.bind("<FocusOut>", lambda e, f=field, m=msg, idx=i: self._on_spin(f, m, idx))
            # Track editing state
            sp.bind("<FocusIn>", lambda e, f=field: self._set_editing(f, True))
            sp.bind("<KeyPress>", lambda e, f=field: self._set_editing(f, True))
            # Detect arrow button clicks
            sp.bind("<ButtonRelease-1>", lambda e, f=field, m=msg, idx=i: self._on_spin_arrow(f, m, idx))
            WIDGET_MAP[field] = {"widget": sp, "var": self.vars[field]}

        elif ctrl_type == "label":
            self.vars[field] = tk.StringVar(value="0")
            lbl = tk.Label(
                self, textvariable=self.vars[field], relief="groove",
                width=self.column_widths[i], anchor="w", font=("Helvetica", 8)
            )
            lbl.grid(row=0, column=i)

        elif ctrl_type == "space":
            lbl = tk.Label(self, text="", width=self.column_widths[i])
            lbl.grid(row=0, column=i)
    
    def _set_editing(self, field: str, is_editing: bool):
        """Track editing state for a specific field"""
        self.editing_fields[field] = is_editing

    def _on_check(self, field: str, msg: Any):
        val = 1 if self.vars[field].get() else 0
        msg.set_value_for_field(field, val)
        from pymsc.gui.script_manager import get_script_manager
        sm = get_script_manager()
        if sm.is_recording:
            sm.write_command(field, "toggle", "on" if val else "off")

    def _on_combo(self, field: str, msg: Any, idx: int):
        cmb = WIDGET_MAP[field]["widget"]
        val_idx = cmb.current()
        msg.set_value_for_field(field, val_idx)
        from pymsc.gui.script_manager import get_script_manager
        sm = get_script_manager()
        if sm.is_recording:
            sm.write_command(field, "set_value", cmb.get())

    def _on_spin(self, field: str, msg: Any, idx: int):
        if self.is_programmatic_change:
            return
        val = float(self.vars[field].get())
        raw = set_correct_value(self.info, idx, val)
        msg.set_value_for_field(field, raw)
        # Clear editing flag after change
        self.editing_fields[field] = False
        from pymsc.gui.script_manager import get_script_manager
        sm = get_script_manager()
        if sm.is_recording:
            sm.write_command(field, "set_value", val)
    
    def _on_spin_arrow(self, field: str, msg: Any, idx: int):
        """Handle spinbox arrow button clicks - send value immediately with small delay"""
        # Small delay to ensure the value has been updated
        self.after(50, lambda: self._send_spin_arrow_value(field, msg, idx))
    
    def _send_spin_arrow_value(self, field: str, msg: Any, idx: int):
        """Send spinbox value after arrow click"""
        if self.is_programmatic_change:
            return
        try:
            val = float(self.vars[field].get())
            raw = set_correct_value(self.info, idx, val)
            msg.set_value_for_field(field, raw)
            # Clear editing flag
            self.editing_fields[field] = False
        except (ValueError, tk.TclError):
            pass

    def check_updated_value(self):
        """
        Syncs sub-controls with their corresponding 1553 message fields.
        Renamed from check_actual_value for consistency with MainWindow refresh loop.
        """
        self.is_programmatic_change = True
        for i in range(1, 4):
            ctrl_type = self.info.get(f"type{i}")
            if not ctrl_type or ctrl_type == "space":
                continue
            field = self.info.get(f"field{i}")
            msg = self.info.get(f"message{i}")
            if not msg or not field:
                continue
            
            # Skip spinbox refresh if user is actively editing
            if ctrl_type == "spinbox" and self.editing_fields.get(field, False):
                continue
            
            raw = msg.get_value_for_field(field)
            readable = get_correct_value(self.info, i, raw)
            if ctrl_type == "checkbox":
                self.vars[field].set(bool(raw))
            elif ctrl_type == "combobox":
                try:
                    self.vars[field].set(self.info[f"values{i}"][int(raw)])
                except IndexError:
                    self.vars[field].set("ERR")
            elif ctrl_type == "spinbox":
                self.vars[field].set(readable)
            elif ctrl_type == "label":
                self.vars[field].set("%.4f" % readable)
        self.is_programmatic_change = False