/*!
 * Built by ShiftSoftware
 * Copyright (c)
 */
import { forceUpdate } from "@stencil/core";
export class FormHook {
    constructor(context, schemaObject, formStateOptions) {
        this.haltValidation = false;
        this.isSubmitted = false;
        this.stepFormValues = {};
        this.watchers = {};
        this.cachedValues = {};
        this.subscribers = [];
        this.validationType = 'onSubmit';
        this.subscribedFields = {};
        this.formErrors = {};
        this.backButtonContexts = [];
        this.pendingRequests = {};
        this.openDialog = () => { };
        this.subscribe = (formName, formElement) => {
            var _a, _b, _c, _d;
            this.subscribers.push({ name: formName, context: formElement });
            if (formElement === null || formElement === void 0 ? void 0 : formElement.partialValidation) {
                const baseField = (((_b = (_a = this.schemaObject) === null || _a === void 0 ? void 0 : _a.fields) === null || _b === void 0 ? void 0 : _b[formName]) || ((_d = (_c = this.basicSchemaObject) === null || _c === void 0 ? void 0 : _c.fields) === null || _d === void 0 ? void 0 : _d[formName]));
                if (baseField) {
                    const nextField = formElement.partialValidation(baseField);
                    if (nextField) {
                        this.schemaObject = this.schemaObject.shape({
                            [formName]: nextField,
                        });
                    }
                }
            }
            if (formElement === null || formElement === void 0 ? void 0 : formElement.validate) {
                this.schemaObject = this.schemaObject.shape({
                    [formName]: formElement === null || formElement === void 0 ? void 0 : formElement.validate(),
                });
            }
        };
        this.unsubscribe = (formName) => {
            var _a, _b;
            this.schemaObject = this.schemaObject.shape({
                [formName]: ((_a = this.basicSchemaObject) === null || _a === void 0 ? void 0 : _a.fields[formName]) ? (_b = this.basicSchemaObject) === null || _b === void 0 ? void 0 : _b.fields[formName] : undefined,
            });
            this.subscribers = this.subscribers.filter(({ name }) => name !== formName);
        };
        this.getStepLabels = (language = 'en', selectedStep) => {
            var _a, _b, _c, _d;
            if (!selectedStep)
                selectedStep = (_a = this.formStructure) === null || _a === void 0 ? void 0 : _a.currentStep;
            let step = {
                back: '',
                title: '',
                stepCell: '',
                stepTitle: '',
                submitButton: '',
            };
            try {
                step = (_d = (_c = (_b = this.context.structure) === null || _b === void 0 ? void 0 : _b.steps) === null || _c === void 0 ? void 0 : _c[selectedStep - 1]) === null || _d === void 0 ? void 0 : _d[language];
            }
            catch (error) { }
            return step;
        };
        this.addWatcher = (key, callback) => {
            if (this.watchers[key])
                return;
            if (!(key === null || key === void 0 ? void 0 : key.trim()))
                this.watchers['all'] = ({ form }) => {
                    form.rerender({ rerenderForm: true });
                };
            else if (callback)
                this.watchers[key] = callback;
            else
                this.watchers[key] = ({ form }) => {
                    form.rerender({ rerenderForm: true });
                };
        };
        this.removeWatcher = (key) => {
            delete this.watchers[key];
        };
        this.onInput = (event) => {
            const target = event.target;
            let value = target.value;
            if (target.type === 'checkbox')
                value = target.checked;
            const values = Object.assign(Object.assign({}, this.getValues()), { [target.name]: value });
            if (this.onValuesUpdate)
                this.onValuesUpdate(values);
            this.validateForm(target.name, value);
        };
        this.resetFormErrorMessage = () => (this.context.errorMessage = '');
        this.getFormErrors = () => this.formErrors;
        this.getFormLocale = () => [this.context.locale, this.context.language];
        this.setCachedValues = (newValues) => {
            this.cachedValues = newValues;
        };
        this.getValue = (name) => {
            return this.getValues()[name];
        };
        this.getValues = () => {
            const formDom = this.context.el.shadowRoot || this.context.el;
            const form = formDom.querySelector('form');
            if (!form)
                return {};
            const formData = new FormData(form);
            const formObject = Object.fromEntries(formData.entries());
            const elements = form.querySelectorAll('[name]');
            elements.forEach(el => {
                var _a;
                const elementContext = (_a = this.subscribers.find(sub => sub.name === el.name)) === null || _a === void 0 ? void 0 : _a.context;
                if (elementContext && (elementContext === null || elementContext === void 0 ? void 0 : elementContext.getValue)) {
                    formObject[el.name] = elementContext.getValue();
                    return;
                }
                if (el.disabled) {
                    formObject[el.name] = el.value;
                    return;
                }
                if (el.type === 'file') {
                    // @ts-ignore
                    const files = Array.from((el === null || el === void 0 ? void 0 : el.files) || []);
                    const signs = this.subscribers.find(sub => sub.name === (el === null || el === void 0 ? void 0 : el.name)).context.sasFiles;
                    const parsed = files.map((file, idx) => (signs === null || signs === void 0 ? void 0 : signs[idx])
                        ? Object.assign({ name: file.name, size: file.size, type: file.type, lastModified: file.lastModified }, signs[idx]) : file);
                    // @ts-ignore
                    formObject[el.name] = parsed;
                }
            });
            // const formObject = Object.fromEntries(formData.entries() as Iterable<[string, FormDataEntryValue]>);
            return Object.assign(Object.assign({}, this.cachedValues), formObject);
        };
        this.focusFirstInput = (errorFields) => {
            if (errorFields.length) {
                const formDom = this.context.el.shadowRoot || this.context.el;
                const domElements = errorFields.map(field => formDom.querySelector(`*[name="${field.name}"]`)).filter(dom => dom);
                const sortedDomElements = domElements.sort((a, b) => {
                    if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING)
                        return -1; // a comes before b
                    if (a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_PRECEDING)
                        return 1; // b comes before a
                    return 0; // They are the same
                });
                if (sortedDomElements[0]) {
                    if (sortedDomElements[0].hidden) {
                        sortedDomElements[0].parentElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    }
                    else
                        sortedDomElements[0].focus();
                }
            }
        };
        this.hasItemInStructure = (target, structure = this.context.structure) => {
            var _a;
            if (!structure)
                return true;
            if (typeof structure === 'string') {
                return structure === target;
            }
            const { tag, name, children, isHidden, step } = structure;
            if (!!step && step != ((_a = this.formStructure) === null || _a === void 0 ? void 0 : _a.currentStep))
                return false;
            if ((tag === target || name === target) && !isHidden)
                return true;
            if (Array.isArray(children)) {
                return children.some(child => this.hasItemInStructure(target, child));
            }
            return false;
        };
        this.updateStep = (newStep, replace = false) => {
            var _a, _b, _c, _d, _e, _f;
            let tempStep = this.formStructure.currentStep;
            if (replace)
                tempStep = newStep;
            else
                tempStep += newStep;
            if (tempStep < 1)
                tempStep = 1;
            if (tempStep > ((_c = (_b = (_a = this.context) === null || _a === void 0 ? void 0 : _a.structure) === null || _b === void 0 ? void 0 : _b.steps) === null || _c === void 0 ? void 0 : _c.length))
                newStep = (_f = (_e = (_d = this.context) === null || _d === void 0 ? void 0 : _d.structure) === null || _e === void 0 ? void 0 : _e.steps) === null || _f === void 0 ? void 0 : _f.length;
            this.formStructure.currentStep = tempStep;
            this.backButtonContexts.forEach(backButton => forceUpdate(backButton));
            this.rerender({ rerenderAll: true });
        };
        this.submitForm = async () => {
            var _a, _b, _c, _d, _e, _f;
            try {
                this.isSubmitted = true;
                this.context.isLoading = true;
                this.signal({ isError: false, disabled: true });
                const formObject = Object.assign(Object.assign({}, this.cachedValues), this.getValues());
                const description = this.schemaObject.describe();
                Object.entries(description.fields)
                    .filter(([_, desc]) => desc.type === 'boolean')
                    .forEach(([name]) => {
                    // @ts-ignore
                    formObject[name] = typeof formObject[name] === 'string' && formObject[name] === 'true';
                });
                const excludedFields = Object.keys(this.schemaObject.fields).filter(key => !this.hasItemInStructure(key));
                const values = await this.schemaObject.omit(excludedFields).validate(formObject, { abortEarly: false, context: this.requiredContext });
                if ((_b = (_a = this.context) === null || _a === void 0 ? void 0 : _a.structure) === null || _b === void 0 ? void 0 : _b.steps) {
                    this.stepFormValues = Object.assign(Object.assign({}, this === null || this === void 0 ? void 0 : this.stepFormValues), values);
                    if (((_e = (_d = (_c = this.context) === null || _c === void 0 ? void 0 : _c.structure) === null || _d === void 0 ? void 0 : _d.steps) === null || _e === void 0 ? void 0 : _e.length) === ((_f = this.formStructure) === null || _f === void 0 ? void 0 : _f.currentStep))
                        await this.context.formSubmit(this.stepFormValues);
                    else
                        this.updateStep(1);
                }
                else
                    await this.context.formSubmit(values);
            }
            catch (error) {
                if (error.name === 'ValidationError') {
                    this.formErrors = {};
                    const errorFields = [];
                    error.inner.forEach((element) => {
                        if (element.path) {
                            this.formErrors[element.path] = element.message;
                            if (!errorFields.find(field => field.name === element.path)) {
                                errorFields.push({
                                    isError: true,
                                    name: element.path,
                                    errorMessage: element.message,
                                });
                            }
                        }
                    });
                    console.log(this.formErrors);
                    this.signal(errorFields);
                    this.focusFirstInput(errorFields);
                    this.rerender({ rerenderAll: true, rerenderForm: true });
                }
                else
                    console.error('Unexpected Error:', error);
            }
            finally {
                this.signal({ disabled: false });
                this.context.isLoading = false;
            }
        };
        this.onSubmit = (formEvent) => {
            formEvent.preventDefault();
            this.submitForm();
        };
        /**
         * Manually trigger form submission programmatically.
         * Works the same as clicking the submit button or pressing Enter.
         */
        this.submit = () => {
            this.submitForm();
        };
        this.getRequiredContext = () => {
            var _a, _b;
            const tempObject = {};
            try {
                if ((_a = this.context.structure) === null || _a === void 0 ? void 0 : _a.requiredContext) {
                    Object.entries((_b = this.context.structure) === null || _b === void 0 ? void 0 : _b.requiredContext).forEach(([key, value]) => {
                        tempObject[`${key}Required`] = value;
                    });
                    return tempObject;
                }
                return tempObject;
            }
            catch (error) {
                return tempObject;
            }
        };
        this.getInputState = (name) => {
            const resolvedSchema = this.schemaObject.resolve({
                context: this.requiredContext,
            });
            const validationDescription = resolvedSchema.describe().fields[name];
            let isRequired = false;
            try {
                resolvedSchema.validateSyncAt(name, { [name]: undefined }, { context: this.requiredContext });
            }
            catch (error) {
                isRequired = true;
            }
            if (!this.subscribedFields[name])
                this.subscribedFields[name] = {
                    name,
                    isRequired,
                    isError: false,
                    disabled: false,
                    errorMessage: '',
                    continuousValidation: false,
                    meta: validationDescription === null || validationDescription === void 0 ? void 0 : validationDescription.meta,
                };
            return this.subscribedFields[name];
        };
        this.signal = (partialSignal) => {
            if (Array.isArray(partialSignal)) {
                partialSignal.forEach(field => {
                    if (this.subscribedFields[field.name])
                        Object.assign(this.subscribedFields[field.name], field);
                });
            }
            else {
                Object.values(this.subscribedFields).forEach(field => Object.assign(field, partialSignal));
            }
        };
        this.validateInput = (name) => {
            const value = (this.getValues()[name] || '');
            return this.validateForm(name, value, false);
        };
        this.rerender = ({ inputName, rerenderAll, rerenderForm }) => {
            var _a;
            if (rerenderForm) {
                forceUpdate(this.context);
                if (this === null || this === void 0 ? void 0 : this.formStructure)
                    forceUpdate(this === null || this === void 0 ? void 0 : this.formStructure);
            }
            if (rerenderAll) {
                this.subscribers.forEach(sub => forceUpdate(sub.context));
            }
            else if (inputName) {
                const ctx = (_a = this.subscribers.find(sub => sub.name === inputName)) === null || _a === void 0 ? void 0 : _a.context;
                forceUpdate(ctx);
            }
        };
        this.validateForm = (name, value, strict = true) => {
            var _a, _b;
            setTimeout(() => {
                const values = Object.assign(Object.assign({}, this.getValues()), { [name]: value });
                const watch = this.watchers[name];
                if (watch)
                    watch({ form: this, values });
                if (this.watchers['all'])
                    this.watchers['all']({ form: this, values });
            }, 50);
            if (strict) {
                if (this.haltValidation)
                    return;
                if (!this.isSubmitted && this.validationType !== 'always' && !((_a = this.subscribedFields[name]) === null || _a === void 0 ? void 0 : _a.continuousValidation))
                    return;
            }
            const wasError = (_b = this.subscribedFields[name]) === null || _b === void 0 ? void 0 : _b.isError;
            try {
                // @ts-ignore
                this.schemaObject.fields[name].validateSync(value, { context: this.requiredContext });
                this.signal([{ name, isError: false }]);
                if (wasError !== false)
                    this.rerender({ inputName: name || '', rerenderForm: true });
                return { isError: false, errorMessage: '' };
            }
            catch (error) {
                if (error.message) {
                    this.signal([{ name, isError: true, errorMessage: error.message }]);
                    this.rerender({ inputName: name || '', rerenderForm: true });
                    return { isError: true, errorMessage: error.message };
                }
            }
            finally {
                try {
                    this.subscribedFields[name].continuousValidation = true;
                }
                catch (error) { }
            }
        };
        this.context = context;
        this.schemaObject = schemaObject;
        this.basicSchemaObject = schemaObject;
        this.formController = { onSubmit: this.onSubmit, onInput: this.onInput };
        if (formStateOptions === null || formStateOptions === void 0 ? void 0 : formStateOptions.validationType)
            this.validationType = formStateOptions.validationType;
        this.requiredContext = this.getRequiredContext();
    }
    reset() {
        this.haltValidation = true;
        this.updateStep(1, true);
        this.stepFormValues = {};
        this.subscribers.forEach(subscriber => {
            var _a, _b;
            if ((_a = subscriber.context) === null || _a === void 0 ? void 0 : _a.reset)
                (_b = subscriber.context) === null || _b === void 0 ? void 0 : _b.reset();
        });
        this.pendingRequests = {};
        this.isSubmitted = false;
        this.signal({ isError: false, disabled: false });
        setTimeout(() => {
            this.haltValidation = false;
            setTimeout(() => {
                this.rerender({ rerenderForm: true, rerenderAll: true });
            }, 50);
        }, 50);
    }
}
