import { createUuid } from '../../AdaptableState/Uuid';
import ObjectFactory from '../ObjectFactory';
export class TeamSharingService {
    constructor(adaptableApi) {
        this.adaptableApi = adaptableApi;
        this.dismissedNotifications = [];
        const teamSharingOptions = adaptableApi.optionsApi.getTeamSharingOptions();
        if (teamSharingOptions.updateInterval > 0) {
            // convert minutes to millis
            const updateInterval = teamSharingOptions.updateInterval * 60000;
            this.updateCheckTimerId = setInterval(() => {
                this.adaptableApi.teamSharingApi.checkForUpdates();
            }, updateInterval);
        }
    }
    buildCustomSharedEntity(Entity, Configuration) {
        const currentTime = Date.now();
        const currentUser = this.adaptableApi.optionsApi.getUserName();
        return {
            EntityType: 'customEntity',
            Entity,
            Uuid: Configuration.Uuid ?? createUuid(),
            Name: Configuration.Name,
            Description: Configuration.Description,
            Tags: Configuration.Tags,
            UserName: currentUser,
            Timestamp: currentTime,
            ChangedBy: currentUser,
            ChangedAt: currentTime,
        };
    }
    buildSharedEntityWithDependencies(adaptableObject, module, configuration) {
        const sharingUserName = this.adaptableApi.optionsApi.getUserName();
        const sharingTimestamp = new Date().getTime();
        const createdSharedEntities = [];
        this.createSharedEntity(adaptableObject, module, configuration, sharingUserName, sharingTimestamp, createdSharedEntities);
        return createdSharedEntities;
    }
    getSharedEntityDependants(sharedEntityId, sharedEntities) {
        return sharedEntities.filter((sharedEntity) => sharedEntity.EntityDependencyIds.includes(sharedEntityId));
    }
    updateActiveSharedEntity(changedAdaptableObject, userName, sharedEntities) {
        let activeSharedEntity = sharedEntities.find((sharedEntity) => sharedEntity.Type === 'Active' && sharedEntity.Entity.Uuid === changedAdaptableObject.Uuid);
        if (!activeSharedEntity) {
            // shared entity may have been removed in the meantime
            return [sharedEntities, []];
        }
        activeSharedEntity = {
            ...activeSharedEntity,
            Entity: changedAdaptableObject,
            ChangedAt: Date.now(),
            ChangedBy: userName,
            Revision: activeSharedEntity.Revision + 1,
        };
        // update EntityDependencyIds (references may have been removed or new ones added)
        const updatedEntityDependencyIds = [];
        const currentEntityDependencies = this.getSharedEntityDependencies(activeSharedEntity, sharedEntities);
        const newTeamSharingReferences = this.getTeamSharingReferences(changedAdaptableObject, activeSharedEntity.Module);
        const newReferenceAdaptableObjectIds = newTeamSharingReferences.map((reference) => reference.Reference.Uuid);
        // 1. add all the existing dependencies which are still referenced
        updatedEntityDependencyIds.push(...currentEntityDependencies
            .filter((entityDependency) => newReferenceAdaptableObjectIds.includes(entityDependency.Entity.Uuid))
            .map((entity) => entity.Uuid));
        // 2. check if there are new references which are not yet shared
        const freshReferences = newTeamSharingReferences.filter((reference) => !currentEntityDependencies
            .map((entityDependency) => entityDependency.Entity.Uuid)
            .includes(reference.Reference.Uuid));
        const newSharedEntityDependencies = [];
        freshReferences.forEach((reference) => {
            this.createSharedEntity(reference.Reference, reference.Module, {
                description: activeSharedEntity.Description,
                type: activeSharedEntity.Type,
            }, activeSharedEntity.UserName, Date.now(), newSharedEntityDependencies);
        });
        updatedEntityDependencyIds.push(...newSharedEntityDependencies.map((sharedEntity) => sharedEntity.Uuid));
        activeSharedEntity.EntityDependencyIds = updatedEntityDependencyIds;
        const updatedSharedEntities = sharedEntities.map((sharedEntity) => sharedEntity.Uuid === activeSharedEntity.Uuid ? activeSharedEntity : sharedEntity);
        updatedSharedEntities.push(...newSharedEntityDependencies);
        const updatedActiveEntities = [activeSharedEntity, ...newSharedEntityDependencies];
        return [updatedSharedEntities, updatedActiveEntities];
    }
    buildSharedEntityImportActions(importedSharedEntity) {
        let configOverwriteNeedsConfirmation = false;
        // either ADD or EDIT(in which case the user will need to confirm the overwrite)
        const getSharedEntityImportAction = (sharedEntity) => {
            const { Module, Entity } = sharedEntity;
            const importInfo = this.adaptableApi.internalApi
                .getModuleService()
                .getTeamSharingAction(Module);
            if (importInfo?.ModuleEntities.some((moduleEntity) => moduleEntity.Uuid === Entity.Uuid)) {
                configOverwriteNeedsConfirmation = true;
                return importInfo?.EditAction(Entity);
            }
            else {
                return importInfo?.AddAction(Entity);
            }
        };
        const existingSharedEntities = this.adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities();
        const sharedEntityDependencyTree = this.getSharedEntityDependencyTree(importedSharedEntity, existingSharedEntities);
        const importSteps = sharedEntityDependencyTree.map((sharedEntity) => ({
            sharedEntity,
            importAction: getSharedEntityImportAction(sharedEntity),
        }));
        return [importSteps, configOverwriteNeedsConfirmation];
    }
    getSharedEntityLocalAndRemoteRevisions(changedAdaptableObjectId, remoteSharedEntities) {
        const localRevision = this.adaptableApi.internalApi.getState().TeamSharing.ActiveSharedEntityMap[changedAdaptableObjectId]?.Revision ?? -1;
        const remoteRevision = remoteSharedEntities.find((sharedEntity) => sharedEntity.Entity.Uuid === changedAdaptableObjectId)?.Revision ?? -1;
        return [localRevision, remoteRevision];
    }
    getStaleActiveSharedEntities() {
        const sharedEntities = this.adaptableApi.teamSharingApi.getLoadedAdaptableSharedEntities();
        const activeEntities = this.adaptableApi.internalApi.getState().TeamSharing.ActiveSharedEntityMap;
        const result = {};
        sharedEntities
            .filter((sharedEntity) => sharedEntity.Type === 'Active')
            .forEach((sharedEntity) => {
            const importedRevision = activeEntities[sharedEntity.Entity.Uuid]?.Revision ?? Number.MAX_VALUE;
            if (sharedEntity.Revision > importedRevision) {
                result[sharedEntity.Uuid] = {
                    sharedEntity,
                    sharedRevision: sharedEntity.Revision,
                    importedRevision: activeEntities[sharedEntity.Entity.Uuid].Revision,
                };
            }
        });
        return result;
    }
    showUpdateNotifications() {
        const { updateNotification } = this.adaptableApi.optionsApi.getTeamSharingOptions();
        if (!updateNotification) {
            return;
        }
        Object.values(this.getStaleActiveSharedEntities()).forEach((sharedEntityActiveInfo) => {
            const notificationMessage = `Active share ${sharedEntityActiveInfo.sharedEntity.Module} has a new Revision: ${sharedEntityActiveInfo.sharedEntity.Revision}`;
            if (updateNotification === 'Alert' || updateNotification === 'AlertWithNotification') {
                this.showUpdateNotificationAlert(sharedEntityActiveInfo, notificationMessage);
            }
            if (updateNotification === 'SystemStatus') {
                this.adaptableApi.systemStatusApi.setInfoSystemStatus(`TeamSharing: ${notificationMessage}`);
            }
            this.adaptableApi.eventApi.internalApi.fireTeamSharingEntityChangedEvent(sharedEntityActiveInfo.sharedEntity);
        });
    }
    showUpdateNotificationAlert(sharedEntityActiveInfo, message) {
        const { updateNotification, showUpdateNotificationOncePerUpdate } = this.adaptableApi.optionsApi.getTeamSharingOptions();
        if (this.dismissedNotifications.some((notification) => notification.entityUuid === sharedEntityActiveInfo.sharedEntity.Uuid)) {
            return;
        }
        const buttons = [
            {
                Label: 'Ok',
                ButtonStyle: {
                    tone: 'neutral',
                    variant: 'raised',
                },
            },
            {
                Label: 'Import',
                Command: (context) => {
                    this.adaptableApi.teamSharingApi.importSharedEntry(sharedEntityActiveInfo.sharedEntity);
                },
                ButtonStyle: {
                    tone: 'info',
                    variant: 'raised',
                },
            },
        ];
        if (!showUpdateNotificationOncePerUpdate) {
            buttons.push({
                Label: 'Dismiss',
                ButtonStyle: {
                    tone: 'error',
                    variant: 'raised',
                },
                Command: () => {
                    this.dismissedNotifications.push({
                        entityUuid: sharedEntityActiveInfo.sharedEntity.Uuid,
                    });
                },
            });
        }
        const alert = {
            alertType: 'generic',
            header: 'Team Sharing Update',
            message: message,
            alertDefinition: {
                ...ObjectFactory.CreateEmptyAlertDefinition(),
                MessageType: 'Info',
                AlertProperties: {
                    DisplayNotification: updateNotification === 'AlertWithNotification',
                    NotificationDuration: 'always',
                },
                AlertForm: {
                    Buttons: buttons,
                },
            },
        };
        this.adaptableApi.alertApi.displayAdaptableAlert(alert);
        if (showUpdateNotificationOncePerUpdate) {
            this.dismissedNotifications.push({
                entityUuid: sharedEntityActiveInfo.sharedEntity.Uuid,
            });
        }
    }
    destroy() {
        clearTimeout(this.updateCheckTimerId);
    }
    createSharedEntity(adaptableObject, module, configuration, sharingUserName, sharingTimestamp, createdSharedEntities) {
        // create main shared entity
        const mainSharedEntity = {
            EntityType: 'adaptableEntity',
            Uuid: createUuid(),
            Entity: adaptableObject,
            EntityDependencyIds: [],
            Module: module,
            Description: configuration.description,
            UserName: sharingUserName,
            Timestamp: sharingTimestamp,
            ChangedBy: sharingUserName,
            ChangedAt: sharingTimestamp,
            Revision: 1,
            Type: configuration.type,
        };
        // load all dependencies (special cols, shared queries etc)
        const teamSharingDependencies = this.getTeamSharingReferences(adaptableObject, module);
        // for every dependency, create recursively the corresponding shared entities
        teamSharingDependencies.forEach((teamSharingDependency) => {
            const sharedEntityDependency = this.createSharedEntity(teamSharingDependency.Reference, teamSharingDependency.Module, configuration, sharingUserName, sharingTimestamp, createdSharedEntities);
            // update dependency IDs for the main shared entity
            mainSharedEntity.EntityDependencyIds.push(sharedEntityDependency.Uuid);
        });
        // add main shared entity to the accumulator
        createdSharedEntities.push(mainSharedEntity);
        return mainSharedEntity;
    }
    // returns the given rootSharedEntity with all its unique dependencies in their dependency order
    getSharedEntityDependencyTree(rootSharedEntity, allSharedEntities) {
        // using a map to handle the case where a SharedEntity occurs multiple times in the dependency tree
        const result = new Map();
        result.set(rootSharedEntity.Uuid, rootSharedEntity);
        rootSharedEntity.EntityDependencyIds.forEach((dependencyId) => {
            const dependencySharedEntity = allSharedEntities.find((sharedEntity) => sharedEntity.Uuid === dependencyId);
            // check for zombie dependencies (possible if state is inconsistent due to dirty reads)
            if (dependencySharedEntity) {
                this.getSharedEntityDependencyTree(dependencySharedEntity, allSharedEntities).forEach((childDependency) => {
                    // check if this dependency is not already present in the dependency tree
                    if (!result.has(childDependency.Uuid)) {
                        result.set(childDependency.Uuid, childDependency);
                    }
                });
            }
        });
        return Array.from(result.values());
    }
    getTeamSharingReferences(adaptableObject, module) {
        return (this.adaptableApi.internalApi
            .getModuleService()
            .getModuleById(module)
            ?.getTeamSharingReferences(adaptableObject) ?? []);
    }
    getSharedEntityDependencies(input, allSharedEntities) {
        return input.EntityDependencyIds.map((dependencyId) => allSharedEntities.find((sharedEntity) => sharedEntity.Uuid === dependencyId)).filter((dependencyEntity) => !!dependencyEntity);
    }
}
