(function (app) {
    'use strict';

    const u = app.modules.util;

    function makeObject(schema, ui, path, value, options) {
        const fs = u.h('fieldset', { className: 'form-fieldset' });
        const label = (ui && ui['ui:title']) || schema.title || options.fieldName;
        if (label) {
            fs.appendChild(u.h('legend', { className: 'form-fieldset__legend' }, label));
        }
        if (schema.description) {
            fs.appendChild(u.h('div', { className: 'form-field__description' }, schema.description));
        }
        const errEl = u.h('div', { className: 'form-field__error', hidden: true });
        fs.appendChild(errEl);

        const props = schema.properties || {};
        const requiredSet = {};
        (schema.required || []).forEach(function (n) { requiredSet[n] = true; });

        // Resolve render order: ui:order first (with '*' as "everything else"),
        // then fall back to declaration order.
        const declared = Object.keys(props);
        const uiOrder = (ui && ui['ui:order']) || null;
        const ordered = [];
        const seen = {};
        if (uiOrder && Array.isArray(uiOrder)) {
            for (let i = 0; i < uiOrder.length; i++) {
                const name = uiOrder[i];
                if (name === '*') {
                    for (let j = 0; j < declared.length; j++) {
                        const dn = declared[j];
                        if (!seen[dn] && uiOrder.indexOf(dn) < 0) {
                            ordered.push(dn);
                            seen[dn] = true;
                        }
                    }
                } else if (props[name] && !seen[name]) {
                    ordered.push(name);
                    seen[name] = true;
                }
            }
            // Append anything declared but not mentioned in ui:order (and no '*' was used).
            for (let j = 0; j < declared.length; j++) {
                if (!seen[declared[j]]) {
                    ordered.push(declared[j]);
                    seen[declared[j]] = true;
                }
            }
        } else {
            for (let j = 0; j < declared.length; j++) {
                ordered.push(declared[j]);
            }
        }

        const children = {};
        const dataObj = (value && typeof value === 'object' && !Array.isArray(value)) ? value : {};

        for (let i = 0; i < ordered.length; i++) {
            const name = ordered[i];
            const childSchema = props[name];
            const childUi = (ui && ui[name]) || {};
            const childPath = u.ptrPush(path, name);
            const childValue = dataObj[name];
            const childWidget = app.modules.render.create(childSchema, childUi, childPath, childValue, {
                fieldName: u.humanize(name),
                required: !!requiredSet[name]
            });
            children[name] = childWidget;
            fs.appendChild(childWidget.el);
        }

        return {
            el: fs,
            path: path,
            type: 'object',
            read: function () {
                const out = {};
                const keys = Object.keys(children);
                for (let i = 0; i < keys.length; i++) {
                    const k = keys[i];
                    const v = children[k].read();
                    if (v !== undefined) {
                        out[k] = v;
                    }
                }
                return out;
            },
            setError: function (msg) {
                errEl.textContent = msg;
                errEl.hidden = false;
            },
            clearErrors: function () {
                errEl.textContent = '';
                errEl.hidden = true;
                const keys = Object.keys(children);
                for (let i = 0; i < keys.length; i++) {
                    children[keys[i]].clearErrors();
                }
            },
            child: function (name) {
                return children[name] || null;
            }
        };
    }

    app.modules.object = { makeObject: makeObject };
})(window.formApp);
