"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const HTMLElement_js_1 = __importDefault(require("../html-element/HTMLElement.cjs"));
const PropertySymbol = __importStar(require("../../PropertySymbol.cjs"));
const ValidityState_js_1 = __importDefault(require("../../validity-state/ValidityState.cjs"));
const HTMLOptionElement_js_1 = __importDefault(require("../html-option-element/HTMLOptionElement.cjs"));
const HTMLOptionsCollection_js_1 = __importDefault(require("./HTMLOptionsCollection.cjs"));
const Event_js_1 = __importDefault(require("../../event/Event.cjs"));
const HTMLLabelElementUtility_js_1 = __importDefault(require("../html-label-element/HTMLLabelElementUtility.cjs"));
const HTMLCollection_js_1 = __importDefault(require("../element/HTMLCollection.cjs"));
const NodeTypeEnum_js_1 = __importDefault(require("../node/NodeTypeEnum.cjs"));
const QuerySelector_js_1 = __importDefault(require("../../query-selector/QuerySelector.cjs"));
const ClassMethodBinder_js_1 = __importDefault(require("../../ClassMethodBinder.cjs"));
const Node_js_1 = __importDefault(require("../node/Node.cjs"));
const Element_js_1 = __importDefault(require("../element/Element.cjs"));
const EventTarget_js_1 = __importDefault(require("../../event/EventTarget.cjs"));
/**
 * HTML Select Element.
 *
 * Reference:
 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement.
 */
class HTMLSelectElement extends HTMLElement_js_1.default {
    // Internal properties.
    [PropertySymbol.validationMessage] = '';
    [PropertySymbol.validity] = new ValidityState_js_1.default(this);
    [PropertySymbol.options] = null;
    [PropertySymbol.selectedOptions] = null;
    [PropertySymbol.selectedIndex] = -1;
    [PropertySymbol.proxy];
    // Events
    onchange = null;
    oninput = null;
    /**
     * Constructor.
     */
    constructor() {
        super();
        const methodBinder = new ClassMethodBinder_js_1.default(this, [
            HTMLSelectElement,
            HTMLElement_js_1.default,
            Element_js_1.default,
            Node_js_1.default,
            EventTarget_js_1.default
        ]);
        const proxy = new Proxy(this, {
            get: (target, property) => {
                if (property in target || typeof property === 'symbol') {
                    methodBinder.bind(property);
                    return target[property];
                }
                const index = Number(property);
                if (!isNaN(index)) {
                    return QuerySelector_js_1.default.querySelectorAll(target, 'option')[PropertySymbol.items][index];
                }
            },
            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;
                }
                if (!newValue || !(newValue instanceof HTMLOptionElement_js_1.default)) {
                    throw new this[PropertySymbol.window].Error(`TypeError: Failed to set an indexed property [${index}] on 'HTMLSelectElement': parameter 2 is not of type 'HTMLOptionElement'.`);
                }
                const options = QuerySelector_js_1.default.querySelectorAll(target, 'option')[PropertySymbol.items];
                const childNodes = target[PropertySymbol.nodeArray];
                while (childNodes.length) {
                    target[PropertySymbol.removeChild](childNodes[0]);
                }
                for (let i = 0; i <= index; i++) {
                    if (i === index) {
                        target[PropertySymbol.appendChild](newValue);
                    }
                    else if (options[i]) {
                        target[PropertySymbol.appendChild](options[i]);
                    }
                    else {
                        target[PropertySymbol.appendChild](target[PropertySymbol.ownerDocument].createElement('option'));
                    }
                }
                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(QuerySelector_js_1.default.querySelectorAll(target, 'option')[PropertySymbol.items]);
            },
            has(target, property) {
                if (property in target) {
                    return true;
                }
                if (typeof property === 'symbol') {
                    return false;
                }
                const index = Number(property);
                if (!isNaN(index)) {
                    return !!QuerySelector_js_1.default.querySelectorAll(target, 'option')[PropertySymbol.items][index];
                }
                return false;
            },
            defineProperty(target, property, descriptor) {
                methodBinder.preventBinding(property);
                const index = Number(property);
                if (isNaN(index)) {
                    Object.defineProperty(target, property, descriptor);
                    return true;
                }
                if (!descriptor.value || !(descriptor.value instanceof HTMLOptionElement_js_1.default)) {
                    throw new this[PropertySymbol.window].Error(`TypeError: Failed to set an indexed property [${index}] on 'HTMLSelectElement': parameter 2 is not of type 'HTMLOptionElement'.`);
                }
                const options = QuerySelector_js_1.default.querySelectorAll(target, 'option')[PropertySymbol.items];
                const childNodes = target[PropertySymbol.nodeArray];
                while (childNodes.length) {
                    target[PropertySymbol.removeChild](childNodes[0]);
                }
                for (let i = 0; i <= index; i++) {
                    if (i === index) {
                        target[PropertySymbol.appendChild](descriptor.value);
                    }
                    else if (options[i]) {
                        target[PropertySymbol.appendChild](options[i]);
                    }
                    else {
                        target[PropertySymbol.appendChild](target[PropertySymbol.ownerDocument].createElement('option'));
                    }
                }
                return true;
            },
            getOwnPropertyDescriptor(target, property) {
                if (property in target) {
                    return Object.getOwnPropertyDescriptor(target, property);
                }
                const index = Number(property);
                if (isNaN(index)) {
                    return;
                }
                const options = QuerySelector_js_1.default.querySelectorAll(target, 'option')[PropertySymbol.items];
                if (!options[index]) {
                    return;
                }
                return {
                    value: options[index],
                    writable: true,
                    enumerable: true,
                    configurable: true
                };
            }
        });
        this[PropertySymbol.proxy] = proxy;
        this[PropertySymbol.selectNode] = proxy;
        return proxy;
    }
    /**
     * Returns length.
     *
     * @returns Length.
     */
    get length() {
        return QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items].length;
    }
    /**
     * Returns options.
     *
     * @returns Options.
     */
    get options() {
        if (!this[PropertySymbol.options]) {
            this[PropertySymbol.options] = new HTMLOptionsCollection_js_1.default(PropertySymbol.illegalConstructor, this);
        }
        return this[PropertySymbol.options];
    }
    /**
     * Returns validation message.
     *
     * @returns Validation message.
     */
    get validationMessage() {
        return this[PropertySymbol.validationMessage];
    }
    /**
     * Returns validity.
     *
     * @returns Validity.
     */
    get validity() {
        return this[PropertySymbol.validity];
    }
    /**
     * Returns name.
     *
     * @returns Name.
     */
    get name() {
        return this.getAttribute('name') || '';
    }
    /**
     * Sets name.
     *
     * @param name Name.
     */
    set name(name) {
        this.setAttribute('name', name);
    }
    /**
     * Returns disabled.
     *
     * @returns Disabled.
     */
    get disabled() {
        return this.getAttribute('disabled') !== null;
    }
    /**
     * Sets disabled.
     *
     * @param disabled Disabled.
     */
    set disabled(disabled) {
        if (!disabled) {
            this.removeAttribute('disabled');
        }
        else {
            this.setAttribute('disabled', '');
        }
    }
    /**
     * Returns multiple.
     *
     * @returns Multiple.
     */
    get multiple() {
        return this.getAttribute('multiple') !== null;
    }
    /**
     * Sets multiple.
     *
     * @param multiple Multiple.
     */
    set multiple(multiple) {
        if (!multiple) {
            this.removeAttribute('multiple');
        }
        else {
            this.setAttribute('multiple', '');
        }
    }
    /**
     * Returns autofocus.
     *
     * @returns Autofocus.
     */
    get autofocus() {
        return this.getAttribute('autofocus') !== null;
    }
    /**
     * Sets autofocus.
     *
     * @param autofocus Autofocus.
     */
    set autofocus(autofocus) {
        if (!autofocus) {
            this.removeAttribute('autofocus');
        }
        else {
            this.setAttribute('autofocus', '');
        }
    }
    /**
     * Returns required.
     *
     * @returns Required.
     */
    get required() {
        return this.getAttribute('required') !== null;
    }
    /**
     * Sets required.
     *
     * @param required Required.
     */
    set required(required) {
        if (!required) {
            this.removeAttribute('required');
        }
        else {
            this.setAttribute('required', '');
        }
    }
    /**
     * Returns type.
     *
     * @returns type.
     */
    get type() {
        return this.hasAttributeNS(null, 'multiple') ? 'select-multiple' : 'select-one';
    }
    /**
     * Returns value.
     *
     * @returns Value.
     */
    get value() {
        const options = QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items];
        for (let i = 0, max = options.length; i < max; i++) {
            const option = options[i];
            if (option[PropertySymbol.selectedness]) {
                return option.value;
            }
        }
        return '';
    }
    /**
     * Sets value.
     *
     * @param value Value.
     */
    set value(value) {
        const options = QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items];
        this[PropertySymbol.selectedIndex] = -1;
        for (let i = 0, max = options.length; i < max; i++) {
            const option = options[i];
            if (option.value === value) {
                option[PropertySymbol.selectedness] = true;
                option[PropertySymbol.dirtyness] = true;
                this[PropertySymbol.selectedIndex] = i;
            }
            else {
                option[PropertySymbol.selectedness] = false;
            }
        }
    }
    /**
     * Returns value.
     *
     * @returns Value.
     */
    get selectedIndex() {
        return this[PropertySymbol.selectedIndex];
    }
    /**
     * Sets value.
     *
     * @param selectedIndex Selected index.
     */
    set selectedIndex(selectedIndex) {
        selectedIndex = Number(selectedIndex);
        if (isNaN(selectedIndex)) {
            return;
        }
        const options = QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items];
        this[PropertySymbol.selectedIndex] = -1;
        if (typeof selectedIndex === 'number' && !isNaN(selectedIndex)) {
            for (let i = 0, max = options.length; i < max; i++) {
                options[i][PropertySymbol.selectedness] = false;
            }
            const selectedOption = options[selectedIndex];
            if (selectedOption) {
                selectedOption[PropertySymbol.selectedness] = true;
                selectedOption[PropertySymbol.dirtyness] = true;
                this[PropertySymbol.selectedIndex] = selectedIndex;
            }
        }
    }
    /**
     * Returns selected options.
     *
     * @returns HTMLCollection.
     */
    get selectedOptions() {
        if (!this[PropertySymbol.selectedOptions]) {
            this[PropertySymbol.selectedOptions] = new HTMLCollection_js_1.default(PropertySymbol.illegalConstructor, () => {
                const options = QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items];
                // If we hit the cache, we should recieve the same Array instance as before, which will then contain the selected options.
                if (options[PropertySymbol.selectedOptions]) {
                    return options[PropertySymbol.selectedOptions];
                }
                const selectedOptions = [];
                for (let i = 0, max = options.length; i < max; i++) {
                    const option = options[i];
                    if (option[PropertySymbol.selectedness]) {
                        selectedOptions.push(option);
                    }
                }
                options[PropertySymbol.selectedOptions] = selectedOptions;
                return selectedOptions;
            });
        }
        return this[PropertySymbol.selectedOptions];
    }
    /**
     * Returns the associated label elements.
     *
     * @returns Label elements.
     */
    get labels() {
        return HTMLLabelElementUtility_js_1.default.getAssociatedLabelElements(this);
    }
    /**
     * Returns the parent form element.
     *
     * @returns Form.
     */
    get form() {
        if (this[PropertySymbol.formNode]) {
            return this[PropertySymbol.formNode];
        }
        const id = this[PropertySymbol.attributes][PropertySymbol.namedItems].get('form')?.[PropertySymbol.value];
        if (!id || !this[PropertySymbol.isConnected]) {
            return null;
        }
        return this[PropertySymbol.ownerDocument].getElementById(id);
    }
    /**
     * Returns "true" if it will validate.
     *
     * @returns "true" if it will validate.
     */
    get willValidate() {
        return (this.type !== 'hidden' &&
            this.type !== 'reset' &&
            this.type !== 'button' &&
            !this.disabled &&
            !this['readOnly']);
    }
    /**
     * Returns item from options collection by index.
     *
     * @param index Index.
     */
    item(index) {
        return this[PropertySymbol.options].item(index);
    }
    /**
     * Adds new option to options collection.
     *
     * @param element HTMLOptionElement to add.
     * @param before HTMLOptionElement or index number.
     */
    add(element, before) {
        const options = QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items];
        if (!before && before !== 0) {
            const childNodes = this[PropertySymbol.nodeArray];
            while (childNodes.length) {
                this[PropertySymbol.removeChild](childNodes[0]);
            }
            for (const option of options) {
                this[PropertySymbol.appendChild](option);
            }
            this[PropertySymbol.appendChild](element);
            return;
        }
        const window = this[PropertySymbol.window];
        if (typeof before !== 'number') {
            if (!(before instanceof HTMLOptionElement_js_1.default)) {
                throw new window.DOMException("Failed to execute 'add' on 'HTMLFormElement': The node before which the new node is to be inserted before is not an 'HTMLOptionElement'.");
            }
            before = options.indexOf(before);
        }
        const optionsElement = options[before];
        if (!optionsElement) {
            throw new window.DOMException("Failed to execute 'add' on 'HTMLFormElement': The node before which the new node is to be inserted before is not a child of this node.");
        }
        const childNodes = this[PropertySymbol.nodeArray];
        while (childNodes.length) {
            this[PropertySymbol.removeChild](childNodes[0]);
        }
        for (let i = 0, max = options.length; i < max; i++) {
            if (i === before) {
                this[PropertySymbol.appendChild](element);
            }
            this[PropertySymbol.appendChild](options[i]);
        }
    }
    /**
     * Removes indexed element from collection or the select element.
     *
     * @param [index] Index.
     */
    remove(index) {
        if (typeof index === 'number') {
            const options = QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items];
            if (!options[index]) {
                return;
            }
            const childNodes = this[PropertySymbol.nodeArray];
            while (childNodes.length) {
                this[PropertySymbol.removeChild](childNodes[0]);
            }
            for (let i = 0, max = options.length; i < max; i++) {
                if (i !== index) {
                    this[PropertySymbol.appendChild](options[i]);
                }
            }
        }
        else {
            super.remove();
        }
    }
    /**
     * Sets validation message.
     *
     * @param message Message.
     */
    setCustomValidity(message) {
        this[PropertySymbol.validationMessage] = String(message);
    }
    /**
     * Checks validity.
     *
     * @returns "true" if the field is valid.
     */
    checkValidity() {
        const valid = this.disabled || this[PropertySymbol.validity].valid;
        if (!valid) {
            this.dispatchEvent(new Event_js_1.default('invalid', { bubbles: true, cancelable: true }));
        }
        return valid;
    }
    /**
     * Reports validity.
     *
     * @returns "true" if the field is valid.
     */
    reportValidity() {
        return this.checkValidity();
    }
    /**
     * Updates option item.
     *
     * Based on:
     * https://github.com/jsdom/jsdom/blob/master/lib/jsdom/living/nodes/HTMLSelectElement-impl.js
     *
     * @see https://html.spec.whatwg.org/multipage/form-elements.html#selectedness-setting-algorithm
     * @param [selectedOption] Selected option.
     */
    [PropertySymbol.updateSelectedness](selectedOption) {
        const isMultiple = this.hasAttribute('multiple');
        const options = QuerySelector_js_1.default.querySelectorAll(this, 'option')[PropertySymbol.items];
        const selected = [];
        if (selectedOption) {
            this[PropertySymbol.selectedIndex] = -1;
        }
        if (!isMultiple) {
            for (let i = 0, max = options.length; i < max; i++) {
                const option = options[i];
                if (selectedOption) {
                    option[PropertySymbol.selectedness] = option === selectedOption;
                    if (option === selectedOption) {
                        this[PropertySymbol.selectedIndex] = i;
                    }
                }
                if (option[PropertySymbol.selectedness]) {
                    selected.push(option);
                }
            }
        }
        const size = this.#getDisplaySize();
        if (size === 1 && !selected.length) {
            this[PropertySymbol.selectedIndex] = -1;
            for (let i = 0, max = options.length; i < max; i++) {
                const option = options[i];
                const parentNode = option[PropertySymbol.parentNode];
                let disabled = option.hasAttributeNS(null, 'disabled');
                if (parentNode &&
                    parentNode[PropertySymbol.nodeType] === NodeTypeEnum_js_1.default.elementNode &&
                    parentNode[PropertySymbol.tagName] === 'OPTGROUP' &&
                    parentNode.hasAttributeNS(null, 'disabled')) {
                    disabled = true;
                }
                if (!disabled) {
                    option[PropertySymbol.selectedness] = true;
                    this[PropertySymbol.selectedIndex] = i;
                    break;
                }
            }
        }
        else if (selected.length >= 2) {
            this[PropertySymbol.selectedIndex] = -1;
            for (let i = 0, max = options.length; i < max; i++) {
                options[i][PropertySymbol.selectedness] = i === selected.length - 1;
                if (i === selected.length - 1) {
                    this[PropertySymbol.selectedIndex] = i;
                }
            }
        }
    }
    /**
     * Returns display size.
     *
     * @returns Display size.
     */
    #getDisplaySize() {
        if (this.hasAttributeNS(null, 'size')) {
            const size = parseInt(this.getAttribute('size'));
            if (!isNaN(size) && size >= 0) {
                return size;
            }
        }
        return this.hasAttributeNS(null, 'multiple') ? 4 : 1;
    }
}
exports.default = HTMLSelectElement;
//# sourceMappingURL=HTMLSelectElement.cjs.map