/**
 * Archive analytics — rich reporting and insights from archived tasks.
 *
 * Moved from CLI archive-stats command to core for reuse across
 * CLI and programmatic consumers.
 *
 * @task T4555
 * @epic T4545
 */
import { getAccessor } from '../store/data-accessor.js';
// ---------------------------------------------------------------------------
// Internal: task extraction from archive data
// ---------------------------------------------------------------------------
/** Type guard for raw objects that have _archive nested metadata (legacy JSON format). */
function hasNestedArchive(t) {
    return '_archive' in t && typeof t._archive === 'object';
}
/**
 * Extract archive metadata from a task record.
 * Handles both:
 * - Legacy JSON format: { _archive: { archivedAt, cycleTimeDays, archiveSource } }
 * - SQLite format: { archivedAt, cycleTimeDays, archiveReason } (flat properties on Task)
 */
function extractArchiveMetadata(t) {
    // Legacy JSON nested format
    if (hasNestedArchive(t)) {
        return t._archive;
    }
    // SQLite flat format — archive fields are tacked onto the Task object
    const raw = t;
    return {
        archivedAt: raw.archivedAt,
        cycleTimeDays: raw.cycleTimeDays,
        archiveSource: raw.archiveReason,
    };
}
/** Convert raw Task[] from the accessor into AnalyticsTask[]. */
function toAnalyticsTasks(tasks) {
    return tasks.map((t) => ({
        id: t.id,
        title: t.title,
        status: t.status,
        priority: t.priority,
        phase: t.phase,
        labels: t.labels,
        archive: extractArchiveMetadata(t),
    }));
}
// ---------------------------------------------------------------------------
// Analysis functions (pure, no I/O)
// ---------------------------------------------------------------------------
/** Filter tasks by date range on archivedAt. */
export function filterByDate(tasks, since, until) {
    let filtered = tasks;
    if (since) {
        const sinceDate = since.includes('T') ? since : `${since}T00:00:00Z`;
        filtered = filtered.filter((t) => (t.archive.archivedAt ?? '') >= sinceDate);
    }
    if (until) {
        const untilDate = until.includes('T') ? until : `${until}T23:59:59Z`;
        filtered = filtered.filter((t) => (t.archive.archivedAt ?? '') <= untilDate);
    }
    return filtered;
}
/** Generate summary statistics. */
export function summaryReport(tasks) {
    const byStatus = {};
    const byPriority = {};
    const sourceBreakdown = {};
    const cycleTimes = [];
    for (const t of tasks) {
        byStatus[t.status] = (byStatus[t.status] ?? 0) + 1;
        const prio = t.priority ?? 'unset';
        byPriority[prio] = (byPriority[prio] ?? 0) + 1;
        const src = t.archive.archiveSource ?? 'unknown';
        sourceBreakdown[src] = (sourceBreakdown[src] ?? 0) + 1;
        if (t.archive.cycleTimeDays != null) {
            cycleTimes.push(t.archive.cycleTimeDays);
        }
    }
    const sorted = tasks
        .filter((t) => t.archive.archivedAt)
        .sort((a, b) => a.archive.archivedAt.localeCompare(b.archive.archivedAt));
    return {
        totalArchived: tasks.length,
        byStatus,
        byPriority,
        averageCycleTime: cycleTimes.length > 0
            ? Math.round((cycleTimes.reduce((a, b) => a + b, 0) / cycleTimes.length) * 100) / 100
            : null,
        oldestArchived: sorted[0]?.archive.archivedAt ?? null,
        newestArchived: sorted[sorted.length - 1]?.archive.archivedAt ?? null,
        archiveSourceBreakdown: sourceBreakdown,
    };
}
/** Group tasks by phase with cycle time averages. */
export function byPhaseReport(tasks) {
    const groups = {};
    for (const t of tasks) {
        const phase = t.phase ?? 'unassigned';
        if (!groups[phase])
            groups[phase] = [];
        groups[phase].push(t);
    }
    return Object.entries(groups)
        .map(([phase, items]) => {
        const cycles = items
            .map((t) => t.archive.cycleTimeDays)
            .filter((c) => c != null);
        return {
            phase,
            count: items.length,
            avgCycleTime: cycles.length > 0
                ? Math.round((cycles.reduce((a, b) => a + b, 0) / cycles.length) * 100) / 100
                : null,
        };
    })
        .sort((a, b) => b.count - a.count);
}
/** Group tasks by label frequency. */
export function byLabelReport(tasks) {
    const counts = {};
    for (const t of tasks) {
        for (const label of t.labels ?? []) {
            counts[label] = (counts[label] ?? 0) + 1;
        }
    }
    return Object.entries(counts)
        .map(([label, count]) => ({ label, count }))
        .sort((a, b) => b.count - a.count);
}
/** Group tasks by priority with cycle time averages. */
export function byPriorityReport(tasks) {
    const PRIO_ORDER = ['critical', 'high', 'medium', 'low', 'unset'];
    const groups = {};
    for (const t of tasks) {
        const prio = t.priority ?? 'unset';
        if (!groups[prio])
            groups[prio] = [];
        groups[prio].push(t);
    }
    return Object.entries(groups)
        .map(([priority, items]) => {
        const cycles = items
            .map((t) => t.archive.cycleTimeDays)
            .filter((c) => c != null);
        return {
            priority,
            count: items.length,
            avgCycleTime: cycles.length > 0
                ? Math.round((cycles.reduce((a, b) => a + b, 0) / cycles.length) * 100) / 100
                : null,
        };
    })
        .sort((a, b) => PRIO_ORDER.indexOf(a.priority) - PRIO_ORDER.indexOf(b.priority));
}
/** Compute cycle time statistics with distribution buckets. */
export function cycleTimesReport(tasks) {
    const times = tasks
        .map((t) => t.archive.cycleTimeDays)
        .filter((c) => c != null)
        .sort((a, b) => a - b);
    if (times.length === 0) {
        return {
            count: 0,
            min: null,
            max: null,
            avg: null,
            median: null,
            distribution: { '0-1 days': 0, '2-7 days': 0, '8-30 days': 0, '30+ days': 0 },
        };
    }
    const median = times.length % 2 === 0
        ? (times[times.length / 2 - 1] + times[times.length / 2]) / 2
        : times[Math.floor(times.length / 2)];
    const percentile = (p) => times[Math.floor(times.length * p)] ?? null;
    return {
        count: times.length,
        min: times[0],
        max: times[times.length - 1],
        avg: Math.round((times.reduce((a, b) => a + b, 0) / times.length) * 100) / 100,
        median,
        distribution: {
            '0-1 days': times.filter((t) => t <= 1).length,
            '2-7 days': times.filter((t) => t > 1 && t <= 7).length,
            '8-30 days': times.filter((t) => t > 7 && t <= 30).length,
            '30+ days': times.filter((t) => t > 30).length,
        },
        percentiles: {
            p25: percentile(0.25),
            p50: percentile(0.5),
            p75: percentile(0.75),
            p90: percentile(0.9),
        },
    };
}
/** Compute archive trends by day and month. */
export function trendsReport(tasks) {
    const withDate = tasks.filter((t) => t.archive.archivedAt);
    const byDay = {};
    for (const t of withDate) {
        const date = t.archive.archivedAt.slice(0, 10);
        byDay[date] = (byDay[date] ?? 0) + 1;
    }
    const dailyEntries = Object.entries(byDay)
        .map(([date, count]) => ({ date, count }))
        .sort((a, b) => a.date.localeCompare(b.date));
    const byMonth = {};
    for (const { date, count } of dailyEntries) {
        const month = date.slice(0, 7);
        byMonth[month] = (byMonth[month] ?? 0) + count;
    }
    const monthlyEntries = Object.entries(byMonth).map(([month, count]) => ({
        month,
        count,
    }));
    const totalPeriod = dailyEntries.reduce((sum, d) => sum + d.count, 0);
    return {
        byDay: dailyEntries,
        byMonth: monthlyEntries,
        totalPeriod,
        averagePerDay: dailyEntries.length > 0 ? Math.round((totalPeriod / dailyEntries.length) * 100) / 100 : 0,
    };
}
// ---------------------------------------------------------------------------
// Public API — orchestrates data loading + report generation
// ---------------------------------------------------------------------------
/**
 * Analyze archived tasks and produce a report.
 *
 * This is the primary entry point for archive analytics. It loads archive
 * data from the DataAccessor, normalizes task records, applies date filters,
 * and delegates to the appropriate report function.
 */
export async function analyzeArchive(opts, accessor) {
    const acc = accessor ?? (await getAccessor(opts.cwd));
    const data = await acc.loadArchive();
    const reportType = opts.report ?? 'summary';
    if (!data || !data.archivedTasks?.length) {
        return {
            report: reportType,
            filters: null,
            data: { totalArchived: 0, message: 'No archived tasks found' },
        };
    }
    const tasks = toAnalyticsTasks(data.archivedTasks);
    const filtered = filterByDate(tasks, opts.since, opts.until);
    let reportData;
    switch (reportType) {
        case 'summary':
            reportData = summaryReport(filtered);
            break;
        case 'by-phase':
            reportData = byPhaseReport(filtered);
            break;
        case 'by-label':
            reportData = byLabelReport(filtered);
            break;
        case 'by-priority':
            reportData = byPriorityReport(filtered);
            break;
        case 'cycle-times':
            reportData = cycleTimesReport(filtered);
            break;
        case 'trends':
            reportData = trendsReport(filtered);
            break;
        default:
            reportData = summaryReport(filtered);
    }
    return {
        report: reportType,
        filters: opts.since || opts.until ? { since: opts.since ?? null, until: opts.until ?? null } : null,
        data: reportData,
    };
}
//# sourceMappingURL=archive-analytics.js.map