#!/usr/bin/env python3
"""
DebSoftwareApp protocol

- full static type‑checking
- no circular imports
- no dependency on the SoftwareGtk class
- explicit documentation of what PackageActionHandler expects

You can later add other objects to the class:
- Cancel button
- Status label
- Spinner
- Progress page
"""

import re
from typing import Protocol
import logging
from enum import Enum, StrEnum

import gi
gi.require_version("Gtk", "4.0")
gi.require_version("AppStream", "1.0")
from gi.repository import Gtk, AppStream, GLib, Gdk


class DebSoftwareApp(Protocol):
    """ DebSoftwareApp protocol """
    logging: logging.Logger
    script_dir: str
    data_dir: str
    images_dir: str
    progressbar: Gtk.ProgressBar
    progressbar_label: Gtk.Label
    window: Gtk.Window
    notebook: Gtk.Notebook
    sum_title: Gtk.Label
    display: Gdk.Display
    icon_theme: Gtk.IconTheme

class FilterKind(Enum):
    """
    Filter kind for filter_components_list
    """
    APP = [AppStream.ComponentKind.DESKTOP_APP,
           AppStream.ComponentKind.CONSOLE_APP,
           AppStream.ComponentKind.WEB_APP]
    ADDON = [AppStream.ComponentKind.ADDON,
             AppStream.ComponentKind.CODEC]

class PackageAction(Enum):
    """
    Package actions
    """
    INSTALL = 'install'
    REMOVE = 'remove'
    UPDATE = 'update'
    REFRESH_CACHE = 'refresh_cache'
    SYSTEM_UPGRADE = 'system_upgrade'

class PackageState(Enum):
    """
    Package states
    """
    INSTALLED = 'installed'
    AVAILABLE = 'available'
    REMOVED = 'removed'
    UPDATED = 'updated'

class AppStreamPackageInfo:
    """
    Represents a single package with all relevant info.
    """
    def __init__(
        self,
        package_id: str,
        package_name: str='',
        name: str='',
        summary: str='',
        description: str='',
        categories: list[str]=None,
        origin: str='',
        homepage: str='',
        icon: str='',
        screenshots: list[str]=None,
        version: str='',
        section: str='',
        is_installed: bool=False,
        installed_size: str='',
        architecture: str='amd64',
        candidate_version: str='',
        additional_packages: list[str]=None,
        executable: str='',
        download_path: str=''
    ):
        self.package_id = package_id
        self.package_name = package_name
        self.name = name
        self.summary = summary
        self.description = description
        self.categories = categories
        self.origin = origin
        self.homepage = homepage
        self.icon = icon
        self.screenshots = screenshots
        self.version = version
        self.section = section
        self.is_installed = is_installed
        self.installed_size = installed_size
        self.architecture = architecture
        self.candidate_version = candidate_version
        self.additional_packages = additional_packages
        self.executable = executable
        self.download_path = download_path

    def to_dict(self):
        """Converts the object to a dictionary."""
        return {
            'package_id': self.package_id,
            'package_name': self.package_name,
            'name': self.name,
            'summary': self.summary,
            'description': self.description,
            'categories': self.categories,
            'origin': self.origin,
            'homepage': self.homepage,
            'icon': self.icon,
            'screenshots': self.screenshots,
            'version': self.version,
            'section': self.section,
            'is_installed': self.is_installed,
            'installed_size': self.installed_size,
            'architecture': '',
            'candidate_version': self.candidate_version,
            'additional_packages': self.additional_packages,
            'executable': self.executable,
            'download_path': self.download_path
        }

class LogLevel(StrEnum):
    """ Possible log levels """
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    DEBUG = 'debug'
    SUCCESS = 'success'
    CANCELED = 'canceled'
    PROGRESS = 'progress'

class DialogIcon(StrEnum):
    """ Possible dialog icons """
    INFO = "dialog-information"
    WARNING = "dialog-warning"
    ERROR = "dialog-error"
    QUESTION = 'dialog-question'

# Map log level to dialog icon
LogLevel_To_DialogIcon = {
    LogLevel.INFO: DialogIcon.INFO,
    LogLevel.WARNING: DialogIcon.WARNING,
    LogLevel.ERROR: DialogIcon.ERROR,
    LogLevel.DEBUG: DialogIcon.INFO,
    LogLevel.SUCCESS: DialogIcon.INFO,
    LogLevel.CANCELED: DialogIcon.WARNING,
    LogLevel.PROGRESS: DialogIcon.INFO,
}

def show_dialog(
    title: str,
    text: str,
    main_window: Gtk.Window = None,
    icon: DialogIcon = DialogIcon.INFO,
    buttons: list = None,
) -> Gtk.ResponseType:
    """
    Show a dialog.

    Args:
        title (str): The title of the dialog.
        text (str): The text to display in the dialog.
        main_window (Gtk.Window, optional): The main window of the application. Defaults to None.
        icon (DialogIcon, optional): The icon to display in the dialog. Defaults to DialogIcon.INFO.
        buttons (list, optional): A list of button labels to display in the dialog.
                                  Defaults to None.

    Returns:
        Gtk.ResponseType: The response type of the dialog.
    """
    if buttons is None:
        buttons = ["OK"]

    # Create the dialog window  
    dialog = Gtk.Window(
        title=title,
        transient_for=main_window,
        modal=True,
        deletable=False,
    )
    dialog.set_default_size(400, -1)
    dialog.set_modal(True)

    # Create a custom header bar with only a close button
    header_bar = Gtk.HeaderBar()
    header_bar.set_title_widget(Gtk.Label(label=title))
    header_bar.set_show_title_buttons(False)  # Hide minimize/maximize buttons

    # Manually add a close button
    close_button = Gtk.Button(icon_name="window-close-symbolic")
    close_button.connect("clicked", lambda _: dialog.close())
    header_bar.pack_end(close_button)

    dialog.set_titlebar(header_bar)

    # Main box for content
    main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
    dialog.set_child(main_box)

    # Icon and text box
    content_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
    content_box.set_spacing(12)
    content_box.set_css_classes(['dialog_box'])
    main_box.append(content_box)

    # Add icon
    picture = Gtk.Picture()
    picture.set_can_shrink(False)
    picture.set_content_fit(Gtk.ContentFit.SCALE_DOWN)
    display = Gdk.Display.get_default()
    icon_theme = Gtk.IconTheme.get_for_display(display)
    icon_paintable = icon_theme.lookup_icon(
        icon.value, None, 64, 1, Gtk.TextDirection.LTR, 0
    )
    if icon_paintable:
        picture.set_paintable(icon_paintable)
    content_box.append(picture)

    # Add text
    label = Gtk.Label(label=text, halign=Gtk.Align.START, wrap=True)
    label.set_css_classes(['dialog_text'])
    content_box.append(label)

    # Add buttons
    button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
    button_box.set_spacing(12)
    button_box.set_halign(Gtk.Align.END)
    button_box.set_css_classes(['dialog_box'])
    main_box.append(button_box)

    response_id = None

    def on_response(_button, response):
        nonlocal response_id
        response_id = response
        dialog.close()

    for btn_text in buttons:
        btn = Gtk.Button(label=btn_text)
        btn.connect("clicked",
                    on_response,
                    getattr(Gtk.ResponseType, btn_text.upper(), Gtk.ResponseType.OK))
        button_box.append(btn)

    # Show the dialog
    dialog.present()

    # Run a local main loop to keep the dialog open
    context = GLib.MainContext.default()
    while dialog.is_visible():
        context.iteration(True)

    return response_id

def html_to_pango(html_text):
    """ Convert HTML to Pango markup """
    if html_text is None:
        return ''
    # Simple regex to convert <b> and <i> tags to Pango markup
    html_text = re.sub(r'<b>(.*?)<\/b>', r'<span weight="bold">\1</span>', html_text)
    html_text = re.sub(r'<i>(.*?)<\/i>', r'<span style="italic">\1</span>', html_text)
    html_text = re.sub(r'<p>', '', html_text)  # Convert <p> to newline
    html_text = re.sub(r'<\/p>', '\n', html_text)  # Remove closing </p>
    # Convert <ul> and <li> tags to bullet points
    # Replace <ul> and </ul> tags with nothing (or a newline if desired)
    html_text = re.sub(r'<ul>|<\/ul>', '', html_text)
    # Replace <li> tags with bullet points and newlines
    html_text = re.sub(r'<li>', '• ', html_text)
    html_text = re.sub(r'<\/li>', '\n', html_text)

    # Remove leading whitespace for each line
    lines = html_text.split('\n')
    cleaned_lines = [line.lstrip() for line in lines]
    html_text = '\n'.join(cleaned_lines)

    # Reduce triple new lines to double new lines
    html_text = re.sub(r'\n\s*\n\s*\n', '\n\n', html_text)

    # Replace new line between two letters with space
    html_text = re.sub(r'([a-zA-Z])\n+([a-zA-Z])', r'\1 \2', html_text)

    return html_text
