import { readIGXB, createIGXB } from "./IGXB";
import Logger from "./Logger";
import { ArticleList } from "./Utilities";
const buildObjectTree = (items) => {
    const articlesByComId = new Map();
    const roots = [];
    for (const item of items) {
        const result = {
            ...item,
            children: [],
            originalItem: item,
        };
        articlesByComId.set(item.comId, result);
        if (item.parent === undefined) {
            roots.push(result);
        }
    }
    for (const article of items) {
        if (article.parent === undefined) {
            continue;
        }
        const parent = articlesByComId.get(article.parent);
        parent.children.push(articlesByComId.get(article.comId));
    }
    return roots;
};
const restoreFields = ["placement", "data"];
const applyRestoreFields = (restoreData, createdData) => {
    for (const key of restoreFields) {
        if (restoreData[key] !== undefined) {
            createdData.originalItem[key] = restoreData[key];
        }
    }
    // TODO: should match articles:
    // 1. by com id
    // 2. by article id
    // 3. by remaining index
    for (let index = 0; index < restoreData.children.length; index++) {
        applyRestoreFields(restoreData.children[index], createdData.children[index]);
    }
};
export const createProjectManager = (serviceManager, articleQuery, articleManager, operationResultHandler, hooks, registry, connectionManager, localizationManager) => {
    const restoreArticles = async (articles) => {
        const results = [];
        const processArticle = async (article) => {
            const operator = serviceManager.getOperatorForSourceId(article.source);
            const operationResults = (await operator.restoreArticle(article));
            const [mainResult] = buildObjectTree(operationResults);
            applyRestoreFields(article, mainResult);
            results.push(...operationResults);
        };
        const articleTree = buildObjectTree(articles);
        await Promise.all(articleTree.map(processArticle));
        return results;
    };
    const restoreFromArticleList = async (articles, catalogRegistrator) => {
        if (catalogRegistrator !== undefined) {
            await catalogRegistrator.loadCatalogs(Array.from(new Set(articles.map(article => article.source))), registry, connectionManager, localizationManager);
        }
        const results = await restoreArticles(articles);
        return operationResultHandler.processOperationResults(results);
    };
    const saveAsArticleList = async () => {
        await hooks.beforeSave.emit();
        return articleQuery.getAllArticles();
    };
    const restoreFromIGXB = async (data, { catalogRegistrator, emptySession } = {}) => {
        const articles = readIGXB(data);
        if (catalogRegistrator !== undefined) {
            await catalogRegistrator.loadCatalogs(Array.from(new Set(articles.map(article => article.source))), registry, connectionManager, localizationManager);
        }
        let serviceRestoreResults;
        if (emptySession) {
            await articleManager.deleteArticles(ArticleList.convertToComIds(articleQuery.getFilteredArticles(article => article.parent === undefined)));
        }
        if (data.SessionInfo !== undefined) {
            serviceRestoreResults = (await restoreServices(data.SessionInfo, (sourceId, error) => Logger.warn(`Could not restore session for source "${sourceId}": ${error}`), catalogRegistrator));
            for (const result of serviceRestoreResults) {
                const articleDataIndex = articles.findIndex(article => article.comId === result.comId);
                if (articleDataIndex === -1) {
                    continue;
                }
                const article = articles[articleDataIndex];
                // copy all instance relevant properties from the igxb article data (placement and data)
                for (const field of restoreFields) {
                    result[field] = article[field];
                }
                // remove service restored articles from articles list, so they won't be recreated
                articles.splice(articleDataIndex, 1);
            }
        }
        else {
            serviceRestoreResults = [];
        }
        const allRestoreResult = [
            ...serviceRestoreResults,
            ...(await restoreArticles(articles)),
        ];
        operationResultHandler.processOperationResults(allRestoreResult);
    };
    const saveAsIGXB = async (options = {}) => {
        await hooks.beforeSave.emit();
        const igxb = createIGXB(articleQuery, options);
        let serviceInfo;
        if (options.useServiceFunctions) {
            serviceInfo = await saveFromServices();
        }
        return {
            ...igxb,
            SessionInfo: serviceInfo,
        };
    };
    const restoreServices = async (data, onSourceRestoreFailure, catalogRegistrator) => {
        const sourceIds = Object.getOwnPropertyNames(data);
        if (catalogRegistrator !== undefined) {
            await catalogRegistrator.loadCatalogs(sourceIds, registry, connectionManager, localizationManager);
        }
        const results = [];
        await Promise.all(sourceIds.map(async (sourceId) => {
            try {
                const operator = serviceManager.getOperatorForSourceId(sourceId);
                if (!operator.is("sessionOperator")) {
                    throw new Error(`Can not restore from registered service for "${sourceId}" because its not a sessionOperator`);
                }
                const serviceData = data[sourceId];
                const sourceResults = await operator.restore(serviceData);
                results.push(...sourceResults);
            }
            catch (err) {
                onSourceRestoreFailure(sourceId, err);
            }
        }));
        return results;
    };
    const restoreFromServices = async (data, catalogRegistrator) => {
        const results = await restoreServices(data, (sourceId, error) => {
            throw error;
        }, catalogRegistrator);
        return operationResultHandler.processOperationResults(results);
    };
    const saveFromServices = async () => {
        const registeredSources = serviceManager.getRegisteredSourceIds();
        const serviceData = {};
        await Promise.all(registeredSources.map(async (sourceId) => {
            const operator = serviceManager.getOperatorForSourceId(sourceId);
            if (!operator.is("sessionOperator")) {
                return;
            }
            serviceData[sourceId] = await operator.save();
        }));
        return serviceData;
    };
    return {
        restoreFromArticleList,
        saveAsArticleList,
        restoreFromIGXB,
        restoreFromServices,
        saveAsIGXB,
        saveFromServices: async () => {
            await hooks.beforeSave.emit();
            return saveFromServices();
        },
    };
};
