(function (app) {
    'use strict';

    const u = app.modules.util;

    // Build the standard label / description / input / help / error scaffold
    // shared by all primitive widgets. Returns { wrap, errEl }.
    function fieldContainer(opts) {
        const wrap = u.h('div', { className: 'form-field' });
        if (opts.label) {
            const lbl = u.h('label', { className: 'form-field__label', for: opts.id });
            lbl.appendChild(document.createTextNode(opts.label));
            if (opts.required) {
                lbl.appendChild(u.h('span', { className: 'required-mark' }, '*'));
            }
            wrap.appendChild(lbl);
        }
        if (opts.description) {
            wrap.appendChild(u.h('div', { className: 'form-field__description' }, opts.description));
        }
        wrap.appendChild(opts.input);
        if (opts.help) {
            wrap.appendChild(u.h('div', { className: 'form-field__help' }, opts.help));
        }
        const errEl = u.h('div', { className: 'form-field__error', hidden: true });
        wrap.appendChild(errEl);
        return { wrap: wrap, errEl: errEl };
    }

    function coerceEnum(rawValue, options) {
        for (let i = 0; i < options.length; i++) {
            if (String(options[i]) === rawValue) {
                return options[i];
            }
        }
        return rawValue;
    }

    function makePrimitive(schema, ui, path, value, options) {
        const id = u.uid('w');
        const required = !!options.required;
        const label = (ui && ui['ui:title']) || schema.title || options.fieldName || '';
        const description = (ui && ui['ui:description']) || schema.description || '';
        const help = (ui && ui['ui:help']) || '';
        const placeholder = (ui && ui['ui:placeholder']) || '';
        const widget = (ui && ui['ui:widget']) || '';
        const readonly = !!(ui && ui['ui:readonly']);
        const autofocus = !!(ui && ui['ui:autofocus']);

        let input;
        let read;

        const t = schema.type;

        if (t === 'boolean') {
            // Render boolean as a single checkbox with an inline label, suppressing
            // the standard label-above layout for cleaner UX.
            const cb = u.h('input', { type: 'checkbox', id: id });
            if (value === true) {
                cb.checked = true;
            }
            if (readonly) {
                cb.disabled = true;
            }
            const wrap = u.h('div', { className: 'form-field form-field--boolean' });
            const inlineLabel = u.h('label', { for: id, className: 'form-field__checkbox-inline' });
            inlineLabel.appendChild(cb);
            inlineLabel.appendChild(document.createTextNode(' '));
            inlineLabel.appendChild(document.createTextNode(label || ''));
            if (required) {
                inlineLabel.appendChild(u.h('span', { className: 'required-mark' }, '*'));
            }
            wrap.appendChild(inlineLabel);
            if (description) {
                wrap.appendChild(u.h('div', { className: 'form-field__description' }, description));
            }
            if (help) {
                wrap.appendChild(u.h('div', { className: 'form-field__help' }, help));
            }
            const errEl = u.h('div', { className: 'form-field__error', hidden: true });
            wrap.appendChild(errEl);

            return widgetObject(wrap, errEl, path, function () {
                return cb.checked;
            });
        }

        if (Array.isArray(schema.enum)) {
            const opts = schema.enum;
            if (widget === 'radio') {
                input = u.h('div', { className: 'form-field__radio-group' });
                opts.forEach(function (opt, idx) {
                    const radioId = id + '-' + idx;
                    const radio = u.h('input', { type: 'radio', name: id, id: radioId, value: String(opt) });
                    if (value === opt) {
                        radio.checked = true;
                    }
                    if (readonly) {
                        radio.disabled = true;
                    }
                    const lbl = u.h('label', { for: radioId });
                    lbl.appendChild(radio);
                    lbl.appendChild(document.createTextNode(' ' + String(opt)));
                    input.appendChild(lbl);
                });
                read = function () {
                    const checked = input.querySelector('input[type="radio"]:checked');
                    return checked ? coerceEnum(checked.value, opts) : undefined;
                };
            } else {
                input = u.h('select', { id: id, className: 'form-field__select' });
                if (!required) {
                    input.appendChild(u.h('option', { value: '' }, '— select —'));
                }
                opts.forEach(function (opt) {
                    const o = u.h('option', { value: String(opt) }, String(opt));
                    if (value === opt) {
                        o.selected = true;
                    }
                    input.appendChild(o);
                });
                if (readonly) {
                    input.disabled = true;
                }
                read = function () {
                    if (input.value === '') {
                        return undefined;
                    }
                    return coerceEnum(input.value, opts);
                };
            }
        } else if (t === 'number' || t === 'integer') {
            input = u.h('input', {
                type: 'number',
                id: id,
                className: 'form-field__input',
                step: t === 'integer' ? '1' : 'any'
            });
            if (placeholder) {
                input.placeholder = placeholder;
            }
            if (value != null) {
                input.value = String(value);
            }
            if (readonly) {
                input.readOnly = true;
            }
            if (autofocus) {
                input.autofocus = true;
            }
            read = function () {
                const v = input.value.trim();
                if (v === '') {
                    return undefined;
                }
                const n = Number(v);
                // If the user typed something non-numeric, return the raw string and
                // let server validation produce a friendly error.
                return Number.isFinite(n) ? n : v;
            };
        } else {
            // Default: string-shaped input.
            const fmt = schema.format;
            if (widget === 'textarea') {
                input = u.h('textarea', { id: id, className: 'form-field__textarea' });
            } else {
                let inputType = 'text';
                if (fmt === 'date') {
                    inputType = 'date';
                } else if (fmt === 'email') {
                    inputType = 'email';
                }
                input = u.h('input', { type: inputType, id: id, className: 'form-field__input' });
            }
            if (placeholder) {
                input.placeholder = placeholder;
            }
            if (value != null) {
                input.value = String(value);
            }
            if (readonly) {
                input.readOnly = true;
            }
            if (autofocus) {
                input.autofocus = true;
            }
            read = function () {
                return input.value === '' ? undefined : input.value;
            };
        }

        const built = fieldContainer({
            id: id,
            label: label,
            description: description,
            help: help,
            required: required,
            input: input
        });

        return widgetObject(built.wrap, built.errEl, path, read);
    }

    // Common widget shape used by both primitive and the wrapper above.
    function widgetObject(wrapEl, errEl, path, read) {
        return {
            el: wrapEl,
            path: path,
            type: 'primitive',
            read: read,
            setError: function (msg) {
                errEl.textContent = msg;
                errEl.hidden = false;
                wrapEl.classList.add('form-field--invalid');
            },
            clearErrors: function () {
                errEl.textContent = '';
                errEl.hidden = true;
                wrapEl.classList.remove('form-field--invalid');
            },
            child: function () { return null; }
        };
    }

    app.modules.widgets = { makePrimitive: makePrimitive };
})(window.formApp);
