import * as i0 from '@angular/core';
import { inject, Injectable, NgModule, signal } from '@angular/core';
import { gettext } from '@c8y/ngx-components/gettext';
import { PreviewService, NavigatorNode, hookRoute, hookNavigator, hookPreview, ViewContext, AlertService, MOChunkLoaderService, DatapointSyncService } from '@c8y/ngx-components';
import { map, first, firstValueFrom } from 'rxjs';
import { InventoryService } from '@c8y/client';
import { ContextDashboardService, ContextDashboardType } from '@c8y/ngx-components/context-dashboard';
import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from 'lz-string';
import { Router } from '@angular/router';

class DatapointExplorerNavigationFactory {
    constructor() {
        this.previewFeatureService = inject(PreviewService);
    }
    get() {
        return this.previewFeatureService.getState$('ui.datapoint-explorer.v2').pipe(map(state => {
            if (state) {
                return [
                    new NavigatorNode({
                        label: gettext('Data explorer'),
                        featureId: 'dataPointExplorer',
                        path: 'datapointexplorer-v2',
                        icon: 'c8y-data-explorer',
                        routerLinkExact: false,
                        preventDuplicates: true
                    })
                ];
            }
            return [];
        }));
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DatapointExplorerNavigationFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DatapointExplorerNavigationFactory }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DatapointExplorerNavigationFactory, decorators: [{
            type: Injectable
        }] });

function canActivateDatapointExplorer() {
    const previewFeatureService = inject(PreviewService);
    return previewFeatureService.getState$('ui.datapoint-explorer.v2').pipe(first());
}
class DatapointExplorerModule {
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DatapointExplorerModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
    static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: DatapointExplorerModule }); }
    static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DatapointExplorerModule, providers: [
            hookRoute([
                {
                    path: 'datapointexplorer-v2',
                    loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent),
                    canActivate: [canActivateDatapointExplorer]
                },
                {
                    context: ViewContext.Group,
                    path: 'data_explorer-v2',
                    priority: 800,
                    icon: 'c8y-data-explorer',
                    label: gettext('Data Explorer'),
                    loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent),
                    featureId: 'dataExplorer',
                    canActivate: [canActivateDatapointExplorer]
                },
                {
                    context: ViewContext.Device,
                    path: 'data_explorer-v2',
                    priority: 800,
                    icon: 'c8y-data-explorer',
                    label: gettext('Data Explorer'),
                    loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent),
                    featureId: 'dataExplorer',
                    canActivate: [canActivateDatapointExplorer]
                }
            ]),
            hookNavigator(DatapointExplorerNavigationFactory),
            hookPreview({
                key: 'ui.datapoint-explorer.v2',
                label: 'Data point explorer',
                description: () => import('@c8y/style/markdown-files/datapoint-explorer-preview.md').then(m => m.default),
                settings: {
                    reload: true
                }
            })
        ] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DatapointExplorerModule, decorators: [{
            type: NgModule,
            args: [{
                    providers: [
                        hookRoute([
                            {
                                path: 'datapointexplorer-v2',
                                loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent),
                                canActivate: [canActivateDatapointExplorer]
                            },
                            {
                                context: ViewContext.Group,
                                path: 'data_explorer-v2',
                                priority: 800,
                                icon: 'c8y-data-explorer',
                                label: gettext('Data Explorer'),
                                loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent),
                                featureId: 'dataExplorer',
                                canActivate: [canActivateDatapointExplorer]
                            },
                            {
                                context: ViewContext.Device,
                                path: 'data_explorer-v2',
                                priority: 800,
                                icon: 'c8y-data-explorer',
                                label: gettext('Data Explorer'),
                                loadComponent: () => import('@c8y/ngx-components/datapoint-explorer/view').then(m => m.DatapointExplorerComponent),
                                featureId: 'dataExplorer',
                                canActivate: [canActivateDatapointExplorer]
                            }
                        ]),
                        hookNavigator(DatapointExplorerNavigationFactory),
                        hookPreview({
                            key: 'ui.datapoint-explorer.v2',
                            label: 'Data point explorer',
                            description: () => import('@c8y/style/markdown-files/datapoint-explorer-preview.md').then(m => m.default),
                            settings: {
                                reload: true
                            }
                        })
                    ]
                }]
        }] });

const DATA_EXPLORER_BASE_CONFIG = {
    datapoints: [],
    alarmsEventsConfigs: [],
    dateFrom: null,
    dateTo: null,
    interval: 'hours',
    aggregation: null,
    realtime: false,
    isAutoRefreshEnabled: false,
    refreshInterval: 0,
    displayMarkedLine: true,
    displayMarkedPoint: true,
    mergeMatchingDatapoints: false,
    forceMergeDatapoints: false,
    showLabelAndUnit: true,
    showSlider: true,
    displayDateSelection: false,
    yAxisSplitLines: false,
    xAxisSplitLines: false,
    numberOfDecimalPlaces: 2
};
const REVERSE_KEY_MAP = {
    d: 'datapoints',
    a: 'alarmsEventsConfigs',
    f: 'fragment',
    s: 'series',
    t: '__target',
    m: '__template',
    l: 'label',
    r: 'filters',
    y: 'timelineType',
    df: 'dateFrom',
    dt: 'dateTo',
    ac: '__active',
    c: 'color',
    i: 'id',
    tp: 'type'
};
const KEY_MAP = Object.fromEntries(Object.entries(REVERSE_KEY_MAP).map(([shortKey, longKey]) => [longKey, shortKey]));

class WorkspaceConfigurationService {
    constructor() {
        this.baseKey = 'c8y-configs';
        this.baseDefaultKey = 'c8y-default-config-id';
        this.contextIdSignal = signal(null, ...(ngDevMode ? [{ debugName: "contextIdSignal" }] : []));
        this.alertService = inject(AlertService);
    }
    get LOCAL_STORAGE_KEY() {
        const id = this.contextIdSignal();
        return id !== null ? `${this.baseKey}-${id}` : this.baseKey;
    }
    get LOCAL_STORAGE_DEFAULT_ID_KEY() {
        const id = this.contextIdSignal();
        return id !== null ? `${this.baseDefaultKey}-${id}` : this.baseDefaultKey;
    }
    /**
     * Generates a full datapoint explorer link from a bare config
     */
    generateExplorerLink(config, label, id) {
        const diffed = this.removeDefaults(config);
        const minified = this.minifyKeys(diffed);
        const compressed = compressToEncodedURIComponent(JSON.stringify(minified));
        const url = `/datapointexplorer-v2?id=${id}&label=${encodeURIComponent(label)}&config=${compressed}`;
        return url;
    }
    /** Load workspace configs from localStorage  */
    getConfigurations() {
        const configurations = localStorage.getItem(this.LOCAL_STORAGE_KEY);
        if (configurations) {
            const parsedConfigs = JSON.parse(configurations);
            return parsedConfigs.map(workspaceConfig => {
                if (typeof workspaceConfig.config === 'string') {
                    return {
                        ...workspaceConfig,
                        config: this.decodeConfig(workspaceConfig.config)
                    };
                }
                return workspaceConfig;
            });
        }
        return [];
    }
    getDefaultConfigurationId() {
        return localStorage.getItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY);
    }
    /** Save workspace configs in localStorage */
    saveConfigurations(configurations, id) {
        localStorage.setItem(this.LOCAL_STORAGE_KEY, JSON.stringify(configurations));
        localStorage.setItem(this.LOCAL_STORAGE_DEFAULT_ID_KEY, id);
    }
    /**
     *
     * @param urlConfig - configuration from the URL, either compressed string or already decoded object
     * @returns
     */
    getConfigurationFromUrl(urlConfig) {
        if (typeof urlConfig === 'string') {
            const decodedConfig = this.decodeConfig(urlConfig);
            const expandedConfig = this.expandKeys(decodedConfig);
            return expandedConfig;
        }
        return this.expandKeys(urlConfig);
    }
    /**
     * Encode a config for the URL:
     * - Cleanup the __target objects to contain only name and id
     * - Remove default values (diff from base)
     * - Minify keys
     * - Compress with lz-string
     */
    encodeConfig(config) {
        const normalized = this.cleanUpTargetObject(config);
        const diffed = this.removeDefaults(normalized);
        const minified = this.minifyKeys(diffed);
        return compressToEncodedURIComponent(JSON.stringify(minified));
    }
    /**
     * Decode a config from the URL:
     * - Decompress
     * - Expand keys
     * - Merge with base config
     */
    decodeConfig(urlConfig) {
        if (!urlConfig)
            return null;
        try {
            const decompressed = decompressFromEncodedURIComponent(urlConfig);
            if (!decompressed)
                return null;
            const parsed = JSON.parse(decompressed);
            const expanded = this.expandKeys(parsed);
            return { ...DATA_EXPLORER_BASE_CONFIG, ...expanded };
        }
        catch {
            this.alertService.danger(gettext('The decoded configuration is invalid and could not be loaded.'));
        }
    }
    /** Minify keys recursively using KEY_MAP */
    minifyKeys(config) {
        if (Array.isArray(config))
            return config.map(object => this.minifyKeys(object));
        if (config !== Object(config))
            return config;
        return Object.fromEntries(Object.entries(config).map(([originalKey, originalValue]) => [
            KEY_MAP[originalKey] || originalKey,
            this.minifyKeys(originalValue)
        ]));
    }
    /** Expand keys recursively using REVERSE_KEY_MAP */
    expandKeys(config) {
        if (Array.isArray(config))
            return config.map(o => this.expandKeys(o));
        if (config !== Object(config))
            return config;
        return Object.fromEntries(Object.entries(config).map(([originalKey, originalValue]) => [
            REVERSE_KEY_MAP[originalKey] || originalKey,
            this.expandKeys(originalValue)
        ]));
    }
    /**
     * Remove properties from `config` that match `base` so only changed values remain.
     */
    removeDefaults(config) {
        const result = {};
        for (const key in config) {
            if (!(key in DATA_EXPLORER_BASE_CONFIG) || DATA_EXPLORER_BASE_CONFIG[key] !== config[key]) {
                result[key] = config[key];
            }
        }
        return result;
    }
    /**
     * Cleans up __target objects to only keep id and name and remove the rest.
     */
    cleanUpTargetObject(config) {
        const configCopy = { ...config };
        // Cleanup the datapoints array
        if (Array.isArray(configCopy.datapoints)) {
            configCopy.datapoints = configCopy.datapoints.map((dp) => {
                if (dp && typeof dp === 'object' && dp.__target && typeof dp.__target === 'object') {
                    const { id, name } = dp.__target;
                    const target = {};
                    if (id !== undefined)
                        target.id = id;
                    if (name !== undefined)
                        target.name = name;
                    return { ...dp, __target: target };
                }
                return dp;
            });
        }
        // Cleanup the alarmEventsConfig array
        if (Array.isArray(configCopy.alarmsEventsConfigs)) {
            configCopy.alarmsEventsConfigs = configCopy.alarmsEventsConfigs.map((a) => {
                if (a && typeof a === 'object' && a.__target && typeof a.__target === 'object') {
                    const { id, name } = a.__target;
                    const target = {};
                    if (id !== undefined)
                        target.id = id;
                    if (name !== undefined)
                        target.name = name;
                    return { ...a, __target: target };
                }
                return a;
            });
        }
        return configCopy;
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WorkspaceConfigurationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WorkspaceConfigurationService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WorkspaceConfigurationService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }] });

class DataExplorerService {
    constructor() {
        this.inventory = inject(InventoryService);
        this.contextDashboardService = inject(ContextDashboardService);
        this.workspaceConfigurationService = inject(WorkspaceConfigurationService);
        this.moChunkLoader = inject(MOChunkLoaderService);
        this.datapointSyncService = inject(DatapointSyncService);
        this.router = inject(Router);
        this.maxNumberOfManagedObjectsPerRequest = 50;
    }
    async fetchReportDashboard(reportId) {
        return (await this.inventory.list({
            pageSize: 1,
            query: `has('c8y_Dashboard!name!report_${reportId}')`
        })).data[0];
    }
    async fetchContextDashboard(dashboardId, contextAsset) {
        const context = contextAsset.c8y_isDevice
            ? ContextDashboardType.Device
            : ContextDashboardType.Group;
        return firstValueFrom(this.contextDashboardService.getDashboard$(dashboardId, [context]));
    }
    async loadManagedObjectsInChunks(uniqIds) {
        const { results, errors } = await this.moChunkLoader.processInChunks(uniqIds, this.maxNumberOfManagedObjectsPerRequest, ids => this.loadAChunkOfManagedObjects(ids));
        return { result: results, errors };
    }
    /**
     * Navigate to datapoint explorer with given config.
     * The goal of this method is to navigate to the data explorer with a provided config from any other application.
     * @param config Configuration to use
     * @param label Label to be displayed for the configuration
     * @param id ID for the configuration
     */
    navigateToDataExplorer(config, label, id) {
        const url = this.getUrlForConfig(config, label, id);
        this.router.navigateByUrl(url);
    }
    /**
     * Generate a URL for the datapoint explorer with the given config.
     * The goal of this method is to generate a shareable link to the data explorer.
     * @param config Configuration to use
     * @param label Label to be displayed for the configuration
     * @param id ID for the configuration
     * @returns The generated URL
     */
    getUrlForConfig(config, label, id) {
        return this.workspaceConfigurationService.generateExplorerLink(config, label, id);
    }
    processAlarmEventConfigs(config) {
        const firstTarget = config.alarmsEventsConfigs.find(ae => ae.__target)?.__target;
        config.alarmsEventsConfigs = config.alarmsEventsConfigs.map((ae, index) => {
            if (ae.__active === undefined)
                ae.__active = true;
            if (!ae.__target && firstTarget)
                ae.__target = firstTarget;
            if (!ae.color)
                ae.color = this.generateColor(index);
            return ae;
        });
    }
    processDatapoints(config) {
        const firstTarget = config.datapoints.find(dp => dp.__target)?.__target;
        config.datapoints = config.datapoints.map((dp, index) => {
            // Default __active
            if (dp.__active === undefined)
                dp.__active = true;
            if (!dp.__target && firstTarget)
                dp.__target = firstTarget;
            if (!dp.color && !dp.__template)
                dp.color = this.generateColor(index);
            if (!dp.label)
                dp.label = `${dp.fragment} -> ${dp.series}`;
            return dp;
        });
    }
    /**
     * Generates a color from a fixed palette based on the index.
     * Used to assign colors to alarm/event configs in the UI.
     */
    generateColor(index) {
        // Simple palette, can expand
        const palette = ['#c87d33', '#8c145f', '#8cd7fd', '#59a036', '#fb00ff', '#8d4c22', '#fbb2d7'];
        return palette[index % palette.length];
    }
    async loadAndAssignManagedObjects(config, uniqueIds) {
        const managedObjectsResult = await this.loadManagedObjectsInChunks([...uniqueIds]);
        const managedObjects = managedObjectsResult.result;
        const errors = managedObjectsResult.errors;
        config.datapoints = this.datapointSyncService.assignUpdatedValues(config.datapoints, managedObjects, errors);
    }
    async loadAChunkOfManagedObjects(uniqIds) {
        return this.moChunkLoader.loadAChunkOfManagedObjectsBase(uniqIds, this.inventory, this.maxNumberOfManagedObjectsPerRequest, id => this.moChunkLoader.getStatusDetails(id));
    }
    static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DataExplorerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
    static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DataExplorerService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: DataExplorerService, decorators: [{
            type: Injectable,
            args: [{
                    providedIn: 'root'
                }]
        }] });

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

export { DataExplorerService, DatapointExplorerModule, canActivateDatapointExplorer };
//# sourceMappingURL=c8y-ngx-components-datapoint-explorer.mjs.map
