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

import traceback
from elementmaker import E
from gettext import gettext as _, ngettext

from ajax import ajax_send
from book_list.add import add_books_panel
from book_list.cover_grid import (
    append_item as cover_grid_append_item, cover_grid_css,
    create_item as create_cover_grid_item, description as COVER_GRID_DESCRIPTION,
    init as init_cover_grid, create_cover_grid_size_control, sync_cover_grid_size_control
)
from book_list.custom_list import (
    append_item as custom_list_append_item, create_item as create_custom_list_item,
    custom_list_css, description as CUSTOM_LIST_DESCRIPTION,
    init as init_custom_list
)
from book_list.details_list import (
    append_item as details_list_append_item, create_item as create_details_list_item,
    description as DETAILS_LIST_DESCRIPTION, details_list_css,
    init as init_details_list
)
from book_list.globals import get_session_data
from book_list.item_list import create_item, create_item_list
from book_list.library_data import (
    add_more_books, all_virtual_libraries, book_metadata, current_book_ids,
    current_library_id, current_sorted_field, ensure_current_library_data, library_data, load_status,
    loaded_books_query, refresh_sidebar_navigation, thumbnail_cache, url_books_query
)
from book_list.router import back, home, push_state, update_window_title
from book_list.saved_virtual_libraries import save_saved_virtual_library
from book_list.saved_virtual_library_dialog import show_saved_virtual_library_dialog
from book_list.search import (
    init as init_search_panel, set_apply_search, tb_config_panel_handler
)
from book_list.top_bar import add_button, create_top_bar, set_title_tooltip
from book_list.ui import query_as_href, set_panel_handler, show_panel
from dom import add_extra_css, build_rule, clear, ensure_id, set_css, svgicon
from modals import error_dialog
from session import get_interface_data
from utils import conditional_timeout, parse_url_params, safe_set_inner_html
from widgets import create_button, create_pager_widget, create_spinner, enable_escape_key, create_select

CLASS_NAME = 'book-list-container'
ITEM_CLASS_NAME = 'book-list-item'
ALLOWED_MODES = {'cover_grid', 'details_list', 'custom_list'}
DEFAULT_MODE = 'cover_grid'

add_extra_css(def ():
    sel = '.' + CLASS_NAME + ' '
    # Unified page gutter for all first-level children
    ans = sel + r'''{
        padding-left: 1rem; padding-right: 1rem;
        @media (max-width: 720px) {
            padding-left: 0.75rem; padding-right: 0.75rem;
        }
    }
    .calibre-showing-label {
        & > span:last-of-type { display: none; }
        @media (max-width: 420px) {
            & > span:last-of-type { display: inline; }
            & > span:first-of-type { display: none; }
        }
    }
    [data-component="book_list"] a { color: currentColor; }
    [data-component="book_list"] a:visited { color: currentColor; }
    '''
    ans += build_rule(sel + '.' + ITEM_CLASS_NAME,
        min_width='0',
        max_width='100%',
        box_sizing='border-box',
    )
    ans += cover_grid_css()
    ans += details_list_css()
    ans += custom_list_css()
    return ans
)

book_list_data = {
    'container_id': None, 'shown_book_ids': set(), 'mode': None, 'fetching_more_books': None,
}


def parse_int(x, default_value):
    try:
        x = int(x)
    except Exception:
        x = default_value
    return x


def clamp(n, lo, hi):
    return max(lo, min(hi, n))


def component(name):
    return document.getElementById(book_list_data.container_id).querySelector(f'[data-component="{name}"]')


def clear_grid():
    # We replace the div entirely so that any styles associated with it are also removed.
    e = component('book_list')
    if not e:
        return
    parent = e.parentNode
    if not parent:
        return
    bl = E.div(data_component='book_list')
    parent.insertBefore(bl, e)
    parent.removeChild(e)
    book_list_data.init_grid(bl)


def has_parent_with_class(elem, cls):
    while elem:
        if elem.classList and elem.classList.contains(cls):
            return elem
        elem = elem.parentNode


def save_scroll_position():
    container = document.getElementById(book_list_data.container_id)
    if container:
        left, top = container.offsetLeft, container.offsetTop
        for y in range(5, 100, 5):
            for x in range(25, 125, 5):
                elem = document.elementFromPoint(left + x, top + y)
                p = has_parent_with_class(elem, ITEM_CLASS_NAME)
                if p:
                    q = parse_url_params()
                    q.book_id = p.dataset.bookId
                    push_state(q, replace=True, call_handler=False)
                    return


def show_subsequent_panel(name, query=None, replace=False):
    save_scroll_position()
    show_panel(name, query, replace)


def show_add_books():
    show_subsequent_panel('book_list^add')


def show_random_book():
    show_subsequent_panel('book_details', {'book_id': '0'})


def add_book_list_top_bar_buttons(container):
    add_button(container, 'plus', show_add_books, _('Add books to the library'))
    add_button(container, 'random', show_random_book, _('Choose a random book from the library'))


def on_book_click(book_id, evt):
    evt.preventDefault()
    show_subsequent_panel('book_details', {'book_id': book_id})


def create_image(book_id, max_width, max_height, on_load):
    return thumbnail_cache.get(book_id, max_width, max_height, on_load)


def render_id(book_id):
    l = book_list_data.shown_book_ids.length
    book_list_data.shown_book_ids.add(book_id)
    if l < book_list_data.shown_book_ids.length:
        metadata = book_metadata(book_id)
        ans = book_list_data.render_book(
            book_id,
            metadata,
            create_image,
            on_book_click.bind(None, book_id),
            query_as_href({'book_id': book_id + ''}, 'book_details')
        )
        ans.classList.add(ITEM_CLASS_NAME)
        ans.dataset.bookId = str(book_id)
        return ans


def render_ids(book_ids):
    book_ids = book_ids or current_book_ids()
    div = component('book_list')
    if div:
        for book_id in book_ids:
            child = render_id(book_id)
            if child:
                book_list_data.append_item(div, child)


def get_view_mode():
    idata = get_interface_data()
    mode = get_session_data().get('view_mode', idata.default_book_list_mode)
    if mode not in ('cover_grid', 'details_list', 'custom_list'):
        mode = DEFAULT_MODE
    return mode


def setup_view_mode(mode, book_list_data):
    if mode not in ALLOWED_MODES:
        mode = DEFAULT_MODE
    book_list_data.mode = mode
    if mode is 'cover_grid':
        book_list_data.render_book = create_cover_grid_item
        book_list_data.init_grid = init_cover_grid
        book_list_data.append_item = cover_grid_append_item
    elif mode is 'details_list':
        book_list_data.render_book = create_details_list_item
        book_list_data.init_grid = init_details_list
        book_list_data.append_item = details_list_append_item
    elif mode is 'custom_list':
        book_list_data.render_book = create_custom_list_item
        book_list_data.init_grid = init_custom_list
        book_list_data.append_item = custom_list_append_item
    return mode


def apply_view_mode(mode=DEFAULT_MODE):
    if book_list_data.mode is not mode:
        setup_view_mode(mode, book_list_data)
        # We are switching the renderer, so the current DOM needs a full re-render.
        # Reset shown ids so render_ids() actually repopulates the new grid/list.
        book_list_data.shown_book_ids = set()
        clear_grid()
        render_ids()
    return mode


# More button {{{


def update_fetching_status():
    more = component('more_button')
    if not more:
        return
    if book_list_data.fetching_more_books:
        more.firstChild.style.display = 'none'
        more.lastChild.style.display = 'block'
    elif library_data.search_result.total_num > book_list_data.shown_book_ids.length:
        more.firstChild.style.display = 'block'
        more.lastChild.style.display = 'none'
    else:
        more.firstChild.style.display = 'none'
        more.lastChild.style.display = 'none'


def abort_get_more_books(no_update):
    if book_list_data.fetching_more_books:
        book_list_data.fetching_more_books.abort()
        book_list_data.fetching_more_books = None
        if not no_update:
            update_fetching_status()


def got_more_books(end_type, xhr, event):
    if book_list_data.fetching_more_books is not xhr:
        return  # Fetching was aborted
    book_list_data.fetching_more_books = None
    update_fetching_status()
    if end_type is 'load':
        try:
            data = JSON.parse(xhr.responseText)
            if not data.search_result.book_ids:
                raise Exception('No books ids object in search result from server')
            add_more_books(data)
            render_ids(data.search_result.book_ids)
        except Exception:
            error_dialog(_('Could not get more books'), _('Server returned an invalid response'), traceback.format_exc())
    elif end_type is not 'abort':
        error_dialog(_('Could not get more books'), xhr.error_html)


def get_more_books():
    data = {'offset': book_list_data.shown_book_ids.length}
    for key in 'query', 'sort', 'sort_order', 'vl':
        data[key] = library_data.search_result[key]
    book_list_data.fetching_more_books = ajax_send(
        'interface-data/more-books', data, got_more_books,
        query={'library_id': loaded_books_query().library_id}
    )
    update_fetching_status()


def create_more_button(more):
    more.appendChild(create_button(
        _('Show more books'), 'cloud-download', get_more_books
    ))
    more.lastChild.setAttribute('rel', 'next')
    set_css(more.firstChild, display='block', margin_left='auto', margin_right='auto', margin_top='1rem')
    set_css(more, font_size='1.5rem', padding_top='1.5rem', margin_bottom='1.5rem', text_align='center', display='flex')
    more.appendChild(E.div(
        create_spinner(), '\xa0' + _('Fetching metadata for more books, please wait') + '…',
        style='margin-left:auto; margin-right:auto; display:none')
    )
    update_fetching_status()


# }}}


def replace_books_page(data):
    # Replace the current page (do not accumulate)
    for key in data.metadata:
        library_data.metadata[key] = data.metadata[key]
    library_data.previous_book_ids = []
    library_data.search_result = data.search_result
    book_list_data.shown_book_ids = set()
    clear_grid()
    render_ids(data.search_result.book_ids)


def fetch_books_page(new_offset, page_size, on_done):
    sr = library_data.search_result
    if not sr:
        if on_done:
            on_done(False)
        return
    abort_get_more_books(True)
    payload = {'offset': new_offset}
    for key in 'query', 'sort', 'sort_order', 'vl':
        payload[key] = sr[key]
    book_list_data.fetching_more_books = ajax_send(
        'interface-data/more-books', payload, def (end_type, xhr, event):
            book_list_data.fetching_more_books = None
            if end_type is 'load':
                try:
                    data = JSON.parse(xhr.responseText)
                    if not data.search_result.book_ids:
                        raise Exception('No books ids object in search result from server')
                    replace_books_page(data)
                    if on_done:
                        on_done(True)
                except Exception:
                    if on_done:
                        on_done(False)
            elif end_type is not 'abort':
                if on_done:
                    on_done(False)
        ,
        query = {'library_id': loaded_books_query().library_id, 'num': str(page_size)}
    )


def fts():
    q = {'restricted': 'y', 'restriction': library_data.search_result.query}
    show_panel('fts', q)


def save_to_saved_virtual_library():
    current_query = loaded_books_query()
    current_search = (current_query.search or '') + ''
    library_id = current_query.library_id or current_library_id()
    virtual_library = (current_query.vl or '') + ''

    def do_save(item):
        if not save_saved_virtual_library(item):
            return False
        refresh_sidebar_navigation()
        return True

    show_saved_virtual_library_dialog(
        _('Save as virtual library'), _('Save'), do_save,
        library_id,
        virtual_library,
        initial_search=current_search,
    )


def icon_action(icon_name, tooltip, action):
    return E.a(
        svgicon(icon_name),
        class_='calibre-icon-button',
        style='margin-left: 0.4em',
        href='javascript:void(0)',
        title=tooltip,
        onclick=def (evt):
            evt.preventDefault()
            action()
    )


def show_top_message(container):
    q = loaded_books_query()
    if not q.search and not q.vl:
        return False
    if q.vl:
        container.appendChild(E.span(
            E.span(E.span(_('Showing Virtual library: {}').format(q.vl)), E.span(_('Virt. lib: {}').format(q.vl)),
            create_button(_('Close'), 'close', def (): show_vl();, _('Show all books in library')),
            class_='calibre-showing-label',
        )))
        if q.search:
            container.appendChild(E.br())
    if q.search:
        c = E.span()
        if library_data.search_result.total_num:
            c.style.display = 'inline-flex'
            c.style.flexWrap = 'wrap'
            c.style.alignItems = 'center'
            c.style.justifyContent = 'space-between'
            c.style.width = '100%'
            c.appendChild(E.span(
                E.span(_('Showing books matching:')), E.span(_('Matching:')), ' ', E.i(library_data.search_result.query),
                class_='calibre-showing-label',
            ))
            buttons = E.div(style='margin-left: auto; display: inline-flex')
            buttons.appendChild(E.span(' ', icon_action('close', _('Clear search'), def (): search();)))
            buttons.appendChild(E.span(' ', icon_action('plus', _('Save as virtual library'), save_to_saved_virtual_library)))
            c.appendChild(buttons)
            if library_data.fts_enabled:
                buttons.appendChild(E.span(' ', icon_action('search', _('Search the text of these books'), fts)))
        else:
            c.appendChild(E.span(_('No books matching:'), ' ', E.i(library_data.search_result.query)))

        container.appendChild(c)
    return True


def clear_book_list_inner(inner):
    if inner:
        clear(inner)


def create_optional_cover_grid_size_control(mode):
    try:
        return create_cover_grid_size_control(mode)
    except Exception as err:
        console.warn('Failed to create cover grid size toolbar control')
        console.warn(err)


def populate_book_list_toolbar(toolbar_el, include_sort, on_view_mode_change=None, initial_mode=None):
    if not toolbar_el:
        return
    clear(toolbar_el)
    cover_grid_size_control = None
    current_mode = initial_mode or get_view_mode()
    mode_wrap, mode_sel = create_select(
        _('Change book list mode'), current_mode, cover_grid=_('Cover grid'),
        details_list=_('Detailed list'), custom_list=_('Custom list'))
    mode_sel.dataset.currentMode = current_mode
    mode_sel.addEventListener('change', def (ev):
        requested_mode = mode_sel.value
        current_mode = mode_sel.dataset.currentMode
        if requested_mode and requested_mode is not current_mode:
            get_session_data().set('view_mode', requested_mode)
            if on_view_mode_change:
                current_mode = on_view_mode_change(requested_mode) or requested_mode
            else:
                current_mode = apply_view_mode(requested_mode) or requested_mode
            mode_sel.dataset.currentMode = current_mode
            mode_sel.value = current_mode
            if cover_grid_size_control:
                sync_cover_grid_size_control(cover_grid_size_control, current_mode)
    )
    toolbar_el.appendChild(mode_wrap)
    cover_grid_size_control = create_optional_cover_grid_size_control(current_mode)
    if cover_grid_size_control:
        toolbar_el.appendChild(cover_grid_size_control)
    if not include_sort:
        return

    def toggle_sort_order(ev=None):
        _csf, _csfo = current_sorted_field()
        if _csf is 'date':
            _csf = 'timestamp'
        new_order = 'desc' if _csfo is 'asc' else 'asc'
        if _csf:
            change_sort(_csf, new_order)

    try:
        if not library_data.sortable_fields:
            return
        csf, csfo = current_sorted_field()
        if csf is 'date':
            csf = 'timestamp'
        found = False
        options = {}
        for field, name in library_data.sortable_fields:
            options[field] = name
            if field is csf:
                found = True
        if not found and library_data.sortable_fields?.length:
            value = library_data.sortable_fields[0][0]
        else:
            value = csf
        sort_wrap, sort_field_sel = create_select(_('Change how the list is sorted'), value, **options)
        sort_field_sel.addEventListener('change', def (ev):
            f = sort_field_sel.value or ''
            if f:
                change_sort(f, None)
        )
        toolbar_el.appendChild(sort_wrap)

        icon_name = 'sort-amount-asc' if (csfo or 'asc') is 'asc' else 'sort-amount-desc'
        toolbar_el.appendChild(create_button('', icon=icon_name, action=toggle_sort_order, tooltip=_('Toggle sort order'), class_='cs-sort-order-btn'))
    except Exception:
        pass


def create_books_list(container):
    book_list_data.container_id = ensure_id(container)
    book_list_data.shown_book_ids = set()
    book_list_data.mode = None
    abort_get_more_books(True)
    pager = E.div(data_component='pager')
    book_list_div = E.div(data_component='book_list')
    bottom_pager_el = E.div(style='margin-bottom:1.5rem')
    single_page_count_el = E.div(style='margin: 0.5ex 0')
    container.appendChild(pager)
    container.appendChild(single_page_count_el)
    container.appendChild(book_list_div)
    container.appendChild(bottom_pager_el)
    top_widget = create_pager_widget(
        def (): go_to_offset((library_data.search_result.offset or 0) - page_size);,
        def (): go_to_offset((library_data.search_result.offset or 0) + page_size);,
    )
    bottom_widget = create_pager_widget(
        def (): go_to_offset((library_data.search_result.offset or 0) - page_size);,
        def (): go_to_offset((library_data.search_result.offset or 0) + page_size);,
    )
    bottom_pager_el.appendChild(bottom_widget.container)

    top_widget.set_top_row_visibility(show_top_message(top_widget.top_row))
    apply_view_mode(get_view_mode())

    q = parse_url_params()
    page_size = clamp(parse_int(q.num, library_data.search_result?.num or 50), 10, 200)

    def go_to_offset(new_offset):
        new_offset = max(0, new_offset)
        qq = parse_url_params()
        qq.offset = str(new_offset)
        qq.num = str(page_size)
        push_state(qq, replace=True, call_handler=False)
        fetch_books_page(new_offset, page_size, def (ok):
            if not ok:
                error_dialog(_('Could not load books'), _('Failed to load the requested page'))
            update_pager()
        )

    def update_pager():
        sr = library_data.search_result
        total = sr.total_num or 0
        offset = sr.offset or 0
        end = min(total, offset + page_size)
        top_widget.count_msg.textContent = _('{0}–{1} of {2}').format((offset + 1) if total else 0, end, total)
        has_prev = offset > 0
        has_next = end < total
        top_widget.set_enabled(has_prev, has_next)
        bottom_widget.set_enabled(has_prev, has_next)
        if not has_prev and not has_next and total and loaded_books_query().search:
            single_page_count_el.textContent = ngettext('{} book found', '{} books found', total).format(total)
        else:
            single_page_count_el.textContent = ''

    update_pager()
    pager.appendChild(top_widget.container)
    populate_book_list_toolbar(top_widget.middle_row, True)
    top_widget.set_middle_row_visibility(top_widget.middle_row.children.length > 0)

    q = parse_url_params()
    scrolled = False
    if q.book_id:
        e = container.querySelector(f'.{ITEM_CLASS_NAME}[data-book-id="{q.book_id}"]')
        if e and e.scrollIntoView:
            e.scrollIntoView(True)
            # Now scroll extra corresponding to top bar size
            window.scrollBy(0, -container.offsetTop)
            scrolled = True
    if not scrolled:
        window.scrollTo(0, 0)
    q = loaded_books_query()
    if not q.search and library_data.search_result.total_num < 1:
        book_list_div.appendChild(E.div(_('No books found'), style='margin: 1ex 0'))


def check_for_books_loaded():
    container = this
    container_id = container.id
    if load_status.loading:
        conditional_timeout(container_id, 5, check_for_books_loaded)
        return
    container = container.lastChild
    if not load_status.ok:
        clear(container)
        if load_status.http_error_code is 401:
            # Unauthorized, keep retrying until the user provides a
            # username/password
            container.appendChild(E.div(_('Username/password required, retrying...')))
            ensure_current_library_data()
            conditional_timeout(container_id, 100, check_for_books_loaded)
            return
        err = E.div()
        safe_set_inner_html(err, load_status.error_html)
        container.appendChild(E.div(
            style='margin: 1ex 1em',
            E.div(_('Failed to load books from calibre library, with error:')),
            err,
            E.div(
                style='margin-top: 1em; border-top: solid 1px currentColor; padding-top: 1ex;',
                E.a(onclick=def (): home(replace=True);, href='javascript: void(0)', class_='blue-link', _('Go back to the home page')))
            ),
        )
        return
    clear_book_list_inner(container)
    create_books_list(container)
    # If the URL specifies an offset, show that page
    q = parse_url_params()
    desired_offset = max(0, parse_int(q.offset, 0))
    if desired_offset:
        page_size = clamp(parse_int(q.num, library_data.search_result?.num or 50), 10, 200)
        fetch_books_page(desired_offset, page_size, def (ok):
            pass
        )
    if library_data.search_result and jstype(library_data.search_result.num_books_without_search) is 'number':
        set_title_tooltip(this, _('Total number of books: {}').format(library_data.search_result.num_books_without_search))


def init(container_id):
    interface_data = get_interface_data()
    ensure_current_library_data()
    container = document.getElementById(container_id)
    lid = container.dataset.library_id = url_books_query().library_id
    title = interface_data.library_map[lid]
    update_window_title(title)
    create_top_bar(container, title=title, action=def (): save_scroll_position(), home();, icon='home')
    add_book_list_top_bar_buttons(container)

    inner = E.div(class_=CLASS_NAME)
    container.appendChild(inner)
    inner.appendChild(E.div(_('Loading books from the {} calibre library, please wait...').format(title), style='margin: 1ex 1em'))
    conditional_timeout(container_id, 5, check_for_books_loaded)


# Sorting {{{


def change_sort(field, order):
    abort_get_more_books()
    sd = get_session_data()
    so = sd.get('last_sort_order')
    order = order or so[field] or 'asc'
    order = 'asc' if order is 'asc' else 'desc'
    q = loaded_books_query()
    q.sort = field + '.' + order
    so[field] = order
    sd.set('last_sort_order', so)
    sd.set_library_option(q.library_id, 'sort', q.sort)
    show_panel('book_list', query=q, replace=True)


def create_sort_panel(container_id):
    if not library_data.sortable_fields:
        show_panel('book_list', replace=True)
        return
    container = document.getElementById(container_id)
    close_action, close_icon = back, 'close'
    if parse_url_params().close_action is 'home':
        close_action, close_icon = def (): home();, 'home'
    create_top_bar(container, title=_('Sort books by…'), action=close_action, icon=close_icon)
    items = []
    csf, csf_order = current_sorted_field()
    new_sort_order = 'desc' if csf_order is 'asc' else 'asc'
    if csf is 'date':
        csf = 'timestamp'
    for field, name in library_data.sortable_fields:
        subtitle = icon_name = None
        if field is csf:
            subtitle = _('Reverse current sort order')
            icon_name = 'sort-amount-asc' if csf_order is 'asc' else 'sort-amount-desc'
            action = change_sort.bind(None, field, new_sort_order)
        else:
            action = change_sort.bind(None, field, None)
        items.push(create_item(name, subtitle=subtitle, icon=icon_name, action=action))
    container.appendChild(E.div())
    create_item_list(container.lastChild, items, _('Change how the list of books is sorted'))
    enable_escape_key(container, close_action)
# }}}

# Searching {{{


def search_query_for(query):
    q = loaded_books_query()
    if query:
        q.search = query
    else:
        v'delete q.search'
    return q


def search(query, replace=False):
    show_panel('book_list', query=search_query_for(query), replace=replace)

set_apply_search(def (query): search(query, True);)

# }}}


# Virtual libraries {{{
def create_vl_panel(container_id):
    if not library_data.sortable_fields:
        show_panel('book_list', replace=True)
        return
    container = document.getElementById(container_id)
    create_top_bar(container, title=_('Choose Virtual library…'), icon='back')
    items = []
    vls = all_virtual_libraries()
    vl_names = Object.keys(vls).sort(def (a, b): return a.toLowerCase().localeCompare(b.toLowerCase());)
    for name in vl_names:
        items.push(create_item(name, subtitle=vls[name], action=show_vl.bind(None, name, True)))
    container.appendChild(E.div())
    create_item_list(container.lastChild, items, _('Choose a Virtual library to browse from the list below'))
    enable_escape_key(container, back)


def show_vl(vl_name, replace):
    q = loaded_books_query()
    q.vl = vl_name or None
    show_panel('book_list', query=q, replace=replace or False)

# }}}


# Modes {{{
def create_mode_panel(container_id):
    if not library_data.sortable_fields:
        show_panel('book_list', replace=True)
        return
    container = document.getElementById(container_id)
    create_top_bar(container, title=_('Choose book list mode…'), icon='back')
    items = []
    current_mode = get_view_mode()

    def ci(title, desc, name):
        ic = 'check' if name is current_mode else None
        items.push(create_item(title, subtitle=desc, icon=ic, action=def ():
            if name is not current_mode:
                get_session_data().set('view_mode', name)
            back()
        ))

    ci(_('Cover grid'), COVER_GRID_DESCRIPTION(), 'cover_grid')
    ci(_('Detailed list'), DETAILS_LIST_DESCRIPTION(), 'details_list')
    ci(_('Custom list'), CUSTOM_LIST_DESCRIPTION(), 'custom_list')
    container.appendChild(E.div())
    create_item_list(container.lastChild, items, _('Choose a display mode for the list of books from below'))
    enable_escape_key(container, back)

# }}}


# Adding books {{{
def create_add_panel(container_id):
    if not library_data.sortable_fields:
        show_panel('book_list', replace=True)
        return
    add_books_panel(container_id)

# }}}


def set_panel_handlers():
    set_panel_handler('book_list', init)
    set_panel_handler('book_list^add', create_add_panel)
    set_panel_handler('book_list^sort', create_sort_panel)
    set_panel_handler('book_list^vl', create_vl_panel)
    set_panel_handler('book_list^search', init_search_panel)
    set_panel_handler('book_list^choose_mode', create_mode_panel)
    set_panel_handler('book_list^search^prefs', tb_config_panel_handler())
