import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { applySpec, filter, length, map, pipe, prop } from 'rambda';
import { exists, mapObj } from '../../../shared/utils.js';
import { totalCycleTime, totalWorkCenterTime, isWIP, isWIPInTimeRange, timeDifference, } from '../../../shared/work-item-utils.js';
import { isAfter, queryPeriodDays, weekLimits, weeks } from '../../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 getOverview = async (collection, project) => JSON.parse(await readFile(join(process.cwd(), 'data', collection, project, 'overview.json'), 'utf8'), parseDate).overview;
const hasWorkItemCompleted = (workItemTimes) => (isMatchingDate) => (workItem) => {
    const { start, end } = workItemTimes(workItem);
    return Boolean(start && end && isMatchingDate(new Date(end)));
};
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 });
};
export const isNewWithTimes = (workItemTimes) => (isWithinTimeRange) => (wi) => {
    const { start } = workItemTimes(wi);
    return Boolean(start && isWithinTimeRange(new Date(start)));
};
export const organiseWorkItemsByTracks = (wis) => wis.reduce((acc, wi) => {
    // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
    const trackName = wi.track;
    acc[trackName] = acc[trackName] || [];
    acc[trackName].push(wi);
    return acc;
}, {});
const metricsForResult = (isInQueryPeriod) => async (result) => {
    var _a, _b, _c;
    const configWorkItemsWithTracks = (_a = result.collectionConfig.workitems.types) === null || _a === void 0 ? void 0 : _a.filter(wit => wit.track);
    if (!configWorkItemsWithTracks)
        return;
    const { times, byId } = await getOverview(result.collectionConfig.name, result.projectConfig.name);
    const workItemsWithTracks = Object.values(byId).filter(prop('track'));
    if (workItemsWithTracks.length === 0)
        return;
    const workItemTimes = (wi) => times[wi.id];
    const computeTimeDifferenceBetween = computeTimeDifference(workItemTimes);
    const wasWorkItemCompletedIn = hasWorkItemCompleted(workItemTimes);
    const wasWorkItemCompletedInQueryPeriod = wasWorkItemCompletedIn(isInQueryPeriod);
    const { ignoreForWIP } = result.projectConfig.workitems;
    const isWIPIn = ([, weekEnd]) => isWIPInTimeRange(workItemTimes, ignoreForWIP)((d, type) => {
        if (type === 'start')
            return d <= weekEnd;
        return d < weekEnd;
    });
    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 wipWorkItems = isWIP(workItemTimes, ignoreForWIP);
    const isNewIn = isNewWithTimes(workItemTimes);
    return {
        collection: result.collectionConfig.name,
        project: result.projectConfig.name,
        filterLabel: (_c = (_b = result.collectionConfig.workitems.filterBy) === null || _b === void 0 ? void 0 : _b.find(f => 
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        f.fields.some(f => configWorkItemsWithTracks.map(w => w.track).includes(f)))) === null || _c === void 0 ? void 0 : _c.label,
        byTrack: pipe(organiseWorkItemsByTracks, mapObj(applySpec({
            count: length,
            new: pipe(filter(isNewIn(isInQueryPeriod)), length),
            newByWeek: (wis) => weeks.map(week => wis.filter(isNewIn(week)).length),
            velocity: pipe(filter(wasWorkItemCompletedInQueryPeriod), length),
            velocityByWeek: (wis) => weeks.map(week => wis.filter(wasWorkItemCompletedIn(week)).length),
            cycleTime: pipe(filter(wasWorkItemCompletedInQueryPeriod), map(computeTimeDifferenceBetween('start', 'end'))),
            cycleTimeByWeek: (wis) => weeks.map(week => wis
                .filter(wasWorkItemCompletedIn(week))
                .map(computeTimeDifferenceBetween('start', 'end'))),
            changeLeadTime: pipe(filter(wasWorkItemCompletedInQueryPeriod), map(computeTimeDifferenceBetween('devComplete', 'end'))),
            changeLeadTimeByWeek: (wis) => weeks.map(week => wis
                .filter(wasWorkItemCompletedIn(week))
                .map(computeTimeDifferenceBetween('devComplete', 'end'))),
            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'))),
        })))(workItemsWithTracks),
    };
};
export const trackMetrics = (config, results) => Promise.all(results
    .map(metricsForResult(isAfter(`${queryPeriodDays(config)} days`)))
    .filter(exists)).then(x => x.filter(exists));
export const trackFeatures = (config, results) => Promise.all(results.map(async (result) => {
    var _a, _b, _c;
    const configWorkItemsWithTracks = (_a = result.collectionConfig.workitems.types) === null || _a === void 0 ? void 0 : _a.filter(wit => wit.track);
    if (!configWorkItemsWithTracks)
        return;
    const { times, byId, types, groups: allGroups, } = await getOverview(result.collectionConfig.name, result.projectConfig.name);
    const workItemsWithTracks = Object.values(byId).filter(prop('track'));
    if (workItemsWithTracks.length === 0)
        return;
    const workItemTimes = workItemsWithTracks.reduce((acc, workItem) => {
        acc[workItem.id] = times[workItem.id];
        return acc;
    }, {});
    const workItemTypes = [
        ...workItemsWithTracks.reduce((acc, workItem) => {
            acc.add(workItem.typeId);
            return acc;
        }, new Set()),
    ].reduce((acc, curr) => ({ ...acc, [curr]: types[curr] }), {});
    const groups = [
        ...workItemsWithTracks.reduce((acc, workItem) => {
            if (workItem.groupId)
                acc.add(workItem.groupId);
            return acc;
        }, new Set()),
    ].reduce((acc, curr) => ({ ...acc, [curr]: allGroups[curr] }), {});
    return {
        collection: result.collectionConfig.name,
        project: result.projectConfig.name,
        workItems: workItemsWithTracks,
        filterLabel: (_c = (_b = result.collectionConfig.workitems.filterBy) === null || _b === void 0 ? void 0 : _b.find(f => 
        // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
        f.fields.some(f => configWorkItemsWithTracks.map(w => w.track).includes(f)))) === null || _c === void 0 ? void 0 : _c.label,
        times: workItemTimes,
        types: workItemTypes,
        groups,
    };
})).then(x => x.filter(exists));
