var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { isServer } from 'lit';
import { property } from 'lit/decorators.js';
import BaseElement from './base-element.js';
import { CustomErrorValidator } from './custom-error-validator.js';
import { WInvalidEvent } from './invalid';
// setFormValue omitted so that we can use `setValue`
export class BaseFormAssociatedElement extends BaseElement {
    static { this.formAssociated = true; }
    /**
     * Validators are static because they have `observedAttributes`, essentially attributes to "watch"
     * for changes. Whenever these attributes change, we want to be notified and update the validator.
     */
    static get validators() {
        return [CustomErrorValidator()];
    }
    // Append all Validator "observedAttributes" into the "observedAttributes" so they can run.
    static get observedAttributes() {
        const parentAttrs = new Set(super.observedAttributes || []);
        for (const validator of this.validators) {
            if (!validator.observedAttributes) {
                continue;
            }
            for (const attr of validator.observedAttributes) {
                parentAttrs.add(attr);
            }
        }
        return [...parentAttrs];
    }
    constructor() {
        super();
        // Form attributes
        /** The name of the input, submitted as a name/value pair with form data. */
        this.name = null;
        /** Disables the form control. */
        this.disabled = false;
        this.required = false;
        this.assumeInteractionOn = ['input'];
        this.validators = [];
        // Should these be private?
        this.valueHasChanged = false;
        this.hasInteracted = false;
        // This works around a limitation in Safari. It is a hacky way for us to preserve custom errors generated by the user.
        this.customError = null;
        this.emittedEvents = [];
        this.emitInvalid = (e) => {
            if (e.target !== this)
                return;
            // An "invalid" event counts as interacted, this is usually triggered by a button "submitting"
            this.hasInteracted = true;
            this.dispatchEvent(new WInvalidEvent());
        };
        this.handleInteraction = (event) => {
            const emittedEvents = this.emittedEvents;
            if (!emittedEvents.includes(event.type)) {
                emittedEvents.push(event.type);
            }
            // Mark it as user-interacted as soon as all associated events have been emitted
            if (emittedEvents.length === this.assumeInteractionOn?.length) {
                this.hasInteracted = true;
            }
        };
        if (!isServer) {
            this.addEventListener('invalid', this.emitInvalid);
        }
    }
    connectedCallback() {
        super.connectedCallback();
        this.updateValidity();
        // Lazily evaluate after the constructor to allow people to override the `assumeInteractionOn`
        this.assumeInteractionOn.forEach((event) => {
            this.addEventListener(event, this.handleInteraction);
        });
    }
    firstUpdated(...args) {
        super.firstUpdated(...args);
        this.updateValidity();
    }
    willUpdate(changedProperties) {
        if (!isServer && changedProperties.has('customError')) {
            // We use null because it we really don't want it to show up in the attributes because `custom-error` does reflect
            if (!this.customError) {
                this.customError = null;
            }
            this.setCustomValidity(this.customError || '');
        }
        if (changedProperties.has('value') || changedProperties.has('disabled')) {
            // @ts-expect-error Some components will use an accessors, other use a property, so we don't want to limit them.
            const value = this.value;
            // Accounts for the snowflake case on `<wa-select>`
            if (Array.isArray(value)) {
                if (this.name) {
                    const formData = new FormData();
                    for (const val of value) {
                        formData.append(this.name, val);
                    }
                    this.setValue(formData, formData);
                }
            }
            else {
                this.setValue(value, value);
            }
        }
        if (changedProperties.has('disabled')) {
            this.customStates.set('disabled', this.disabled);
            if (this.hasAttribute('disabled') || (!isServer && !this.matches(':disabled'))) {
                this.toggleAttribute('disabled', this.disabled);
            }
        }
        this.updateValidity();
        super.willUpdate(changedProperties);
    }
    get labels() {
        return this.internals.labels;
    }
    getForm() {
        return this.internals.form;
    }
    get validity() {
        return this.internals.validity;
    }
    // Not sure if this supports `novalidate`. Will need to test.
    get willValidate() {
        return this.internals.willValidate;
    }
    get validationMessage() {
        return this.internals.validationMessage;
    }
    checkValidity() {
        this.updateValidity();
        return this.internals.checkValidity();
    }
    reportValidity() {
        this.updateValidity();
        // This seems reasonable. `reportValidity()` is kind of like "we expect you to have interacted"
        this.hasInteracted = true;
        return this.internals.reportValidity();
    }
    /**
     * Override this to change where constraint validation popups are anchored.
     */
    get validationTarget() {
        return (this.input || undefined);
    }
    setValidity(...args) {
        const flags = args[0];
        const message = args[1];
        let anchor = args[2];
        if (!anchor) {
            anchor = this.validationTarget;
        }
        this.internals.setValidity(flags, message, anchor || undefined);
        this.requestUpdate('validity');
        this.setCustomStates();
    }
    setCustomStates() {
        const required = Boolean(this.required);
        const isValid = this.internals.validity.valid;
        const hasInteracted = this.hasInteracted;
        this.customStates.set('required', required);
        this.customStates.set('optional', !required);
        this.customStates.set('invalid', !isValid);
        this.customStates.set('valid', isValid);
        this.customStates.set('user-invalid', !isValid && hasInteracted);
        this.customStates.set('user-valid', isValid && hasInteracted);
    }
    /**
     * Do not use this when creating a "Validator". This is intended for end users of components.
     * We track manually defined custom errors so we don't clear them on accident in our validators.
     *
     */
    setCustomValidity(message) {
        if (!message) {
            // We use null because it we really don't want it to show up in the attributes because `custom-error` does reflect
            this.customError = null;
            this.setValidity({});
            return;
        }
        this.customError = message;
        this.setValidity({ customError: true }, message, this.validationTarget);
    }
    formResetCallback() {
        this.resetValidity();
        this.hasInteracted = false;
        this.valueHasChanged = false;
        this.emittedEvents = [];
        this.updateValidity();
    }
    formDisabledCallback(isDisabled) {
        this.disabled = isDisabled;
        this.updateValidity();
    }
    /**
     * Called when the browser is trying to restore element’s state to state in which case reason is "restore", or when
     * the browser is trying to fulfill autofill on behalf of user in which case reason is "autocomplete". In the case of
     * "restore", state is a string, File, or FormData object previously set as the second argument to setFormValue.
     */
    formStateRestoreCallback(state, reason) {
        // @ts-expect-error We purposely do not have a value property. It makes things hard to extend.
        this.value = state;
        if (reason === 'restore') {
            this.resetValidity();
        }
        this.updateValidity();
    }
    setValue(...args) {
        const [value, state] = args;
        this.internals.setFormValue(value, state);
    }
    get allValidators() {
        const staticValidators = this.constructor.validators || [];
        const validators = this.validators || [];
        return [...staticValidators, ...validators];
    }
    /**
     * Reset validity is a way of removing manual custom errors and native validation.
     */
    resetValidity() {
        this.setCustomValidity('');
        this.setValidity({});
    }
    updateValidity() {
        if (this.disabled ||
            this.hasAttribute('disabled') ||
            !this.willValidate //
        ) {
            this.resetValidity();
            return;
        }
        const validators = this.allValidators;
        if (!validators?.length) {
            // If there's no validators, we do nothing. We also don't want to mess with custom errors, so we just stop here.
            return;
        }
        const flags = {
            // Don't trust custom errors from the Browser. Safari breaks the spec.
            customError: Boolean(this.customError),
        };
        const formControl = this.validationTarget || this.input || undefined;
        let finalMessage = '';
        for (const validator of validators) {
            const { isValid, message, invalidKeys } = validator.checkValidity(this);
            if (isValid) {
                continue;
            }
            if (!finalMessage) {
                finalMessage = message;
            }
            if (invalidKeys?.length >= 0) {
                invalidKeys.forEach((str) => (flags[str] = true));
            }
        }
        // This is a workaround for preserving custom errors
        if (!finalMessage) {
            finalMessage = this.validationMessage;
        }
        this.setValidity(flags, finalMessage, formControl);
    }
}
__decorate([
    property({ reflect: true })
], BaseFormAssociatedElement.prototype, "name", void 0);
__decorate([
    property({ type: Boolean })
], BaseFormAssociatedElement.prototype, "disabled", void 0);
__decorate([
    property({ state: true, attribute: false })
], BaseFormAssociatedElement.prototype, "valueHasChanged", void 0);
__decorate([
    property({ state: true, attribute: false })
], BaseFormAssociatedElement.prototype, "hasInteracted", void 0);
__decorate([
    property({ attribute: 'custom-error', reflect: true })
], BaseFormAssociatedElement.prototype, "customError", void 0);
__decorate([
    property({ attribute: false, state: true, type: Object })
], BaseFormAssociatedElement.prototype, "validity", null);
