"""
ttkHyperlinkLabel.py - Clickable ttk labels.

Copyright (c) EDCD, All Rights Reserved
Licensed under the GNU General Public License.
See LICENSE file.

In addition to standard ttk.Label arguments, takes the following arguments:
  url: The URL as a string that the user will be sent to on clicking on
  non-empty label text. If url is a function it will be called on click with
  the current label text and should return the URL as a string.
  underline: If True/False the text is always/never underlined. If None (the
  default) the text is underlined only on hover.
  popup_copy: Whether right-click on non-empty label text pops up a context
  menu with a 'Copy' option. Defaults to no context menu. If popup_copy is a
  function it will be called with the current label text and should return a
  boolean.

May be imported by plugins
"""
from __future__ import annotations
import html
from functools import partial
import sys
import tkinter as tk
import webbrowser
from tkinter import font as tk_font
from tkinter import ttk
from typing import Any
import plug
from config import config, logger
from l10n import translations as tr
from monitor import monitor

SHIPYARD_HTML_TEMPLATE = """
<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="refresh" content="0; url={link}">
        <title>Redirecting you to your {ship_name} at {provider_name}...</title>
    </head>
    <body>
        <a href="{link}">
            You should be redirected to your {ship_name} at {provider_name} shortly...
        </a>
    </body>
</html>
"""


class HyperlinkLabel(tk.Label or ttk.Label):  # type: ignore
    """Clickable label for HTTP links."""

    def __init__(self, master: ttk.Frame | tk.Frame | None = None, **kw: Any) -> None:
        """
        Initialize the HyperlinkLabel.

        :param master: The master widget.
        :param kw: Additional keyword arguments.
        """
        self.font_u: tk_font.Font
        self.font_n = None
        self.url = kw.pop('url', None)
        self.popup_copy = kw.pop('popup_copy', False)
        self.underline = kw.pop('underline', None)  # override ttk.Label's underline
        self.foreground = kw.get('foreground', 'blue')
        self.disabledforeground = kw.pop('disabledforeground', ttk.Style().lookup(
            'TLabel', 'foreground', ('disabled',)))  # ttk.Label doesn't support disabledforeground option
        ttk.Label.__init__(self, master, **kw)

        self.bind('<Button-1>', self._click)
        self.bind('<Button-3>', self._contextmenu)

        self.bind('<Enter>', self._enter)
        self.bind('<Leave>', self._leave)

        # set up initial appearance
        self.configure(state=kw.get('state', tk.NORMAL),
                       text=kw.get('text'),
                       font=kw.get('font', ttk.Style().lookup('TLabel', 'font')))

        # Add Menu Options
        self.plug_options = kw.pop('plug_options', None)
        self.name = kw.get('name', None)

    def open_shipyard(self, url: str):
        """Open the Current Ship Loadout in the Selected Provider."""
        if not (loadout := monitor.ship()):
            logger.warning('No ship loadout, aborting.')
            return ''
        if not bool(config.get_int("use_alt_shipyard_open")):
            opener = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta)
            if opener:
                return webbrowser.open(opener)
        else:
            # Avoid file length limits if possible
            target = plug.invoke(url, 'EDSY', 'shipyard_url', loadout, monitor.is_beta)
            file_name = config.app_dir_path / "last_shipyard.html"

            with open(file_name, 'w') as f:
                f.write(SHIPYARD_HTML_TEMPLATE.format(
                    link=html.escape(str(target)),
                    provider_name=html.escape(str(url)),
                    ship_name=html.escape("Ship")
                ))

            webbrowser.open(f'file://localhost/{file_name}')

    def open_system(self, url: str):
        """Open the Current System in the Selected Provider."""
        opener = plug.invoke(url, 'EDSM', 'system_url', monitor.state['SystemName'])
        if opener:
            return webbrowser.open(opener)

    def open_station(self, url: str):
        """Open the Current Station in the Selected Provider."""
        opener = plug.invoke(
            url, 'EDSM', 'station_url',
            monitor.state['SystemName'], monitor.state['StationName']
        )
        if opener:
            return webbrowser.open(opener)

    def configure(  # noqa: CCR001
        self, cnf: dict[str, Any] | None = None, **kw: Any
    ) -> dict[str, tuple[str, str, str, Any, Any]] | None:
        """Change cursor and appearance depending on state and text."""
        # This class' state
        for thing in ('url', 'popup_copy', 'underline'):
            if thing in kw:
                setattr(self, thing, kw.pop(thing))
        for thing in ('foreground', 'disabledforeground'):
            if thing in kw:
                setattr(self, thing, kw[thing])

        # Emulate disabledforeground option for ttk.Label
        if 'state' in kw:
            state = kw['state']
            if state == tk.DISABLED and 'foreground' not in kw:
                kw['foreground'] = self.disabledforeground
            elif state != tk.DISABLED and 'foreground' not in kw:
                kw['foreground'] = self.foreground

        if 'font' in kw:
            self.font_n = kw['font']
            self.font_u = tk_font.Font(font=self.font_n)
            self.font_u.configure(underline=True)
            kw['font'] = self.font_u if self.underline is True else self.font_n

        if 'cursor' not in kw:
            state = kw.get('state', str(self['state']))
            if state == tk.DISABLED:
                kw['cursor'] = 'arrow'  # System default
            elif self.url and (kw['text'] if 'text' in kw else self['text']):
                kw['cursor'] = 'hand2'
            else:
                kw['cursor'] = ('no' if sys.platform == 'win32' else 'circle')

        return super().configure(cnf, **kw)

    def __setitem__(self, key: str, value: Any) -> None:
        """
        Allow for dict member style setting of options.

        :param key: option name
        :param value: option value
        """
        self.configure(**{key: value})

    def _enter(self, event: tk.Event) -> None:
        if self.url and self.underline is not False and str(self['state']) != tk.DISABLED:
            super().configure(font=self.font_u)

    def _leave(self, event: tk.Event) -> None:
        if not self.underline:
            super().configure(font=self.font_n)

    def _click(self, event: tk.Event) -> None:
        if self.url and self['text'] and str(self['state']) != tk.DISABLED:
            url = self.url(self['text']) if callable(self.url) else self.url
            if url:
                self._leave(event)  # Remove underline before we change window to browser
                webbrowser.open(url)

    def _contextmenu(self, event: tk.Event) -> None:
        """
        Display the context menu when right-clicked.

        :param event: The event object.
        """
        menu = tk.Menu(tearoff=tk.FALSE)
        # LANG: Label for 'Copy' as in 'Copy and Paste'
        menu.add_command(label=tr.tl('Copy'), command=self.copy)  # As in Copy and Paste

        if self.name == 'ship':
            # LANG: Copy the Inara SLEF Format of the active ship to the clipboard
            menu.add_command(label=tr.tl('Copy Inara SLEF'), command=self.copy_slef, state=tk.DISABLED)
            menu.entryconfigure(1, state=monitor.slef and tk.NORMAL or tk.DISABLED)

            menu.add_separator()
            for url in plug.provides('shipyard_url'):
                menu.add_command(
                    label=tr.tl("Open in {URL}").format(URL=url),  # LANG: Open Element In Selected Provider
                    command=partial(self.open_shipyard, url)
                )

        if self.name == 'station':
            menu.add_separator()
            for url in plug.provides('station_url'):
                menu.add_command(
                    label=tr.tl("Open in {URL}").format(URL=url),  # LANG: Open Element In Selected Provider
                    command=partial(self.open_station, url)
                )

        if self.name == 'system':
            menu.add_separator()
            for url in plug.provides('system_url'):
                menu.add_command(
                    label=tr.tl("Open in {URL}").format(URL=url),  # LANG: Open Element In Selected Provider
                    command=partial(self.open_system, url)
                )

        if self['text'] and (self.popup_copy(self['text']) if callable(self.popup_copy) else self.popup_copy):
            menu.post(event.x_root, event.y_root)

    def copy(self) -> None:
        """Copy the current text to the clipboard."""
        self.clipboard_clear()
        self.clipboard_append(self['text'])

    def copy_slef(self) -> None:
        """Copy the current text to the clipboard."""
        self.clipboard_clear()
        self.clipboard_append(monitor.slef)