import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { anyPass, applySpec, filter, length, map, pipe, sum } from 'rambda';
import { z } from 'zod';
import { collections, getConfig, configForProject } from '../config.js';
import { isBug, noGroup, isNewInTimeRange, isWIP, isWIPInTimeRange, totalCycleTime, totalWorkCenterTime, timeDifference, } from '../../shared/work-item-utils.js';
import { isAfter, queryPeriodDays, weekLimits, weeks } from '../utils.js';
import { mapObj } from '../../shared/utils.js';
const looksLikeDate = (value) => /\d{4}-[01]\d-[0-3]\dT[0-2](?:\d:[0-5]){2}\d(.*Z)/.test(value);
const parseDate = (_, value) => {
    if (typeof value !== 'string')
        return value;
    if (!looksLikeDate(value))
        return value;
    return new Date(value);
};
const parseFile = async (collection, project, file) => JSON.parse(await readFile(join(process.cwd(), 'data', collection, project, file), 'utf8'), parseDate);
const getOverview = async (collection, project) => (await parseFile(collection, project, 'overview.json'))
    .overview;
const concernedTypes = [
    (wit) => wit.name[0] === 'Feature',
    isBug,
    (wit) => wit.name[0] === 'User Story',
];
const computeTimeDifference = (workItemTimes) => (start, end) => (workItem) => {
    const wits = workItemTimes(workItem);
    const { [start]: startTime } = wits;
    const endTime = end ? wits[end] : undefined;
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    return timeDifference({ start: startTime, end: endTime });
};
const hasWorkItemCompleted = (workItemTimes) => (isMatchingDate) => (workItem) => {
    const { start, end } = workItemTimes(workItem);
    return Boolean(start && end && isMatchingDate(new Date(end)));
};
const organiseWorkItemsIntoGroups = (workItems) => workItems.reduce((acc, workItem) => {
    acc[workItem.typeId] = acc[workItem.typeId] || {};
    acc[workItem.typeId][workItem.groupId || noGroup] =
        acc[workItem.typeId][workItem.groupId || noGroup] || [];
    acc[workItem.typeId][workItem.groupId || noGroup].push(workItem);
    return acc;
}, {});
const analyseProjects = (collectionName, projects) => {
    const isInQueryPeriod = isAfter(`${queryPeriodDays(getConfig())} days`);
    return Promise.all(projects.map(async ({ name: project }) => {
        const overview = await getOverview(collectionName, project);
        const projectConfig = configForProject(collectionName, project);
        const workItemTimes = (wi) => overview.times[wi.id];
        const workItemType = (witId) => overview.types[witId];
        const computeTimeDifferenceBetween = computeTimeDifference(workItemTimes);
        const wasWorkItemCompletedIn = hasWorkItemCompleted(workItemTimes);
        const wasWorkItemCompletedInQueryPeriod = wasWorkItemCompletedIn(isInQueryPeriod);
        const wipWorkItems = isWIP(workItemTimes, (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.workitems.ignoreForWIP) || []);
        const isOfType = (isType) => (workItem) => isType(overview.types[workItem.typeId]);
        const leakage = isNewInTimeRange(workItemType, workItemTimes);
        const flowEfficiency = (() => {
            const tct = totalCycleTime(workItemTimes);
            const wct = totalWorkCenterTime(workItemTimes);
            return (wis) => {
                const total = tct(wis);
                if (total === 0) {
                    return { total: 0, wcTime: 0 };
                }
                const wcTime = wct(wis);
                return { total, wcTime };
            };
        })();
        const isWIPIn = ([, weekEnd]) => isWIPInTimeRange(workItemTimes, (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.workitems.ignoreForWIP) || [])((d, type) => {
            if (type === 'start')
                return d <= weekEnd;
            return d < weekEnd;
        });
        return {
            project,
            types: overview.types,
            groups: overview.groups,
            byType: pipe(filter(anyPass(concernedTypes.map(isOfType))), organiseWorkItemsIntoGroups, pipe(mapObj, mapObj)(applySpec({
                count: length,
                velocity: pipe(filter(wasWorkItemCompletedInQueryPeriod), length),
                velocityByWeek: (wis) => weeks.map(week => wis.filter(wasWorkItemCompletedIn(week)).length),
                cycleTime: pipe(filter(wasWorkItemCompletedInQueryPeriod), map(computeTimeDifferenceBetween('start', 'end')), times => ({ count: sum(times), wis: times.length })),
                cycleTimeByWeek: (wis) => weeks.map(week => {
                    const cycleTimes = wis
                        .filter(wasWorkItemCompletedIn(week))
                        .map(computeTimeDifferenceBetween('start', 'end'));
                    return { count: sum(cycleTimes), wis: cycleTimes.length };
                }),
                changeLeadTime: pipe(filter(wasWorkItemCompletedInQueryPeriod), map(computeTimeDifferenceBetween('devComplete', 'end')), times => ({ count: sum(times), wis: times.length })),
                changeLeadTimeByWeek: (wis) => weeks.map(week => {
                    const clts = wis
                        .filter(wasWorkItemCompletedIn(week))
                        .map(computeTimeDifferenceBetween('devComplete', 'end'));
                    return { count: sum(clts), wis: clts.length };
                }),
                flowEfficiency: pipe(filter(wasWorkItemCompletedInQueryPeriod), flowEfficiency),
                flowEfficiencyByWeek: (wis) => weeks.map(week => flowEfficiency(wis.filter(wasWorkItemCompletedIn(week)))),
                wipTrend: (wis) => weekLimits.map(limit => wis.filter(isWIPIn(limit)).length),
                wipCount: pipe(filter(wipWorkItems), length),
                wipAge: pipe(filter(wipWorkItems), map(computeTimeDifferenceBetween('start')), ages => ({ count: sum(ages), wis: ages.length })),
                leakage: pipe(filter(leakage(isInQueryPeriod)), length),
                leakageByWeek: (wis) => weeks.map(week => wis.filter(leakage(week)).length),
            })))(Object.values(overview.byId)),
        };
    }));
};
const collectionWorkitemSummary = () => {
    return Promise.all(collections().map(async ({ name: collectionName, projects }) => {
        const analysed = await analyseProjects(collectionName, projects);
        const { types, groups, projects: analysedProjects, } = analysed.reduce((acc, p) => {
            const { types, groups, ...rest } = p;
            return {
                types: { ...acc.types, ...types },
                projects: [...acc.projects, rest],
                groups: { ...acc.groups, ...groups },
            };
        }, { types: {}, groups: {}, projects: [] });
        return writeFile(join(process.cwd(), 'data', collectionName, 'collection-summary.json'), JSON.stringify({
            collectionName,
            projects: analysedProjects,
            types: Object.fromEntries(Object.entries(types).filter(([, value]) => {
                return concernedTypes.some(x => x(value));
            })),
            groups,
        }));
    }));
};
export default collectionWorkitemSummary;
export const ReadSummaryInputParser = z.object({
    collectionName: z.string(),
});
export const readCollectionWorkItemsSummary = async ({ collectionName, }) => {
    return JSON.parse(await readFile(join(process.cwd(), 'data', collectionName, 'collection-summary.json'), 'utf8'));
};
