"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.renderNameValuePair = renderNameValuePair;
exports.fieldLabel = fieldLabel;
exports.fieldStore = fieldStore;
exports.basicField = basicField;
const rdflib_1 = require("rdflib");
const solid_logic_1 = require("solid-logic");
const ns_1 = __importDefault(require("../../ns"));
const style_1 = require("../../style");
const styleConstants_1 = __importDefault(require("../../styleConstants"));
const utils_1 = require("../../utils");
const error_1 = require("../error");
const fieldFunction_1 = require("./fieldFunction");
const fieldParams_1 = require("./fieldParams");
const store = solid_logic_1.solidLogicSingleton.store;
/*  Style and create a name, value pair
*/
function renderNameValuePair(dom, kb, box, form, label) {
    // const property = kb.any(form, ns.ui('property'))
    box.style.display = 'flex';
    box.style.flexDirection = 'row';
    const lhs = box.appendChild(dom.createElement('div'));
    lhs.style.width = styleConstants_1.default.formFieldNameBoxWidth;
    const rhs = box.appendChild(dom.createElement('div'));
    lhs.setAttribute('class', 'formFieldName');
    lhs.setAttribute('style', style_1.style.formFieldNameBoxStyle);
    rhs.setAttribute('class', 'formFieldValue');
    if (label) {
        lhs.appendChild(dom.createTextNode(label));
    }
    else if (kb.any(form, ns_1.default.ui('property'))) { // Assume more space for error on right
        lhs.appendChild(fieldLabel(dom, kb.any(form, ns_1.default.ui('property')), form));
    }
    else {
        rhs.appendChild((0, error_1.errorMessageBlock)(dom, 'No property or label given for form field: ' + form));
        lhs.appendChild(dom.createTextNode('???'));
    }
    return rhs;
}
/**
 * Create an anchor element with a label as the anchor text.
 *
 * @param dom The DOM
 * @param property href for the anchor element
 * @param fieldInQuestion field to produce a label for
 *
 * @internal exporting this only for unit tests
 */
function fieldLabel(dom, property, fieldInQuestion) {
    let lab = store.any(fieldInQuestion, ns_1.default.ui('label'));
    if (!lab)
        lab = (0, utils_1.label)(property, true); // Init capital
    if (property === undefined) {
        return dom.createTextNode('@@Internal error: undefined property');
    }
    const anchor = dom.createElement('a');
    /* istanbul ignore next */
    if (property.uri)
        anchor.setAttribute('href', property.uri);
    anchor.setAttribute('style', 'color: #3B5998; text-decoration: none;'); // Not too blue and no underline
    anchor.textContent = lab;
    return anchor;
}
/**
 * Returns the document for the first quad that matches
 * the subject and predicate provided, or default if that
 * store is not editable.
 *
 * @param subject Subject about which we want to find an editable RDF document
 * @param predicate Predicate about which we want to find an editable RDF document
 * @param def default RDF document to return if none found
 *
 * @internal exporting this only for unit tests
 */
function fieldStore(subject, predicate, def) {
    const sts = store.statementsMatching(subject, predicate);
    if (sts.length === 0)
        return def; // can used default as no data yet
    if (!store.updater) {
        throw new Error('Store has no updater');
    }
    if (sts.length > 0 &&
        sts[0].why.value &&
        store.updater.editable(sts[0].why.value, store)) {
        return store.sym(sts[0].why.value);
    }
    return def;
}
/**
 * Render a basic form field
 *
 * The same function is used for many similar one-value fields, with different
 * regexps used to validate.
 *
 * @param dom The HTML Document object aka Document Object Model
 * @param container  If present, the created widget will be appended to this
 * @param already A hash table of (form, subject) kept to prevent recursive forms looping
 * @param subject The thing about which the form displays/edits data
 * @param form The form or field to be rendered
 * @param doc The web document in which the data is
 * @param callbackFunction Called when data is changed?
 *
 * @returns The HTML widget created
 */
function basicField(dom, container, already, subject, form, doc, callbackFunction) {
    const kb = store;
    const formDoc = form.doc ? form.doc() : null; // @@ if blank no way to know
    const box = dom.createElement('div');
    const property = kb.any(form, ns_1.default.ui('property'));
    if (container)
        container.appendChild(box);
    if (!property) {
        return box.appendChild((0, error_1.errorMessageBlock)(dom, 'Error: No property given for text field: ' + form));
    }
    const rhs = renderNameValuePair(dom, kb, box, form);
    // It can be cleaner to just remove empty fields if you can't edit them anyway
    const suppressEmptyUneditable = kb.anyJS(form, ns_1.default.ui('suppressEmptyUneditable'), null, formDoc);
    const uri = (0, fieldFunction_1.mostSpecificClassURI)(form);
    let params = fieldParams_1.fieldParams[uri];
    if (params === undefined)
        params = { style: '' }; // non-bottom field types can do this
    const paramStyle = params.style || '';
    const inputStyle = style_1.style.textInputStyle + paramStyle;
    const field = dom.createElement('input');
    field.style = inputStyle;
    rhs.appendChild(field);
    field.setAttribute('type', params.type ? params.type : 'text');
    const size = kb.anyJS(form, ns_1.default.ui('size')) || styleConstants_1.default.textInputSize || 20;
    field.setAttribute('size', size);
    const maxLength = kb.any(form, ns_1.default.ui('maxLength'));
    field.setAttribute('maxLength', maxLength ? '' + maxLength : styleConstants_1.default.basicMaxLength);
    doc = doc || fieldStore(subject, property, doc);
    let obj = kb.any(subject, property, undefined, doc);
    if (!obj) {
        obj = kb.any(form, ns_1.default.ui('default'));
    }
    if (obj && obj.value && params.uriPrefix) {
        // eg tel: or mailto:
        field.value = decodeURIComponent(obj.value.replace(params.uriPrefix, '')) // should have no spaces but in case
            .replace(/ /g, '');
    }
    else if (obj) {
        /* istanbul ignore next */
        field.value = obj.value || obj.value || '';
    }
    field.setAttribute('style', inputStyle);
    if (!kb.updater) {
        throw new Error('kb has no updater');
    }
    if (!kb.updater.editable(doc.uri)) {
        field.readOnly = true // was: disabled. readOnly is better
        ;
        field.style = style_1.style.textInputStyleUneditable + paramStyle;
        if (suppressEmptyUneditable && field.value === '') {
            box.style.display = 'none'; // clutter
        }
        return box;
    }
    // read-write:
    field.addEventListener('keyup', function (_e) {
        if (params.pattern) {
            field.setAttribute('style', inputStyle +
                (field.value.match(params.pattern)
                    ? 'color: green;'
                    : 'color: red;'));
        }
    }, true);
    field.addEventListener('change', function (_e) {
        // i.e. lose focus with changed data
        if (params.pattern && !field.value.match(params.pattern))
            return;
        field.disabled = true; // See if this stops getting two dates from fumbling e.g the chrome datepicker.
        field.setAttribute('style', inputStyle + 'color: gray;'); // pending
        const ds = kb.statementsMatching(subject, property); // remove any multiple values
        let result;
        if (params.namedNode) {
            result = kb.sym(field.value);
        }
        else if (params.uriPrefix) {
            result = encodeURIComponent(field.value.replace(/ /g, ''));
            result = kb.sym(params.uriPrefix + field.value);
        }
        else {
            if (params.dt) {
                result = new rdflib_1.Literal(field.value.trim(), undefined, ns_1.default.xsd(params.dt));
            }
            else {
                result = new rdflib_1.Literal(field.value);
            }
        }
        let is = ds.map(statement => (0, rdflib_1.st)(statement.subject, statement.predicate, result, statement.why)); // can include >1 doc
        if (is.length === 0) {
            // or none
            is = [(0, rdflib_1.st)(subject, property, result, doc)];
        }
        function updateMany(ds, is, callback) {
            const docs = [];
            is.forEach(st => {
                if (!docs.includes(st.why.uri))
                    docs.push(st.why.uri);
            });
            ds.forEach(st => {
                /* istanbul ignore next */
                if (!docs.includes(st.why.uri))
                    docs.push(st.why.uri);
            });
            /* istanbul ignore next */
            if (docs.length === 0) {
                throw new Error('updateMany has no docs to patch');
            }
            if (!kb.updater) {
                throw new Error('kb has no updater');
            }
            if (docs.length === 1) {
                return kb.updater.update(ds, is, callback);
            }
            // return kb.updater.update(ds, is, callback)
            const doc = docs.pop();
            const is1 = is.filter(st => st.why.uri === doc);
            const is2 = is.filter(st => st.why.uri !== doc);
            const ds1 = ds.filter(st => st.why.uri === doc);
            const ds2 = ds.filter(st => st.why.uri !== doc);
            kb.updater.update(ds1, is1, function (uri, ok, body) {
                if (ok) {
                    updateMany(ds2, is2, callback);
                }
                else {
                    callback(uri, ok, body);
                }
            });
        }
        updateMany(ds, is, function (uri, ok, body) {
            // kb.updater.update(ds, is, function (uri, ok, body) {
            if (ok) {
                field.disabled = false;
                field.setAttribute('style', inputStyle);
            }
            else {
                box.appendChild((0, error_1.errorMessageBlock)(dom, body));
            }
            callbackFunction(ok, body);
        });
    }, true);
    return box;
}
//# sourceMappingURL=basic.js.map