# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals

from elementmaker import E
from gettext import gettext as _

from ajax import ajax, ajax_send
from book_list.globals import get_session_data
from book_list.theme import get_color, get_font_size
from dom import add_extra_css, build_rule, clear, set_css, svgicon, unique_id
from popups import MODAL_Z_INDEX
from utils import safe_set_inner_html
from widgets import create_button

modal_container = None
modal_count = 0

add_extra_css(def ():
    modal_max = 'min(48rem, calc(100vw - 2rem))'
    style = build_rule(
        '#modal-container > div > a:hover',
        color=get_color('dialog-foreground') + ' !important',
        background_color=get_color('dialog-background') + ' !important'
    )
    style += build_rule(
        '#modal-container',
        overflow_x='auto',
        overflow_y='auto',
        overscroll_behavior_x='none',
        overscroll_behavior_y='contain',
        touch_action='pan-x pan-y',
        padding=(
            'max(1rem, env(safe-area-inset-top, 0px)) max(1rem, env(safe-area-inset-right, 0px))'
            ' max(1rem, env(safe-area-inset-bottom, 0px)) max(1rem, env(safe-area-inset-left, 0px))'
        ),
        box_sizing='border-box',
    )
    style += build_rule(
        '#modal-container > div',
        # Must not clip the absolutely positioned close control (it sits slightly outside the box)
        overflow_x='visible',
        overflow_y='visible',
        width='max-content',
        min_width='0',
        max_width=modal_max,
        box_sizing='border-box',
    )
    style += build_rule(
        '.modal_height_container',
        min_width='0',
        max_width='100%',
        overflow_wrap='anywhere',
        word_break='break-word',
        overflow_x='auto',
        overflow_y='auto',
        overscroll_behavior_x='none',
        touch_action='pan-x pan-y',
    )
    style += build_rule(
        '#modal-container .modal_height_container pre, #modal-container .modal_height_container code',
        white_space='pre-wrap',
        overflow_wrap='anywhere',
        word_break='break-word',
    )
    style += build_rule(
        '#modal-container .modal_height_container img, #modal-container .modal_height_container iframe',
        max_width='100%',
        height='auto',
    )
    style += build_rule(
        '#modal-container .modal_height_container table',
        max_width='100%',
        box_sizing='border-box',
    )
    style += build_rule(
        '#modal-container .modal_height_container th, #modal-container .modal_height_container td',
        overflow_wrap='anywhere',
        word_break='break-word',
    )
    style += build_rule(
        '#modal-container .modal_height_container input:not([type="checkbox"]):not([type="radio"]):not([type="color"]):not([type="file"]),' +
        '#modal-container .modal_height_container textarea, #modal-container .modal_height_container select',
        max_width='100%',
        box_sizing='border-box',
    )
    style += build_rule(
        '#modal-container .button-box',
        display='flex', justify_content='space-between', align_items='stretch',
        flex_wrap='wrap', gap='0.5rem',
        padding='1rem 0rem', min_width='0',
    )
    style += build_rule(
        '#modal-container .button-box > a.calibre-push-button',
        flex='1 1 0', min_width='0', justify_content='center',
    )
    style += build_rule(
        '#modal-container .button-box > a.calibre-push-button:only-of-type',
        flex='0 1 auto', margin_left='auto',
    )
    style += build_rule(
        '#modal-container .button-box.button-box--wrap-chips',
        justify_content='flex-start',
    )
    style += build_rule(
        '#modal-container .button-box.button-box--wrap-chips > a.calibre-push-button',
        flex='0 1 auto',
    )
    style += build_rule(
        '#modal-container > div > a:last-child',
        z_index='2',
    )
    return style
)


class Modal:
    def __init__(self, create_func, on_close, show_close, onkeydown):
        nonlocal modal_count
        self.create_func, self.on_close, self.show_close = create_func, on_close, show_close
        self.onkeydown = onkeydown
        modal_count += 1
        self.id = modal_count


class ModalContainer:
    def __init__(self):
        div = E.div(id='modal-container', tabindex='0',
            E.div(  # popup
                E.div(class_='modal_height_container'),  # content area
                E.a(svgicon('close'), title=_('Close'), onclick=self.close_current_modal.bind(self))
            )
        )
        div.addEventListener('keydown', self.onkeydown.bind(self), {'passive': False})
        document.body.appendChild(div)

        # Container style
        set_css(div,
            position='fixed', top='0', right='0', bottom='0', left='0',  # Stretch over entire window
            background_color='rgba(0,0,0,0.5)', backdrop_filter='blur(2px)', webkit_backdrop_filter='blur(2px)',
            z_index=MODAL_Z_INDEX + '',
            display='none', user_select='none', justify_content='center', align_items='center',
            overscroll_behavior_x='none', overscroll_behavior_y='contain',
        )

        # Popup style (no background gradient — solid dialog-background for readability)
        popup_max = 'min(48rem, calc(100vw - 2rem))'
        set_css(div.firstChild,
            position='relative', display='inline-block',
            min_width='min(16rem, calc(100vw - 2rem))', max_width=popup_max,
            overflow_x='visible', overflow_y='visible', box_sizing='border-box',
            border_radius='1em', padding='1em 2em', margin_right='1em', margin_left='1em',
            background_color=get_color('dialog-background'),
            color=get_color('dialog-foreground'),
            background_image=get_color('dialog-background-image'),
            box_shadow='0 12px 40px rgba(0,0,0,0.2)',
            border='var(--calibre-border-style)',
        )

        # Close button style
        set_css(div.firstChild.lastChild,
            font_size='1.5em', line_height='100%', cursor='pointer', position='absolute',
            right='-0.5em', top='-0.5em', width='1em', height='1em',
            background_color=get_color('window-foreground'), color=get_color('window-background'), display='inline-box',
            border_radius='50%', padding='4px', text_align='center', box_shadow='1px 1px 3px black',
            z_index='2',
        )
        # Content container style
        # need padding: 1px to avoid scrollbar in modal on Qt WebEngine 6.3
        set_css(div.firstChild.firstChild, user_select='text', max_height='min(60vh, calc(100vh - 6rem))',
            overflow_x='auto', overflow_y='auto', overscroll_behavior_x='none', padding='1px', min_width='0', max_width='100%')

        self.modals = []
        self.current_modal = None
        self.hide = self.close_current_modal.bind(self)

    @property
    def modal_container(self):
        return document.getElementById('modal-container')

    def show_modal(self, create_func, on_close=None, show_close=True, onkeydown=None):
        self.modals.push(Modal(create_func, on_close, show_close, onkeydown))
        modal_id = self.modals[-1].id
        self.update()
        window.setTimeout(def (): self.modal_container.focus();, 0)
        return modal_id

    def hide_modal(self, modal_id):
        if self.current_modal is not None and self.current_modal.id is modal_id:
            self.clear_current_modal()
        else:
            doomed_modal = None
            for i, modal in enumerate(self.modals):
                if modal.id is modal_id:
                    doomed_modal = i
                    break
            if doomed_modal is not None:
                self.modals.splice(doomed_modal, 1)

    def update(self):
        if self.current_modal is None and self.modals:
            self.current_modal = self.modals.shift()
            c = self.modal_container
            try:
                self.current_modal.create_func(c.firstChild.firstChild, self.hide)
            except:
                self.current_modal = None
                raise
            if c.style.display is 'none':
                c.style.display = 'flex'
            c.firstChild.lastChild.style.visibility = 'visible' if self.current_modal.show_close else 'hidden'

    def clear_current_modal(self):
        self.current_modal = None
        c = self.modal_container
        clear(c.firstChild.firstChild)
        if self.modals.length is 0:
            set_css(c, display='none')
        else:
            self.update()

    def close_current_modal(self, event):
        if self.current_modal is not None:
            if self.current_modal.on_close is not None and self.current_modal.on_close(event) is True:
                return
            self.clear_current_modal()

    def close_all_modals(self):
        while self.current_modal is not None:
            self.close_current_modal()

    def onkeydown(self, event):
        if self.current_modal is not None and self.current_modal.onkeydown:
            return self.current_modal.onkeydown(event, self.clear_current_modal.bind(self))
        if (event.key is 'Escape' or event.key is 'Esc') and not event.altKey and not event.ctrlKey and not event.metaKey and not event.shiftKey:
            event.preventDefault(), event.stopPropagation()
            self.close_current_modal(event)


def create_simple_dialog_markup(title, msg, details, icon, prefix, icon_color, parent):
    details = details or ''
    show_details = E.a(class_='blue-link', style='padding-top:1em; display:inline-block; margin-left: auto', _('Show details'))
    show_details.addEventListener('click', def ():
        show_details.style.display = 'none'
        show_details.nextSibling.style.display = 'block'
    )
    is_html_msg = /<[a-zA-Z]/.test(msg)
    html_container = E.div()
    if is_html_msg:
        safe_set_inner_html(html_container, msg)
    details_container = E.span()
    if /<[a-zA-Z]/.test(details):
        safe_set_inner_html(details_container, details)
    else:
        details_container.textContent = details
    if prefix:
        prefix = E.span(' ' + prefix + ' ', style='white-space:pre; font-variant: small-caps')
    else:
        prefix = '\xa0'
    parent.appendChild(E.div(
        style='max-width:100%; min-width:0; box-sizing:border-box; text-align:left; overflow-wrap:anywhere; word-break:break-word',
        E.h2(
            E.span(svgicon(icon), style=f'color:{icon_color}'), prefix, title,
            style='font-weight: bold; font-size: ' + get_font_size('title') + '; overflow-wrap:anywhere; word-break:break-word',
        ),
        E.div(
            (html_container if is_html_msg else msg),
            style='padding-top: 1em; margin-top: 1em; border-top: 1px solid currentColor; min-width:0; overflow-wrap:anywhere; word-break:break-word',
        ),
        E.div(
            style='display: ' + ('block' if details else 'none'),
            show_details,
            E.div(details_container,
                  style='display:none; white-space:pre-wrap; font-size: smaller; margin-top: 1em; border-top: solid 1px currentColor; padding-top: 1em'),
        ),
    ))


def create_simple_dialog(title, msg, details, icon, prefix, on_close=None, icon_color='red'):
    show_modal(create_simple_dialog_markup.bind(None, title, msg, details, icon, prefix, icon_color), on_close=on_close)


def create_custom_dialog(title, content_generator_func, on_close=None, onkeydown=None):
    def create_func(parent, close_modal):
        content_div = E.div()
        parent.appendChild(
            E.div(
                style='max-width:100%; min-width:0; box-sizing:border-box; text-align:left; overflow-wrap:anywhere; word-break:break-word',
                E.h2(title, style='font-weight: bold; font-size: ' + get_font_size('title') + '; overflow-wrap:anywhere; word-break:break-word'),
                E.div(content_div, style='padding-top: 1em; margin-top: 1em; border-top: 1px solid currentColor; min-width:0'),
        ))
        content_generator_func(content_div, close_modal)
    show_modal(create_func, on_close=on_close, onkeydown=onkeydown)


def get_text_dialog(title, callback, initial_text=None, msg=None, rows=12):
    called = {}
    cid = unique_id()

    def keyaction(ok, close_modal):
        if called.done:
            return
        called.done = True
        text = document.getElementById(cid).value or ''
        if close_modal:
            close_modal()
        callback(ok, text)

    def on_keydown(event, close_modal):
        if event.altKey or event.ctrlKey or event.metaKey or event.shiftKey:
            return
        if event.key is 'Escape' or event.key is 'Esc':
            event.preventDefault(), event.stopPropagation()
            keyaction(False, close_modal)

    create_custom_dialog(
        title, def (parent, close_modal):
            parent.appendChild(E.div(
                E.textarea(initial_text or '', placeholder=msg or '', id=cid, rows=rows + '',
                           style='width:100%; max-width:100%; min-width:0; box-sizing:border-box; resize:vertical'),
                E.div(class_='button-box',
                    create_button(_('Cancel'), 'close', keyaction.bind(None, False, close_modal)),
                    create_button(_('OK'), 'check', keyaction.bind(None, True, close_modal), highlight=True),
                ),
            ))
            window.setTimeout(def (): parent.querySelector('textarea').focus();, 10)
        ,
        on_close = keyaction.bind(None, False, None),
        onkeydown = on_keydown
    )


def question_dialog(
    title, msg, callback, yes_text=None, no_text=None,
    skip_dialog_name=None, skip_dialog_msg=None,
    skip_dialog_skipped_value=True, skip_dialog_skip_precheck=True,
):
    yes_text = yes_text or _('Yes')
    no_text = no_text or _('No')
    called = {}

    def keyaction(yes, close_modal):
        if called.done:
            return
        called.done = True
        if skip_dialog_name:
            if not skip_box.querySelector('input').checked:
                sd = get_session_data()
                skipped_dialogs = Object.assign(v'{}', sd.get('skipped_dialogs', v'{}'))
                skipped_dialogs[skip_dialog_name] = Date().toISOString()
                sd.set('skipped_dialogs', skipped_dialogs)
        if close_modal:
            close_modal()
        callback(yes)

    def on_keydown(event, close_modal):
        if event.altKey or event.ctrlKey or event.metaKey or event.shiftKey:
            return
        if event.key is 'Escape' or event.key is 'Esc':
            event.preventDefault(), event.stopPropagation()
            keyaction(False, close_modal)
        if event.key is 'Enter' or event.key is 'Return' or event.key is 'Space':
            event.preventDefault(), event.stopPropagation()
            keyaction(True, close_modal)

    skip_box = E.div(style='margin-top: 2ex;')
    if skip_dialog_name:
        sd = get_session_data()
        skipped_dialogs = sd.get('skipped_dialogs', v'{}')
        if skipped_dialogs[skip_dialog_name]:
            return callback(skip_dialog_skipped_value)
        skip_dialog_msg = skip_dialog_msg or _('Show this confirmation again')
        skip_box.appendChild(E.label(E.input(type='checkbox', name='skip_dialog'), '\xa0', skip_dialog_msg))
        if skip_dialog_skip_precheck:
            skip_box.querySelector('input').checked = True
    else:
        skip_box.style.display = 'none'

    create_custom_dialog(
        title, def (parent, close_modal):
            parent.appendChild(E.div(
                E.div(msg),
                skip_box,
                E.div(class_='button-box',
                    create_button(no_text, 'close', keyaction.bind(None, False, close_modal)),
                    create_button(yes_text, 'check', keyaction.bind(None, True, close_modal), highlight=True),
                ))
            )
        ,
        on_close = keyaction.bind(None, False, None),
        onkeydown = on_keydown
    )


def create_progress_dialog(msg, on_close):
    msg = msg or _('Loading, please wait...')
    pbar, msg_div = E.progress(style='display:inline-block'), E.div(msg, style='text-align:center; padding-top:1ex')

    def create_func(parent):
        parent.appendChild(E.div(style='text-align: center; max-width:100%; min-width:0; box-sizing:border-box', pbar, msg_div))
    show_close = on_close is not None
    modal_id = show_modal(create_func, on_close, show_close)
    return {
        'close': def (): modal_container.hide_modal(modal_id);,
        'update_progress': def (amount, total): pbar.max, pbar.value = total, amount;,
        'set_msg': def (new_msg): safe_set_inner_html(msg_div, new_msg);,
    }

# def test_progress():
#     counter = 0
#     pd = progress_dialog('Testing progress dialog, please wait...', def():
#         nonlocal counter
#         counter = 101
#         console.log('pd canceled')
#     )
#     def update():
#         nonlocal counter
#         counter += 1
#         pd.update_progress(counter, 100)
#         if counter < 100:
#             setTimeout(update, 150)
#         else:
#             pd.close()
#     update()
#
# def test_error():
#     error_dialog(
#         'Hello, world!',
#         'Some long text to test rendering/line breaking in error dialogs that contain lots of text'
#         ' like this test error dialog from hell in a handbasket with fruits and muppets making a scene.',
#         ['a word ' for i in range(10000)].join(' ')
#     )


def create_modal_container():
    nonlocal modal_container
    if modal_container is None:
        modal_container = ModalContainer()
    # window.setTimeout(def():
    #     document.getElementById('books-view-1').addEventListener('click', test_progress)
    # , 10)
    return modal_container


def show_modal(create_func, on_close=None, show_close=True, onkeydown=None):
    return modal_container.show_modal(create_func, on_close, show_close, onkeydown)


def close_all_modals():
    return modal_container.close_all_modals()


def error_dialog(title, msg, details=None, on_close=None):
    create_simple_dialog(title, msg, details, 'bug', _('Error:'), on_close)


def warning_dialog(title, msg, details=None, on_close=None):
    create_simple_dialog(title, msg, details, 'warning', _('Warning:'), on_close)


def info_dialog(title, msg, details=None, on_close=None):
    create_simple_dialog(title, msg, details, 'check', '', on_close, icon_color='green')


def progress_dialog(msg, on_close=None):
    # Show a modal dialog with a progress bar and an optional close button.
    # If the user clicks the close button, on_close is called and the dialog is closed.
    # Returns an object with the methods: close(), update_progress(amount, total), set_msg()
    # Call update_progress() to update the progress bar
    # Call set_msg() to change the displayed message
    # Call close() once the task being performed is finished
    return create_progress_dialog(msg, on_close)


def ajax_progress_dialog(path, on_complete, msg, extra_data_for_callback=None, **kw):
    pd = None

    def on_complete_callback(event_type, xhr, ev):
        nonlocal pd
        pd.close()
        pd = undefined
        return on_complete(event_type, xhr, ev)

    def on_progress_callback(loaded, total, xhr):
        pd.update_progress(loaded, total)
    xhr = ajax(path, on_complete_callback, on_progress=on_progress_callback, **kw)
    xhr.extra_data_for_callback = extra_data_for_callback
    pd = progress_dialog(msg, xhr.abort.bind(xhr))
    xhr.send()
    return xhr, pd


def ajax_send_progress_dialog(path, data, on_complete, msg, **kw):
    pd = None

    def on_complete_callback(event_type, xhr, ev):
        nonlocal pd
        pd.close()
        pd = undefined
        return on_complete(event_type, xhr, ev)

    def on_progress_callback(loaded, total, xhr):
        pd.update_progress(loaded, total)
    xhr = ajax_send(path, data, on_complete_callback, on_progress=on_progress_callback, **kw)
    pd = progress_dialog(msg, xhr.abort.bind(xhr))
    return xhr, pd
