import { of, ReplaySubject, throwError } from 'rxjs';
import { isPlatformBrowser } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, signal, inject, PLATFORM_ID, DOCUMENT, Injectable } from '@angular/core';
import { Meta } from '@angular/platform-browser';
import { take, tap, map, switchMap } from 'rxjs/operators';

/**
 * Copyright (c) Siemens 2016 - 2026
 * SPDX-License-Identifier: MIT
 */
/**
 * SiThemeStore object is used by the theme service to load and
 * store the themes. You can inject your own implementation to provide
 * a backend implementation. Otherwise a localStorage based implementation
 * is used.
 */
class SiThemeStore {
}
const SI_THEME_LOCAL_STORAGE_KEY = 'si-themes';
class SiDefaultThemeStore extends SiThemeStore {
    isBrowser;
    constructor(isBrowser) {
        super();
        this.isBrowser = isBrowser;
    }
    loadActiveTheme() {
        if (!this.isBrowser) {
            return of(undefined);
        }
        const store = this.loadStore();
        if (store.activeTheme) {
            return of(store.themes[store.activeTheme]);
        }
        else {
            return of(undefined);
        }
    }
    activateTheme(name) {
        if (!this.isBrowser) {
            return of(false);
        }
        const store = this.loadStore();
        if (store.themes[name]) {
            store.activeTheme = name;
            this.saveStore(store);
            return of(true);
        }
        else {
            return of(false);
        }
    }
    deactivateTheme() {
        if (!this.isBrowser) {
            return of(false);
        }
        const store = this.loadStore();
        store.activeTheme = undefined;
        this.saveStore(store);
        return of(true);
    }
    loadThemeNames() {
        if (!this.isBrowser) {
            return of([]);
        }
        const store = this.loadStore();
        return of(Array.from(Object.keys(store.themes)));
    }
    saveTheme(theme) {
        if (!this.isBrowser) {
            return of(false);
        }
        const store = this.loadStore();
        store.themes[theme.name] = theme;
        this.saveStore(store);
        return of(true);
    }
    loadTheme(name) {
        if (!this.isBrowser) {
            return of(undefined);
        }
        const store = this.loadStore();
        return of(store.themes[name]);
    }
    deleteTheme(name) {
        if (!this.isBrowser) {
            return of(false);
        }
        const store = this.loadStore();
        delete store.themes[name];
        if (store.activeTheme === name) {
            store.activeTheme = undefined;
        }
        this.saveStore(store);
        return of(true);
    }
    loadStore() {
        const storeStr = localStorage.getItem(SI_THEME_LOCAL_STORAGE_KEY);
        if (storeStr) {
            return JSON.parse(storeStr);
        }
        else {
            return { activeTheme: undefined, themes: {} };
        }
    }
    saveStore(store) {
        localStorage.setItem(SI_THEME_LOCAL_STORAGE_KEY, JSON.stringify(store));
    }
}

const ELEMENT_THEME_NAME = 'element';
const elementTheme = {
    name: 'element',
    groups: [
        {
            name: 'Branding',
            description: 'Configure the brand logo.',
            properties: [
                {
                    name: '--element-brand-logo',
                    usage: 'Enter a brand logo in the format: \'url("https://company/logo.svg")\'',
                    type: 'text'
                },
                {
                    name: '--element-brand-logo-width',
                    usage: "Enter the width of the logo as CSS width value. Example: '100px'",
                    type: 'text'
                },
                {
                    name: '--element-brand-logo-height',
                    usage: "Enter the height of the logo as CSS width value. Max: 48px; Example: '16px'",
                    type: 'text'
                },
                {
                    name: '--element-brand-logo-text',
                    usage: 'Enter a textual representation of the logo. Example: \'"Siemens logo"\'. The text must be wrapped in double quotes.',
                    type: 'text'
                }
            ]
        },
        {
            name: 'Basic definitions',
            description: 'Basic definitions',
            properties: [
                {
                    name: '--element-body-font-family',
                    usage: 'The theme only uses one font everywhere.',
                    type: 'font'
                },
                { name: '--element-button-radius', usage: 'Button border radius', type: 'text' },
                { name: '--element-button-focus-width', usage: 'Focus width', type: 'text' },
                {
                    name: '--element-button-focus-overlay-width',
                    usage: 'Focus overlay width',
                    type: 'text'
                },
                {
                    name: '--element-button-focus-overlay-color',
                    usage: 'Focus overlay color',
                    type: 'text'
                },
                { name: '--element-input-radius', usage: 'Input field border radius', type: 'text' },
                { name: '--element-logo-color', usage: 'Logo color', type: 'text' },
                { name: '--element-radius-1', usage: 'Border radius-1', type: 'text' },
                { name: '--element-radius-2', usage: 'Border radius-2', type: 'text' },
                { name: '--element-radius-3', usage: 'Border radius-3', type: 'text' }
            ]
        },
        {
            name: 'UI',
            description: 'UI colors are used on structural properties and icons and provide good contrast when used over any background.',
            properties: [
                { name: '--element-ui-0', usage: 'Logo, selected (active) elements', type: 'color' },
                { name: '--element-ui-0-hover', usage: 'Selected hover', type: 'color' },
                { name: '--element-ui-1', usage: 'Primary icons', type: 'color' },
                { name: '--element-ui-2', usage: 'Secondary icons', type: 'color' },
                { name: '--element-ui-3', usage: 'Disabled', type: 'color' },
                { name: '--element-ui-4', usage: 'Borders', type: 'color' },
                { name: '--element-ui-5', usage: 'Inverse', type: 'color' },
                { name: '--element-ui-6', usage: 'Shadows', type: 'color' },
                { name: '--element-box-shadow-color-1', usage: 'Box shadow color 1', type: 'color' },
                { name: '--element-box-shadow-color-2', usage: 'Box shadow color 2', type: 'color' },
                { name: '--element-focus-default', usage: 'Default focus color', type: 'color' }
            ]
        },
        {
            name: 'Base',
            description: 'Base colors are used as backgrounds of containers.',
            properties: [
                { name: '--element-base-0', usage: 'Page background, row background', type: 'color' },
                {
                    name: '--element-base-1',
                    usage: 'Header, navigation, card, table background',
                    type: 'color'
                },
                {
                    name: '--element-base-1-hover',
                    usage: 'Hover on base-1 backgrounds, like table, tree, or menu',
                    type: 'color'
                },
                {
                    name: '--element-base-1-selected',
                    usage: 'Selected on base-1 backgrounds, like table, tree, or menu',
                    type: 'color'
                },
                {
                    name: '--element-base-2',
                    usage: 'Page background with higher contrast pages in dark mode',
                    type: 'color'
                },
                {
                    name: '--element-base-information',
                    usage: 'Informational component background for e.g. badges',
                    type: 'gradient'
                },
                {
                    name: '--element-base-success',
                    usage: 'Success component background for e.g. badges',
                    type: 'gradient'
                },
                {
                    name: '--element-base-caution',
                    usage: 'Caution component background for e.g. badges',
                    type: 'gradient'
                },
                {
                    name: '--element-base-warning',
                    usage: 'Warning component background for e.g. badges',
                    type: 'gradient'
                },
                {
                    name: '--element-base-danger',
                    usage: 'Danger component background for e.g. badges',
                    type: 'gradient'
                },
                {
                    name: '--element-base-translucent-1',
                    usage: 'Translucent background, e.g. backdrop',
                    type: 'gradient'
                },
                {
                    name: '--element-base-translucent-2',
                    usage: 'Slightly translucent background, e.g. toasts',
                    type: 'gradient'
                }
            ]
        },
        {
            name: 'Actions',
            description: 'Action colors are used to indicate actions that users can perform.',
            properties: [
                { name: '--element-action-primary', usage: 'Primary interaction', type: 'gradient' },
                {
                    name: '--element-action-primary-hover',
                    usage: 'Primary action background on hover',
                    type: 'gradient'
                },
                { name: '--element-action-primary-text', usage: 'Primary text color', type: 'color' },
                { name: '--element-action-secondary', usage: 'Secondary interaction', type: 'gradient' },
                {
                    name: '--element-action-secondary-hover',
                    usage: 'Secondary action background on hover',
                    type: 'gradient'
                },
                { name: '--element-action-secondary-text', usage: 'Secondary text color', type: 'color' },
                {
                    name: '--element-action-secondary-text-hover',
                    usage: 'Secondary text color on hover',
                    type: 'color'
                },
                {
                    name: '--element-action-secondary-border',
                    usage: 'Secondary border color',
                    type: 'color'
                },
                {
                    name: '--element-action-secondary-border-hover',
                    usage: 'Secondary border color on hover',
                    type: 'color'
                },
                { name: '--element-action-warning', usage: 'Warning', type: 'gradient' },
                {
                    name: '--element-action-warning-hover',
                    usage: 'Warning action background on hover',
                    type: 'gradient'
                },
                { name: '--element-action-warning-text', usage: 'Warning text color', type: 'color' },
                { name: '--element-action-danger', usage: 'Danger', type: 'gradient' },
                {
                    name: '--element-action-danger-hover',
                    usage: 'Danger action background on hover',
                    type: 'gradient'
                },
                { name: '--element-action-danger-text', usage: 'Danger text color', type: 'color' },
                {
                    name: '--element-action-disabled-opacity',
                    usage: 'Disabled action opacity',
                    type: 'number'
                }
            ]
        },
        {
            name: 'Text',
            description: 'Similarly, categories for typography colors are also defined in this system.',
            properties: [
                { name: '--element-text-primary', usage: 'Primary', type: 'color' },
                { name: '--element-text-secondary', usage: 'Secondary', type: 'color' },
                { name: '--element-text-disabled', usage: 'Disabled', type: 'color' },
                { name: '--element-text-inverse', usage: 'Inverse', type: 'color' },
                { name: '--element-text-active', usage: 'Active', type: 'color' },
                { name: '--element-text-information', usage: 'Informational', type: 'color' },
                { name: '--element-text-success', usage: 'Success', type: 'color' },
                { name: '--element-text-caution', usage: 'Caution', type: 'color' },
                { name: '--element-text-warning', usage: 'Warning', type: 'color' },
                { name: '--element-text-danger', usage: 'Danger', type: 'color' }
            ]
        },
        {
            name: 'Status',
            description: 'Status colors are used to describe and/or report on the status of different things.',
            properties: [
                { name: '--element-status-information', usage: 'Informational', type: 'color' },
                {
                    name: '--element-status-information-contrast',
                    usage: 'Informational contrast',
                    type: 'color'
                },
                { name: '--element-status-success', usage: 'Success', type: 'color' },
                { name: '--element-status-success-contrast', usage: 'Success contrast', type: 'color' },
                { name: '--element-status-caution', usage: 'Caution', type: 'color' },
                { name: '--element-status-caution-contrast', usage: 'Caution contrast', type: 'color' },
                { name: '--element-status-warning', usage: 'Warning', type: 'color' },
                { name: '--element-status-warning-contrast', usage: 'Warning contrast', type: 'color' },
                { name: '--element-status-danger', usage: 'Danger', type: 'color' },
                { name: '--element-status-danger-contrast', usage: 'Danger contrast', type: 'color' }
            ]
        },
        {
            name: 'Form feedback icons',
            description: 'Icons that are used to indicate a form item status',
            properties: [{ name: '--element-invalid-feedback-icon', usage: 'Danger', type: 'text' }]
        }
    ]
};

/**
 * Copyright (c) Siemens 2016 - 2026
 * SPDX-License-Identifier: MIT
 */
class SiThemeService {
    /**
     * The current color scheme. (e.g. light or dark).
     */
    _resolvedColorScheme = 'light';
    resolvedColorSchemeSub = new ReplaySubject(1);
    /**
     * Emits events when the color scheme changes.
     *
     * @defaultValue this.resolvedColorSchemeSub.asObservable()
     */
    resolvedColorScheme$ = this.resolvedColorSchemeSub.asObservable();
    /**
     * The current color scheme. (e.g. light or dark).
     */
    get resolvedColorScheme() {
        return this._resolvedColorScheme;
    }
    /**
     * All available theme names, including element theme name.
     */
    _themeNames = [ELEMENT_THEME_NAME];
    themeNamesSub = new ReplaySubject(1);
    /**
     * Emits events when the list of available theme names changes.
     *
     * @defaultValue this.themeNamesSub.asObservable()
     */
    themeNames$ = this.themeNamesSub.asObservable();
    /**
     * All available theme names, including element theme name.
     */
    get themeNames() {
        return this._themeNames;
    }
    /**
     * Emits events when the currently applied theme changes. Either by
     * changing to another theme or by re-applying a theme with updated
     * properties. When switching to default theme element, `undefined`
     * is emitted.
     */
    themeChange = new EventEmitter();
    /**
     * The name of the theme that is active. Theme name `element` is the default.
     */
    activeThemeName;
    /**
     * Icon overrides by the currently activeTheme.
     * @defaultValue
     * ```
     * {}
     * ```
     */
    themeIcons = signal({}, ...(ngDevMode ? [{ debugName: "themeIcons" }] : []));
    themes = new Map();
    darkMediaQuery;
    mediaQueryListener;
    isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
    previewTheme;
    themeStore = inject(SiThemeStore, { optional: true }) ?? new SiDefaultThemeStore(this.isBrowser);
    meta = inject(Meta);
    document = inject(DOCUMENT);
    constructor() {
        this.resolvedColorScheme$.subscribe(scheme => (this._resolvedColorScheme = scheme));
        this.themeNames$.subscribe(names => (this._themeNames = names));
        if (this.isBrowser) {
            this.darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
            this.mediaQueryListener = () => this.toggleDark(this.darkMediaQuery.matches);
        }
        this.getActiveTheme()
            .pipe(take(1))
            .subscribe({
            next: theme => this.applyThemeStyle(theme, this._resolvedColorScheme),
            error: error => console.error('Cannot load active theme', error)
        });
        this.themeStore
            .loadThemeNames()
            .pipe(take(1))
            .subscribe({
            next: names => {
                names.push(ELEMENT_THEME_NAME);
                this.themeNamesSub.next(names);
            },
            error: error => console.error('Cannot load theme names', error)
        });
    }
    /**
     * Returns `true` if a theme with the given name is available.
     */
    hasTheme(name) {
        return this._themeNames.includes(name);
    }
    /**
     * Returns a clone of the theme with the given name or `undefined` if not
     * available or name is `element`.
     * @param name - The name of the theme to be returned.
     * @returns The theme with the given name and `undefined` if name is `element`.
     */
    getTheme(name) {
        if (name === ELEMENT_THEME_NAME || !this.hasTheme(name)) {
            return of(undefined);
        }
        if (this.themes.has(name)) {
            const theme = this.themes.get(name);
            // return a clone to avoid changes as a side effect
            return of(this.cloneTheme(theme));
        }
        return this.themeStore.loadTheme(name).pipe(tap(theme => {
            if (theme) {
                this.themes.set(theme.name, this.cloneTheme(theme));
            }
        }));
    }
    /**
     * Loads and returns the currently active theme. Returns undefined if no custom theme is used.
     * @returns A custom theme or `undefined` if the default element theme is used.
     */
    getActiveTheme() {
        if (this.activeThemeName) {
            return of(this.themes.get(this.activeThemeName));
        }
        return this.themeStore.loadActiveTheme().pipe(map(theme => {
            if (theme) {
                this.activeThemeName = theme.name;
                this.themes.set(theme.name, theme);
                this.applyTheme(theme, this._resolvedColorScheme);
                return theme;
            }
            this.activeThemeName = ELEMENT_THEME_NAME;
            return undefined;
        }));
    }
    /**
     * Adds or updates the given theme in the theme store.
     * @param theme - The theme to be saved.
     * @returns `true` if the theme was saved successfully.
     */
    addOrUpdateTheme(theme) {
        const result = new ReplaySubject(1);
        this.themeStore.saveTheme(theme).subscribe({
            next: saveResult => {
                if (saveResult) {
                    this.themes.set(theme.name, this.cloneTheme(theme));
                    // Update list of theme names when this is a new one
                    if (!this.hasTheme(theme.name)) {
                        const names = this._themeNames.concat(theme.name);
                        this.themeNamesSub.next(names);
                    }
                }
                result.next(saveResult);
                result.complete();
            },
            error: error => result.error(error),
            complete: () => result.complete()
        });
        return result;
    }
    /**
     * Deletes the theme with the given name from the theme store.
     */
    deleteTheme(name) {
        const result = new ReplaySubject(1);
        if (!this.hasTheme(name) || name === ELEMENT_THEME_NAME) {
            result.next(false);
            result.complete();
        }
        else {
            this.removeThemeCSS(name);
            this.themeStore.deleteTheme(name).subscribe({
                next: deleteResult => {
                    if (deleteResult) {
                        this.themes.delete(name);
                        const names = this._themeNames.filter(themeName => themeName !== name);
                        this.themeNamesSub.next(names);
                    }
                    result.next(deleteResult);
                    result.complete();
                },
                error: error => result.error(error),
                complete: () => result.complete()
            });
        }
        return result;
    }
    /**
     * Resets the preview theme to the default element theme.
     */
    resetPreview() {
        this.previewTheme = { name: '__preview', schemes: { dark: {}, light: {} } };
        this.removeThemeCSS(this.previewTheme.name);
    }
    /**
     * Sets the active theme to the given name. If no name is given, the default element theme is used.
     */
    setActiveTheme(name, type) {
        const theType = type ?? this._resolvedColorScheme ?? 'auto';
        const theName = name ?? this.activeThemeName ?? ELEMENT_THEME_NAME;
        if (theName === this.activeThemeName) {
            return of(true);
        }
        if (!this.hasTheme(theName)) {
            return of(false);
        }
        const result = new ReplaySubject(1);
        this.activeThemeName = theName;
        // Make the change in the remove store
        const store = theName !== ELEMENT_THEME_NAME
            ? this.themeStore.activateTheme(theName)
            : this.themeStore.deactivateTheme();
        store
            .pipe(
        // On success, load the theme
        switchMap(storeResult => {
            if (storeResult) {
                return this.getTheme(theName);
            }
            else {
                return throwError(() => 'Cannot change active theme in theme store.');
            }
        }), 
        // Only take one to avoid unsubscription issues
        take(1))
            .subscribe({
            next: theme => {
                this.applyTheme(theme, theType);
                result.next(true);
            },
            error: error => {
                console.error(`Activating theme ${name} failed`, error);
                result.next(false);
            },
            complete: () => result.complete()
        });
        return result;
    }
    /**
     * Apply `light` or `dark` theme to the document.
     */
    applyThemeType(type) {
        if (type === this._resolvedColorScheme || !this.darkMediaQuery || !this.mediaQueryListener) {
            return;
        }
        this.darkMediaQuery.removeEventListener('change', this.mediaQueryListener);
        if (type === 'light') {
            this.toggleDark(false);
        }
        else if (type === 'dark') {
            this.toggleDark(true);
        }
        else {
            this.mediaQueryListener();
            this.darkMediaQuery.addEventListener('change', this.mediaQueryListener);
        }
    }
    /**
     * Applies the given theme to the document. If no theme is given, the active theme is applied.
     */
    applyTheme(theme, type, overwrite) {
        if (theme) {
            this.applyThemeStyle(theme, type, overwrite);
        }
        else {
            this.getActiveTheme()
                .pipe(take(1))
                .subscribe({
                next: loadedTheme => this.applyThemeStyle(loadedTheme, type),
                error: error => console.error('Cannot load active theme', error)
            });
        }
    }
    /**
     * Updates the given property of the preview theme.
     */
    updateProperty(name, value, type) {
        if (!this.previewTheme) {
            this.resetPreview();
        }
        this.previewTheme.schemes[type][name] = value;
        this.createThemeCSS(this.previewTheme);
        this.document.documentElement.classList.add('theme-__preview');
        this.dispatchThemeSwitchEvent();
    }
    /**
     * Checks if the given theme JSON object is a valid theme.
     */
    isThemeValid(data) {
        const theme = data;
        return (!!data &&
            typeof data === 'object' &&
            'name' in data &&
            'schemes' in data &&
            typeof theme.name === 'string' &&
            typeof theme.schemes === 'object' &&
            this.isThemeTypeValid(theme, 'light') &&
            this.isThemeTypeValid(theme, 'dark'));
    }
    isThemeTypeValid(theme, type) {
        return (!(type in theme.schemes) ||
            (typeof theme.schemes[type] === 'object' &&
                Object.values(theme.schemes[type]).every(item => typeof item === 'string') &&
                Object.keys(theme.schemes[type]).every(item => typeof item === 'string')));
    }
    applyThemeStyle(theme, type, overwrite) {
        if (!this.isBrowser) {
            return;
        }
        this.resetPreview();
        if (theme && theme.name !== ELEMENT_THEME_NAME && (overwrite || !this.hasThemeCSS(theme))) {
            this.createThemeCSS(theme);
        }
        this.activateTheme(theme);
        if (type && type !== this._resolvedColorScheme) {
            this.applyThemeType(type);
        }
        this.themeIcons.set(Object.fromEntries(Object.entries(theme?.icons ?? {}).map(([key, icon]) => [key, icon])));
        this.themeChange.emit(theme);
    }
    activateTheme(theme) {
        const classList = this.document.documentElement.classList;
        classList.forEach(c => {
            if (c.startsWith('theme-')) {
                classList.remove(c);
            }
        });
        if (theme) {
            classList.add(`theme-${theme.name}`);
        }
    }
    hasThemeCSS(theme) {
        const id = `__theme-${theme.name}`;
        return !!this.document.getElementById(id);
    }
    createThemeCSS(theme) {
        let css = '';
        const themeSelector = `:root.theme-${theme.name}`;
        if (theme.schemes.light) {
            css = this.createThemeVariantCSS(css, themeSelector, theme.schemes.light);
        }
        if (theme.schemes.dark) {
            css = this.createThemeVariantCSS(css, themeSelector + '.app--dark', theme.schemes.dark);
        }
        this.removeThemeCSS(theme.name);
        const cssElement = this.document.createElement('style');
        cssElement.id = `__theme-${theme.name}`;
        cssElement.textContent = css;
        this.document.body.appendChild(cssElement);
    }
    createThemeVariantCSS(css, selector, scheme) {
        css += `${selector} {\n`;
        for (const key of Object.keys(scheme)) {
            css += `${key}: ${scheme[key]};\n`;
        }
        css += '}\n';
        return css;
    }
    removeThemeCSS(name) {
        const id = `__theme-${name}`;
        this.document.getElementById(id)?.remove();
        this.document.documentElement.classList.remove(`theme-${name}`);
    }
    toggleDark(dark) {
        if (!this.isBrowser) {
            return;
        }
        this.document.documentElement.classList.toggle('app--dark', dark);
        const colorScheme = dark ? 'dark' : 'light';
        this.meta.updateTag({ name: 'color-scheme', content: colorScheme });
        this.resolvedColorSchemeSub.next(colorScheme);
        this.dispatchThemeSwitchEvent();
    }
    cloneTheme(theme) {
        const clone = { ...theme, schemes: {} };
        if (theme.schemes.light) {
            clone.schemes.light = { ...theme.schemes.light };
        }
        if (theme.schemes.dark) {
            clone.schemes.dark = { ...theme.schemes.dark };
        }
        return clone;
    }
    dispatchThemeSwitchEvent() {
        window.dispatchEvent(new CustomEvent('theme-switch', {
            detail: {
                dark: this._resolvedColorScheme === 'dark',
                colorScheme: this._resolvedColorScheme
            }
        }));
    }
    static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SiThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
    static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SiThemeService, providedIn: 'root' });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.8", ngImport: i0, type: SiThemeService, decorators: [{
            type: Injectable,
            args: [{ providedIn: 'root' }]
        }], ctorParameters: () => [] });

/**
 * Copyright (c) Siemens 2016 - 2026
 * SPDX-License-Identifier: MIT
 */

/**
 * Generated bundle index. Do not edit.
 */

export { ELEMENT_THEME_NAME, SI_THEME_LOCAL_STORAGE_KEY, SiDefaultThemeStore, SiThemeService, SiThemeStore, elementTheme };
//# sourceMappingURL=siemens-element-ng-theme.mjs.map
