#!/usr/bin/env python

# This file is part of PixelFlasher https://github.com/badabing2005/PixelFlasher
#
# Copyright (C) 2025 Badabing2005
# SPDX-FileCopyrightText: 2025 Badabing2005
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License
# for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# Also add information on how to contact you by electronic and paper mail.
#
# If your software can interact with users remotely through a computer network,
# you should also make sure that it provides a way for users to get its source.
# For example, if your program is a web application, its interface could
# display a "Source" link that leads users to an archive of the code. There are
# many ways you could offer source, and different solutions will be better for
# different programs; see section 13 for the specific requirements.
#
# You should also get your employer (if you work as a programmer) or school, if
# any, to sign a "copyright disclaimer" for the program, if necessary. For more
# information on this, and how to apply and follow the GNU AGPL, see
# <https://www.gnu.org/licenses/>.

import wx
import wx.lib.mixins.listctrl as listmix
import images
from i18n import _


# ============================================================================
#                               Class ListCtrl
# ============================================================================
class ListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin):
    def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0):
        wx.ListCtrl.__init__(self, parent, ID, pos, size, style)
        listmix.ListCtrlAutoWidthMixin.__init__(self)


# ============================================================================
#                               Class DeviceSelectorDialog
# ============================================================================
class DeviceSelectorDialog(wx.Dialog, listmix.ColumnSorterMixin):

    # ============================================================================
    #                               Function __init__
    # ============================================================================
    def __init__(self, parent, devices, title=_("Select Device"), message=_("Select a device:"), select_device=None, show_filename=True):
        wx.Dialog.__init__(self, parent, title=title, style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)

        self.devices = devices
        self.selected_device = None
        self._select_device = select_device
        self.show_filename = show_filename

        self._create_ui(message)
        self._bind_events()
        self._size_and_center()
        self.Bind(wx.EVT_CLOSE, self.on_close)

    # ============================================================================
    #                               Function _create_ui
    # ============================================================================
    def _create_ui(self, message):
        main_sizer = wx.BoxSizer(wx.VERTICAL)

        # Message
        message_label = wx.StaticText(self, label=message)
        main_sizer.Add(message_label, 0, wx.ALL | wx.EXPAND, 10)

        # Sort arrow images
        self.il = wx.ImageList(16, 16)
        self.sm_up = self.il.Add(images.SmallUpArrow.GetBitmap())
        self.sm_dn = self.il.Add(images.SmallDnArrow.GetBitmap())

        # Device list
        self.device_list = ListCtrl(self, -1, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.BORDER_NONE)
        self.device_list.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
        self.device_list.AppendColumn(_("Device"), width=200)
        if self.show_filename:
            self.device_list.AppendColumn(_("Filename"), width=400)

        has_dates = any('last_updated' in d for d in self.devices)
        if has_dates:
            self.device_list.AppendColumn(_("Last Updated"), width=110)

        # Populate the list
        self.itemDataMap = {}
        selected_index = None

        for i, device in enumerate(self.devices):
            index = self.device_list.InsertItem(i, device.get('device', 'Unknown'))
            self.device_list.SetItemColumnImage(index, 0, -1)  # suppress row icon
            col = 1
            if self.show_filename:
                self.device_list.SetItem(index, col, device.get('zip_filename', ''))
                col += 1
            if has_dates:
                self.device_list.SetItem(index, col, device.get('last_updated', ''))
            self.device_list.SetItemData(index, i)

            # Build sort map tuple, must match column order
            map_vals = [device.get('device', '')]
            if self.show_filename:
                map_vals.append(device.get('zip_filename', ''))
            if has_dates:
                map_vals.append(device.get('last_updated', ''))
            self.itemDataMap[i] = tuple(map_vals)

            if selected_index is None and self._matches_select_device(device):
                selected_index = index

        # Select first item by default
        if selected_index is not None:
            self.device_list.Select(selected_index)
            self.device_list.EnsureVisible(selected_index)
        elif self.devices:
            self.device_list.Select(0)

        # Init column sorter, must be after itemDataMap and device_list are ready
        num_cols = 1 + (1 if self.show_filename else 0) + (1 if has_dates else 0)
        listmix.ColumnSorterMixin.__init__(self, num_cols)

        main_sizer.Add(self.device_list, 1, wx.ALL | wx.EXPAND, 10)

        # Buttons
        button_sizer = wx.StdDialogButtonSizer()

        ok_button = wx.Button(self, wx.ID_OK, _("OK"))
        ok_button.SetDefault()
        button_sizer.AddButton(ok_button)

        cancel_button = wx.Button(self, wx.ID_CANCEL, _("Cancel"))
        button_sizer.AddButton(cancel_button)

        button_sizer.Realize()
        main_sizer.Add(button_sizer, 0, wx.ALL | wx.ALIGN_RIGHT, 10)

        self.SetSizer(main_sizer)

    # ============================================================================
    #                               Function GetListCtrl
    # ============================================================================
    # Used by ColumnSorterMixin
    def GetListCtrl(self):
        return self.device_list

    # ============================================================================
    #                               Function GetSortImages
    # ============================================================================
    # Used by ColumnSorterMixin
    def GetSortImages(self):
        return (self.sm_dn, self.sm_up)

    # ============================================================================
    #                               Function _matches_select_device
    # ============================================================================
    def _matches_select_device(self, device):
        if not self._select_device:
            return False

        target = self._select_device

        if isinstance(target, dict):
            if device is target:
                return True
            target_device = target.get('device')
            target_filename = target.get('zip_filename')
        else:
            target_device = target
            target_filename = target

        def normalize(value):
            if value is None:
                return None
            if isinstance(value, str):
                return value.strip().lower()
            return str(value).strip().lower()

        device_name = normalize(device.get('device'))
        filename = normalize(device.get('zip_filename'))

        if isinstance(target, dict):
            target_device_normalized = normalize(target_device)
            target_filename_normalized = normalize(target_filename)
            return (
                target_device_normalized is not None and target_device_normalized == device_name
            ) or (
                target_filename_normalized is not None and target_filename_normalized == filename
            )

        normalized_target = normalize(target_device)
        return normalized_target is not None and (
            normalized_target == device_name or normalized_target == filename
        )

    # ============================================================================
    #                               Function _bind_events
    # ============================================================================
    def _bind_events(self):
        self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.on_item_activated)
        self.Bind(wx.EVT_BUTTON, self.on_ok, id=wx.ID_OK)

    # ============================================================================
    #                               Function _size_and_center
    # ============================================================================
    def _size_and_center(self):
        self.SetSize((750, 850))
        self.CenterOnParent()

    # ============================================================================
    #                               Function on_item_activated
    # ============================================================================
    def on_item_activated(self, event):
        # Handle double-click on list item
        self.EndModal(wx.ID_OK)

    # ============================================================================
    #                               Function on_ok
    # ============================================================================
    def on_ok(self, event):
        selection = self.device_list.GetFirstSelected()
        if selection != -1:
            device_index = self.device_list.GetItemData(selection)
            self.selected_device = self.devices[device_index]
            self.EndModal(wx.ID_OK)
        else:
            wx.MessageBox(_("Please select a device."), _("No Selection"), wx.OK | wx.ICON_WARNING, self)

    # ============================================================================
    #                               Function on_close
    # ============================================================================
    def on_close(self, event):
        self.EndModal(wx.ID_CANCEL)

    # ============================================================================
    #                               Function get_selected_device
    # ============================================================================
    def get_selected_device(self):
        return self.selected_device

# ============================================================================
#                               Function show_device_selector
# ============================================================================
def show_device_selector(parent, devices, title=_("Select Device"), message=_("Select a device:"), select_device=None, show_filename=True):
    """
    Show device selector dialog and return selected device.

    Args:
        parent: Parent window
        devices: List of device dictionaries with 'device', 'zip_filename', 'url' keys
        title: Dialog title
        message: Message to display above the list
        select_device: Preferred device (dict or string identifier) to pre-select if available
        show_filename: Whether to show the Filename column (default True)

    Returns:
        Selected device dictionary or None if cancelled
    """
    if not devices:
        wx.MessageBox(_("No devices available."), _("Error"), wx.OK | wx.ICON_ERROR, parent)
        return None

    dialog = DeviceSelectorDialog(parent, devices, title, message, select_device=select_device, show_filename=show_filename)

    try:
        if dialog.ShowModal() == wx.ID_OK:
            return dialog.get_selected_device()
        else:
            return None
    finally:
        dialog.Destroy()
