# -*- coding: utf-8 -*-

"""
    Библиотека поддержки Фронт-енд (общие методы не привязанные прямо к view)
"""

# pylint: disable=E0401,W0108


from browser import console, window, document, ajax, DOMNode

from browser.timer import set_timeout, clear_timeout


# import cfg


class GMeta(type):
    def __getattr__(cls, name): return None;  # Не существующий атрибут в G возвращает None вместо исключения
    
class G(metaclass=GMeta):                     # Глобальное Хранилище переменных в памяти
    pass


G.language = window.navigator.language

console.debug("navigator.language:", G.language)


def copy_to_clipboard(el):
    """
        FIXME старый метод (для совместимости с устаревающими мобильными браузерами)
    """
    
    # временно разблокируем для выделения, если нужно
    prevReadOnly = el.readOnly
    prevUserSelect = el.style.userSelect
    el.readOnly = False;  # чтобы можно было выделить на некоторых мобильных
    el.style.userSelect = 'text'

    # запоминаем старую позицию выделения и выделяем весь текст
    start = el.selectionStart
    end = el.selectionEnd
    el.select()

    succeeded = False
    try: succeeded = document.execCommand('copy')
    except: console.error(f"Copy to clipboard failed: {el}")

    # восстановим состояние и позицию
    el.readOnly = prevReadOnly
    el.style.userSelect = prevUserSelect
    try: el.setSelectionRange(start, end)
    except: pass

    return succeeded


def write_to_clipboard(text):
    """
        FIXME Старый способ для старых браузеров или HTTP
    """
    tmp = document.createElement('textarea')
    tmp.value = text

    # Ключевые настройки, чтобы лайаут не дергался:
    tmp.style.position = 'fixed'  # Вынимаем из потока (не влияет на соседей)
    tmp.style.left = '-9999px'    # Уводим далеко за край экрана
    tmp.style.top = '0'
    tmp.style.opacity = '0'       # На всякий случай делаем прозрачным

    # Запрещаем изменение размеров и скролл (для iOS)
    tmp.setAttribute('readonly', '') 

    document.body.appendChild(tmp)
    
    # Выделяем и копируем
    tmp.select()
    # Для iOS иногда нужно дополнительно:
    tmp.setSelectionRange(0, 99999) 

    succeeded = False
    try:
        succeeded = document.execCommand('copy')
    except:
        console.error(f"Write to clipboard failed: {text}")
    finally:
        document.body.removeChild(tmp)

    return succeeded
    

def download_from_text(content, filename="filename.csv"):
    content = '\uFEFF' + content;  # BOM префикс, чтобы Exel понимал что содержимое в utf8

    blob_options = {'type': 'text/csv;charset=utf-8;'}
    blob = window.Blob.new([content], blob_options)
    fileurl = window.URL.createObjectURL(blob)

    link = document.createElement('a')
    link.href = fileurl
    link.download = filename

    link.style.display = 'none' 
    
    document.body.appendChild(link)
    link.click()
    document.body.removeChild(link)
    window.URL.revokeObjectURL(fileurl)
        


float_epsilon = 2.220446049250313e-16;  # sys.float_info.epsilon

def debounce(ms=200):
    """
        Декоратор для debounce обработчиков событий. 200ms - это примерная скорость печатания на клавиатуре
        (задерживает вызов обработчика до окончания серии событий; полезно для input/resize/search)
    """
    def decor(func):
        timer = None
        def wrap(*args):
            nonlocal timer
            clear_timeout(timer)
            timer = set_timeout(func, ms, *args)

        wrap.__name__ = func.__name__
        return wrap

    return decor



_post_complete_handlers = []; _ajax_post = ajax.post;  # Оригинальный ajax.post

def ajax_post(*args, **kwargs):
    """
        Враппер для перехвата oncomplete в eneter-ах
    """    
    oncomplete = kwargs.pop('oncomplete')
    timeout = kwargs.pop('timeout')
    
    def _oncomplete(req, timeout=timeout):                  # Реализация oncomplete JustBry
        if req.status:                                      # Вызов зарегистрированных oncomplete
            for handler in _post_complete_handlers:
                try:
                    handler(req)
                except Exception as e:
                    console.error(f"Oncomplete handler {handler} error: {e}")
                    
        if oncomplete: oncomplete(req, timeout);            # Вызов оригинальной реализации

    _ajax_post(*args, **kwargs, timeout=timeout, oncomplete = _oncomplete);  # oncomplete Подменен

ajax.post = ajax_post;                                      # ajax.post Подменен (в цепочке)


def register_post_complete(handler: "handler(req)"):
    _post_complete_handlers.append(handler)
                                                
def unregister_post_complete(handler):
    try:
        _post_complete_handlers.remove(handler)
    except:
        pass


                    
class PopupPortal:
    """
        Для отображения всплывающих лайаутов (например поверх клика) изначально внедренных внутрь составных контейнеров
    """

    popups = {}

    __slots__ = {
        '_source', 'source',
        'target',
        '_parent',  '_nextSibling', '_className', '_cssText',
        'classes', 'style',
        'showed'
    }
    
    def __init__(self, source, target=None, *, classes="", style=""):
        """
            source: родительский элемент лайаута (например dropdown-content контейнер) или его селектор в документе
            target: document.body или иной контейнер для добавления popup в DOM

            XXX source.parent обязан иметь в качестве child только один элемент source (например dropdown-menu контейнер)
        """
        (self._source,
         self.source) = (source,
                         document.select_one(source) if isinstance(source, str) else source)
                            
        self.target = target

        self._parent = self._className = self._cssText = None
        
        self.classes = classes; self.style = style
        
        self.showed = False


        assert self.source, f"Container {self._source} does not exist"


        if not (popup := self.popups.get(self.source)) or not popup.showed:
            self.popups.pop(self.source, None)

            self.target = self.target or document.body
            
            self._parent = self.source.parent;            # Есть всегда
            self._className = self.source.className;      # '' если нет
            self._cssText = self.source.style.cssText;    # '' если нет

            self.popups[self.source] = self

        else:
            # Уже есть и отображается, - из этого состояния можно только закрыть и target, classes и style не важны 
            self._parent = popup._parent
            self._className = popup._className
            self._cssText = popup._cssText

            self.showed = True
            

    def show(self, touching=None):
        """
            touching: Элемент по отношению к которому требуется позиционирование popup или непосредственно .getBoundingClientRect
        """
        # XXX по спецификации DOM если у layout уже есть parent, то он перемещается в новое место (биндинги событий сохраняются)
        # --bulma-dropdown-content-z: 20; --bulma-navbar-z: 30; --bulma-modal-z: 40; 
        
        if not self.showed:
        
            source = self.target.appendChild(self.source);  # переноситься из self.source
            self.showed = True

            if isinstance(touching, DOMNode):
                touch_rect = touching.getBoundingClientRect()
            else:
                touch_rect = touching

            target_rect = self.target.getBoundingClientRect()

            if touch_rect and target_rect:
                top_pos = touch_rect['bottom'] - target_rect['top']  # + self.target.scrollTop
            else:
                top_pos = None
            
            # Должны применить стили из конструктора

            if self.classes:
                source.className = self.classes
                
            if self.style:
                source.style.cssText = self.style
            
            # Позиционирование
            source.style.display = "block"; source.style.position = "absolute"; source.style.zIndex = "31";  # var(--bulma-navbar-z)+1

            # XXX Помним что ';' при таком присвоении стилей НЕ нужна (она искажает единицу измерения и все срется)
            source.style.left = "50%"

            transformX = "-50%"; transformY = "-0%"
            
            if top_pos is not None:
                source.style.top = f"{top_pos}px";                        # Нижний край
            else:
                source.style.top = "50%"                                  # По центру
                transformY = "-50%"

            source.style.marginTop = f"{0.5}rem";                         # Отступ сверху от touching
            
            source.style.transform = f"translate({transformX}, {transformY})"

            def _missclick(ev):  # Клик вне popup закрывает его
                if not source.contains(ev.target):
                    document.body.unbind('click', _missclick)
                    set_timeout(lambda: self.hide(), 33)
                    
            set_timeout(lambda: document.body.bind('click', _missclick), 33)


    def hide(self):
        # Все возвращаем на место
        if self.showed:
            
            source = self.source

            # Восстанавливаем изначальные стили
            source.className = self._className
            source.style.cssText = self._cssText

            if not self._parent.children:  # morphing ничего нового не добавил
                self._parent.appendChild(source);  # переноситься из self.target
            else:
                self.target.removeChild(source)

            self.popups.pop(source, None)

            self.showed = False


    def __del__(self): self.hide();  # Авто подчистка

        
            
