import pluralize from 'pluralize';
import { reduce } from 'rambda';
import { exists } from '../../../shared/utils.js';
import { noRCAValue } from '../../../shared/work-item-utils.js';
import { unique } from '../../utils.js';
import workItemIconSvgs from '../../work-item-icon-svgs.js';
import azure from '../network/azure.js';
import { queryForCollectionWorkItems } from '../queries/collection-work-items.js';
import { getOverviewData } from './work-item-overview.js';
import { workItemTypeId, workItemTypeIconColor } from './work-item-utils.js';
const queryName = 'features-and-bugs';
const workItemTypesByType = reduce((acc, workItemType) => {
    acc[workItemType.name.toLowerCase()] = workItemType;
    return acc;
}, {});
const getWorkItemTypesForCollection = (getWorkItemTypes, collection) => Promise.all(collection.projects.map(async (project) => ({
    [project.name.toLowerCase()]: workItemTypesByType(await getWorkItemTypes(collection.name, project.name)),
}))).then(reduce((acc, cur) => Object.assign(acc, cur), {}));
const workItemsById = (getCollectionWorkItems, collection) => async (workItemTreeForCollection) => {
    const ids = unique(workItemTreeForCollection.workItemRelations.flatMap(wir => {
        var _a, _b;
        return [
            (_a = wir.source) === null || _a === void 0 ? void 0 : _a.id,
            (_b = wir.target) === null || _b === void 0 ? void 0 : _b.id,
        ];
    })).filter(exists);
    const workItems = await getCollectionWorkItems(collection.name, ids, queryName);
    return workItems.reduce((acc, wi) => {
        // Not immutable to avoid memory trashing
        acc[wi.id] = wi;
        return acc;
    }, {});
};
const sourceByWorkItemId = (workItemRelations) => workItemRelations.reduce((acc, wir) => {
    var _a;
    if (wir.target) {
        acc[wir.target.id] = [...(acc[wir.target.id] || []), (_a = wir.source) === null || _a === void 0 ? void 0 : _a.id].filter(exists);
    }
    return acc;
}, {});
const sourcesUntilMatch = (sourcesByWorkItemId, workItemsById, projectConfig) => {
    const recurse = (workItemId, seen) => {
        if (projectConfig.workitems.groupUnder.includes(workItemsById[workItemId].fields['System.WorkItemType'])) {
            return [workItemId];
        }
        const sources = sourcesByWorkItemId[workItemId];
        if (!sources)
            return [];
        if (!sources.length)
            return [];
        return sources
            .flatMap(source => {
            if (seen.includes(source))
                return [];
            seen.push(source);
            if (projectConfig.workitems.groupUnder.includes(workItemsById[source].fields['System.WorkItemType'])) {
                return [source];
            }
            return recurse(source, seen);
        })
            .filter(exists);
    };
    return (workItemId) => recurse(workItemId, []);
};
const createWorkItemTypeGetter = (workItemTypesForCollection) => (workItem) => {
    const projectName = workItem.fields['System.TeamProject'];
    const workItemTypeName = workItem.fields['System.WorkItemType'];
    return workItemTypesForCollection[projectName.toLowerCase()][workItemTypeName.toLowerCase()];
};
const rca = (collectionConfig, workItem, workItemType) => {
    var _a;
    const match = (_a = collectionConfig.workitems.types) === null || _a === void 0 ? void 0 : _a.find(({ type }) => type === workItemType.name);
    return {
        rca: ((match === null || match === void 0 ? void 0 : match.rootCause) || []).map(field => workItem.fields[field] || noRCAValue),
    };
};
const track = (collectionConfig, workItem, workItemType) => {
    var _a;
    const match = (_a = collectionConfig.workitems.types) === null || _a === void 0 ? void 0 : _a.find(({ type }) => type === workItemType.name);
    return { track: (match === null || match === void 0 ? void 0 : match.track) ? workItem.fields[match.track] : undefined };
};
const filterTags = (collectionConfig, workItem) => {
    var _a;
    if ((collectionConfig.workitems.filterBy || []).length === 0)
        return;
    const filterBy = (_a = collectionConfig.workitems.filterBy) === null || _a === void 0 ? void 0 : _a.map(filter => {
        const tags = filter.fields
            .flatMap(field => {
            const fieldValue = workItem.fields[field];
            if (fieldValue && filter.delimiter) {
                return fieldValue.split(filter.delimiter);
            }
            return fieldValue;
        })
            .filter(exists)
            .filter(x => x.length);
        if (tags.length === 0)
            return;
        return {
            label: filter.label,
            tags,
        };
    }).filter(exists);
    if (!filterBy)
        return;
    if (filterBy.length === 0)
        return;
    return { filterBy };
};
const uiWorkItemCreator = (collectionConfig, getWorkItemType) => (workItem) => {
    const workItemType = getWorkItemType(workItem);
    return {
        id: workItem.id,
        title: workItem.fields['System.Title'],
        url: workItem.url.replace('_apis/wit/workItems', '_workitems/edit'),
        typeId: workItemTypeId(workItemType),
        state: workItem.fields['System.State'],
        project: workItem.fields['System.TeamProject'],
        iterationPath: workItem.fields['System.IterationPath'],
        created: {
            on: workItem.fields['System.CreatedDate'].toISOString(),
        },
        updated: {
            on: workItem.fields['System.ChangedDate'].toISOString(),
        },
        env: collectionConfig.workitems.environmentField
            ? workItem.fields[collectionConfig.workitems.environmentField]
            : undefined,
        priority: workItem.fields['Microsoft.VSTS.Common.Priority'],
        severity: workItem.fields['Microsoft.VSTS.Common.Severity'],
        ...rca(collectionConfig, workItem, workItemType),
        ...filterTags(collectionConfig, workItem),
        ...track(collectionConfig, workItem, workItemType),
    };
};
const fireOffCollectionAPICalls = (config, collection) => {
    const { getCollectionWorkItemIdsForQuery, getWorkItemTypes, getCollectionWorkItems } = azure(config);
    const pGetWorkItemType = getWorkItemTypesForCollection(getWorkItemTypes, collection).then(createWorkItemTypeGetter);
    const workItemIdTree = workItemsById(getCollectionWorkItems, collection);
    const workItemDetails = async () => {
        const workItemTreeForCollection = await getCollectionWorkItemIdsForQuery(collection.name, queryForCollectionWorkItems(config.azure.queryFrom, collection), queryName);
        const workItems = await workItemIdTree(workItemTreeForCollection);
        const { workItemRelations } = workItemTreeForCollection;
        const reducedWorkItemIds = new Set(workItemRelations.flatMap(wir => { var _a, _b; return [(_a = wir.source) === null || _a === void 0 ? void 0 : _a.id, (_b = wir.target) === null || _b === void 0 ? void 0 : _b.id]; }).filter(exists));
        const reducedWorkItems = Object.entries(workItems).reduce((acc, [workItemId, workItem]) => {
            if (reducedWorkItemIds.has(Number(workItemId))) {
                acc[Number(workItemId)] = workItem;
            }
            return acc;
        }, {});
        return [workItemRelations, reducedWorkItems];
    };
    return Promise.all([pGetWorkItemType, workItemDetails()]);
};
export default (config) => (collection, workItemFieldsPromise) => {
    const pCollectionData = fireOffCollectionAPICalls(config, collection);
    return async (project) => {
        const [[getWorkItemType, [workItemRelations, workItemsById]], workItemFields] = await Promise.all([pCollectionData, workItemFieldsPromise]);
        const createUIWorkItem = uiWorkItemCreator(collection, getWorkItemType);
        const workItemsForProject = Object.values(workItemsById).filter(wi => wi.fields['System.TeamProject'] === project.name);
        const sourcesForWorkItem = sourcesUntilMatch(sourceByWorkItemId(workItemRelations), workItemsById, project);
        const topLevelItems = unique(workItemsForProject.flatMap(workItem => sourcesForWorkItem(workItem.id)));
        const workItemFieldsByReferenceName = workItemFields.reduce((acc, field) => {
            acc[field.referenceName] = field;
            return acc;
        }, {});
        const getFieldName = (f) => { var _a; return ((_a = workItemFieldsByReferenceName[f]) === null || _a === void 0 ? void 0 : _a.name) || undefined; };
        const { byId, types } = Object.entries(workItemsById).reduce((acc, [id, workItem]) => {
            var _a;
            acc.byId[Number(id)] = createUIWorkItem(workItem);
            const workItemType = getWorkItemType(workItem);
            const witId = workItemTypeId(workItemType);
            if (!acc.types[witId]) {
                const iconColor = workItemTypeIconColor(workItemType);
                const matchingWit = (_a = collection.workitems.types) === null || _a === void 0 ? void 0 : _a.find(wit => wit.type === workItemType.name);
                acc.types[witId] = {
                    name: [workItemType.name, pluralize(workItemType.name)],
                    color: workItemType.color,
                    icon: `data:image/svg+xml;utf8,${encodeURIComponent(workItemIconSvgs[workItemType.icon.id](iconColor))}`,
                    iconColor,
                    startDateFields: matchingWit === null || matchingWit === void 0 ? void 0 : matchingWit.startDate.map(getFieldName).filter(exists),
                    endDateFields: matchingWit === null || matchingWit === void 0 ? void 0 : matchingWit.endDate.map(getFieldName).filter(exists),
                    devCompleteFields: matchingWit === null || matchingWit === void 0 ? void 0 : matchingWit.devCompletionDate.map(getFieldName).filter(exists),
                    workCenters: ((matchingWit === null || matchingWit === void 0 ? void 0 : matchingWit.workCenters) || []).map(wc => ({
                        label: wc.label,
                        startDateField: wc.startDate.map(getFieldName).filter(exists),
                        endDateField: wc.endDate.map(getFieldName).filter(exists),
                    })),
                    groupLabel: matchingWit === null || matchingWit === void 0 ? void 0 : matchingWit.groupLabel,
                    rootCauseFields: matchingWit === null || matchingWit === void 0 ? void 0 : matchingWit.rootCause.map(getFieldName).filter(exists),
                };
            }
            return acc;
        }, { byId: {}, types: {} });
        const ids = workItemRelations.reduce((acc, wir) => {
            var _a;
            if (!wir.source)
                return acc;
            const parent = wir.source ? wir.source.id : 0;
            acc[parent] = unique([...(acc[parent] || []), (_a = wir.target) === null || _a === void 0 ? void 0 : _a.id].filter(exists));
            return acc;
        }, { 0: topLevelItems });
        return {
            analysedWorkItems: {
                byId,
                ids,
                types,
            },
            overview: getOverviewData(config, collection, workItemsForProject, byId, types, getWorkItemType, ids),
        };
    };
};
