# target_simulator/gui/waypoint_editor_window.py
"""
A modal dialog window for creating or editing a single Waypoint.
"""

import tkinter as tk
from tkinter import ttk, messagebox
from typing import Optional
import math

from target_simulator.core.models import (
    Waypoint,
    ManeuverType,
    TurnDirection,
    KNOTS_TO_FPS,
    FPS_TO_KNOTS,
    G_IN_FPS2,
)


class WaypointEditorWindow(tk.Toplevel):
    """A dialog for entering and editing data for a single waypoint."""

    def __init__(
        self,
        master,
        is_first_waypoint: bool = False,
        waypoint_to_edit: Optional[Waypoint] = None,
    ):
        super().__init__(master)
        self.transient(master)
        self.grab_set()
        self.title("Edit Waypoint" if waypoint_to_edit else "Add Waypoint")
        if is_first_waypoint:
            self.title("Set Initial Position")
        self.resizable(False, False)

        self.is_first_waypoint = is_first_waypoint
        self.result_waypoint: Optional[Waypoint] = None
        self._is_updating_turn_def = False  # Flag to prevent update loops

        self._setup_variables()
        self._create_widgets()

        if waypoint_to_edit:
            self._load_waypoint_data(waypoint_to_edit)

        self._on_maneuver_type_change()
        self._center_window()

        self.protocol("WM_DELETE_WINDOW", self._on_cancel)
        self.wait_window(self)

    def _setup_variables(self):
        """Initializes all tk Variables."""
        self.maneuver_type_var = tk.StringVar()

        self.ftp_duration_var = tk.DoubleVar(value=10.0)
        self.ftp_range_var = tk.DoubleVar(value=20.0)
        self.ftp_az_var = tk.DoubleVar(value=0.0)
        self.ftp_alt_var = tk.DoubleVar(value=10000.0)
        self.ftp_vel_var = tk.DoubleVar(value=300.0)
        self.ftp_hdg_var = tk.DoubleVar(value=180.0)

        self.ffd_duration_var = tk.DoubleVar(value=10.0)
        self.ffd_vel_var = tk.DoubleVar(value=300.0)
        self.ffd_hdg_var = tk.DoubleVar(value=90.0)
        self.ffd_alt_var = tk.DoubleVar(value=10000.0)

        self.dm_duration_var = tk.DoubleVar(value=10.0)
        self.dm_speed_kn_var = tk.DoubleVar(value=300.0)
        self.dm_lon_accel_g_var = tk.DoubleVar(value=0.0)
        self.dm_ver_accel_g_var = tk.DoubleVar(value=0.0)
        self.dm_turn_def_var = tk.StringVar(value="g_force")
        self.dm_lat_accel_g_var = tk.DoubleVar(value=1.0)
        self.dm_turn_rate_var = tk.DoubleVar(value=3.0)
        self.dm_turn_dir_var = tk.StringVar(value=TurnDirection.RIGHT.value)

    def _load_waypoint_data(self, wp: Waypoint):
        """Populates the fields with data from an existing waypoint."""
        self.maneuver_type_var.set(wp.maneuver_type.value)
        if wp.maneuver_type == ManeuverType.FLY_TO_POINT:
            self.ftp_duration_var.set(wp.duration_s or 10.0)
            self.ftp_range_var.set(wp.target_range_nm or 0.0)
            self.ftp_az_var.set(wp.target_azimuth_deg or 0.0)
            self.ftp_alt_var.set(wp.target_altitude_ft or 10000.0)
            self.ftp_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS)
            self.ftp_hdg_var.set(wp.target_heading_deg or 0.0)
        elif wp.maneuver_type == ManeuverType.FLY_FOR_DURATION:
            self.ffd_duration_var.set(wp.duration_s or 10.0)
            self.ffd_vel_var.set((wp.target_velocity_fps or 0.0) * FPS_TO_KNOTS)
            self.ffd_hdg_var.set(wp.target_heading_deg or 0.0)
            self.ffd_alt_var.set(wp.target_altitude_ft or 10000.0)
        elif wp.maneuver_type == ManeuverType.DYNAMIC_MANEUVER:
            self.dm_duration_var.set(wp.duration_s or 10.0)
            self.dm_speed_kn_var.set((wp.maneuver_speed_fps or 300.0) * FPS_TO_KNOTS)
            self.dm_lon_accel_g_var.set(wp.longitudinal_acceleration_g or 0.0)
            self.dm_lat_accel_g_var.set(wp.lateral_acceleration_g or 0.0)
            self.dm_ver_accel_g_var.set(wp.vertical_acceleration_g or 0.0)
            if wp.turn_direction:
                self.dm_turn_dir_var.set(wp.turn_direction.value)
            # Sync the turn rate spinbox from the loaded G value
            self._update_turn_rate_from_g()

    def _center_window(self):
        self.update_idletasks()
        parent = self.master
        x = parent.winfo_x() + (parent.winfo_width() // 2) - (self.winfo_width() // 2)
        y = parent.winfo_y() + (parent.winfo_height() // 2) - (self.winfo_height() // 2)
        self.geometry(f"+{x}+{y}")

    def _create_widgets(self):
        main_frame = ttk.Frame(self, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)
        type_frame = ttk.Frame(main_frame)
        type_frame.pack(fill=tk.X, pady=(0, 10))
        ttk.Label(type_frame, text="Maneuver Type:").pack(side=tk.LEFT)
        self.maneuver_combo = ttk.Combobox(
            type_frame,
            textvariable=self.maneuver_type_var,
            values=[m.value for m in ManeuverType],
            state="readonly",
        )
        self.maneuver_combo.pack(side=tk.LEFT, expand=True, fill=tk.X, padx=5)
        self.maneuver_combo.bind("<<ComboboxSelected>>", self._on_maneuver_type_change)
        self.fly_to_point_frame = self._create_fly_to_point_frame(main_frame)
        self.fly_for_duration_frame = self._create_fly_for_duration_frame(main_frame)
        self.dynamic_maneuver_frame = self._create_dynamic_maneuver_frame(main_frame)
        button_frame = ttk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=(15, 0), side=tk.BOTTOM)
        ttk.Button(button_frame, text="OK", command=self._on_ok).pack(
            side=tk.RIGHT, padx=5
        )
        ttk.Button(button_frame, text="Cancel", command=self._on_cancel).pack(
            side=tk.RIGHT
        )
        if self.is_first_waypoint:
            self.maneuver_type_var.set(ManeuverType.FLY_TO_POINT.value)
            self.maneuver_combo.config(state=tk.DISABLED)
        else:
            self.maneuver_type_var.set(ManeuverType.FLY_FOR_DURATION.value)

    def _create_fly_to_point_frame(self, parent) -> ttk.Frame:
        frame = ttk.LabelFrame(parent, text="Position and State", padding=10)
        frame.columnconfigure(1, weight=1)
        ttk.Label(frame, text="Duration to Point (s):").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=0.1, to=3600 * 24, textvariable=self.ftp_duration_var, width=10
        ).grid(row=0, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Target Range (NM):").grid(
            row=1, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=0, to=1000, textvariable=self.ftp_range_var, width=10
        ).grid(row=1, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Target Azimuth (°):").grid(
            row=2, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=-180, to=180, textvariable=self.ftp_az_var, width=10
        ).grid(row=2, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Target Altitude (ft):").grid(
            row=3, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=-1000, to=80000, textvariable=self.ftp_alt_var, width=10
        ).grid(row=3, column=1, sticky=tk.EW, pady=2)
        if self.is_first_waypoint:
            ttk.Label(frame, text="Initial Velocity (kn):").grid(
                row=4, column=0, sticky=tk.W, pady=2
            )
            ttk.Spinbox(
                frame, from_=0, to=2000, textvariable=self.ftp_vel_var, width=10
            ).grid(row=4, column=1, sticky=tk.EW, pady=2)
            ttk.Label(frame, text="Initial Heading (°):").grid(
                row=5, column=0, sticky=tk.W, pady=2
            )
            ttk.Spinbox(
                frame,
                from_=0,
                to=359.9,
                format="%.1f",
                increment=0.1,
                textvariable=self.ftp_hdg_var,
                width=10,
            ).grid(row=5, column=1, sticky=tk.EW, pady=2)
        return frame

    def _create_fly_for_duration_frame(self, parent) -> ttk.Frame:
        frame = ttk.LabelFrame(parent, text="Constant State Flight", padding=10)
        frame.columnconfigure(1, weight=1)
        ttk.Label(frame, text="Duration (s):").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=0.1, to=3600 * 24, textvariable=self.ffd_duration_var, width=10
        ).grid(row=0, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Constant Velocity (kn):").grid(
            row=1, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=0, to=2000, textvariable=self.ffd_vel_var, width=10
        ).grid(row=1, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Constant Heading (°):").grid(
            row=2, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame,
            from_=0,
            to=359.9,
            format="%.1f",
            increment=0.1,
            textvariable=self.ffd_hdg_var,
            width=10,
        ).grid(row=2, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Constant Altitude (ft):").grid(
            row=3, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=-1000, to=80000, textvariable=self.ffd_alt_var, width=10
        ).grid(row=3, column=1, sticky=tk.EW, pady=2)
        return frame

    def _create_dynamic_maneuver_frame(self, parent) -> ttk.Frame:
        frame = ttk.LabelFrame(parent, text="Dynamic Maneuver Controls", padding=10)
        frame.columnconfigure(1, weight=1)
        ttk.Label(frame, text="Duration (s):").grid(
            row=0, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame, from_=0.1, to=3600 * 24, textvariable=self.dm_duration_var, width=10
        ).grid(row=0, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Maneuver Speed (kn):").grid(
            row=1, column=0, sticky=tk.W, pady=2
        )
        speed_spinbox = ttk.Spinbox(
            frame,
            from_=0,
            to=2000,
            textvariable=self.dm_speed_kn_var,
            width=10,
            command=self._update_g_from_turn_rate,
        )
        speed_spinbox.grid(row=1, column=1, sticky=tk.EW, pady=2)
        turn_frame = ttk.LabelFrame(frame, text="Turn Definition", padding=5)
        turn_frame.grid(row=2, column=0, columnspan=2, sticky=tk.EW, pady=5)
        g_force_rb = ttk.Radiobutton(
            turn_frame,
            text="By G-Force",
            variable=self.dm_turn_def_var,
            value="g_force",
            command=self._on_turn_definition_change,
        )
        g_force_rb.grid(row=0, column=0, sticky=tk.W)
        self.dm_lat_g_spinbox = ttk.Spinbox(
            turn_frame,
            from_=0,
            to=9,
            format="%.2f",
            increment=0.1,
            textvariable=self.dm_lat_accel_g_var,
            width=8,
            command=self._update_turn_rate_from_g,
        )
        self.dm_lat_g_spinbox.grid(row=0, column=1, padx=5)
        turn_rate_rb = ttk.Radiobutton(
            turn_frame,
            text="By Turn Rate (°/s)",
            variable=self.dm_turn_def_var,
            value="turn_rate",
            command=self._on_turn_definition_change,
        )
        turn_rate_rb.grid(row=1, column=0, sticky=tk.W)
        self.dm_turn_rate_spinbox = ttk.Spinbox(
            turn_frame,
            from_=0,
            to=30,
            format="%.2f",
            increment=0.1,
            textvariable=self.dm_turn_rate_var,
            width=8,
            command=self._update_g_from_turn_rate,
        )
        self.dm_turn_rate_spinbox.grid(row=1, column=1, padx=5)
        ttk.Label(turn_frame, text="Direction:").grid(
            row=0, column=2, sticky=tk.W, padx=(10, 2)
        )
        ttk.Combobox(
            turn_frame,
            textvariable=self.dm_turn_dir_var,
            values=[d.value for d in TurnDirection],
            state="readonly",
            width=7,
        ).grid(row=0, column=3, sticky=tk.W)
        self._on_turn_definition_change()
        ttk.Label(frame, text="Longitudinal Accel (g):").grid(
            row=3, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame,
            from_=-5,
            to=10,
            format="%.2f",
            increment=0.1,
            textvariable=self.dm_lon_accel_g_var,
            width=10,
        ).grid(row=3, column=1, sticky=tk.EW, pady=2)
        ttk.Label(frame, text="Vertical Accel (g):").grid(
            row=4, column=0, sticky=tk.W, pady=2
        )
        ttk.Spinbox(
            frame,
            from_=-5,
            to=5,
            format="%.2f",
            increment=0.1,
            textvariable=self.dm_ver_accel_g_var,
            width=10,
        ).grid(row=4, column=1, sticky=tk.EW, pady=2)
        return frame

    def _update_g_from_turn_rate(self):
        if self._is_updating_turn_def:
            return
        self._is_updating_turn_def = True
        try:
            rate_deg_s = self.dm_turn_rate_var.get()
            speed_kn = self.dm_speed_kn_var.get()
            if speed_kn > 0:
                accel_fps2 = math.radians(rate_deg_s) * (speed_kn * KNOTS_TO_FPS)
                g_load = accel_fps2 / G_IN_FPS2
                self.dm_lat_accel_g_var.set(round(g_load, 2))
        except (ValueError, tk.TclError):
            pass
        finally:
            self._is_updating_turn_def = False

    def _update_turn_rate_from_g(self):
        if self._is_updating_turn_def:
            return
        self._is_updating_turn_def = True
        try:
            g_load = self.dm_lat_accel_g_var.get()
            speed_kn = self.dm_speed_kn_var.get()
            speed_fps = speed_kn * KNOTS_TO_FPS
            if speed_fps > 0:
                accel_fps2 = g_load * G_IN_FPS2
                rate_rad_s = accel_fps2 / speed_fps
                self.dm_turn_rate_var.set(round(math.degrees(rate_rad_s), 2))
        except (ValueError, tk.TclError):
            pass
        finally:
            self._is_updating_turn_def = False

    def _on_turn_definition_change(self):
        if self.dm_turn_def_var.get() == "g_force":
            self.dm_lat_g_spinbox.config(state=tk.NORMAL)
            self.dm_turn_rate_spinbox.config(state=tk.DISABLED)
            self._update_turn_rate_from_g()
        else:
            self.dm_lat_g_spinbox.config(state=tk.DISABLED)
            self.dm_turn_rate_spinbox.config(state=tk.NORMAL)
            self._update_g_from_turn_rate()

    def _on_maneuver_type_change(self, event=None):
        try:
            m_type = ManeuverType(self.maneuver_type_var.get())
        except ValueError:
            return
        self.fly_to_point_frame.pack_forget()
        self.fly_for_duration_frame.pack_forget()
        self.dynamic_maneuver_frame.pack_forget()
        if m_type == ManeuverType.FLY_TO_POINT:
            self.fly_to_point_frame.pack(fill=tk.BOTH, expand=True)
        elif m_type == ManeuverType.FLY_FOR_DURATION:
            self.fly_for_duration_frame.pack(fill=tk.BOTH, expand=True)
        elif m_type == ManeuverType.DYNAMIC_MANEUVER:
            self.dynamic_maneuver_frame.pack(fill=tk.BOTH, expand=True)

    def _on_ok(self):
        try:
            m_type = ManeuverType(self.maneuver_type_var.get())
            wp = Waypoint(maneuver_type=m_type)
            if m_type == ManeuverType.FLY_TO_POINT:
                (
                    wp.duration_s,
                    wp.target_range_nm,
                    wp.target_azimuth_deg,
                    wp.target_altitude_ft,
                ) = (
                    self.ftp_duration_var.get(),
                    self.ftp_range_var.get(),
                    self.ftp_az_var.get(),
                    self.ftp_alt_var.get(),
                )
                if self.is_first_waypoint:
                    wp.target_velocity_fps, wp.target_heading_deg = (
                        self.ftp_vel_var.get() * KNOTS_TO_FPS,
                        self.ftp_hdg_var.get(),
                    )
            elif m_type == ManeuverType.FLY_FOR_DURATION:
                (
                    wp.duration_s,
                    wp.target_velocity_fps,
                    wp.target_heading_deg,
                    wp.target_altitude_ft,
                ) = (
                    self.ffd_duration_var.get(),
                    self.ffd_vel_var.get() * KNOTS_TO_FPS,
                    self.ffd_hdg_var.get(),
                    self.ffd_alt_var.get(),
                )
            elif m_type == ManeuverType.DYNAMIC_MANEUVER:
                (
                    wp.duration_s,
                    wp.maneuver_speed_fps,
                    wp.longitudinal_acceleration_g,
                    wp.vertical_acceleration_g,
                    wp.turn_direction,
                ) = (
                    self.dm_duration_var.get(),
                    self.dm_speed_kn_var.get() * KNOTS_TO_FPS,
                    self.dm_lon_accel_g_var.get(),
                    self.dm_ver_accel_g_var.get(),
                    TurnDirection(self.dm_turn_dir_var.get()),
                )
                if self.dm_turn_def_var.get() == "g_force":
                    wp.lateral_acceleration_g = self.dm_lat_accel_g_var.get()
                else:
                    self._update_g_from_turn_rate()
                    wp.lateral_acceleration_g = self.dm_lat_accel_g_var.get()
            self.result_waypoint = wp
            self.destroy()
        except (ValueError, tk.TclError) as e:
            messagebox.showerror(
                "Invalid Input", f"Please enter valid numbers.\nError: {e}", parent=self
            )

    def _on_cancel(self):
        self.result_waypoint = None
        self.destroy()
