import HTMLElement from '../html-element/HTMLElement.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import Event from '../../event/Event.js';
import SubmitEvent from '../../event/events/SubmitEvent.js';
import HTMLFormControlsCollection from './HTMLFormControlsCollection.js';
import BrowserFrameNavigator from '../../browser/utilities/BrowserFrameNavigator.js';
import QuerySelector from '../../query-selector/QuerySelector.js';
import RadioNodeList from './RadioNodeList.js';
import WindowBrowserContext from '../../window/WindowBrowserContext.js';
import ClassMethodBinder from '../../utilities/ClassMethodBinder.js';
import Node from '../node/Node.js';
import Element from '../element/Element.js';
import EventTarget from '../../event/EventTarget.js';
import ElementEventAttributeUtility from '../element/ElementEventAttributeUtility.js';
/**
 * HTML Form Element.
 *
 * Reference:
 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement.
 */
export default class HTMLFormElement extends HTMLElement {
    // Internal properties.
    [PropertySymbol.elements] = null;
    [PropertySymbol.proxy];
    /* eslint-enable jsdoc/require-jsdoc */
    /**
     * Constructor.
     */
    constructor() {
        super();
        const methodBinder = new ClassMethodBinder(this, [
            HTMLFormElement,
            HTMLElement,
            Element,
            Node,
            EventTarget
        ]);
        const proxy = new Proxy(this, {
            get: (target, property) => {
                if (property === 'length') {
                    return target[PropertySymbol.getFormControlItems]().length;
                }
                if (property in target || typeof property === 'symbol') {
                    methodBinder.bind(property);
                    return target[property];
                }
                const index = Number(property);
                if (!isNaN(index)) {
                    return target[PropertySymbol.getFormControlItems]()[index];
                }
                return target[PropertySymbol.getFormControlNamedItem](property) || undefined;
            },
            set(target, property, newValue) {
                methodBinder.bind(property);
                if (typeof property === 'symbol') {
                    target[property] = newValue;
                    return true;
                }
                const index = Number(property);
                if (isNaN(index)) {
                    target[property] = newValue;
                }
                return true;
            },
            deleteProperty(target, property) {
                if (typeof property === 'symbol') {
                    delete target[property];
                    return true;
                }
                const index = Number(property);
                if (isNaN(index)) {
                    delete target[property];
                }
                return true;
            },
            ownKeys(target) {
                return Object.keys(target[PropertySymbol.getFormControlItems]());
            },
            has(target, property) {
                if (property in target) {
                    return true;
                }
                if (typeof property === 'symbol') {
                    return false;
                }
                const items = target[PropertySymbol.getFormControlItems]();
                const index = Number(property);
                if (!isNaN(index) && index >= 0 && index < items.length) {
                    return true;
                }
                property = String(property);
                for (let i = 0; i < items.length; i++) {
                    const item = items[i];
                    const name = item.getAttribute('id') || item.getAttribute('name');
                    if (name && name === property) {
                        return true;
                    }
                }
                return false;
            },
            defineProperty(target, property, descriptor) {
                methodBinder.preventBinding(property);
                if (!descriptor.value) {
                    Object.defineProperty(target, property, descriptor);
                    return true;
                }
                const index = Number(descriptor.value);
                if (isNaN(index)) {
                    Object.defineProperty(target, property, descriptor);
                    return true;
                }
                return false;
            },
            getOwnPropertyDescriptor(target, property) {
                if (property in target) {
                    return Object.getOwnPropertyDescriptor(target, property);
                }
                const items = target[PropertySymbol.getFormControlItems]();
                const index = Number(property);
                if (!isNaN(index) && index >= 0 && index < items.length) {
                    return {
                        value: items[index],
                        writable: false,
                        enumerable: true,
                        configurable: true
                    };
                }
                for (let i = 0; i < items.length; i++) {
                    const item = items[i];
                    const name = item.getAttribute('id') || item.getAttribute('name');
                    if (name && name === property) {
                        return {
                            value: item,
                            writable: false,
                            enumerable: true,
                            configurable: true
                        };
                    }
                }
            }
        });
        this[PropertySymbol.proxy] = proxy;
        this[PropertySymbol.formNode] = proxy;
        return proxy;
    }
    // Events
    /* eslint-disable jsdoc/require-jsdoc */
    get onformdata() {
        return ElementEventAttributeUtility.getEventListener(this, 'onformdata');
    }
    set onformdata(value) {
        this[PropertySymbol.propertyEventListeners].set('onformdata', value);
    }
    get onreset() {
        return ElementEventAttributeUtility.getEventListener(this, 'onreset');
    }
    set onreset(value) {
        this[PropertySymbol.propertyEventListeners].set('onreset', value);
    }
    get onsubmit() {
        return ElementEventAttributeUtility.getEventListener(this, 'onsubmit');
    }
    set onsubmit(value) {
        this[PropertySymbol.propertyEventListeners].set('onsubmit', value);
    }
    /**
     * Returns elements.
     *
     * @returns Elements.
     */
    get elements() {
        if (!this[PropertySymbol.elements]) {
            this[PropertySymbol.elements] = new HTMLFormControlsCollection(PropertySymbol.illegalConstructor, this);
        }
        return this[PropertySymbol.elements];
    }
    /**
     * Returns length.
     *
     * @returns Length.
     */
    get length() {
        return this[PropertySymbol.getFormControlItems]().length;
    }
    /**
     * Returns name.
     *
     * @returns Name.
     */
    get name() {
        return this.getAttribute('name') || '';
    }
    /**
     * Sets name.
     *
     * @param name Name.
     */
    set name(name) {
        this.setAttribute('name', name);
    }
    /**
     * Returns method.
     *
     * @returns Method.
     */
    get method() {
        return this.getAttribute('method') || 'get';
    }
    /**
     * Sets method.
     *
     * @param method Method.
     */
    set method(method) {
        this.setAttribute('method', method);
    }
    /**
     * Returns target.
     *
     * @returns Target.
     */
    get target() {
        return this.getAttribute('target') || '';
    }
    /**
     * Sets target.
     *
     * @param target Target.
     */
    set target(target) {
        this.setAttribute('target', target);
    }
    /**
     * Returns action.
     *
     * @returns Action.
     */
    get action() {
        if (!this.hasAttribute('action')) {
            return this[PropertySymbol.ownerDocument].location.href;
        }
        try {
            return new URL(this.getAttribute('action'), this[PropertySymbol.ownerDocument].location.href)
                .href;
        }
        catch (e) {
            return '';
        }
    }
    /**
     * Sets action.
     *
     * @param action Action.
     */
    set action(action) {
        this.setAttribute('action', action);
    }
    /**
     * Returns encoding.
     *
     * @returns Encoding.
     */
    get encoding() {
        return this.getAttribute('encoding') || '';
    }
    /**
     * Sets encoding.
     *
     * @param encoding Encoding.
     */
    set encoding(encoding) {
        this.setAttribute('encoding', encoding);
    }
    /**
     * Returns enctype.
     *
     * @returns Enctype.
     */
    get enctype() {
        return this.getAttribute('enctype') || '';
    }
    /**
     * Sets enctype.
     *
     * @param enctype Enctype.
     */
    set enctype(enctype) {
        this.setAttribute('enctype', enctype);
    }
    /**
     * Returns autocomplete.
     *
     * @returns Autocomplete.
     */
    get autocomplete() {
        return this.getAttribute('autocomplete') || '';
    }
    /**
     * Sets autocomplete.
     *
     * @param autocomplete Autocomplete.
     */
    set autocomplete(autocomplete) {
        this.setAttribute('autocomplete', autocomplete);
    }
    /**
     * Returns accept charset.
     *
     * @returns Accept charset.
     */
    get acceptCharset() {
        return this.getAttribute('acceptcharset') || '';
    }
    /**
     * Sets accept charset.
     *
     * @param acceptCharset Accept charset.
     */
    set acceptCharset(acceptCharset) {
        this.setAttribute('acceptcharset', acceptCharset);
    }
    /**
     * Returns no validate.
     *
     * @returns No validate.
     */
    get noValidate() {
        return this.getAttribute('novalidate') !== null;
    }
    /**
     * Sets no validate.
     *
     * @param noValidate No validate.
     */
    set noValidate(noValidate) {
        if (!noValidate) {
            this.removeAttribute('novalidate');
        }
        else {
            this.setAttribute('novalidate', '');
        }
    }
    /**
     * Submits form. No submit event is raised. In particular, the form's "submit" event handler is not run.
     */
    submit() {
        this.#submit();
    }
    /**
     * Submits form, reports validity and raises submit event.
     *
     * @param [submitter] Submitter.
     */
    requestSubmit(submitter) {
        const noValidate = submitter?.formNoValidate || this.noValidate;
        if (noValidate || this.checkValidity()) {
            const event = new SubmitEvent('submit', {
                bubbles: true,
                cancelable: true,
                submitter: submitter || this[PropertySymbol.proxy]
            });
            this.dispatchEvent(event);
            if (!event.defaultPrevented) {
                this.#submit(submitter);
            }
        }
    }
    /**
     * Resets form.
     */
    reset() {
        for (const element of this[PropertySymbol.getFormControlItems]()) {
            switch (element[PropertySymbol.tagName]) {
                case 'TEXTAREA':
                    element[PropertySymbol.value] = null;
                    break;
                case 'INPUT':
                    element[PropertySymbol.value] = null;
                    element[PropertySymbol.checked] = null;
                    break;
                case 'OUTPUT':
                    element.textContent = element[PropertySymbol.defaultValue];
                    break;
                case 'SELECT':
                    let hasSelectedAttribute = false;
                    for (const option of element.options) {
                        if (option.hasAttribute('selected')) {
                            hasSelectedAttribute = true;
                            option.selected = true;
                            break;
                        }
                    }
                    if (!hasSelectedAttribute && element.options.length > 0) {
                        element.options[0].selected = true;
                    }
                    break;
            }
        }
        this.dispatchEvent(new Event('reset', { bubbles: true, cancelable: true }));
    }
    /**
     * Checks validity.
     *
     * @returns "true" if validation does'nt fail.
     */
    checkValidity() {
        const radioValidationState = {};
        let isFormValid = true;
        for (const element of this[PropertySymbol.getFormControlItems]()) {
            if (element[PropertySymbol.tagName] === 'INPUT' && element.type === 'radio' && element.name) {
                if (!radioValidationState[element.name]) {
                    radioValidationState[element.name] = true;
                    if (!element.checkValidity()) {
                        isFormValid = false;
                    }
                }
            }
            else if (!element.checkValidity()) {
                isFormValid = false;
            }
        }
        return isFormValid;
    }
    /**
     * Reports validity.
     *
     * @returns "true" if validation does'nt fail.
     */
    reportValidity() {
        return this.checkValidity();
    }
    /**
     * @override
     */
    [PropertySymbol.cloneNode](deep = false) {
        return super[PropertySymbol.cloneNode](deep);
    }
    /**
     * Returns form control items.
     *
     * @returns Form control items.
     */
    [PropertySymbol.getFormControlItems]() {
        const elements = (QuerySelector.querySelectorAll(this, 'input,select,textarea,button,fieldset,object,output')[PropertySymbol.items].slice());
        if (this[PropertySymbol.isConnected]) {
            const id = this.getAttribute('id');
            if (id) {
                for (const element of (QuerySelector.querySelectorAll(this[PropertySymbol.ownerDocument], `input[form="${id}"],select[form="${id}"],textarea[form="${id}"],button[form="${id}"],fieldset[form="${id}"],object[form="${id}"],output[form="${id}"]`)[PropertySymbol.items])) {
                    if (!elements.includes(element)) {
                        elements.push(element);
                    }
                }
            }
        }
        return elements;
    }
    /**
     * Returns form control named item.
     *
     * @param name
     * @returns Form control named item.
     */
    [PropertySymbol.getFormControlNamedItem](name) {
        const items = this[PropertySymbol.getFormControlItems]();
        const namedItems = [];
        name = String(name);
        for (const item of items) {
            if (item.getAttribute('id') === name || item.getAttribute('name') === name) {
                namedItems.push(item);
            }
        }
        if (!namedItems.length) {
            return null;
        }
        if (namedItems.length === 1) {
            return namedItems[0];
        }
        return new RadioNodeList(PropertySymbol.illegalConstructor, namedItems);
    }
    /**
     * Submits form.
     *
     * @param browserFrame Browser frame. Injected by the constructor.
     * @param [submitter] Submitter.
     */
    #submit(submitter) {
        const method = submitter?.formMethod || this.method;
        if (method === 'dialog') {
            let dialog = null;
            let parent = this;
            while (parent) {
                if (parent[PropertySymbol.tagName] === 'DIALOG') {
                    dialog = parent;
                    break;
                }
                parent = parent.parentElement;
            }
            if (dialog) {
                dialog.close(submitter?.value);
                return;
            }
        }
        const action = submitter?.hasAttribute('formaction')
            ? submitter?.formAction || this.action
            : this.action;
        const browserFrame = new WindowBrowserContext(this[PropertySymbol.window]).getBrowserFrame();
        if (!browserFrame) {
            return;
        }
        if (!action) {
            // The URL is invalid when the action is empty.
            // This is what Chrome does when the URL is invalid.
            this[PropertySymbol.ownerDocument].location.hash = '#blocked';
            return;
        }
        const formData = new this[PropertySymbol.window].FormData(this, submitter);
        let targetFrame;
        switch (submitter?.formTarget || this.target) {
            default:
            case '_self':
                targetFrame = browserFrame;
                break;
            case '_top':
                targetFrame = browserFrame.page.mainFrame;
                break;
            case '_parent':
                targetFrame = browserFrame.parentFrame ?? browserFrame;
                break;
            case '_blank':
                const newPage = browserFrame.page.context.newPage();
                targetFrame = newPage.mainFrame;
                targetFrame[PropertySymbol.openerFrame] = browserFrame;
                break;
        }
        if (method === 'get') {
            const url = new URL(action);
            url.search = '';
            for (const [key, value] of formData) {
                if (typeof value === 'string') {
                    url.searchParams.append(key, value);
                }
            }
            BrowserFrameNavigator.navigate({
                windowClass: (this[PropertySymbol.ownerDocument][PropertySymbol.defaultView]?.constructor),
                frame: targetFrame,
                url: url.href,
                goToOptions: {
                    referrer: browserFrame.page.mainFrame.window.location.origin
                }
            });
            return;
        }
        BrowserFrameNavigator.navigate({
            windowClass: (this[PropertySymbol.ownerDocument][PropertySymbol.defaultView]?.constructor),
            frame: targetFrame,
            method: method,
            url: action,
            formData,
            goToOptions: {
                referrer: browserFrame.page.mainFrame.window.location.origin
            }
        });
    }
}
//# sourceMappingURL=HTMLFormElement.js.map