#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
WMM Applet - Cinnamon Edition
----------------------------
panel.py – Panel de control de WMM (Ajustes). ORQUESTADOR.

Instancia las secciones (Opciones, Favoritos, Fuentes, Monitores,
Thumbnails) y las empaqueta en la ventana principal.
Gestiona la comunicación entre secciones mediante callbacks.
"""

import os
import sys
import json
import signal
import subprocess
import time
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib

# ==========================================================
# CONFIGURACIÓN DEL PATH DEL PROYECTO
# ==========================================================
_PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _PROJECT_ROOT)

# ==========================================================
# IMPORTS DE MÓDULOS DEL PROYECTO
# ==========================================================
from wmm_platform.core import PlatformManager
from config_handler import ConfigHandler
from backend import WMMBackend
from image_engine import ImageEngine
from debug_logger import log_event
from i18n import _, set_app_domain, set_locale_dir

# Secciones del panel
from panel.options_section import OptionsSection
from panel.favorites_section import FavoritesSection
from panel.sources_section import SourcesSection
from panel.monitors_section import MonitorsSection
from panel.thumbnails_section import ThumbnailsSection


class WMMControlPanel(Gtk.ApplicationWindow):
    """
    Ventana principal de configuración de WMM.
    """

    def __init__(self, app):
        super().__init__(application=app, title="WMM - " + _("Control Center"))
        self.set_icon_name("preferences-system")
        self.set_size_request(1205, 925)
        self.set_position(Gtk.WindowPosition.CENTER)

        # ----------------------------------------------------------
        # INICIALIZACIÓN DE DEPENDENCIAS
        # ----------------------------------------------------------
        # Obtener la ruta de caché de la plataforma
        platform = PlatformManager()
        cache_base = None
        if hasattr(platform, 'get_cache_dir') and platform.get_cache_dir:
            cache_base = platform.get_cache_dir()
        log_event(f"Cache dir desde PlatformManager: {platform.cache_dir}", origin="PANEL", level="DEBUG", reason="SETTINGS")
        self.handler = ConfigHandler(cache_base_dir=platform.cache_dir)
        self.backend = WMMBackend(self.handler)
        self.ie = ImageEngine(self.handler, None)

        # Configurar el sistema de traducciones
        set_app_domain(platform.app_domain)
        set_locale_dir(platform.locale_dir)

        # ----------------------------------------------------------
        # VARIABLES DE ESTADO DEL PANEL
        # ----------------------------------------------------------
        self._loading = False
        self._busy_counter = 0
        self._debug_log_viewer = None
        self._help_viewer = None
        self._sources_need_refresh = False

        # ----------------------------------------------------------
        # CSS DEL PANEL
        # ----------------------------------------------------------
        css_provider = Gtk.CssProvider()
        css_provider.load_from_data(b"""
            .favorite-thumbnail {
                border: 2px solid @theme_selected_bg_color;
                border-radius: 4px;
                padding: 2px;
            }
            .favorites-panel {
                border: 2px solid @theme_selected_bg_color;
                border-radius: 4px;
            }
            .dnd-source {
                border: 2px solid @warning_color;
            }
            .monitor-frame {
                border: 2px solid @theme_fg_color;
                background: alpha(@theme_fg_color, 0.05);
            }
            .dnd-target {
                border-color: @theme_selected_bg_color;
                border-width: 2px;
            }
            .align-active {
                background-color: alpha(@theme_fg_color, 0.3);
                border-radius: 3px;
                padding: 0px 2px;
            }
            .edit-aspects-box {
                border: 1px solid alpha(@theme_fg_color, 0.4);
                border-radius: 4px;
                padding: 4px 4px;
            }
        """)
        Gtk.StyleContext.add_provider_for_screen(
            Gdk.Screen.get_default(),
            css_provider,
            Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )

        # ----------------------------------------------------------
        # CONSTRUCCIÓN DE LA INTERFAZ
        # ----------------------------------------------------------
        self.main_layout = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=8)
        self.main_layout.set_border_width(10)
        self.add(self.main_layout)

        # COLUMNA IZQUIERDA
        self.left_col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        self.left_col.set_size_request(350, -1)
        self.main_layout.pack_start(self.left_col, False, False, 0)

        # COLUMNA DERECHA
        self.right_col = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)
        self.main_layout.pack_start(self.right_col, True, True, 0)

        # ----------------------------------------------------------
        # WIDGETS COMPARTIDOS ENTRE SECCIONES
        # ----------------------------------------------------------
        # Estos widgets son usados por más de una sección.
        # Los creamos aquí y los pasamos como parámetros.

        # Checkbox de favorito (usado por Monitors y Favorites)
        self.fav_checkbox = Gtk.CheckButton(label="")
        self.fav_checkbox.set_tooltip_text(_("Create/update preset on accept"))

        # Botón Aceptar (usado por Monitors y Favorites)
        self.accept_btn = Gtk.Button.new_from_icon_name("emblem-ok-symbolic", Gtk.IconSize.BUTTON)
        self.accept_btn.set_tooltip_text(_("Save composition as preset"))
        self.accept_btn.set_sensitive(False)

        # Barra de progreso y espaciador (compartidos entre Sources y Thumbnails)
        self.scan_progress = Gtk.ProgressBar()
        self.scan_progress.set_no_show_all(True)
        self.scan_progress.hide()
        self.btn_spacer = Gtk.Box()
        # ----------------------------------------------------------
        # INSTANCIACIÓN DE SECCIONES
        # ----------------------------------------------------------
        # Todas las secciones que requieren callbacks se crean sin ellos
        # y los reciben después mediante set_callbacks(), evitando
        # dependencias circulares.

        # 1. Monitores
        self.monitors_section = MonitorsSection(
            self.handler, self.backend, self.ie, self.right_col,
            fav_checkbox=self.fav_checkbox,
            accept_btn=self.accept_btn,
            spanned_switch=None
        )

        # 2. Opciones
        self.options_section = OptionsSection(
            self.handler, self.backend, self.ie, self.left_col
        )

        # 3. Favoritos
        self.favorites_section = FavoritesSection(
            self.handler, self.backend, self.ie, self.left_col
        )

        # 4. Asignar callbacks a las tres primeras
        self.monitors_section.set_callbacks(self._build_monitors_callbacks())
        self.options_section.set_callbacks(self._build_options_callbacks())
        self.favorites_section.set_callbacks(self._build_favorites_callbacks())

        # 5. Asignar spanned_switch de Options a Monitors
        self.monitors_section.spanned_switch = self.options_section.spanned_switch

        # 6. Fuentes (independiente)
        self.sources_section = SourcesSection(
            self.handler, self.backend, self.ie, self.left_col,
            scan_progress=self.scan_progress, btn_spacer=self.btn_spacer
        )
        self.sources_section.set_callbacks({
            'increment_busy': self._increment_busy,
            'decrement_busy': self._decrement_busy,
            'set_progress_active': self._set_progress_active,
        })

        # 7. Thumbnails (sin callbacks aún)
        self.thumbnails_section = ThumbnailsSection(
            self.handler, self.backend, self.right_col,
            scan_progress=self.scan_progress,
            btn_spacer=self.btn_spacer
        )
        # Ahora que existe, le asignamos sus callbacks
        self.thumbnails_section.set_callbacks(self._build_thumbnails_callbacks())

        # Conectar SourcesSection con ThumbnailsSection para refrescar miniaturas
        self.sources_section._callbacks['load_thumbnails'] = self.thumbnails_section._load_thumbnails

        # ----------------------------------------------------------
        # CARGA INICIAL DE DATOS
        # ----------------------------------------------------------
        self.sources_section.load_sources_into_treeview()
        self._load_settings()
        self.favorites_section._load_presets()
        self.favorites_section._load_bookmarks_single()
        self.thumbnails_section._load_thumbnails()
        GLib.idle_add(self.monitors_section._load_monitors_normal)

        # ----------------------------------------------------------
        # SEÑALES DEL SISTEMA
        # ----------------------------------------------------------
        self._register_pid_and_signal()
        log_event("Registro de PID completado.", origin="PANEL", level="DEBUG", reason="NOTIFY")
        self.connect("delete-event", self._on_delete_event)
        self.show_all()

    # ==========================================================
    # CALLBACKS PARA OPTIONS SECTION
    # ==========================================================
    def _build_options_callbacks(self):
        """Construye el diccionario de callbacks para OptionsSection."""
        return {
            'create_section_label': self._create_section_label,
            'notify_engine': self._notify_engine,
            'load_monitors': self.monitors_section._load_monitors_normal,
            'set_monitor_switches_sensitive': self.monitors_section._set_monitor_switches_sensitive,
            'get_edit_mode_active': lambda: self.backend.edit_mode_active,
            'get_loading_state': lambda: self._loading,
            'get_fav_rotation_switch': lambda: self.favorites_section.fav_rotation_switch,
            'get_parent_window': lambda: self,
            'get_application': lambda: self.get_application(),
            'on_open_debug_log': self._on_open_debug_log,
            'on_open_help': self._on_open_help,
        }

    # ==========================================================
    # CALLBACKS PARA MONITORS SECTION
    # ==========================================================
    def _build_monitors_callbacks(self):
        """Construye el diccionario de callbacks para MonitorsSection."""
        return {
            'create_section_label': self._create_section_label,
            'notify_engine': self._notify_engine,
            'load_monitors': self.monitors_section._load_monitors_normal,
            'load_presets': self.favorites_section._load_presets,
            'load_bookmarks_single': self.favorites_section._load_bookmarks_single,
            'set_monitor_switches_sensitive': self.monitors_section._set_monitor_switches_sensitive,
            'get_loading_state': lambda: self._loading,
            'get_edit_mode_active': lambda: self.backend.edit_mode_active,
            'get_parent_window': lambda: self,
        }

    # ==========================================================
    # CALLBACKS PARA FAVORITES SECTION
    # ==========================================================
    def _build_favorites_callbacks(self):
        """Construye el diccionario de callbacks para FavoritesSection."""
        return {
            'on_load_preset_to_edit': self._on_load_preset_to_edit,
            'on_update_mode_info_label': self.options_section._update_mode_info_label,
            'on_show_warning_dialog': self._show_warning_dialog,
            'get_loading_state': lambda: self._loading,
            'get_parent_window': lambda: self,
        }

    # ==========================================================
    # CALLBACKS PARA THUMBNAILS SECTION
    # ==========================================================
    def _build_thumbnails_callbacks(self):
        """Construye el diccionario de callbacks para ThumbnailsSection."""
        log_event(f"Decrement busy: contador={self._busy_counter}", origin="PANEL", level="DEBUG", reason="LIBRARY"),
        return {
            'increment_busy': self._increment_busy,
            'decrement_busy': self._decrement_busy,
            'set_progress_active': self._set_progress_active,
            'load_bookmarks_single': self.favorites_section._load_bookmarks_single,
            'load_thumbnails': self.thumbnails_section._load_thumbnails,
        }

    # ==========================================================
    # MÉTODOS AUXILIARES DEL PANEL
    # ==========================================================
    def _create_section_label(self, text):
        """Crea una etiqueta de sección con formato."""
        label = Gtk.Label()
        label.set_markup(f"<span weight='bold' size='large'>{text}</span>")
        label.set_halign(Gtk.Align.START)
        return label

    def _notify_engine(self, action=None):
        if action is None:
            action = {"action": "force_rotation"}
        # TIMING TEST: registrar envío de petición
        with open("/tmp/wmm_timing_test.csv", "a") as f:
            import csv, fcntl
            fcntl.flock(f, fcntl.LOCK_EX)
            writer = csv.writer(f)
            writer.writerow([time.strftime("%Y-%m-%d %H:%M:%S"), "PANEL", "send_command", time.perf_counter()])
            fcntl.flock(f, fcntl.LOCK_UN)
        self.backend.notify_engine(action)
        time.sleep(0.05)

    def _increment_busy(self):
        """Incrementa el contador de ocupación y desactiva la interfaz si es el primero."""
        self._busy_counter += 1
        if self._busy_counter == 1:
            self._set_sources_sensitive(False)

    def _decrement_busy(self):
        """Decrementa el contador de ocupación y reactiva la interfaz si llega a cero."""
        if self._busy_counter == 0:
            return
        self._busy_counter -= 1
        if self._busy_counter <= 0:
            self._busy_counter = 0
            self._set_sources_sensitive(True)
            # Forzar la aparición de las flechas de expansión tras completar el proceso
            if hasattr(self, 'sources_section'):
                GLib.idle_add(self.sources_section.load_sources_into_treeview)

    def _set_sources_sensitive(self, sensitive):
        """Activa/desactiva la sección de fuentes durante operaciones largas."""
        if hasattr(self, 'sources_section'):
            self.sources_section._set_sources_sensitive(sensitive)

    def _set_progress_active(self, active, text=None):
        """Muestra/oculta la barra de progreso."""
        if active:
            log_event(f"Barra de progreso activada: {text or ''}",
                      origin="PANEL", level="INFO", reason="LIBRARY")
        else:
            log_event("Barra de progreso desactivada",
                      origin="PANEL", level="INFO", reason="LIBRARY")
        if active:
            self.btn_spacer.hide()
            self.scan_progress.show()
            self.scan_progress.set_fraction(0.0)
            if text:
                self.scan_progress.set_show_text(True)
                self.scan_progress.set_text(text)
        else:
            self.scan_progress.hide()
            self.btn_spacer.show()
            self.scan_progress.set_fraction(0.0)
            self.scan_progress.set_show_text(False)

    def _show_warning_dialog(self, message):
        """Muestra un diálogo de advertencia."""
        dialog = Gtk.MessageDialog(
            transient_for=self,
            flags=0,
            message_type=Gtk.MessageType.WARNING,
            buttons=Gtk.ButtonsType.OK,
            text=message
        )
        dialog.run()
        dialog.destroy()

    # ==========================================================
    # CARGA DE PREFERENCIAS DESDE SETTINGS.JSON
    # ==========================================================
    def _load_settings(self):
        """Carga los ajustes desde settings.json y actualiza la interfaz."""
        self._loading = True
        settings = self.backend.load_settings()

        # Sincronizar switches y combos de OptionsSection
        opts = self.options_section
        opts.slideshow_switch.set_active(settings.get("slideshow_enabled", True))
        opts.mode_switch.set_active(settings.get("slideshow_mode", "sync") == "sync")
        opts.max_interval_spin.set_value(settings.get("slideshow_max_interval", 60))
        opts.rotation_spin.set_value(settings.get("slideshow_interval", 15))
        opts.persist_switch.set_active(not settings.get("persist_on_reboot", True))
        opts.spanned_switch.set_active(settings.get("spanned_enabled", False))

        mode = settings.get("wallpaper_mode", "fit")
        keys = list(ConfigHandler.ASPECT_MODES.keys())
        index = keys.index(mode) if mode in keys else 0
        opts.aspect_combo.set_active(index)

        wallpaper_effect = settings.get("wallpaper_effect", "none")
        keys = list(ConfigHandler.IMAGE_EFFECT.keys())
        index = keys.index(wallpaper_effect) if wallpaper_effect in ConfigHandler.IMAGE_EFFECT else 0
        opts.image_effect_combo.set_active(index)

        wallpaper_effect_scope = settings.get("wallpaper_effect_scope", "blur")
        keys = list(ConfigHandler.BACK_EFFECT.keys())
        index = keys.index(wallpaper_effect_scope) if wallpaper_effect_scope in ConfigHandler.BACK_EFFECT else 0
        opts.back_effect_combo.set_active(index)

        color_mode = settings.get("color_mode", "solid_color")
        keys = list(ConfigHandler.COLOR_MODES.keys())
        index = keys.index(color_mode) if color_mode in keys else 0
        opts.bg_mode_combo.set_active(index)
        opts._update_bg_color_buttons(color_mode)

        # Colores
        self._set_btn_color(opts.solid_color_btn, settings.get("solid_color", "#000000"))
        grad_h = settings.get("gradient_h", ["#000000", "#8D9797"])
        self._set_btn_color(opts.grad_h_start_btn, grad_h[0])
        self._set_btn_color(opts.grad_h_end_btn, grad_h[1])
        grad_v = settings.get("gradient_v", ["#000000", "#8D9797"])
        self._set_btn_color(opts.grad_v_start_btn, grad_v[0])
        self._set_btn_color(opts.grad_v_end_btn, grad_v[1])

        # Sincronizar switch de favoritos
        self.favorites_section.fav_rotation_switch.set_active(settings.get("slideshow_bookmark", False))

        # Actualizar label de modo
        opts._update_mode_info_label()

        # Restaurar alineación del grupo de monitores
        align_value = settings.get("monitor_group_align", "START")
        align_map = {
            "START": Gtk.Align.START,
            "CENTER": Gtk.Align.CENTER,
            "END": Gtk.Align.END
        }
        self.monitors_section.monitor_group_box.set_halign(align_map.get(align_value, Gtk.Align.START))
        self.monitors_section._update_align_labels(align_map.get(align_value, Gtk.Align.START))

        self._loading = False

    @staticmethod
    def _set_btn_color(btn, hex_color):
        """Aplica un color hexadecimal a un Gtk.ColorButton."""
        rgba = Gdk.RGBA()
        rgba.parse(hex_color)
        btn.set_rgba(rgba)

    # ==========================================================
    # CARGA DE PRESET EN MODO EDICIÓN (LLAMADO DESDE FAVORITOS)
    # ==========================================================
    def _on_load_preset_to_edit(self, preset_name):
        """Callback cuando FavoritesSection solicita cargar un preset en modo edición."""
        # Activar modo edición si no lo está
        if not self.backend.edit_mode_active:
            self.monitors_section.edit_mode_btn.set_active(True)

        # Delegar en el backend la carga de datos del preset
        preset_monitors = self.backend.load_preset_for_edit(preset_name)
        if preset_monitors is None:
            return

        # Actualizar UI
        self.monitors_section._current_preset_name = preset_name
        self.fav_checkbox.set_active(True)

        # Sincronizar UI de edición con valores temporales del backend
        if self.backend.edit_temp_spanned_enabled is not None:
            self.monitors_section.spanned_switch_edit.set_active(self.backend.edit_temp_spanned_enabled)
        if self.backend.edit_temp_image_effect is not None:
            keys = list(ConfigHandler.IMAGE_EFFECT.keys())
            index = keys.index(self.backend.edit_temp_image_effect) if self.backend.edit_temp_image_effect in keys else 0
            self.monitors_section.image_effect_combo_edit.set_active(index)
        if self.backend.edit_temp_wp_scope is not None:
            # No hay combo para esto en edición, se guarda por consistencia
            pass

        # Pintar los monitores virtuales con los datos del preset
        self.monitors_section._load_monitors_edit()
        self.monitors_section._load_preset_to_edit_monitors(preset_monitors)

        # Sincronizar los switches de activación
        self.monitors_section._build_monitor_switches()

        # Actualizar la etiqueta de edición con el nombre del preset
        self.monitors_section.edit_preset_label.set_markup(
            "<b>" + _("Editing") + ":" + "\n" + preset_name + "</b>"
        )
        self.monitors_section.edit_preset_label.set_ellipsize(3)  # Pango.EllipsizeMode.END
        self.monitors_section.edit_preset_label.set_max_width_chars(24)
        self.monitors_section.edit_label_stack.set_visible_child_name("edit_preset")

        log_event(f"Preset cargado en edición: {preset_name}", origin="PANEL", level="INFO", reason="BOOKMARK")

    # ==========================================================
    # APERTURA DE VENTANAS EXTERNAS
    # ==========================================================
    def _on_open_debug_log(self, widget=None):
        """Abre el visor de logs o lo trae al frente si ya está abierto."""
        if self._debug_log_viewer and self._debug_log_viewer.is_visible():
            self._debug_log_viewer.present()
        else:
            from debug_log_viewer import DebugLogViewer
            self._debug_log_viewer = DebugLogViewer(self)
            self._debug_log_viewer.connect("destroy", self._on_debug_log_viewer_closed)
            self._debug_log_viewer.show_all()

    def _on_debug_log_viewer_closed(self, widget):
        """Libera la referencia cuando se cierra el visor de logs."""
        self._debug_log_viewer = None

    def _on_open_help(self, widget=None):
        """Abre la ventana de ayuda o la trae al frente si ya está abierta."""
        if self._help_viewer and self._help_viewer.is_visible():
            self._help_viewer.present()
        else:
            from help_viewer import HelpViewer
            self._help_viewer = HelpViewer(self)
            self._help_viewer.connect("destroy", self._on_help_viewer_closed)
            self._help_viewer.show_all()

    def _on_help_viewer_closed(self, widget):
        """Libera la referencia cuando se cierra el visor de ayuda."""
        self._help_viewer = None

    # ==========================================================
    # GESTIÓN DE PID Y SEÑALES DEL MOTOR
    # ==========================================================
    def _register_pid_and_signal(self):
        """Registra el PID del panel e instala el manejador de señales."""
        pid_path = os.path.join(self.handler.cache_dir, "pid_panel.pid")
        os.makedirs(os.path.dirname(pid_path), exist_ok=True)

        if os.path.exists(pid_path):
            try:
                with open(pid_path, "r") as f:
                    old_pid = int(f.read().strip())
                os.kill(old_pid, 0)
                log_event(f"Otro panel ya está corriendo (PID {old_pid})", origin="PANEL", level="WARN", reason="NOTIFY")
            except (OSError, ValueError):
                pass

        with open(pid_path, "w") as f:
            f.write(str(os.getpid()))
        log_event(f"PID {os.getpid()} registrado en {pid_path}", origin="PANEL", level="INFO", reason="NOTIFY")

        signal.signal(signal.SIGUSR1, self._handle_panel_signal)
        log_event("Manejador SIGUSR1 instalado", origin="PANEL", level="INFO", reason="SIGNAL")

    def _handle_panel_signal(self, signum, frame):
        """Manejador de la señal SIGUSR1 enviada por el motor."""
        log_event("Señal SIGUSR1 recibida del motor", origin="PANEL", level="DEBUG", reason="SIGNAL")
        command_path = os.path.join(self.handler.data_dir, "command_panel.json")
        try:
            if os.path.exists(command_path):
                with open(command_path, "r", encoding="utf-8") as f:
                    event = json.load(f)
                action = event.get("action", "")
                log_event(f"Evento recibido del motor: {action}", origin="PANEL", level="DEBUG", reason="COMMAND")

                if action == "wallpaper_changed":
                    GLib.idle_add(self.monitors_section._load_monitors_normal)
                elif action == "settings_updated":
                    GLib.idle_add(self._load_settings)
                elif action == "bookmarks_updated":
                    GLib.idle_add(self.favorites_section._load_presets)
                    GLib.idle_add(self.favorites_section._load_bookmarks_single)
                elif action == "hardware_changed":
                    GLib.idle_add(self.monitors_section._load_monitors_normal)
                else:
                    log_event(f"Acción desconocida recibida del motor: {action}", origin="PANEL", level="WARN", reason="COMMAND")

                os.remove(command_path)
        except Exception as e:
            log_event(f"Error al procesar señal del motor: {e}", origin="PANEL", level="ERROR", reason="SIGNAL")

    def _on_delete_event(self, widget, event):
        """Limpia el PID del panel al cerrar."""
        pid_path = os.path.join(self.handler.cache_dir, "pid_panel.pid")
        if os.path.exists(pid_path):
            try:
                os.remove(pid_path)
                log_event("Archivo PID eliminado. Cierre limpio", origin="PANEL", level="INFO", reason="NOTIFY")
            except Exception as e:
                log_event(f"Error al eliminar PID: {e}", origin="PANEL", level="ERROR", reason="NOTIFY")

        app = self.get_application()
        if app:
            app.quit()
        return True


class PanelApplication(Gtk.Application):
    """Aplicación GTK para el panel de control (instancia única)."""
    def __init__(self):
        super().__init__(application_id="org.wmm.Panel")
        self.window = None

    def do_activate(self):
        if not self.window:
            self.window = WMMControlPanel(self)
            self.add_window(self.window)
        self.window.present()


if __name__ == "__main__":
    app = PanelApplication()
    app.run(sys.argv)
