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

from elementmaker import E
from gettext import gettext as _

from ajax import absolute_path, ajax_send, ajax
from book_list.cover_grid import BORDER_RADIUS
from book_list.globals import get_db
from book_list.library_data import (
    all_libraries, last_virtual_library_for, sync_library_books
)
from book_list.router import open_book, update_window_title
from book_list.top_bar import add_button, create_top_bar
from book_list.ui import set_default_panel_handler, show_panel
from dom import add_extra_css, build_rule, clear, ensure_id, set_css, unique_id
from modals import create_custom_dialog
from session import get_device_uuid, get_interface_data
from utils import conditional_timeout, safe_set_inner_html, username_key
from widgets import create_button

CLASS_NAME = 'home-page'

add_extra_css(def ():
    ans = ''
    sel = f'.{CLASS_NAME} '
    ans += build_rule('.' + CLASS_NAME,
        min_width='0',
        max_width='100%',
        box_sizing='border-box',
    )
    # Page gutter + consistent width like other pages (narrow: never wider than viewport)
    ans += build_rule(sel + '.cs-home-content',
        padding='0.75rem 1rem 1rem 1rem',
        max_width='980px',
        width='100%',
        min_width='0',
        box_sizing='border-box',
        margin_left='auto',
        margin_right='auto'
    )
    ans += r'''
@media (max-width: 720px) {
  .home-page .cs-home-content { padding-left: 0.75rem; padding-right: 0.75rem; }
}
@media (max-width: 380px) {
  .home-page .cs-home-content { padding-left: 0.5rem; padding-right: 0.5rem; }
}
'''
    # Section cards (surface hierarchy)
    ans += build_rule(sel + '.cs-home-section',
        margin_top='0.95rem',
        padding='1rem',
        border_radius='18px',
        border='var(--calibre-border-style-subtle)',
        background='var(--calibre-color-surface-1)',
        box_shadow='0 16px 44px rgba(0,0,0,0.10)',
        min_width='0',
        max_width='100%',
        box_sizing='border-box',
        overflow_x='hidden',
    )
    ans += build_rule(sel + '.cs-home-section:first-child', margin_top='0')
    ans += build_rule(sel + '.cs-home-section h2',
        margin='0 0 0.85rem 0',
        padding='0',
        font_size='1.1em',
        font_weight='800',
        opacity='0.92'
    )
    ans += build_rule(sel + '.cs-home-actions',
        display='flex',
        flex_wrap='wrap',
        gap='0.65rem',
        min_width='0',
        max_width='100%',
    )
    ans += build_rule(sel + '.cs-home-download-actions', margin_top='0.55rem')
    ans += build_rule(sel + '.cs-home-actions a.calibre-push-button', margin='0')
    # Covers row — match book-list-cover-grid gap (10px); no per-tile margins (see inline styles removed in JS)
    ans += build_rule(sel + '.cs-home-covers',
        display='flex',
        flex_wrap='wrap',
        align_items='flex-start',
        align_content='flex-start',
        gap='10px',
        row_gap='10px',
        column_gap='10px',
        width='100%',
        min_width='0',
        max_width='100%',
        box_sizing='border-box',
    )
    ans += build_rule(sel + '.cs-home-covers > div',
        margin='0',
        flex_grow='0',
        flex_shrink='1',
        flex_basis='auto',
        min_width='0',
        width='min(118px, max(72px, calc((100% - 20px) / 3.15)))',
        max_width='100%',
        box_sizing='border-box',
    )
    ans += build_rule(sel + '.cs-home-covers > div > a',
        display='block',
        width='100%',
        min_width='0',
        box_sizing='border-box',
    )
    img_sel = sel + '.recently-read img'
    ans += build_rule(img_sel,
        width='100%',
        height='auto',
        aspect_ratio='2 / 3',
        border_radius=f'{BORDER_RADIUS}px',
        display='block',
        object_fit='cover',
        border='var(--calibre-border-style)',
        box_shadow='0 10px 26px rgba(0,0,0,0.14)',
        transition='box-shadow 160ms ease, filter 160ms ease',
    )
    ans += build_rule(f'{img_sel}:hover',
        box_shadow='0 16px 44px rgba(0,0,0,0.18), 0 0 0 4px var(--calibre-color-primary-light)',
        filter='brightness(1.03)'
    )
    ans += build_rule(f'{img_sel}:active',
        box_shadow='0 12px 34px rgba(0,0,0,0.18), 0 0 0 4px var(--calibre-color-primary-light-2)',
        filter='brightness(1.01)'
    )
    return ans
)

recently_read_by_user = {'updated': False}


def update_recently_read_by_user(items):
    if items:
        recently_read_by_user.items = items
    recently_read_by_user.updated = True


def update_book_in_recently_read_by_user_on_home_page(library_id, book_id, book_format, cfi):
    if recently_read_by_user.items:
        for item in recently_read_by_user.items:
            if item.library_id is library_id and item.book_id is book_id and item.format is book_format:
                item.cfi = cfi


def show_cover(blob, name, mt, book):
    img = document.getElementById(this)
    if not img:
        return
    img.onload = def ():
        window.URL.revokeObjectURL(this.src)
    img.src = window.URL.createObjectURL(blob)


def read_book(library_id, book_id, fmt, extra_query):
    open_book(book_id, fmt, library_id, extra_query=extra_query)


def view_book_page(library_id, book_id):
    show_panel('book_details', {'library_id': library_id, 'book_id': book_id + ''}, replace=False)


def get_last_read_position(last_read_positions, prev_last_read):
    prev_epoch = prev_last_read.getTime()
    dev = get_device_uuid()
    newest_epoch = ans = None
    for data in last_read_positions:
        if data.device is not dev and data.epoch > prev_epoch:
            if ans is None or data.epoch > newest_epoch:
                newest_epoch = data.epoch
                ans = data
    return ans


def sync_data_received(library_id, lrmap, load_type, xhr, ev):
    if load_type is not 'load':
        print('Failed to get book sync data')
        return
    data = JSON.parse(xhr.responseText)

    db = get_db()
    for key in data:
        new_vals = data[key]
        entry = {'last_read': None, 'last_read_position': None, 'annotations_map': None}
        prev_last_read = lrmap[key]
        if prev_last_read:
            last_read_positions = new_vals.last_read_positions
            new_last_read = get_last_read_position(last_read_positions, prev_last_read)
            if new_last_read:
                last_read = new Date(new_last_read.epoch * 1000)
                cfi = new_last_read.cfi
                if cfi:
                    entry.last_read = last_read
                    entry.last_read_position = cfi
        new_amap = new_vals.annotations_map or {}
        is_empty = True
        v'for(var ikey in new_amap) { is_empty = false; break; }'
        if ! is_empty:
            entry.annotations_map = new_amap
        if entry.last_read_position or entry.annotations_map:
            book_id, fmt = key.partition(':')[::2]
            db.update_annotations_data_from_key(library_id, int(book_id), fmt, entry)


def start_sync(to_sync):
    libraries = {}
    for key, last_read in to_sync:
        library_id = key[0]
        if not libraries[library_id]:
            libraries[library_id] = []
        libraries[library_id].push([key, last_read])
    for lid in libraries:
        sync_library_books(lid, libraries[lid], sync_data_received)


def prepare_recent_container(container):
    container.style.display = 'block'
    container.appendChild(E.h2(_('Continue reading…')))
    container.appendChild(E.div(class_='cs-home-covers'))
    cover_container = container.lastChild
    container.appendChild(E.div(class_='cs-home-actions cs-home-download-actions',
        create_button(
            _('Browse all downloaded books…'),
            icon='cloud-download',
            action=def ():
                show_panel('local_books')
    )))
    return cover_container


def show_recent_for_user(container_id):
    container = document.getElementById(container_id)
    images = prepare_recent_container(container)
    for item in recently_read_by_user.items[:3]:
        q = {}
        if item.cfi:
            q.bookpos = item.cfi
        rb = read_book.bind(None, item.library_id, item.book_id, item.format, q)
        img = E.img(alt=item.tooltip, src=absolute_path(f'get/cover/{item.book_id}/{item.library_id}'))
        images.appendChild(E.div(
            E.a(title=item.tooltip, href='javascript:void(0)', img, onclick=rb)
        ))
        img.onerror = def (err):
            failed = err.target
            failed.parentNode.parentNode.style.display = 'none'


def show_recent_stage2(books):
    container = document.getElementById(this)
    if not container or not books.length:
        return
    images = prepare_recent_container(container)
    db = get_db()
    to_sync = []
    username = get_interface_data().username
    for book in books:
        if to_sync.length < 10:
            lr = book.last_read[username_key(username)] or new Date(0)
            to_sync.push([book.key, lr])
        authors = book.metadata.authors.join(' & ') if book.metadata.authors else _('Unknown')
        img = E.img(
            alt=_('{} by {}').format(book.metadata.title, authors)
        )
        img_id = ensure_id(img)
        images.appendChild(E.div(
            E.a(img, href='javascript: void(0)', title=img.alt,
                onclick=read_book.bind(None, book.key[0], book.key[1], book.key[2], None)
            ),
        ))
        if book.cover_name:
            db.get_file(book, book.cover_name, show_cover.bind(img_id))
    start_sync(to_sync)


def show_recent_for_user_if_fetched():
    container = this
    if not recently_read_by_user.updated:
        return conditional_timeout(container.id, 5, show_recent_for_user_if_fetched)
    if recently_read_by_user.items and recently_read_by_user.items.length > 0:
        return show_recent_for_user(container.id)
    return show_recent.call(container)


def show_recent():
    container = this
    db = get_db()
    if not db.initialized:
        conditional_timeout(container.id, 5, show_recent)
        return
    if db.is_ok:
        db.get_recently_read_books(show_recent_stage2.bind(container.id))
    else:
        print(db.initialize_error_msg)


def newly_added_received(newly_container_id, end_type, xhr, ev):
    container = document.getElementById(newly_container_id)
    if not container or end_type is not 'load':
        return
    data = JSON.parse(xhr.responseText)
    if not data.books or not data.books.length:
        return
    container.style.display = 'block'
    container.appendChild(E.div(class_='cs-home-covers'))
    images = container.lastChild

    for book_id in data.books:
        authors = data.authors[book_id].join(' & ')
        alt = _('{} by {}').format(data.titles[book_id], authors)
        img = E.img(alt=alt, src=absolute_path(f'get/cover/{book_id}/{data.library_id}'))
        images.appendChild(E.div(
            E.a(img, href='javascript: void(0)', title=img.alt,
                onclick=view_book_page.bind(None, data.library_id, book_id)
            ),
        ))


# User account {{{


def change_password():
    interface_data = get_interface_data()
    create_custom_dialog(_('Change password for: {}').format(interface_data.username), def (parent, close_modal):
        ids = unique_id(), unique_id(), unique_id()
        parent.appendChild(E.table(
            E.tr(
                E.td(E.label(_('Current password:') + '\xa0', for_=ids[0])), set_css(E.td(E.input(type='password'), id=ids[0]), padding_bottom='1.5ex')
            ),
            E.tr(
                E.td(E.label(_('New password:') + '\xa0', for_=ids[1])), set_css(E.td(E.input(type='password'), id=ids[1]), padding_bottom='1.5ex')
            ),
            E.tr(
                E.td(E.label(_('New password again:') + '\xa0', for_=ids[2])), set_css(E.td(E.input(type='password'), id=ids[2]), padding_bottom='1.5ex')
            )
        ))
        parent.appendChild(E.div())
        def show_msg(html, is_info):
            msg = parent.firstChild.nextSibling
            safe_set_inner_html(msg, html)
            msg.style.color = 'red' if not is_info else 'currentColor'
        def on_complete(end_type, xhr, ev):
            if end_type is 'load':
                clear(parent)
                parent.appendChild(E.div(_(
                    'Password successfully changed, you will be asked for the new password'
                    ' the next time the browser has to contact the calibre server.')))
                parent.appendChild(
                    E.div(class_='button-box',
                        create_button(_('OK'), None, close_modal),
                ))
                return
            show_msg(_('Failed to change password, with error: {}').format(xhr.error_html))
        def ok():
            pws = parent.firstChild.getElementsByTagName('input')
            oldpw, pw1, pw2 = pws[0].value, pws[1].value, pws[2].value
            if pw1 is not pw2:
                show_msg(_('The two new passwords do not match'))
                return
            if not pw1 or not oldpw:
                show_msg(_('Empty passwords are not allowed'))
                return
            ajax_send('users/change-pw', {'oldpw': oldpw, 'newpw': pw1}, on_complete)
            show_msg(_('Contacting server, please wait...'), True)
            parent.lastChild.display = 'none'
        parent.appendChild(
            E.div(class_='button-box',
                create_button(_('Cancel'), None, close_modal),
                create_button(_('OK'), None, ok, highlight=True),
            )
        )
    )


def show_user_details():
    interface_data = get_interface_data()
    create_custom_dialog(_('Logged in as {}').format(interface_data.username), def (parent, close_modal):
            msg = E.div()
            safe_set_inner_html(msg, _(
                'You are logged in as the user <b>{}</b>. To log in '
                'as a different user, you will have to restart the browser.').format(interface_data.username))
            parent.appendChild(msg)
            parent.appendChild(
                E.div(class_='button-box',
                    create_button(_('Close'), None, close_modal),
                    create_button(_('Change password'), None, def ():
                        setTimeout(change_password, 0)
                        close_modal()
                    , highlight = True),
                )
            )
    )
# }}}


def init(container_id):
    update_window_title()
    container = document.getElementById(container_id)
    container.classList.add(CLASS_NAME)
    create_top_bar(container, run_animation=True)
    interface_data = get_interface_data()
    if interface_data.username:
        add_button(container, 'user', show_user_details, _('Logged in as {}').format(interface_data.username))

    content = E.div(class_='cs-home-content')
    container.appendChild(content)

    # Recent books
    recent = E.div(style='display:none', class_='cs-home-section recently-read')
    recent_container_id = ensure_id(recent)
    content.appendChild(recent)
    if interface_data.username:
        conditional_timeout(recent_container_id, 5, show_recent_for_user_if_fetched)
    else:
        conditional_timeout(recent_container_id, 5, show_recent)

    # Choose library
    choose = E.div(class_='cs-home-section')
    content.appendChild(choose)
    choose.appendChild(E.h2(_('Choose the calibre library to browse…')))
    choose.appendChild(E.div(class_='cs-home-actions'))
    cl = choose.lastChild
    for library_id, library_name in all_libraries():
        library_name = interface_data.library_map[library_id]
        if library_name:
            cl.appendChild(
                create_button(library_name, icon='library', action=def (ev):
                    lib_id = ev.currentTarget.dataset.lid
                    q = {'library_id': lib_id}
                    vlid = last_virtual_library_for(lib_id)
                    if vlid:
                        q.vl = vlid
                    show_panel('book_list', q)
            ))
            cl.lastChild.dataset.lid = library_id

    # Newly added
    newly = E.div(style='display: none', class_='cs-home-section recently-read')
    newly.appendChild(E.h2(_('Newly added…')))
    newly_container_id = ensure_id(newly)
    content.appendChild(newly)
    ajax('interface-data/newly-added', newly_added_received.bind(None, newly_container_id)).send()

    # Service worker status
    offline_mode = E.div(
        style='font-size:smaller', class_='cs-home-section recently-read')
    offline_mode.textContent = _('Offline mode is not supported')
    content.appendChild(offline_mode)
    if window.performance?.getEntriesByType:
        nav = window.performance.getEntriesByType('navigation')
        if nav and nav.length > 0:
            nav = nav[0]
            if nav.workerStart > 0:
                if (nav.transferSize is 0 and nav.decodedBodySize > 0):
                    offline_mode.textContent = _('Loaded from cache in offline mode')
                else:
                    offline_mode.textContent = _('Offline mode is available')
            else:
                if window.isSecureContext:
                    offline_mode.textContent = _('Offline mode is possible, reload the page to enable it')
                else:
                    offline_mode.textContent = _('Offline mode is not available, you need to setup HTTPS support in the calibre Content server')


def set_panel_handlers():
    set_default_panel_handler(init)
