// #region Imports
import { LitElement, nothing, unsafeCSS } from 'lit';
import { Container } from '../../DI/Container';
import { css } from '../../Dom/Css';
import { html } from '../../Dom/Html';
/**
 * @private
 */
function isFunction(value) {
    return typeof value === 'function';
}
/**
 * @private
 */
function isString(value) {
    return typeof value === 'string';
}
/**
 * @private
 */
function transformHostSelectorsForLightDom(styleText, tagName) {
    return styleText
        .replace(/:host\(([^)]+)\)/g, `${tagName}$1`)
        .replace(/:host\b/g, tagName);
}
/**
 * @private
 */
function injectLightDomStyles(host, styles) {
    void host.updateComplete.then(() => {
        if (host.renderRoot !== host) {
            return;
        }
        if (host.querySelector('style[mosaik-light-style]')) {
            return;
        }
        const style = document.createElement('style');
        style.setAttribute('mosaik-light-style', '');
        style.textContent = transformHostSelectorsForLightDom(stringifyStyles(styles), host.tagName.toLowerCase());
        host.prepend(style);
    });
}
/**
 * @private
 */
function stringifyStyles(styles) {
    if (Array.isArray(styles)) {
        return styles.map((s) => stringifyStyles(s)).join('\n');
    }
    if (styles instanceof CSSStyleSheet) {
        return Array.from(styles.cssRules).map((rule) => rule.cssText)
            .join('\n');
    }
    return styles.cssText;
}
/**
 * @private
 */
function getPrototypes(object) {
    const prototypes = new Array();
    prototypes.push(object);
    let currentPrototype = Object.getPrototypeOf(object);
    while (currentPrototype !== null) {
        prototypes.push(currentPrototype);
        currentPrototype = Object.getPrototypeOf(currentPrototype);
    }
    return prototypes;
}
/**
 * @private
 */
function createStyleSheets(styles) {
    let newStyles = css ``;
    if (Array.isArray(styles)) {
        newStyles = styles.map((s) => createStyleSheets(s));
        return newStyles;
    }
    else {
        if (isFunction(styles)) {
            newStyles = styles();
        }
        else if (isString(styles)) {
            newStyles = css `${unsafeCSS(styles)}`;
        }
        else if (styles !== undefined) {
            newStyles = styles;
        }
    }
    return newStyles;
}
/**
 * @private
 */
function createThemes(themes) {
    const newThemes = Object.entries(themes).reduce((acc, [key, value]) => {
        acc[key] = value();
        return acc;
    }, {});
    return newThemes;
}
/**
 * @private
 */
function assignStyles(ctor, styles, styleDomInit) {
    if (styleDomInit?.styleAdoption !== 'inline') {
        // Get the existing styles from the constructor
        const existingStyles = ctor.styles;
        // Redefine the 'styles' property with the combined styles
        Object.defineProperty(ctor, 'styles', {
            value: existingStyles
                ? [
                    existingStyles,
                    styles
                ]
                : styles,
            writable: true, // Make the property writable
            configurable: true // Allow future modifications to the property
        });
        if (styleDomInit?.projection === 'light') {
            // eslint-disable-next-line @typescript-eslint/unbound-method
            const originalConnectedCallback = ctor.prototype.connectedCallback;
            ctor.prototype.connectedCallback = function () {
                injectLightDomStyles(this, styles);
                if (originalConnectedCallback) {
                    originalConnectedCallback.call(this);
                }
            };
        }
    }
    else {
        // eslint-disable-next-line @typescript-eslint/unbound-method
        const originalConnectedCallback = ctor.prototype.connectedCallback;
        ctor.prototype.connectedCallback = function () {
            if (!this.querySelector('style[mosaik-style]')) {
                const style = document.createElement('style');
                style.setAttribute('mosaik-style', '');
                style.textContent = stringifyStyles(styles);
                this.prepend(style);
            }
            // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
            if (originalConnectedCallback) {
                originalConnectedCallback.call(this);
            }
        };
    }
}
/**
 * @private
 */
function assignThemes(ctor, themes, styleInit) {
    if (styleInit?.styleAdoption !== 'inline') {
        if ('registerThemes' in ctor) {
            ctor.registerThemes(themes);
        }
        else {
            throw new Error(`The element '${String(ctor)}' does not support themes. It could be that the element does not inherit from 'Themeable'.`);
        }
    }
    else {
        const originalConnectedCallback = ctor.prototype.connectedCallback;
        ctor.prototype.connectedCallback = function () {
            const currentTheme = document.documentElement.getAttribute('theme');
            const themeStyles = Object.entries(themes).find(([key]) => key === currentTheme)?.[1];
            if (themeStyles) {
                if (!this.querySelector('style[mosaik-theme]')) {
                    const style = document.createElement('style');
                    style.setAttribute('mosaik-theme', '');
                    style.textContent = stringifyStyles(themeStyles);
                    this.prepend(style);
                }
            }
            if (originalConnectedCallback) {
                originalConnectedCallback.call(this);
            }
        };
    }
}
/**
 * @private
 */
function assignShadowRootOptions(ctor, options) {
    ctor.shadowRootOptions = {
        ...LitElement.shadowRootOptions,
        ...ctor.shadowRootOptions,
        ...options
    };
}
/**
 * @private
 */
function assignRender(ctor, template) {
    const p = getPrototypes(ctor).reverse();
    p.forEach((prototype) => {
        if ('render' in prototype) {
            throw new Error(`The element '${ctor.name}' already defines a 'render' method.`);
        }
    });
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    // eslint-disable-next-line @typescript-eslint/unbound-method
    const baseRender = ctor.prototype.render;
    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-ignore
    ctor.prototype.render = function () {
        // this is really a ugly workaround to detect if the render method in the base class returns nothing or not.
        // if it returns nothing we will not call the template function.
        const baseRenderResult = baseRender.call(this);
        if (baseRenderResult === nothing) {
            return baseRenderResult;
        }
        return isFunction(template)
            ? template(this)
            : isString(template)
                ? html `${template}`
                : template;
    };
}
/**
 * @private
 */
function assignHost(ctor, host) {
    // eslint-disable-next-line @typescript-eslint/unbound-method
    const originalConnectedCallback = ctor.prototype.connectedCallback;
    ctor.prototype.connectedCallback = function () {
        for (const [key, value] of Object.entries(host)) {
            if (key.startsWith('style.')) {
                const styleProperty = key.split('.')[1];
                // eslint-disable-next-line no-restricted-syntax
                this.style.setProperty(styleProperty, value);
            }
            else if (key.startsWith('attr.')) {
                const attrName = key.split('.')[1];
                this.setAttribute(attrName, value);
            }
            else if (key.startsWith('class.')) {
                const className = key.split('.')[1];
                if (value) {
                    this.classList.add(className);
                }
                else {
                    this.classList.remove(className);
                }
            }
            else {
                this[key] = value;
            }
        }
        // Call the original connectedCallback, if it exists
        // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
        if (originalConnectedCallback) {
            originalConnectedCallback.call(this);
        }
    };
}
/**
 * @private
 */
function assignProjection(ctor, projection) {
    if (projection === 'light') {
        ctor.prototype['createRenderRoot'] = function () {
            return this;
        };
    }
}
/**
 * @private
 */
function legacyCustomElement(ctor, options) {
    assignProjection(ctor, options.options?.projection);
    assignShadowRootOptions(ctor, options.options);
    if (options.styles) {
        assignStyles(ctor, createStyleSheets(options.styles), options.options);
    }
    if (options.themes) {
        assignThemes(ctor, createThemes(options.themes), options.options);
    }
    if (options.template) {
        assignRender(ctor, options.template);
    }
    if (options.host) {
        assignHost(ctor, options.host);
    }
    if (options.providers) {
        options.providers.forEach(({ provide, useClass }) => {
            Container.instance.provide(provide, useClass);
        });
    }
    if (!customElements.get(options.selector)) {
        customElements.define(options.selector, ctor);
    }
    else {
        console.warn(`[Component]: The element '${options.selector}' is already defined.`);
    }
    return ctor;
}
/**
 * @private
 */
function standardCustomElement(descriptor, options) {
    return {
        kind: descriptor.kind,
        elements: descriptor.elements,
        finisher: (ctor) => {
            assignProjection(ctor, options.options?.projection);
            assignShadowRootOptions(ctor, options.options);
            if (options.styles) {
                assignStyles(ctor, createStyleSheets(options.styles), options.options);
            }
            if (options.themes) {
                assignThemes(ctor, createThemes(options.themes), options.options);
            }
            if (options.template) {
                assignRender(ctor, options.template);
            }
            if (options.host) {
                assignHost(ctor, options.host);
            }
            if (options.providers) {
                options.providers.forEach(({ provide, useClass }) => {
                    Container.instance.provide(provide, useClass);
                });
            }
            if (!customElements.get(options.selector)) {
                customElements.define(options.selector, ctor);
            }
            else {
                console.warn(`[Component]: The element '${options.selector}' is already defined.`);
            }
        }
    };
}
/**
 * Class decorator factory that defines the decorated class as a custom element.
 *
 * @param options - The options to apply to the custom element.
 * @public
 *
 * @example
 * ```ts
 * @Component({ selector: 'my-element' })
 * class MyElement extends LitElement {
 *   render() {
 *     return html.nothing ;
 *   }
 * }
 * ```
 */
export function Component(options) {
    return (classOrDescriptor) => typeof classOrDescriptor === 'function'
        ? legacyCustomElement(classOrDescriptor, options)
        : standardCustomElement(classOrDescriptor, options);
}
//# sourceMappingURL=ComponentDecorator.js.map