import { hookComputedProperty } from '@c8y/ngx-components/asset-properties';
import { inject, Injector } from '@angular/core';
import { MeasurementService, InventoryService, AlarmService, EventService, OperationService } from '@c8y/client';
import { MeasurementRealtimeService, DatePipe, ManagedObjectRealtimeService, AlarmRealtimeService, EventRealtimeService, OperationRealtimeService } from '@c8y/ngx-components';
import { from, map, filter, startWith, take, switchMap, distinctUntilChanged, pairwise, share, NEVER, scan, tap, of, catchError, combineLatest, merge } from 'rxjs';
import { gettext } from '@c8y/ngx-components/gettext';

class LastMeasurementStrategy {
    constructor(config, injector) {
        this.config = config;
        this.measurementService = injector.get(MeasurementService);
        this.measurementRealtime = injector.get(MeasurementRealtimeService);
        this.datePipe = injector.get(DatePipe);
        this.datapoint = config.dp.filter(dp => dp.__active)[0];
    }
    fetchCurrentValue() {
        const measurementFilter = {
            valueFragmentSeries: this.datapoint.series,
            valueFragmentType: this.datapoint.fragment,
            pageSize: 1,
            revert: true,
            dateFrom: '1970-01-01',
            source: this.datapoint.__target.id
        };
        return from(this.measurementService.list(measurementFilter)).pipe(map(({ data }) => data[0]), filter(measurement => !!measurement), map(measurement => this.formatMeasurement(measurement)));
    }
    createRealtimeStream(initialValue) {
        return this.measurementRealtime
            .onCreateOfSpecificMeasurement$(this.datapoint.fragment, this.datapoint.series, this.datapoint.__target)
            .pipe(map(measurement => this.formatMeasurement(measurement)), startWith(initialValue));
    }
    formatMeasurement(measurement) {
        const resultType = this.config?.resultType;
        const fragment = this.datapoint.fragment;
        const series = this.datapoint.series;
        if (resultType === 1) {
            return measurement[fragment][series].value;
        }
        else if (resultType === 2) {
            return measurement[fragment][series].value + ' ' + measurement[fragment][series].unit;
        }
        else if (resultType === 3) {
            const date = this.datePipe.transform(new Date(measurement.time), 'short');
            return `${date} | ${measurement[fragment][series].value} ${measurement[fragment][series].unit}`;
        }
        else if (resultType === 4) {
            return measurement[fragment][series];
        }
        return '';
    }
}

/**
 * Generic handler for realtime values following the three modes pattern
 * Closed for modification but open for extension through strategies
 */
class RealtimeValueHandler {
    constructor(strategy, config) {
        this.strategy = strategy;
        this.config = {
            refetchOnResume: true,
            preserveValueOnPause: true
        };
        if (config) {
            this.config = { ...this.config, ...config };
        }
    }
    /**
     * Creates an Observable based on the metadata mode
     */
    getValue(metadata = { mode: 'realtime' }) {
        if (metadata.mode === 'singleValue') {
            return this.strategy.fetchCurrentValue().pipe(take(1));
        }
        if (metadata.mode === 'realtime' && !metadata.realtimeControl$) {
            return this.handleUncontrolledRealtime();
        }
        if (metadata.mode === 'realtime' && metadata.realtimeControl$) {
            return this.handleControlledRealtime(metadata.realtimeControl$);
        }
    }
    handleUncontrolledRealtime() {
        return this.strategy
            .fetchCurrentValue()
            .pipe(switchMap(initialValue => this.strategy.createRealtimeStream(initialValue)));
    }
    handleControlledRealtime(control$) {
        const controlWithPrevious$ = control$.pipe(distinctUntilChanged(), startWith(null), pairwise(), share());
        return controlWithPrevious$.pipe(switchMap(([previous, current]) => {
            if (!current) {
                // Realtime is disabled
                if (previous === null) {
                    // Initial emission while disabled
                    return this.strategy.fetchCurrentValue().pipe(take(1));
                }
                else if (this.config.preserveValueOnPause) {
                    // Was previously enabled - preserve last value
                    return NEVER;
                }
                else {
                    // Don't preserve value - fetch current
                    return this.strategy.fetchCurrentValue().pipe(take(1));
                }
            }
            else {
                // Realtime is enabled
                if (this.config.refetchOnResume || previous === null) {
                    // Re-fetch current value and start streaming
                    return this.strategy
                        .fetchCurrentValue()
                        .pipe(switchMap(currentValue => this.strategy.createRealtimeStream(currentValue)));
                }
                else {
                    // Continue with realtime stream without re-fetching
                    return this.strategy.createRealtimeStream(null);
                }
            }
        }));
    }
}

/**
 * Creates an Observable that tracks the latest measurement value for a specific datapoint.
 * Combines initial server fetch with real-time measurement updates.
 *
 * @param config - Measurement configuration (datapoint, result type, etc.)
 * @param metadata - Configuration controlling the behavior of the function
 * @returns Observable<string> - Stream of measurement string values
 */
function lastMeasurementValue(config, metadata = { mode: 'realtime' }) {
    const injector = inject(Injector);
    const strategy = new LastMeasurementStrategy(config, injector);
    const handler = new RealtimeValueHandler(strategy);
    return handler.getValue(metadata);
}

const lastMeasurement = {
    name: 'lastMeasurement',
    contextType: ['device', 'asset', 'group'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                lastMeasurement: {
                    label: 'Last measurement',
                    type: 'string'
                }
            }
        },
        name: 'lastMeasurement',
        label: gettext('Last measurement'),
        type: 'string',
        config: { dp: [], resultType: 1 },
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    loadConfigComponent: () => import('./c8y-ngx-components-computed-asset-properties-last-measurement-config.component-DkrSvf9F.mjs').then(m => m.ComputedPropertyLastMeasurementConfigComponent),
    value: ({ config }) => {
        return lastMeasurementValue(config);
    }
};

class ChildCountStrategy {
    constructor(asset, childType, injector) {
        this.asset = asset;
        this.childType = childType;
        this.inventoryService = injector.get(InventoryService);
        this.moRealtimeService = injector.get(ManagedObjectRealtimeService);
    }
    fetchCurrentValue() {
        return from(this.inventoryService.detail(this.asset.id, { withChildren: true })).pipe(map(resp => resp?.data?.[this.childType]?.references?.length || 0));
    }
    createRealtimeStream(initialValue) {
        return this.moRealtimeService.onAll$(this.asset.id).pipe(map(resp => resp?.data?.[this.childType]?.references?.length || 0), startWith(initialValue));
    }
}

/**
 * Shared function that tracks the count of child items (devices or assets) for a specific managed object.
 * Supports real-time updates when child items are added or removed.
 *
 * @param asset - The managed object to track child items for
 * @param childType - Type of child items ('childDevices' or 'childAssets')
 * @param metadata - Configuration controlling the behavior of the function
 * @returns Observable<number> - Stream of child items count values
 */
function childCountValue(asset, metadata = { mode: 'realtime' }, childType) {
    const injector = inject(Injector);
    const strategy = new ChildCountStrategy(asset, childType, injector);
    const handler = new RealtimeValueHandler(strategy);
    return handler.getValue(metadata);
}

const childAssetsCount = {
    name: 'childAssetsCount',
    contextType: ['group', 'asset'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                childAssetsCount: {
                    label: 'Number of child assets',
                    type: 'number'
                }
            }
        },
        name: 'childAssetsCount',
        label: gettext('Number of child assets'),
        type: 'number',
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    value: ({ context, metadata }) => childAssetsCountValue(context, metadata)
};
/**
 * Creates an Observable that tracks the count of child assets for a specific asset.
 * Supports real-time updates when child assets are added or removed.
 *
 * @param asset - The managed object (asset) to track child assets for
 * @param metadata - Configuration controlling the behavior of the function
 * @returns Observable<number> - Stream of child assets count values
 */
function childAssetsCountValue(asset, metadata = { mode: 'realtime' }) {
    return childCountValue(asset, metadata, 'childAssets');
}

/**
 * Base class for count-based strategies
 * Provides common accumulation logic
 */
class CountStrategyBase {
    createRealtimeStream(initialValue) {
        return this.getRealtimeIncrement$().pipe(scan((count, increment) => count + increment, initialValue), startWith(initialValue));
    }
}

class AlarmCountStrategy extends CountStrategyBase {
    constructor(config, asset, dateFrom, injector) {
        super();
        this.config = config;
        this.asset = asset;
        this.dateFrom = dateFrom;
        this.alarmService = injector.get(AlarmService);
        this.alarmRealtimeService = injector.get(AlarmRealtimeService);
        this.startTime = new Date();
    }
    fetchCurrentValue() {
        const severities = Object.keys(this.config.severities || {}).filter(key => this.config.severities[key]);
        const filters = {
            source: this.asset.id,
            dateFrom: this.dateFrom.toISOString(),
            type: this.config.type,
            pageSize: 1,
            withTotalElements: true,
            ...(severities.length && { severity: severities.join(',') })
        };
        return from(this.alarmService.list(filters)).pipe(map(resp => resp?.paging?.totalElements || 0), tap(() => (this.startTime = new Date())) // Update start time after fetch
        );
    }
    getRealtimeIncrement$() {
        return this.alarmRealtimeService.onAll$(this.asset.id).pipe(map(({ data }) => data), filter(alarm => alarm.type === this.config.type &&
            this.config.severities[alarm.severity] &&
            new Date(alarm.creationTime) > this.startTime), tap(() => (this.startTime = new Date())), map(() => 1) // Each matching alarm increments by 1
        );
    }
}

/**
 * Creates an Observable that tracks alarm count for a specific asset.
 * When real-time is paused and resumed, it re-fetches the current count from server
 * to account for alarms that occurred during the pause.
 *
 * @param config - Alarm filtering configuration (type, severities, etc.)
 * @param asset - The managed object (device/asset) to track alarms for
 * @param dateFrom - Start date for counting alarms
 * @param metadata - Configuration controlling the behavior of the function
 * @returns Observable<number> - Stream of alarm count values
 */
function alarmCountValue(config, asset, dateFrom, metadata = { mode: 'realtime' }) {
    const injector = inject(Injector);
    const strategy = new AlarmCountStrategy(config, asset, dateFrom, injector);
    const handler = new RealtimeValueHandler(strategy);
    return handler.getValue(metadata);
}

const alarmCount3Months = {
    name: 'alarmCount3Months',
    contextType: ['device', 'group', 'asset'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                alarmCount3Months: {
                    label: 'Alarm count 3 months',
                    type: 'number'
                }
            }
        },
        name: 'alarmCount3Months',
        label: gettext('Alarm count 3 months'),
        type: 'number',
        config: { type: '' },
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    loadConfigComponent: () => import('./c8y-ngx-components-computed-asset-properties-alarm-count-config.component-SA0syLy7.mjs').then(m => m.ComputedPropertyAlarmCountConfigComponent),
    value: ({ config, context, metadata }) => alarmCount3MonthsValue(config, context, metadata)
};
function alarmCount3MonthsValue(config, asset, metadata) {
    const threeMonthsAgo = new Date();
    threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
    return alarmCountValue(config, asset, threeMonthsAgo, metadata);
}

const alarmCountToday = {
    name: 'alarmCountToday',
    contextType: ['device', 'group', 'asset'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                alarmCountToday: {
                    label: 'Alarm count today',
                    type: 'number'
                }
            }
        },
        name: 'alarmCountToday',
        label: gettext('Alarm count today'),
        type: 'number',
        config: { type: '' },
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    loadConfigComponent: () => import('./c8y-ngx-components-computed-asset-properties-alarm-count-config.component-SA0syLy7.mjs').then(m => m.ComputedPropertyAlarmCountConfigComponent),
    value: ({ config, context, metadata }) => alarmCountTodayValue(config, context, metadata)
};
function alarmCountTodayValue(config, asset, metadata) {
    const oneDayAgo = new Date();
    oneDayAgo.setDate(oneDayAgo.getDate() - 1);
    return alarmCountValue(config, asset, oneDayAgo, metadata);
}

class EventCountStrategy extends CountStrategyBase {
    constructor(config, asset, dateFrom, injector) {
        super();
        this.config = config;
        this.asset = asset;
        this.dateFrom = dateFrom;
        this.eventService = injector.get(EventService);
        this.eventRealtimeService = injector.get(EventRealtimeService);
    }
    fetchCurrentValue() {
        const filters = {
            source: this.asset.id,
            dateFrom: this.dateFrom.toISOString(),
            type: this.config.type,
            pageSize: 1,
            withTotalElements: true
        };
        return from(this.eventService.list(filters)).pipe(map(resp => resp?.paging?.totalElements || 0));
    }
    getRealtimeIncrement$() {
        return this.eventRealtimeService.onAll$(this.asset.id).pipe(map(({ data }) => data), filter(event => event.type === this.config.type && new Date(event.time) >= this.dateFrom), map(() => 1));
    }
}

/**
 * Creates an Observable that tracks event count for a specific asset.
 * When real-time is paused and resumed, it re-fetches the current count from server
 * to account for events that occurred during the pause.
 *
 * @param config - Event filtering configuration (type, etc.)
 * @param asset - The managed object (device/asset) to track events for
 * @param dateFrom - Start date for counting events
 * @param metadata - Configuration controlling the behavior of the function
 * @returns Observable<number> - Stream of event count values
 */
function eventCountValue(config, asset, dateFrom, metadata = { mode: 'realtime' }) {
    const injector = inject(Injector);
    const strategy = new EventCountStrategy(config, asset, dateFrom, injector);
    const handler = new RealtimeValueHandler(strategy);
    return handler.getValue(metadata);
}

const eventCountToday = {
    name: 'eventCountToday',
    contextType: ['device', 'group', 'asset'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                eventCountToday: {
                    label: 'Event count today',
                    type: 'number'
                }
            }
        },
        name: 'eventCountToday',
        label: gettext('Event count today'),
        type: 'number',
        config: { type: '' },
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    loadConfigComponent: () => import('./c8y-ngx-components-computed-asset-properties-event-count-config.component-CaTb9cph.mjs').then(m => m.ComputedPropertyEventCountConfigComponent),
    value: ({ config, context, metadata }) => eventCountTodayValue(config, context, metadata)
};
function eventCountTodayValue(config, asset, metadata) {
    const today = new Date();
    today.setHours(0, 0, 0, 0);
    return eventCountValue(config, asset, today, metadata);
}

const eventCount3Months = {
    name: 'eventCount3Months',
    contextType: ['device', 'group', 'asset'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                eventCount3Months: {
                    label: 'Event count 3 months',
                    type: 'number'
                }
            }
        },
        name: 'eventCount3Months',
        label: gettext('Event count 3 months'),
        type: 'number',
        config: { type: '' },
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    loadConfigComponent: () => import('./c8y-ngx-components-computed-asset-properties-event-count-config.component-CaTb9cph.mjs').then(m => m.ComputedPropertyEventCountConfigComponent),
    value: ({ config, context, metadata }) => eventCount3MonthsValue(config, context, metadata)
};
function eventCount3MonthsValue(config, asset, metadata) {
    const threeMonthsAgo = new Date();
    threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3);
    return eventCountValue(config, asset, threeMonthsAgo, metadata);
}

const configurationSnapshot = {
    name: 'configurationSnapshot',
    contextType: ['device', 'asset'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                configurationSnapshot: {
                    label: 'Configuration snapshot',
                    type: 'string'
                }
            }
        },
        contextType: ['device', 'asset'],
        name: 'configurationSnapshot',
        label: gettext('Configuration snapshot'),
        type: 'string',
        config: { legacy: true },
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    loadConfigComponent: () => import('./c8y-ngx-components-computed-asset-properties-configuration-snapshot-config.component-BOmgJI14.mjs').then(m => m.ConfigurationSnapshotConfigComponent),
    value: ({ config, context }) => configurationSnapshotValue(config, context)
};
function configurationSnapshotValue(config, asset) {
    if (config.legacy) {
        const configId = asset.c8y_ConfigurationDump?.id;
        if (!configId) {
            return of(null);
        }
        const injector = inject(Injector);
        const inventoryService = injector.get(InventoryService);
        return from(inventoryService.detail(configId)).pipe(switchMap(({ data }) => {
            return of(data?.name);
        }));
    }
    else {
        const fragment = `c8y_Configuration_${config.type}`;
        return of(asset[fragment]?.name);
    }
}

const childDevicesCount = {
    name: 'childDevicesCount',
    contextType: ['group', 'device', 'asset'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                childDevicesCount: {
                    label: 'Number of child devices',
                    type: 'number'
                }
            }
        },
        name: 'childDevicesCount',
        label: gettext('Number of child devices'),
        type: 'number',
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    value: ({ context, metadata }) => childDevicesCountValue(context, metadata)
};
/**
 * Creates an Observable that tracks the count of child devices for a specific asset.
 * Supports real-time updates when child devices are added or removed.
 *
 * @param asset - The managed object (asset) to track child devices for
 * @param metadata - Configuration controlling the behavior of the function
 * @returns Observable<number> - Stream of child devices count values
 */
function childDevicesCountValue(asset, metadata = { mode: 'realtime' }) {
    return childCountValue(asset, metadata, 'childDevices');
}

class LastDeviceMessageStrategy {
    constructor(asset, injector) {
        this.asset = asset;
        this.measurementService = injector.get(MeasurementService);
        this.measurementRealtime = injector.get(MeasurementRealtimeService);
        this.eventService = injector.get(EventService);
        this.eventRealtimeService = injector.get(EventRealtimeService);
        this.alarmService = injector.get(AlarmService);
        this.alarmRealtimeService = injector.get(AlarmRealtimeService);
        this.operationService = injector.get(OperationService);
        this.operationRealtimeService = injector.get(OperationRealtimeService);
        this.startTime = new Date();
    }
    fetchCurrentValue() {
        const fetchFilter = {
            source: this.asset.id,
            pageSize: 1,
            revert: true
        };
        const fetchLatestMeasurement = () => {
            return from(this.measurementService.list(fetchFilter)).pipe(map(resp => resp.data?.[0]?.time || null), catchError(() => of(null)));
        };
        const fetchLatestEvent = () => {
            return from(this.eventService.list(fetchFilter)).pipe(map(resp => resp.data?.[0]?.time || null), catchError(() => of(null)));
        };
        const fetchLatestAlarm = () => {
            return from(this.alarmService.list(fetchFilter)).pipe(map(resp => resp.data?.[0]?.time || null), catchError(() => of(null)));
        };
        const fetchLatestOperation = () => {
            return from(this.operationService.list(fetchFilter)).pipe(map(resp => resp.data?.[0]?.creationTime || null), catchError(() => of(null)));
        };
        return combineLatest([
            fetchLatestMeasurement(),
            fetchLatestEvent(),
            fetchLatestAlarm(),
            fetchLatestOperation()
        ]).pipe(map(timestamps => {
            const validTimestamps = timestamps.filter(Boolean);
            if (validTimestamps.length === 0) {
                return null;
            }
            const latest = validTimestamps.reduce((latest, current) => {
                return new Date(current) > new Date(latest) ? current : latest;
            });
            // Update start time for realtime filtering
            this.startTime = new Date();
            return latest;
        }));
    }
    createRealtimeStream(initialValue) {
        const measurementStream$ = this.measurementRealtime.onAll$(this.asset.id).pipe(map(({ data }) => data.time), filter(time => new Date(time) > this.startTime));
        const eventStream$ = this.eventRealtimeService.onAll$(this.asset.id).pipe(map(({ data }) => data.time), filter(time => new Date(time) > this.startTime));
        const alarmStream$ = this.alarmRealtimeService.onAll$(this.asset.id).pipe(map(({ data }) => data.time), filter(time => time && new Date(time) > this.startTime));
        const operationStream$ = this.operationRealtimeService.onAll$(this.asset.id).pipe(map(({ data }) => data.creationTime), filter(time => time && new Date(time) > this.startTime));
        return merge(measurementStream$, eventStream$, alarmStream$, operationStream$).pipe(scan((latestTimestamp, newTimestamp) => {
            return new Date(newTimestamp) > new Date(latestTimestamp) ? newTimestamp : latestTimestamp;
        }, initialValue), startWith(initialValue));
    }
}

/**
 * Gets the latest timestamp from events, alarms, measurements, and operations for a device.
 * Returns the most recent timestamp across all these sources.
 *
 * @param asset - The managed object (device/asset) to track
 * @param metadata - Configuration controlling the behavior of the function
 * @returns Observable<string> - Stream of latest timestamp values
 */
function getLastDeviceMessage(asset, metadata = { mode: 'realtime' }) {
    const injector = inject(Injector);
    const strategy = new LastDeviceMessageStrategy(asset, injector);
    const handler = new RealtimeValueHandler(strategy);
    return handler.getValue(metadata);
}

const lastDeviceMessage = {
    name: 'lastDeviceMessage',
    contextType: ['device'],
    prop: {
        c8y_JsonSchema: {
            properties: {
                lastDeviceMessage: {
                    label: 'Last device message',
                    type: 'string'
                }
            }
        },
        name: 'lastDeviceMessage',
        label: gettext('Last device message'),
        printFormat: 'datetime',
        type: 'string',
        computed: true,
        isEditable: false,
        isStandardProperty: true
    },
    value: ({ context }) => {
        return getLastDeviceMessage(context);
    }
};

const computedAssetPropertiesProviders = [
    AlarmRealtimeService,
    EventRealtimeService,
    MeasurementRealtimeService,
    OperationRealtimeService,
    ManagedObjectRealtimeService,
    hookComputedProperty([
        lastMeasurement,
        lastDeviceMessage,
        childAssetsCount,
        childDevicesCount,
        alarmCount3Months,
        alarmCountToday,
        eventCountToday,
        eventCount3Months,
        configurationSnapshot
    ])
];

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

export { computedAssetPropertiesProviders };
//# sourceMappingURL=c8y-ngx-components-computed-asset-properties.mjs.map
