import Event from '../../event/Event.js';
import * as PropertySymbol from '../../PropertySymbol.js';
import HTMLElement from '../html-element/HTMLElement.js';
import CrossOriginBrowserWindow from '../../window/CrossOriginBrowserWindow.js';
import DOMTokenList from '../../dom/DOMTokenList.js';
import BrowserFrameFactory from '../../browser/utilities/BrowserFrameFactory.js';
import BrowserFrameURL from '../../browser/utilities/BrowserFrameURL.js';
import DOMExceptionNameEnum from '../../exception/DOMExceptionNameEnum.js';
import WindowBrowserContext from '../../window/WindowBrowserContext.js';
import ElementEventAttributeUtility from '../element/ElementEventAttributeUtility.js';
const SANDBOX_FLAGS = [
    'allow-downloads',
    'allow-forms',
    'allow-modals',
    'allow-orientation-lock',
    'allow-pointer-lock',
    'allow-popups',
    'allow-popups-to-escape-sandbox',
    'allow-presentation',
    'allow-same-origin',
    'allow-scripts',
    'allow-top-navigation',
    'allow-top-navigation-by-user-activation',
    'allow-top-navigation-to-custom-protocols'
];
/**
 * HTML Iframe Element.
 *
 * Reference:
 * https://developer.mozilla.org/en-US/docs/Web/API/HTMLIFrameElement.
 */
export default class HTMLIFrameElement extends HTMLElement {
    // Internal properties
    [PropertySymbol.sandbox] = null;
    // Private properties
    #contentWindowContainer = {
        window: null
    };
    #iframe = null;
    #loadedSrcdoc = null;
    // Events
    /* eslint-disable jsdoc/require-jsdoc */
    get onload() {
        return ElementEventAttributeUtility.getEventListener(this, 'onload');
    }
    set onload(value) {
        this[PropertySymbol.propertyEventListeners].set('onload', value);
    }
    get onerror() {
        return ElementEventAttributeUtility.getEventListener(this, 'onerror');
    }
    set onerror(value) {
        this[PropertySymbol.propertyEventListeners].set('onerror', value);
    }
    /* eslint-enable jsdoc/require-jsdoc */
    /**
     * Returns source.
     *
     * @returns Source.
     */
    get src() {
        if (!this.hasAttribute('src')) {
            return '';
        }
        try {
            return new URL(this.getAttribute('src'), this[PropertySymbol.ownerDocument].location.href)
                .href;
        }
        catch (e) {
            return this.getAttribute('src');
        }
    }
    /**
     * Sets source.
     *
     * @param src Source.
     */
    set src(src) {
        this.setAttribute('src', src);
    }
    /**
     * Returns allow.
     *
     * @returns Allow.
     */
    get allow() {
        return this.getAttribute('allow') || '';
    }
    /**
     * Sets allow.
     *
     * @param allow Allow.
     */
    set allow(allow) {
        this.setAttribute('allow', allow);
    }
    /**
     * Returns height.
     *
     * @returns Height.
     */
    get height() {
        return this.getAttribute('height') || '';
    }
    /**
     * Sets height.
     *
     * @param height Height.
     */
    set height(height) {
        this.setAttribute('height', height);
    }
    /**
     * Returns width.
     *
     * @returns Width.
     */
    get width() {
        return this.getAttribute('width') || '';
    }
    /**
     * Sets width.
     *
     * @param width Width.
     */
    set width(width) {
        this.setAttribute('width', width);
    }
    /**
     * Returns name.
     *
     * @returns Name.
     */
    get name() {
        return this.getAttribute('name') || '';
    }
    /**
     * Sets name.
     *
     * @param name Name.
     */
    set name(name) {
        this.setAttribute('name', name);
    }
    /**
     * Returns sandbox.
     *
     * @returns Sandbox.
     */
    get sandbox() {
        if (!this[PropertySymbol.sandbox]) {
            this[PropertySymbol.sandbox] = new DOMTokenList(PropertySymbol.illegalConstructor, this, 'sandbox');
        }
        return this[PropertySymbol.sandbox];
    }
    /**
     * Sets sandbox.
     */
    set sandbox(sandbox) {
        this.setAttribute('sandbox', sandbox);
    }
    /**
     * Returns srcdoc.
     *
     * @returns Srcdoc.
     */
    get srcdoc() {
        return this.getAttribute('srcdoc') || '';
    }
    /**
     * Sets srcdoc.
     *
     * @param srcdoc Srcdoc.
     */
    set srcdoc(srcdoc) {
        this.setAttribute('srcdoc', srcdoc);
    }
    /**
     * Returns referrer policy.
     */
    get referrerPolicy() {
        return this.getAttribute('referrerpolicy') || '';
    }
    /**
     * Sets referrer policy.
     *
     * @param referrerPolicy Referrer policy.
     */
    set referrerPolicy(referrerPolicy) {
        this.setAttribute('referrerpolicy', referrerPolicy);
    }
    /**
     * Returns content document.
     *
     * @returns Content document.
     */
    get contentDocument() {
        return this.#contentWindowContainer.window?.document ?? null;
    }
    /**
     * Returns content window.
     *
     * @returns Content window.
     */
    get contentWindow() {
        return this.#contentWindowContainer.window;
    }
    /**
     * @override
     */
    get tabIndex() {
        const tabIndex = this.getAttribute('tabindex');
        if (tabIndex !== null) {
            const parsed = Number(tabIndex);
            return isNaN(parsed) ? 0 : parsed;
        }
        return 0;
    }
    /**
     * @override
     */
    set tabIndex(tabIndex) {
        super.tabIndex = tabIndex;
    }
    /**
     * @override
     */
    [PropertySymbol.connectedToDocument]() {
        super[PropertySymbol.connectedToDocument]();
        this.#loadPage();
    }
    /**
     * Called when disconnected from document.
     * @param e
     */
    [PropertySymbol.disconnectedFromDocument]() {
        super[PropertySymbol.disconnectedFromDocument]();
        this.#unloadPage();
    }
    /**
     * @override
     */
    [PropertySymbol.cloneNode](deep = false) {
        return super[PropertySymbol.cloneNode](deep);
    }
    /**
     * @override
     */
    [PropertySymbol.onSetAttribute](attribute, replacedAttribute) {
        super[PropertySymbol.onSetAttribute](attribute, replacedAttribute);
        if (attribute[PropertySymbol.name] === 'srcdoc') {
            this.#loadPage();
        }
        if (attribute[PropertySymbol.name] === 'src' &&
            attribute[PropertySymbol.value] &&
            !this.hasAttribute('srcdoc') &&
            attribute[PropertySymbol.value] !== replacedAttribute?.[PropertySymbol.value]) {
            this.#loadPage();
        }
        if (attribute[PropertySymbol.name] === 'sandbox') {
            this.#validateSandboxFlags();
        }
    }
    /**
     * @override
     */
    [PropertySymbol.onRemoveAttribute](removedAttribute) {
        super[PropertySymbol.onRemoveAttribute](removedAttribute);
        if (removedAttribute[PropertySymbol.name] === 'srcdoc' ||
            removedAttribute[PropertySymbol.name] === 'src') {
            this.#loadPage();
        }
    }
    /**
     *
     * @param tokens
     * @param vconsole
     */
    #validateSandboxFlags() {
        const window = this[PropertySymbol.window];
        const invalidFlags = [];
        for (const token of this.sandbox) {
            if (!SANDBOX_FLAGS.includes(token)) {
                invalidFlags.push(token);
            }
        }
        if (invalidFlags.length === 1) {
            window.console.error(`Error while parsing the 'sandbox' attribute: '${invalidFlags[0]}' is an invalid sandbox flag.`);
        }
        else if (invalidFlags.length > 1) {
            window.console.error(`Error while parsing the 'sandbox' attribute: '${invalidFlags.join(`', '`)}' are invalid sandbox flags.`);
        }
    }
    /**
     * Loads an iframe page.
     */
    #loadPage() {
        if (!this[PropertySymbol.isConnected]) {
            this.#unloadPage();
            return;
        }
        const srcdoc = this.getAttribute('srcdoc');
        const window = this[PropertySymbol.window];
        const browserFrame = new WindowBrowserContext(window).getBrowserFrame();
        if (!browserFrame) {
            return;
        }
        if (srcdoc !== null) {
            if (this.#loadedSrcdoc === srcdoc) {
                return;
            }
            this.#unloadPage();
            this.#iframe = BrowserFrameFactory.createChildFrame(browserFrame);
            this.#iframe.url = 'about:srcdoc';
            this.#contentWindowContainer.window = this.#iframe.window;
            this.#iframe.window[PropertySymbol.top] = browserFrame.window.top;
            this.#iframe.window[PropertySymbol.parent] = browserFrame.window;
            this.#iframe.window.document.open();
            this.#iframe.window.document.write(srcdoc);
            this.#loadedSrcdoc = srcdoc;
            this[PropertySymbol.window].requestAnimationFrame(() => this.dispatchEvent(new Event('load')));
            return;
        }
        if (this.#loadedSrcdoc !== null) {
            this.#unloadPage();
        }
        const originURL = browserFrame.window.location;
        const targetURL = BrowserFrameURL.getRelativeURL(browserFrame, this.src);
        if (this.#iframe && this.#iframe.window.location.href === targetURL.href) {
            return;
        }
        if (browserFrame.page.context.browser.settings.disableIframePageLoading) {
            const error = new window.DOMException(`Failed to load iframe page "${targetURL.href}". Iframe page loading is disabled.`, DOMExceptionNameEnum.notSupportedError);
            browserFrame.page.console.error(error);
            this.dispatchEvent(new Event('error'));
            return;
        }
        // Iframes has a special rule for CORS and doesn't allow access between frames when the origin is different.
        const isSameOrigin = originURL.origin === targetURL.origin || targetURL.origin === 'null';
        const parentWindow = isSameOrigin ? window : new CrossOriginBrowserWindow(window);
        this.#iframe = this.#iframe ?? BrowserFrameFactory.createChildFrame(browserFrame);
        this.#iframe.window[PropertySymbol.top] = parentWindow;
        this.#iframe.window[PropertySymbol.parent] = parentWindow;
        this.#iframe
            .goto(targetURL.href, {
            referrer: originURL.origin,
            referrerPolicy: this.referrerPolicy
        })
            .then(() => this.dispatchEvent(new Event('load')))
            .catch((error) => {
            browserFrame.page.console.error(error);
            this.dispatchEvent(new Event('error'));
        });
        this.#contentWindowContainer.window = isSameOrigin
            ? this.#iframe.window
            : new CrossOriginBrowserWindow(this.#iframe.window, window);
    }
    /**
     * Unloads an iframe page.
     */
    #unloadPage() {
        if (this.#iframe) {
            BrowserFrameFactory.destroyFrame(this.#iframe);
            this.#iframe = null;
        }
        this.#contentWindowContainer.window = null;
        this.#loadedSrcdoc = null;
    }
}
//# sourceMappingURL=HTMLIFrameElement.js.map