"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TabWidgetElement = void 0;
exports.tabWidget = tabWidget;
const widgets_1 = require("./widgets");
const utils_1 = require("./utils");
const style_1 = require("./style");
const solid_logic_1 = require("solid-logic");
/**
 * @ignore
 */
class ContainerElement extends HTMLElement {
    asSettings;
}
class TabWidgetElement extends HTMLElement {
    bodyContainer;
    refresh;
    tabContainer;
}
exports.TabWidgetElement = TabWidgetElement;
/**
 * @ignore
 */
class TabElement extends HTMLElement {
    bodyTR;
    subject;
}
/**
 * Use this widget to generate tabs from triples set in the global store.
 *
 * [Here you can see examples of the tabs](https://solidos.github.io/solid-ui/examples/tabs/).
 *
 * It assumes that items to use for tabs will be in a collection by default,
 * e.g.:
 *
 * ```turtle
 * :subject :predicate ( :item1 :item2 ) .
 * ```
 *
 * You can override this by setting `ordered: false`, in which case it expects
 * unordered triples:
 *
 * ```turtle
 * :subject :predicate :item1, :item 2 .
 * ```
 *
 * Triples that are not ordered in collection are in principle not sorted,
 * which means that tabs could change order every time you render the widget.
 * But in this case the widget will try to sort it in order to keep it
 * consistent.
 *
 * In both of these cases you need to define options `subject` and `predicate`
 * to tell the widget which triples it should be looking for.
 *
 * Finally you can set items manually, using the `items` option, e.g.:
 *
 * ```javascript
 * {
 *   items: [
 *     namedNode('https://domain.tld/#item1'),
 *     namedNode('https://domain.tld/#item2')
 *   ]
 * }
 * ```
 *
 * When you set items manually you do not need to set `subject` and
 * `predicate`.
 *
 * In any case you probably want to set the renderMain option to specify
 * what should be rendered for the various items, e.g.:
 *
 * ```javascript
 * {
 *   renderMain: (bodyMain, subject) => {
 *     bodyMain.innerHTML = renderItem(subject)
 *   }
 * }
 * ```
 *
 * **Note:** `renderItem` is a custom function that you need to define yourself.
 *
 * The option `renderTabSettings` allows you to render a custom view in the
 * body container that is shown when you hold the ALT key and click on a
 * tab. It works very much like the `renderMain` option:
 *
 * ```javascript
 * {
 *   renderTabSettings: (bodyMain, subject) => {
 *     bodyMain.innerHTML = renderTabSettings(subject)
 *   }
 * }
 * ```
 *
 * **Note:** `renderTabSettings` is a custom function that you need to define
 * yourself.
 *
 * By default the widget will try to guess the label by using the
 * [[utils.label]] function. If you want to customize this yourself, you can
 * use the `renderTab` option:
 *
 * ```javascript
 * {
 *   renderTab: (tabDiv, subject) => {
 *     tabDiv.innerText = renderTabText(subject)
 *   }
 * }
 * ```
 *
 * **Note:** `renderTabText` is a custom function you need to define yourself.
 *
 * The option renderTab is also important if you want to set which tab should
 * be selected once the widget is rendered. By default it will simply select
 * the first tab, but you can override by setting `dataset.name` on the tab
 * and referring to the same string in `selectedTab`:
 *
 * ```javascript
 * {
 *   renderTab: (tabDiv, subject)  => {
 *     tabDiv.dataset.name = subject.uri
 *   },
 *   selectedTab: item2.uri
 * }
 * ```
 *
 * You can apply a color to use for tabs and border of the container by using
 * option `background-color`. This is #ddddcc by default.
 *
 * You can override the document object that the widget uses to generate DOM
 * elements by setting the option `dom`. This is encouraged to set if you
 * intend your functionality to be used in environments that don't provide
 * a global `document` object.
 *
 * If you want to render a close button next to the tabs you can set option
 * `onClose` which takes a callback function that is triggered when the
 * button is clicked:
 *
 * ```javascript
 * {
 *   onClose: (event) => {
 *     // do something that hides the widget altogether
 *   }
 * }
 * ```
 *
 * The option `orientation` allows you to set which side the tabs should be
 * located: `'0'` = Top, `'1'` = Left, `'2'` = Bottom, `'3'` = Right
 *
 * If you don't want to render anything in the body container by default,
 * you can set the option `startEmpty` to `true`.
 *
 * @param options
 */
const tabsDefaultBackgroundColor = '#ddddcc';
function tabWidget(options) {
    const subject = options.subject;
    const dom = options.dom || document;
    const orientation = parseInt(options.orientation || '0');
    const backgroundColor = options.backgroundColor || tabsDefaultBackgroundColor;
    const flipped = orientation & 2;
    const vertical = orientation & 1;
    const onClose = options.onClose;
    const [selectedColor, color] = getColors(backgroundColor);
    const bodyMainStyle = `flex: 2; width: auto; height: 100%; border: 0.1em; border-style: solid; border-color: ${selectedColor}; padding: 1em;`;
    const rootElement = dom.createElement('div'); // 20200117a
    rootElement.setAttribute('style', style_1.style.tabsRootElement);
    rootElement.style.flexDirection = (vertical ? 'row' : 'column') + (flipped ? '-reverse' : '');
    const navElement = rootElement.appendChild(dom.createElement('nav'));
    navElement.setAttribute('style', style_1.style.tabsNavElement);
    const mainElement = rootElement.appendChild(dom.createElement('main'));
    mainElement.setAttribute('style', style_1.style.tabsMainElement); // override tabbedtab.css
    const tabContainer = navElement.appendChild(dom.createElement('ul'));
    tabContainer.setAttribute('style', style_1.style.tabContainer);
    tabContainer.style.flexDirection = `${vertical ? 'column' : 'row'}`;
    const tabElement = 'li';
    const bodyContainer = mainElement;
    rootElement.tabContainer = tabContainer;
    rootElement.bodyContainer = bodyContainer;
    const corners = ['0.2em', '0.2em', '0', '0']; // top left, TR, BR, BL
    const cornersPrepped = corners.concat(corners).slice(orientation, orientation + 4);
    const cornersStyle = `border-radius: ${cornersPrepped.join(' ')};`;
    const margins = ['0.3em', '0.3em', '0', '0.3em']; // top, right, bottom, left
    const marginsPrepped = margins.concat(margins).slice(orientation, orientation + 4);
    const marginsStyle = `margin: ${marginsPrepped.join(' ')};`;
    const paddingStyle = `padding: ${marginsPrepped.join(' ')};`;
    const tabStyle = cornersStyle + `position: relative; padding: 0.7em; max-width: 20em; color: ${color};`;
    const unselectedStyle = `${tabStyle + marginsStyle} opacity: 50%; background-color: ${backgroundColor};`;
    const selectedStyle = `${tabStyle + marginsStyle} background-color: ${selectedColor};`;
    const shownStyle = 'height: 100%; width: 100%;';
    const hiddenStyle = shownStyle + 'display: none;';
    rootElement.refresh = orderedSync;
    orderedSync();
    if (!options.startEmpty && tabContainer.children.length && options.selectedTab) {
        const selectedTab0 = Array.from(tabContainer.children) // Version left for compatibility with ??
            .map((tab) => tab.firstChild)
            .find((tab) => tab.dataset.name === options.selectedTab);
        const selectedTabURI = options.selectedTab.uri;
        const selectedTab1 = Array.from(tabContainer.children)
            // @ts-ignore
            .find((tab) => tab.subject &&
            // @ts-ignore
            tab.subject.uri &&
            // @ts-ignore
            tab.subject.uri === selectedTabURI);
        const tab = selectedTab1 || selectedTab0 || tabContainer.children[0];
        const clickMe = tab.firstChild;
        // @ts-ignore
        if (clickMe)
            clickMe.click();
    }
    else if (!options.startEmpty) {
        tabContainer.children[0].firstChild.click(); // Open first tab
    }
    return rootElement;
    function addCancelButton(tabContainer) {
        if (tabContainer.dataset.onCloseSet) {
            // @@ TODO: this is only here to make the browser tests work
            // Discussion at https://github.com/solidos/solid-ui/pull/110#issuecomment-527080663
            const existingCancelButton = tabContainer.querySelector('.unstyled');
            tabContainer.removeChild(existingCancelButton);
        }
        const extraTab = dom.createElement(tabElement);
        extraTab.classList.add('unstyled');
        const tabCancelButton = (0, widgets_1.cancelButton)(dom, onClose);
        tabCancelButton.setAttribute('style', tabCancelButton.getAttribute('style') + paddingStyle);
        extraTab.appendChild(tabCancelButton);
        tabContainer.appendChild(extraTab);
        tabContainer.dataset.onCloseSet = 'true';
    }
    function getItems() {
        if (options.items)
            return options.items;
        if (options.ordered !== false) {
            // options.ordered defaults to true
            return solid_logic_1.store.the(subject, options.predicate).elements;
        }
        else {
            return solid_logic_1.store.each(subject, options.predicate);
        }
    }
    function makeNewSlot(item) {
        const ele = dom.createElement(tabElement);
        ele.setAttribute('style', unselectedStyle);
        ele.subject = item;
        const div = ele.appendChild(dom.createElement('button'));
        div.setAttribute('style', style_1.style.makeNewSlot);
        div.onclick = function () {
            resetTabStyle();
            resetBodyStyle();
            ele.setAttribute('style', selectedStyle);
            if (!ele.bodyTR)
                return;
            ele.bodyTR.setAttribute('style', shownStyle);
            const bodyMain = getOrCreateContainerElement(ele);
            if (options.renderMain && ele.subject && bodyMain.asSettings !== false) {
                bodyMain.innerHTML = 'loading item ...' + item;
                options.renderMain(bodyMain, ele.subject);
                bodyMain.asSettings = false;
            }
        };
        if (options.renderTabSettings && ele.subject) {
            const ellipsis = dom.createElement('button');
            ellipsis.textContent = '...';
            ellipsis.setAttribute('style', style_1.style.ellipsis);
            ellipsis.onclick = function () {
                resetTabStyle();
                resetBodyStyle();
                ele.setAttribute('style', selectedStyle);
                if (!ele.bodyTR)
                    return;
                ele.bodyTR.setAttribute('style', shownStyle);
                const bodyMain = getOrCreateContainerElement(ele);
                if (options.renderTabSettings && ele.subject && bodyMain.asSettings !== true) {
                    bodyMain.innerHTML = 'loading settings ...' + item;
                    options.renderTabSettings(bodyMain, ele.subject);
                    bodyMain.asSettings = true;
                }
            };
            ele.appendChild(ellipsis);
        }
        if (options.renderTab) {
            options.renderTab(div, item);
        }
        else {
            div.innerHTML = (0, utils_1.label)(item);
        }
        return ele;
        function getOrCreateContainerElement(ele) {
            const bodyMain = ele.bodyTR?.children[0];
            if (bodyMain)
                return bodyMain;
            const newBodyMain = ele.bodyTR.appendChild(dom.createElement('main'));
            newBodyMain.setAttribute('style', bodyMainStyle);
            return newBodyMain;
        }
    }
    // @@ Use common one from utils?
    function orderedSync() {
        const items = getItems();
        let slot, i, j, left, right;
        let differ = false;
        // Find how many match at each end
        for (left = 0; left < tabContainer.children.length; left++) {
            slot = tabContainer.children[left];
            if (left >= items.length || (slot.subject && !slot.subject.sameTerm(items[left]))) {
                differ = true;
                break;
            }
        }
        if (!differ && items.length === tabContainer.children.length) {
            return; // The two just match in order: a case to optimize for
        }
        for (right = tabContainer.children.length - 1; right >= 0; right--) {
            slot = tabContainer.children[right];
            j = right - tabContainer.children.length + items.length;
            if (slot.subject && !slot.subject.sameTerm(items[j])) {
                break;
            }
        }
        // The elements left ... right in tabContainer.children do not match
        const insertables = items.slice(left, right - tabContainer.children.length + items.length + 1);
        while (right >= left) {
            // remove extra
            tabContainer.removeChild(tabContainer.children[left]);
            bodyContainer.removeChild(bodyContainer.children[left]);
            right -= 1;
        }
        for (i = 0; i < insertables.length; i++) {
            const newSlot = makeNewSlot(insertables[i]);
            const newBodyDiv = dom.createElement('div');
            newSlot.bodyTR = newBodyDiv;
            if (left === tabContainer.children.length) {
                // None left of original on right
                tabContainer.appendChild(newSlot);
                bodyContainer.appendChild(newBodyDiv);
            }
            else {
                tabContainer.insertBefore(newSlot, tabContainer.children[left + i]);
                bodyContainer.insertBefore(newBodyDiv, bodyContainer.children[left + i]);
            }
        }
        if (onClose) {
            addCancelButton(tabContainer);
        }
    }
    function resetTabStyle() {
        for (let i = 0; i < tabContainer.children.length; i++) {
            const tab = tabContainer.children[i];
            if (tab.classList.contains('unstyled')) {
                continue;
            }
            else {
                tab.setAttribute('style', unselectedStyle);
            }
        }
    }
    function resetBodyStyle() {
        for (let i = 0; i < bodyContainer.children.length; i++) {
            bodyContainer.children[i].setAttribute('style', hiddenStyle);
        }
    }
}
/**
 * @internal
 */
function getColors(backgroundColor) {
    return isLight(backgroundColor)
        ? [colorBlend(backgroundColor, '#ffffff', 0.3), '#000000']
        : [colorBlend(backgroundColor, '#000000', 0.3), '#ffffff'];
}
/**
 * @internal
 */
function colorBlend(a, b, mix) {
    let ca, cb, res;
    let str = '#';
    const hex = '0123456789abcdef';
    for (let i = 0; i < 3; i++) {
        ca = parseInt(a.slice(i * 2 + 1, i * 2 + 3), 16);
        cb = parseInt(b.slice(i * 2 + 1, i * 2 + 3), 16);
        res = ca * (1.0 - mix) + cb * mix; // @@@ rounding
        const res2 = parseInt(('' + res).split('.')[0]); // @@ ugh
        const h = parseInt(('' + res2 / 16).split('.')[0]); // @@ ugh
        const l = parseInt(('' + (res2 % 16)).split('.')[0]); // @@ ugh
        str += hex[h] + hex[l];
    }
    return str;
}
/**
 * @internal
 */
function isLight(x) {
    let total = 0;
    for (let i = 0; i < 3; i++) {
        total += parseInt(x.slice(i * 2 + 1, i * 2 + 3), 16);
    }
    return total > 128 * 3;
}
//# sourceMappingURL=tabs.js.map