/*  Buttons
*/
import { st, sym, uri, Util } from 'rdflib';
import { icons } from '../iconBase';
import ns from '../ns';
import { style } from '../style';
import * as debug from '../debug';
import { info } from '../log';
import { uploadFiles, makeDraggable, makeDropTarget } from './dragAndDrop';
import { store } from 'solid-logic';
import * as utils from '../utils';
import { errorMessageBlock } from './error';
import { addClickListenerToElement, createImageDiv, wrapDivInATR } from './widgetHelpers';
import { linkIcon, createLinkForURI } from './buttons/iconLinks';
/**
 * UI Widgets such as buttons
 * @packageDocumentation
 */
/* global alert */
const { iconBase } = icons;
const cancelIconURI = iconBase + 'noun_1180156.svg'; // black X
const checkIconURI = iconBase + 'noun_1180158.svg'; // green checkmark; Continue
function getStatusArea(context) {
    let box = (context && context.statusArea) || (context && context.div) || null;
    if (box)
        return box;
    let dom = context && context.dom;
    if (!dom && typeof document !== 'undefined') {
        dom = document;
    }
    if (dom) {
        const body = dom.getElementsByTagName('body')[0];
        box = dom.createElement('div');
        body.insertBefore(box, body.firstElementChild);
        if (context) {
            context.statusArea = box;
        }
        return box;
    }
    return null;
}
/**
 * Display an error message block
 */
export function complain(context, err) {
    if (!err)
        return; // only if error
    const ele = getStatusArea(context);
    debug.log('Complaint: ' + err);
    if (ele)
        ele.appendChild(errorMessageBlock((context && context.dom) || document, err));
    else
        alert(err);
}
/**
 * Remove all the children of an HTML element
 */
export function clearElement(ele) {
    while (ele.firstChild) {
        ele.removeChild(ele.firstChild);
    }
    return ele;
}
/**
 * To figure out the log URI from the full URI used to invoke the reasoner
 */
export function extractLogURI(fullURI) {
    const logPos = fullURI.search(/logFile=/);
    const rulPos = fullURI.search(/&rulesFile=/);
    return fullURI.substring(logPos + 8, rulPos);
}
/**
 * By default, converts e.g. '2020-02-19T19:35:28.557Z' to '19:35'
 * if today is 19 Feb 2020, and to 'Feb 19' if not.
 * @@@ TODO This needs to be changed to local time
 * @param noTime Return a string like 'Feb 19' even if it's today.
 */
export function shortDate(str, noTime) {
    if (!str)
        return '???';
    const month = [
        'Jan',
        'Feb',
        'Mar',
        'Apr',
        'May',
        'Jun',
        'Jul',
        'Aug',
        'Sep',
        'Oct',
        'Nov',
        'Dec'
    ];
    try {
        const nowZ = new Date().toISOString();
        // var nowZ = $rdf.term(now).value
        // var n = now.getTimezoneOffset() // Minutes
        if (str.slice(0, 10) === nowZ.slice(0, 10) && !noTime) {
            return str.slice(11, 16);
        }
        if (str.slice(0, 4) === nowZ.slice(0, 4)) {
            return (month[parseInt(str.slice(5, 7), 10) - 1] +
                ' ' +
                parseInt(str.slice(8, 10), 10));
        }
        return str.slice(0, 10);
    }
    catch (e) {
        return 'shortdate:' + e;
    }
}
/**
 * Format a date and time
 * @param date for instance `new Date()`
 * @param format  for instance '{FullYear}-{Month}-{Date}T{Hours}:{Minutes}:{Seconds}.{Milliseconds}'
 * @returns for instance '2000-01-15T23:14:23.002'
 */
export function formatDateTime(date, format) {
    return format
        .split('{')
        .map(function (s) {
        const k = s.split('}')[0];
        const width = { Milliseconds: 3, FullYear: 4 };
        const d = { Month: 1 };
        return s
            ? ('000' + (date['get' + k]() + (d[k] || 0))).slice(-(width[k] || 2)) + s.split('}')[1]
            : '';
    })
        .join('');
}
/**
 * Get a string representation of the current time
 * @returns for instance '2000-01-15T23:14:23.002'
 */
export function timestamp() {
    return formatDateTime(new Date(), '{FullYear}-{Month}-{Date}T{Hours}:{Minutes}:{Seconds}.{Milliseconds}');
}
/**
 * Get a short string representation of the current time
 * @returns for instance '23:14:23.002'
 */
export function shortTime() {
    return formatDateTime(new Date(), '{Hours}:{Minutes}:{Seconds}.{Milliseconds}');
}
// ///////////////////// Handy UX widgets
/**
 * Sets the best name we have and looks up a better one
 */
export function setName(element, x) {
    const kb = store;
    const findName = function (x) {
        const name = kb.any(x, ns.vcard('fn')) ||
            kb.any(x, ns.foaf('name')) ||
            kb.any(x, ns.vcard('organization-name'));
        return name ? name.value : null;
    };
    const name = x.sameTerm(ns.foaf('Agent')) ? 'Everyone' : findName(x);
    element.textContent = name || utils.label(x);
    if (!name && x.uri) {
        if (!kb.fetcher) {
            throw new Error('kb has no fetcher');
        }
        // Note this is only a fetch, not a lookUP of all sameAs etc
        kb.fetcher.nowOrWhenFetched(x.doc(), undefined, function (_ok) {
            element.textContent = findName(x) || utils.label(x); // had: (ok ? '' : '? ') +
        });
    }
}
/**
 * Set of suitable images
 * See also [[findImage]]
 * @param x The thing for which we want to find an image
 * @param kb The RDF store to look in
 * @returns It goes looking for triples in `kb`,
 *          `(subject: x), (predicate: see list below) (object: image-url)`
 *          to find any image linked from the thing with one of the following
 *          predicates (in order):
 *          * ns.sioc('avatar')
 *          * ns.foaf('img')
 *          * ns.vcard('logo')
 *          * ns.vcard('hasPhoto')
 *          * ns.vcard('photo')
 *          * ns.foaf('depiction')

 */
export function imagesOf(x, kb) {
    return kb
        .each(x, ns.sioc('avatar'))
        .concat(kb.each(x, ns.foaf('img')))
        .concat(kb.each(x, ns.vcard('logo')))
        .concat(kb.each(x, ns.vcard('hasPhoto')))
        .concat(kb.each(x, ns.vcard('photo')))
        .concat(kb.each(x, ns.foaf('depiction')));
}
/**
 * Best logo or avatar or photo etc to represent someone or some group etc
 */
export const iconForClass = {
    // Potentially extendable by other apps, panes, etc
    // Relative URIs to the iconBase
    'solid:AppProviderClass': 'noun_144.svg', //  @@ classs name should not contain 'Class'
    'solid:AppProvider': 'noun_15177.svg', // @@
    'solid:Pod': 'noun_Cabinet_1434380.svg',
    'vcard:Group': 'noun_339237.svg',
    'vcard:Organization': 'noun_143899.svg',
    'vcard:Individual': 'noun_15059.svg',
    'schema:Person': 'noun_15059.svg',
    'foaf:Person': 'noun_15059.svg',
    'foaf:Agent': 'noun_98053.svg',
    'acl:AuthenticatedAgent': 'noun_99101.svg',
    'prov:SoftwareAgent': 'noun_Robot_849764.svg', // Bot
    'vcard:AddressBook': 'noun_15695.svg',
    'trip:Trip': 'noun_581629.svg',
    'meeting:LongChat': 'noun_1689339.svg',
    'meeting:Meeting': 'noun_66617.svg',
    'meeting:Project': 'noun_1036577.svg',
    'ui:Form': 'noun_122196.svg',
    'rdfs:Class': 'class-rectangle.svg', // For RDF developers
    'rdf:Property': 'property-diamond.svg',
    'owl:Ontology': 'noun_classification_1479198.svg',
    'wf:Tracker': 'noun_122196.svg',
    'wf:Task': 'noun_17020_gray-tick.svg',
    'wf:Open': 'noun_17020_sans-tick.svg',
    'wf:Closed': 'noun_17020.svg'
};
/**
 * Returns the origin of the URI of a NamedNode
 */
function tempSite(x) {
    // use only while one in rdflib fails with origins 2019
    const str = x.uri.split('#')[0];
    const p = str.indexOf('//');
    if (p < 0)
        throw new Error('This URI does not have a web site part (origin)');
    const q = str.indexOf('/', p + 2);
    if (q < 0) {
        // no third slash?
        return str.slice(0) + '/'; // Add slash to a bare origin
    }
    else {
        return str.slice(0, q + 1);
    }
}
/**
 * Find an image for this thing as a class
 */
export function findImageFromURI(x) {
    const iconDir = iconBase;
    // Special cases from URI scheme:
    if (typeof x !== 'string' && x.uri) {
        if (x.uri.split('/').length === 4 &&
            !x.uri.split('/')[1] &&
            !x.uri.split('/')[3]) {
            return iconDir + 'noun_15177.svg'; // App -- this is an origin
        }
        // Non-HTTP URI types imply types
        if (x.uri.startsWith('message:') || x.uri.startsWith('mid:')) {
            // message: is apple bug-- should be mid:
            return iconDir + 'noun_480183.svg'; // envelope  noun_567486
        }
        if (x.uri.startsWith('mailto:')) {
            return iconDir + 'noun_567486.svg'; // mailbox - an email desitination
        }
        // For HTTP(s) documents, we could look at the MIME type if we know it.
        if (x.uri.startsWith('https:') && x.uri.indexOf('#') < 0) {
            return tempSite(x) + 'favicon.ico'; // was x.site().uri + ...
            // Todo: make the document icon a fallback for if the favicon does not exist
            // todo: pick up a possible favicon for the web page itself from a link
            // was: return iconDir + 'noun_681601.svg' // document - under solid assumptions
        }
        return null;
    }
    return iconDir + 'noun_10636_grey.svg'; // Grey Circle -  some thing
}
/**
 * Find something we have as explicit image data for the thing
 * See also [[imagesOf]]
 * @param thing The thing for which we want to find an image
 * @returns The URL of a globe icon if thing equals `ns.foaf('Agent')`
 *          or `ns.rdf('Resource')`. Otherwise, it goes looking for
 *          triples in `store`,
 *          `(subject: thing), (predicate: see list below) (object: image-url)`
 *          to find any image linked from the thing with one of the following
 *          predicates (in order):
 *          * ns.sioc('avatar')
 *          * ns.foaf('img')
 *          * ns.vcard('logo')
 *          * ns.vcard('hasPhoto')
 *          * ns.vcard('photo')
 *          * ns.foaf('depiction')
 */
export function findImage(thing) {
    const kb = store;
    const iconDir = iconBase;
    if (thing.sameTerm(ns.foaf('Agent')) || thing.sameTerm(ns.rdf('Resource'))) {
        return iconDir + 'noun_98053.svg'; // Globe
    }
    const image = kb.any(thing, ns.sioc('avatar')) ||
        kb.any(thing, ns.foaf('img')) ||
        kb.any(thing, ns.vcard('logo')) ||
        kb.any(thing, ns.vcard('hasPhoto')) ||
        kb.any(thing, ns.vcard('photo')) ||
        kb.any(thing, ns.foaf('depiction'));
    return image ? image.uri : null;
}
/**
 * Do the best you can with the data available
 *
 * @return {Boolean} Are we happy with this icon?
 * Sets src AND STYLE of the image.
 */
function trySetImage(element, thing, iconForClassMap) {
    const kb = store;
    const explitImage = findImage(thing);
    if (explitImage) {
        element.setAttribute('src', explitImage);
        return true;
    }
    // This is one of the classes we know about - the class itself?
    const typeIcon = iconForClassMap[thing.uri];
    if (typeIcon) {
        element.setAttribute('src', typeIcon);
        element.style = style.classIconStyle;
        // element.style.border = '0.1em solid green;'
        // element.style.backgroundColor = '#eeffee' // pale green
        return true;
    }
    const schemeIcon = findImageFromURI(thing);
    if (schemeIcon) {
        element.setAttribute('src', schemeIcon);
        return true; // happy with this -- don't look it up
    }
    // Do we have a generic icon for something in any class its in?
    const types = kb.findTypeURIs(thing);
    for (const typeURI in types) {
        if (iconForClassMap[typeURI]) {
            element.setAttribute('src', iconForClassMap[typeURI]);
            return false; // maybe we can do better
        }
    }
    element.setAttribute('src', iconBase + 'noun_10636_grey.svg'); // Grey Circle -  some thing
    return false; // we can do better
}
/**
 * ToDo: Also add icons for *properties* like  home, work, email, range, domain, comment,
 */
export function setImage(element, thing) {
    const kb = store;
    const iconForClassMap = {};
    for (const k in iconForClass) {
        const pref = k.split(':')[0];
        const id = k.split(':')[1];
        const theClass = ns[pref](id);
        iconForClassMap[theClass.uri] = uri.join(iconForClass[k], iconBase);
    }
    const happy = trySetImage(element, thing, iconForClassMap);
    if (!happy && thing.uri) {
        if (!kb.fetcher) {
            throw new Error('kb has no fetcher');
        }
        kb.fetcher.nowOrWhenFetched(thing.doc(), undefined, (ok) => {
            if (ok) {
                trySetImage(element, thing, iconForClassMap);
            }
        });
    }
}
// If a web page, then a favicon, with a fallback to ???
// See, e.g., http://stackoverflow.com/questions/980855/inputting-a-default-image
export function faviconOrDefault(dom, x) {
    const image = dom.createElement('img');
    image.style = style.iconStyle;
    const isOrigin = function (x) {
        if (!x.uri)
            return false;
        const parts = x.uri.split('/');
        return parts.length === 3 || (parts.length === 4 && parts[3] === '');
    };
    image.setAttribute('src', iconBase + (isOrigin(x) ? 'noun_15177.svg' : 'noun_681601.svg') // App symbol vs document
    );
    if (x.uri && x.uri.startsWith('https:') && x.uri.indexOf('#') < 0) {
        const res = dom.createElement('object'); // favico with a fallback of a default image if no favicon
        res.setAttribute('data', tempSite(x) + 'favicon.ico');
        res.setAttribute('type', 'image/x-icon');
        res.appendChild(image); // fallback
        return res;
    }
    else {
        setImage(image, x);
        return image;
    }
}
/* Two-option dialog pop-up
*/
function renderDeleteConfirmPopup(dom, refererenceElement, prompt, deleteFunction) {
    function removePopup() {
        refererenceElement.parentElement.removeChild(refererenceElement);
    }
    function removePopupAndDoDeletion() {
        removePopup();
        deleteFunction();
    }
    const popup = dom.createElement('div');
    popup.style = style.confirmPopupStyle;
    popup.style.position = 'absolute';
    popup.style.top = '-1em'; // try leaving original button clear
    popup.style.display = 'grid';
    popup.style.gridTemplateColumns = 'auto auto';
    const affirm = dom.createElement('div');
    affirm.style.gridColumn = '1/2';
    affirm.style.gridRow = '1'; // @@ sigh; TS. could pass number in fact
    const cancel = dom.createElement('div');
    cancel.style.gridColumn = '1/2';
    cancel.style.gridRow = '2';
    const xButton = cancelButton(dom, removePopup);
    popup.appendChild(xButton);
    xButton.style.gridColumn = '1';
    xButton.style.gridRow = '2';
    const cancelPrompt = popup.appendChild(dom.createElement('button'));
    cancelPrompt.style = style.buttonStyle;
    cancelPrompt.style.gridRow = '2';
    cancelPrompt.style.gridColumn = '2';
    cancelPrompt.textContent = 'Cancel'; // @@ I18n
    const affirmIcon = button(dom, icons.iconBase + 'noun_925021.svg', 'Delete it'); // trashcan
    popup.appendChild(affirmIcon);
    affirmIcon.style.gridRow = '1';
    affirmIcon.style.gridColumn = '1';
    const sureButtonElt = popup.appendChild(dom.createElement('button'));
    sureButtonElt.style = style.buttonStyle;
    sureButtonElt.style.gridRow = '1';
    sureButtonElt.style.gridColumn = '2';
    sureButtonElt.textContent = prompt;
    popup.appendChild(sureButtonElt);
    affirmIcon.addEventListener('click', removePopupAndDoDeletion);
    sureButtonElt.addEventListener('click', removePopupAndDoDeletion);
    // xButton.addEventListener('click', removePopup)
    cancelPrompt.addEventListener('click', removePopup);
    return popup;
}
/**
 * Delete button with a check you really mean it
 * @@ Supress check if command key held down?
 */
export function deleteButtonWithCheck(dom, container, noun, deleteFunction) {
    function createPopup() {
        const refererenceElement = dom.createElement('div');
        container.insertBefore(refererenceElement, deleteButton);
        refererenceElement.style.position = 'relative'; // Needed as reference for popup
        refererenceElement.appendChild(renderDeleteConfirmPopup(dom, refererenceElement, prompt, deleteFunction));
    }
    const minusIconURI = iconBase + 'noun_2188_red.svg'; // white minus in red #cc0000 circle
    const deleteButton = dom.createElement('img');
    deleteButton.setAttribute('src', minusIconURI);
    deleteButton.setAttribute('style', style.smallButtonStyle); // @@tsc - would set deleteButton.style
    deleteButton.style.float = 'right'; // Historically this has alwaus floated right
    const prompt = 'Remove this ' + noun;
    deleteButton.title = prompt;
    // @@ In an ideal world, make use of hover an accessibility option
    deleteButton.classList.add('hoverControlHide');
    deleteButton.addEventListener('click', createPopup);
    container.classList.add('hoverControl');
    container.appendChild(deleteButton);
    deleteButton.setAttribute('data-testid', 'deleteButtonWithCheck');
    return deleteButton; // or button div?  caller may change size of image
}
/*  Make a button
 *
 * @param dom - the DOM document object
 * @Param iconURI - the URI of the icon to use (if any)
 * @param text - the tooltip text or possibly button contents text
 * @param handler <function> - A handler to called when button is clicked
 *
 * @returns <dDomElement> - the button
 */
export function button(dom, iconURI, text, handler, options = { buttonColor: 'Primary', needsBorder: false }) {
    const button = dom.createElement('button');
    button.setAttribute('type', 'button');
    // button.innerHTML = text  // later, user preferences may make text preferred for some
    if (iconURI) {
        const img = button.appendChild(dom.createElement('img'));
        img.setAttribute('src', iconURI);
        img.setAttribute('style', style.iconStyle);
        img.title = text;
        button.setAttribute('style', style.buttonStyle);
    }
    else {
        button.textContent = text.toLocaleUpperCase();
        button.onmouseover = function () {
            if (options.buttonColor === 'Secondary') {
                if (options.needsBorder) {
                    button.setAttribute('style', style.secondaryButtonNoBorderHover);
                }
                else {
                    button.setAttribute('style', style.secondaryButtonHover);
                }
            }
            else {
                if (options.needsBorder) {
                    button.setAttribute('style', style.primaryButtonNoBorderHover);
                }
                else {
                    button.setAttribute('style', style.primaryButtonHover);
                }
            }
        };
        button.onmouseout = function () {
            if (options.buttonColor === 'Secondary') {
                if (options.needsBorder) {
                    button.setAttribute('style', style.secondaryButtonNoBorder);
                }
                else {
                    button.setAttribute('style', style.secondaryButton);
                }
            }
            else {
                if (options.needsBorder) {
                    button.setAttribute('style', style.primaryButtonNoBorder);
                }
                else {
                    button.setAttribute('style', style.primaryButton);
                }
            }
        };
        if (options.buttonColor === 'Secondary') {
            if (options.needsBorder) {
                button.setAttribute('style', style.secondaryButtonNoBorder);
            }
            else {
                button.setAttribute('style', style.secondaryButton);
            }
        }
        else {
            if (options.needsBorder) {
                button.setAttribute('style', style.primaryButtonNoBorder);
            }
            else {
                button.setAttribute('style', style.primaryButton);
            }
        }
    }
    if (handler) {
        button.addEventListener('click', handler, false);
    }
    return button;
}
/*  Make a cancel button
 *
 * @param dom - the DOM document object
 * @param handler <function> - A handler to called when button is clicked
 *
 * @returns <dDomElement> - the button
 */
export function cancelButton(dom, handler) {
    const b = button(dom, cancelIconURI, 'Cancel', handler);
    if (b.firstChild) { // sigh for tsc
        b.firstChild.style.opacity = '0.3'; // Black X is too harsh: current language is grey X
    }
    return b;
}
/*  Make a continue button
 *
 * @param dom - the DOM document object
 * @param handler <function> - A handler to called when button is clicked
 *
 * @returns <dDomElement> - the button
 */
export function continueButton(dom, handler) {
    return button(dom, checkIconURI, 'Continue', handler);
}
/* Grab a name for a new thing
 *
 * Form to get the name of a new thing before we create it
 * @params theClass  Misspelt to avoid clashing with the JavaScript keyword
 * @returns: a promise of (a name or null if cancelled)
 */
export function askName(dom, kb, container, predicate, theClass, noun) {
    return new Promise(function (resolve, _reject) {
        const form = dom.createElement('div'); // form is broken as HTML behaviour can resurface on js error
        // classLabel = utils.label(ns.vcard('Individual'))
        predicate = predicate || ns.foaf('name'); // eg 'name' in user's language
        noun = noun || (theClass ? utils.label(theClass) : '  '); // eg 'folder' in users's language
        const prompt = noun + ' ' + utils.label(predicate) + ': ';
        form.appendChild(dom.createElement('p')).textContent = prompt;
        const namefield = dom.createElement('input');
        namefield.setAttribute('type', 'text');
        namefield.setAttribute('size', '100');
        namefield.setAttribute('maxLength', '2048'); // No arbitrary limits
        namefield.setAttribute('style', style.textInputStyle);
        namefield.select(); // focus next user input
        form.appendChild(namefield);
        container.appendChild(form);
        // namefield.focus()
        function gotName() {
            form.parentNode.removeChild(form);
            resolve(namefield.value.trim());
        }
        namefield.addEventListener('keyup', function (e) {
            if (e.keyCode === 13) {
                gotName();
            }
        }, false);
        form.appendChild(dom.createElement('br'));
        form.appendChild(cancelButton(dom, function (_event) {
            form.parentNode.removeChild(form);
            resolve(null);
        }));
        form.appendChild(continueButton(dom, function (_event) {
            gotName();
        }));
        namefield.focus();
    }); // Promise
}
/**
 * A TR to represent a draggable person, etc in a list
 *
 * pred is unused param at the moment
 */
export const personTR = renderAsRow; // The legacy name is used in a lot of places
export function renderAsRow(dom, pred, obj, options) {
    const tr = dom.createElement('tr');
    options = options || {};
    // tr.predObj = [pred.uri, obj.uri]   moved to acl-control
    const td1 = tr.appendChild(dom.createElement('td'));
    const td2 = tr.appendChild(dom.createElement('td'));
    const td3 = tr.appendChild(dom.createElement('td'));
    // const image = td1.appendChild(dom.createElement('img'))
    const image = options.image || faviconOrDefault(dom, obj);
    td1.setAttribute('style', 'vertical-align: middle; width: var(--sui-avatar-size, 2.5em); padding: 0.5em; height: var(--sui-avatar-size, 2.5em);');
    td2.setAttribute('style', 'vertical-align: middle; text-align:left;');
    td3.setAttribute('style', 'vertical-align: middle; width: var(--sui-icon-size, 2em); padding: 0.5em; height: 4em;');
    td1.appendChild(image);
    if (options.title) {
        td2.textContent = options.title;
    }
    else {
        setName(td2, obj); // This is async
    }
    if (options.deleteFunction) {
        deleteButtonWithCheck(dom, td3, options.noun || 'one', options.deleteFunction);
    }
    if (obj.uri) {
        // blank nodes need not apply
        if (options.link !== false) {
            const anchor = td3.appendChild(linkIcon(dom, obj));
            anchor.classList.add('HoverControlHide');
            td3.appendChild(dom.createElement('br'));
        }
        if (options.draggable !== false) {
            // default is on
            image.setAttribute('draggable', 'false'); // Stop the image being dragged instead - just the TR
            makeDraggable(tr, obj);
        }
    }
    ;
    tr.subject = obj;
    return tr;
}
/* A helper function for renderAsDiv
*  creates the NameDiv for the person
*  Note: could not move it to the helper file because they call exported functions
*  from buttons
*  @internal exporting this only for unit tests
*/
export function createNameDiv(dom, div, title, obj) {
    const nameDiv = div.appendChild(dom.createElement('div'));
    if (title) {
        nameDiv.textContent = title;
    }
    else {
        setName(nameDiv, obj); // This is async
    }
}
/* A helper function for renderAsDiv
*  creates the linkDiv for the person
*  Note: could not move it to the helper file because they call exported functions
*  from buttons
* @internal exporting this only for unit tests
*/
export function createLinkDiv(dom, div, obj, options) {
    const linkDiv = div.appendChild(dom.createElement('div'));
    linkDiv.setAttribute('style', style.linkDivStyle);
    if (options.deleteFunction) {
        deleteButtonWithCheck(dom, linkDiv, options.noun || 'one', options.deleteFunction);
    }
    if (obj.uri) {
        // blank nodes need not apply
        if (options.link !== false) {
            createLinkForURI(dom, linkDiv, obj);
        }
        makeDraggable(div, obj);
    }
}
/**
 * A Div to represent a draggable person, etc in a list
 * configurable to add an onClick listener
 */
export function renderAsDiv(dom, obj, options) {
    const div = dom.createElement('div');
    div.setAttribute('style', style.renderAsDivStyle);
    options = options || {};
    const image = options.image || faviconOrDefault(dom, obj);
    createImageDiv(dom, div, image);
    createNameDiv(dom, div, options.title, obj);
    createLinkDiv(dom, div, obj, options);
    if (options.clickable && options.onClickFunction) {
        addClickListenerToElement(div, options.onClickFunction);
    }
    // to be compatible with the SolidOS table layout
    if (options.wrapInATR) {
        const tr = wrapDivInATR(dom, div, obj);
        return tr;
    }
    return div;
}
/**
 * Refresh a DOM tree recursively
 */
export function refreshTree(root) {
    if (root.refresh) {
        root.refresh();
        return;
    }
    for (let i = 0; i < root.children.length; i++) {
        refreshTree(root.children[i]);
    }
}
/**
 * Component that displays a list of resources, for instance
 * the attachments of a message, or the various documents related
 * to a meeting.
 * Accepts dropping URLs onto it to add attachments to it.
 */
export function attachmentList(dom, subject, div, options = {}) {
    // options = options || {}
    const deleteAttachment = function (target) {
        if (!kb.updater) {
            throw new Error('kb has no updater');
        }
        kb.updater.update(st(subject, predicate, target, doc), [], function (uri, ok, errorBody, _xhr) {
            if (ok) {
                refresh();
            }
            else {
                complain(undefined, 'Error deleting one: ' + errorBody);
            }
        });
    };
    function createNewRow(target) {
        const theTarget = target;
        const opt = { noun };
        if (modify) {
            opt.deleteFunction = function () {
                deleteAttachment(theTarget);
            };
        }
        return personTR(dom, predicate, target, opt);
    }
    const refresh = function () {
        const things = kb.each(subject, predicate);
        things.sort();
        utils.syncTableToArray(attachmentTable, things, createNewRow);
    };
    function droppedURIHandler(uris) {
        const ins = [];
        uris.forEach(function (u) {
            const target = sym(u); // Attachment needs text label to disinguish I think not icon.
            debug.log('Dropped on attachemnt ' + u); // icon was: iconBase + 'noun_25830.svg'
            ins.push(st(subject, predicate, target, doc));
        });
        if (!kb.updater) {
            throw new Error('kb has no updater');
        }
        kb.updater.update([], ins, function (uri, ok, errorBody, _xhr) {
            if (ok) {
                refresh();
            }
            else {
                complain(undefined, 'Error adding one: ' + errorBody);
            }
        });
    }
    function droppedFileHandler(files) {
        var _a, _b;
        uploadFiles(kb.fetcher, files, (_a = options.uploadFolder) === null || _a === void 0 ? void 0 : _a.uri, // Files
        (_b = options.uploadFolder) === null || _b === void 0 ? void 0 : _b.uri, // Pictures
        function (theFile, destURI) {
            const ins = [st(subject, predicate, kb.sym(destURI), doc)];
            if (!kb.updater) {
                throw new Error('kb has no updater');
            }
            kb.updater.update([], ins, function (uri, ok, errorBody, _xhr) {
                if (ok) {
                    refresh();
                }
                else {
                    complain(undefined, 'Error adding link to uploaded file: ' + errorBody);
                }
            });
        });
    }
    const doc = options.doc || subject.doc();
    if (options.modify === undefined)
        options.modify = true;
    const modify = options.modify;
    const promptIcon = options.promptIcon || iconBase + 'noun_748003.svg'; //    target
    // const promptIcon = options.promptIcon || (iconBase + 'noun_25830.svg') //  paperclip
    const predicate = options.predicate || ns.wf('attachment');
    const noun = options.noun || 'attachment';
    const kb = store;
    const attachmentOuter = div.appendChild(dom.createElement('table'));
    attachmentOuter.setAttribute('style', 'margin-top: 1em; margin-bottom: 1em;');
    const attachmentOne = attachmentOuter.appendChild(dom.createElement('tr'));
    const attachmentLeft = attachmentOne.appendChild(dom.createElement('td'));
    const attachmentRight = attachmentOne.appendChild(dom.createElement('td'));
    const attachmentTable = attachmentRight.appendChild(dom.createElement('table'));
    attachmentTable.appendChild(dom.createElement('tr')) // attachmentTableTop
    ;
    attachmentOuter.refresh = refresh; // Participate in downstream changes
    // ;(attachmentTable as any).refresh = refresh   <- outer should be best?
    refresh();
    if (modify) {
        // const buttonStyle = 'width; 2em; height: 2em; margin: 0.5em; padding: 0.1em;'
        const paperclip = button(dom, promptIcon, 'Drop attachments here');
        // paperclip.style = buttonStyle // @@ needed?  default has white background
        attachmentLeft.appendChild(paperclip);
        const fhandler = options.uploadFolder ? droppedFileHandler : null;
        makeDropTarget(paperclip, droppedURIHandler, fhandler); // beware missing the wire of the paparclip!
        makeDropTarget(attachmentLeft, droppedURIHandler, fhandler); // just the outer won't do it
        if (options.uploadFolder) { // Addd an explicit file upload button as well
            const buttonDiv = fileUploadButtonDiv(dom, droppedFileHandler);
            attachmentLeft.appendChild(buttonDiv);
            // buttonDiv.children[1].style =  buttonStyle
        }
    }
    return attachmentOuter;
}
// /////////////////////////////////////////////////////////////////////////////
/**
 * Event Handler for links within solid apps.
 *
 * Note that native links have constraints in Firefox, they
 * don't work with local files for instance (2011)
 */
export function openHrefInOutlineMode(e) {
    e.preventDefault();
    e.stopPropagation();
    const target = utils.getTarget(e);
    const uri = target.getAttribute('href');
    if (!uri)
        return debug.log('openHrefInOutlineMode: No href found!\n');
    const dom = window.document;
    if (dom.outlineManager) {
        // @@ TODO Remove the use of document as a global object
        // TODO fix dependency cycle to solid-panes by calling outlineManager
        ;
        dom.outlineManager.GotoSubject(store.sym(uri), true, undefined, true, undefined);
    }
    else if (window && window.panes && window.panes.getOutliner) {
        // @@ TODO Remove the use of window as a global object
        ;
        window.panes
            .getOutliner()
            .GotoSubject(store.sym(uri), true, undefined, true, undefined);
    }
    else {
        debug.log('ERROR: Can\'t access outline manager in this config');
    }
    // dom.outlineManager.GotoSubject(store.sym(uri), true, undefined, true, undefined)
}
/**
 * Make a URI in the Tabulator.org annotation store out of the URI of the thing to be annotated.
 *
 * @@ Todo: make it a personal preference.
 */
export function defaultAnnotationStore(subject) {
    if (subject.uri === undefined)
        return undefined;
    let s = subject.uri;
    if (s.slice(0, 7) !== 'http://')
        return undefined;
    s = s.slice(7); // Remove
    const hash = s.indexOf('#');
    if (hash >= 0)
        s = s.slice(0, hash);
    // Strip trailing
    else {
        const slash = s.lastIndexOf('/');
        if (slash < 0)
            return undefined;
        s = s.slice(0, slash);
    }
    return store.sym('http://tabulator.org/wiki/annnotation/' + s);
}
/**
 * Retrieve all RDF class URIs from solid-ui's RDF store
 * @returns an object `ret` such that `Object.keys(ret)` is
 * the list of all class URIs.
 */
export function allClassURIs() {
    const set = {};
    store
        .statementsMatching(undefined, ns.rdf('type'), undefined)
        .forEach(function (st) {
        if (st.object.value)
            set[st.object.value] = true;
    });
    store
        .statementsMatching(undefined, ns.rdfs('subClassOf'), undefined)
        .forEach(function (st) {
        if (st.object.value)
            set[st.object.value] = true;
        if (st.subject.value)
            set[st.subject.value] = true;
    });
    store
        .each(undefined, ns.rdf('type'), ns.rdfs('Class'))
        .forEach(function (c) {
        if (c.value)
            set[c.value] = true;
    });
    return set;
}
/**
 * Figuring which properties we know about
 *
 * When the user inputs an RDF property, like for a form field
 * or when specifying the relationship between two arbitrary things,
 * then er can prompt them with properties the session knows about
 *
 * TODO: Look again by catching this somewhere. (On the kb?)
 * TODO: move to diff module? Not really a button.
 * @param {Store} kb The quadstore to be searched.
 */
export function propertyTriage(kb) {
    const possibleProperties = {};
    // if (possibleProperties === undefined) possibleProperties = {}
    // const kb = store
    const dp = {};
    const op = {};
    let no = 0;
    let nd = 0;
    let nu = 0;
    const pi = kb.predicateIndex; // One entry for each pred
    for (const p in pi) {
        const object = pi[p][0].object;
        if (object.termType === 'Literal') {
            dp[p] = true;
            nd++;
        }
        else {
            op[p] = true;
            no++;
        }
    } // If nothing discovered, then could be either:
    const ps = kb.each(undefined, ns.rdf('type'), ns.rdf('Property'));
    for (let i = 0; i < ps.length; i++) {
        const p = ps[i].toNT();
        if (!op[p] && !dp[p]) {
            dp[p] = true;
            op[p] = true;
            nu++;
        }
    }
    possibleProperties.op = op;
    possibleProperties.dp = dp;
    info(`propertyTriage: ${no} non-lit, ${nd} literal. ${nu} unknown.`);
    return possibleProperties;
}
/**
 * General purpose widgets
 */
/**
 * A button for jumping
 */
export function linkButton(dom, object) {
    const b = dom.createElement('button');
    b.setAttribute('type', 'button');
    b.textContent = 'Goto ' + utils.label(object);
    b.addEventListener('click', function (_event) {
        // b.parentNode.removeChild(b)
        // TODO fix dependency cycle to solid-panes by calling outlineManager
        ;
        dom.outlineManager.GotoSubject(object, true, undefined, true, undefined);
    }, true);
    return b;
}
/**
 * A button to remove some other element from the page
 */
export function removeButton(dom, element) {
    const b = dom.createElement('button');
    b.setAttribute('type', 'button');
    b.textContent = '✕'; // MULTIPLICATION X
    b.addEventListener('click', function (_event) {
        ;
        element.parentNode.removeChild(element);
    }, true);
    return b;
}
//      Description text area
//
// Make a box to demand a description or display existing one
//
// @param dom - the document DOM for the user interface
// @param kb - the graph which is the knowledge base we are working with
// @param subject - a term, the subject of the statement(s) being edited.
// @param predicate - a term, the predicate of the statement(s) being edited
// @param store - The web document being edited
// @param callbackFunction - takes (boolean ok, string errorBody)
// /////////////////////////////////////// Random I/O widgets /////////////
// ////              Column Header Buttons
//
//  These are for selecting different modes, sources,styles, etc.
//
/*
buttons.headerButtons = function (dom, kb, name, words) {
    const box = dom.createElement('table')
    var i, word, s = '<tr>'
    box.setAttribute('style', 'width: 90%; height: 1.5em')
    for (i=0; i<words.length; i++) {
        s += '<td><input type="radio" name="' + name + '" id="' + words[i] + '" value='
    }
    box.innerHTML = s + '</tr>'

}
*/
// ////////////////////////////////////////////////////////////
//
//     selectorPanel
//
//  A vertical panel for selecting connections to left or right.
//
//   @param inverse means this is the object rather than the subject
//
export function selectorPanel(dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback) {
    return selectorPanelRefresh(dom.createElement('div'), dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback);
}
export function selectorPanelRefresh(list, dom, kb, type, predicate, inverse, possible, options, callbackFunction, linkCallback) {
    const style0 = 'border: 0.1em solid #ddd; border-bottom: none; width: 95%; height: 2em; padding: 0.5em;';
    let selected = null;
    list.innerHTML = '';
    const refreshItem = function (box, x) {
        // Scope to hold item and x
        let item;
        // eslint-disable-next-line prefer-const
        let image;
        const setStyle = function () {
            const already = inverse
                ? kb.each(undefined, predicate, x)
                : kb.each(x, predicate);
            iconDiv.setAttribute('class', already.length === 0 ? 'hideTillHover' : ''); // See tabbedtab.css
            image.setAttribute('src', options.connectIcon || iconBase + 'noun_25830.svg');
            image.setAttribute('title', already.length ? already.length : 'attach');
        };
        const f = index.twoLine.widgetForClass(type);
        // eslint-disable-next-line prefer-const
        item = f(dom, x);
        item.setAttribute('style', style0);
        const nav = dom.createElement('div');
        nav.setAttribute('class', 'hideTillHover'); // See tabbedtab.css
        nav.setAttribute('style', 'float:right; width:10%');
        const a = dom.createElement('a');
        a.setAttribute('href', x.uri);
        a.setAttribute('style', 'float:right');
        nav.appendChild(a).textContent = '>';
        box.appendChild(nav);
        const iconDiv = dom.createElement('div');
        iconDiv.setAttribute('style', (inverse ? 'float:left;' : 'float:right;') + ' width:30px;');
        image = dom.createElement('img');
        setStyle();
        iconDiv.appendChild(image);
        box.appendChild(iconDiv);
        item.addEventListener('click', function (event) {
            if (selected === item) {
                // deselect
                item.setAttribute('style', style0);
                selected = null;
            }
            else {
                if (selected)
                    selected.setAttribute('style', style0);
                item.setAttribute('style', style0 + 'background-color: #ccc; color:black;');
                selected = item;
            }
            callbackFunction(x, event, selected === item);
            setStyle();
        }, false);
        image.addEventListener('click', function (event) {
            linkCallback(x, event, inverse, setStyle);
        }, false);
        box.appendChild(item);
        return box;
    };
    for (let i = 0; i < possible.length; i++) {
        const box = dom.createElement('div');
        list.appendChild(box);
        refreshItem(box, possible[i]);
    }
    return list;
}
// ###########################################################################
//
//      Small compact views of things
//
export let index = {};
// ///////////////////////////////////////////////////////////////////////////
// We need these for anything which is a subject of an attachment.
//
// These should be moved to type-dependeent UI code. Related panes maybe
function twoLineDefault(dom, x) {
    // Default
    const box = dom.createElement('div');
    box.textContent = utils.label(x);
    return box;
}
/**
 * Find a function that can create a widget for a given class
 * @param c The RDF class for which we want a widget generator function
 */
function twoLineWidgetForClass(c) {
    let widget = index.twoLine[c.uri];
    const kb = store;
    if (widget)
        return widget;
    const sup = kb.findSuperClassesNT(c);
    for (const cl in sup) {
        widget = index.twoLine[kb.fromNT(cl).uri];
        if (widget)
            return widget;
    }
    return index.twoLine[''];
}
/**
 * Display a transaction
 * @param x Should have attributes through triples in store:
 *          * ns.qu('payee') -> a named node
 *          * ns.qu('date) -> a literal
 *          * ns.qu('amount') -> a literal
 */
function twoLineTransaction(dom, x) {
    let failed = '';
    const enc = function (p) {
        const y = store.any(x, ns.qu(p));
        if (!y)
            failed += '@@ No value for ' + p + '! ';
        return y ? utils.escapeForXML(y.value) : '?'; // @@@@
    };
    const box = dom.createElement('table');
    box.innerHTML = `
      <tr>
      <td colspan="2"> ${enc('payee')}</td>
      < /tr>
      < tr >
      <td>${enc('date').slice(0, 10)}</td>
      <td style = "text-align: right;">${enc('amount')}</td>
      </tr>`;
    if (failed) {
        box.innerHTML = `
      <tr>
        <td><a href="${utils.escapeForXML(x.uri)}">${utils.escapeForXML(failed)}</a></td>
      </tr>`;
    }
    return box;
}
/**
 * Display a trip
 * @param x Should have attributes through triples in store:
 *          * ns.dc('title') -> a literal
 *          * ns.cal('dtstart') -> a literal
 *          * ns.cal('dtend') -> a literal
 */
function twoLineTrip(dom, x) {
    const enc = function (p) {
        const y = store.any(x, p);
        return y ? utils.escapeForXML(y.value) : '?';
    };
    const box = dom.createElement('table');
    box.innerHTML = `
    <tr>
      <td colspan="2">${enc(ns.dc('title'))}</td>
    </tr>
    <tr style="color: #777">
      <td>${enc(ns.cal('dtstart'))}</td>
      <td>${enc(ns.cal('dtend'))}</td>
    </tr>`;
    return box;
}
/**
 * Stick a stylesheet link the document if not already there
 */
export function addStyleSheet(dom, href) {
    const links = dom.querySelectorAll('link');
    for (let i = 0; i < links.length; i++) {
        if ((links[i].getAttribute('rel') || '') === 'stylesheet' &&
            (links[i].getAttribute('href') || '') === href) {
            return;
        }
    }
    const link = dom.createElement('link');
    link.setAttribute('rel', 'stylesheet');
    link.setAttribute('type', 'text/css');
    link.setAttribute('href', href);
    dom.getElementsByTagName('head')[0].appendChild(link);
}
// Figure (or guess) whether this is an image, etc
//
export function isAudio(file) {
    return isImage(file, 'audio');
}
export function isVideo(file) {
    return isImage(file, 'video');
}
/**
 *
 */
export function isImage(file, kind) {
    const dcCLasses = {
        audio: 'http://purl.org/dc/dcmitype/Sound',
        image: 'http://purl.org/dc/dcmitype/Image',
        video: 'http://purl.org/dc/dcmitype/MovingImage'
    };
    const what = kind || 'image';
    // See https://github.com/linkeddata/rdflib.js/blob/e367d5088c/src/formula.ts#L554
    //
    const typeURIs = store.findTypeURIs(file);
    // See https://github.com/linkeddata/rdflib.js/blob/d5000f/src/utils-js.js#L14
    // e.g.'http://www.w3.org/ns/iana/media-types/audio'
    const prefix = Util.mediaTypeClass(what + '/*').uri.split('*')[0];
    for (const t in typeURIs) {
        if (t.startsWith(prefix))
            return true;
    }
    if (dcCLasses[what] in typeURIs)
        return true;
    return false;
}
/**
 * File upload button
 * @param dom The DOM aka document
 * @param  droppedFileHandler Same handler function as drop, takes array of file objects
 * @returns {Element} - a div with a button and a inout in it
 * The input is hidden, as it is uglky - the user clicks on the nice icons and fires the input.
 */
// See https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications
export function fileUploadButtonDiv(dom, droppedFileHandler) {
    const div = dom.createElement('div');
    const input = div.appendChild(dom.createElement('input'));
    input.setAttribute('type', 'file');
    input.setAttribute('multiple', 'true');
    input.addEventListener('change', (event) => {
        debug.log('File drop event: ', event);
        if (event.files) {
            droppedFileHandler(event.files);
        }
        else if (event.target && event.target.files) {
            droppedFileHandler(event.target.files);
        }
        else {
            alert('Sorry no files .. internal error?');
        }
    }, false);
    input.style = 'display:none';
    const buttonElt = div.appendChild(button(dom, iconBase + 'noun_Upload_76574_000000.svg', 'Upload files', _event => {
        input.click();
    }));
    makeDropTarget(buttonElt, null, droppedFileHandler); // Can also just drop on button
    return div;
}
index = {
    line: { // Approx 80em
    },
    twoLine: {
        '': twoLineDefault,
        'http://www.w3.org/2000/10/swap/pim/qif#Transaction': twoLineTransaction,
        'http://www.w3.org/ns/pim/trip#Trip': twoLineTrip,
        widgetForClass: twoLineWidgetForClass
    }
};
//# sourceMappingURL=buttons.js.map