• Aggregates weekly skip metrics from daily metrics

    Returns Promise<
        Record<
            string,
            {
                weekId: string;
                startDate: string;
                endDate: string;
                totalSkips: number;
                uniqueTracksSkipped: string[];
                skipsByType: Record<string, number>;
                skipsByDay: number[];
                manualSkips: number;
                autoSkips: number;
                topSkipHour: number;
            },
        >,
    >

    Object with weekly skip metrics

    export async function aggregateWeeklySkipMetrics() {
    try {
    // First make sure daily metrics are up to date
    const dailyMetrics = await aggregateDailySkipMetrics();
    const statistics = await getStatistics();

    // Create a map to hold weekly metrics
    const weeklyMetrics: Record<
    string,
    {
    weekId: string;
    startDate: string;
    endDate: string;
    totalSkips: number;
    uniqueTracksSkipped: string[];
    skipsByType: Record<string, number>;
    skipsByDay: number[];
    manualSkips: number;
    autoSkips: number;
    topSkipHour: number;
    }
    > = {};

    // Process each day's metrics
    for (const [dateStr, metrics] of Object.entries(dailyMetrics)) {
    try {
    // Validate date string before creating Date object
    if (!dateStr || typeof dateStr !== "string") {
    continue;
    }

    const date = new Date(dateStr);

    // Check if date is valid
    if (isNaN(date.getTime())) {
    continue;
    }

    const weekId = getISOWeekIdentifier(date);

    // Get start and end dates for this week
    const { startDate, endDate } = getWeekStartAndEndDates(date);

    // Validate start and end dates
    if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
    continue;
    }

    const startDateStr = startDate.toISOString().split("T")[0];
    const endDateStr = endDate.toISOString().split("T")[0];

    // Initialize this week's metrics if needed
    if (!weeklyMetrics[weekId]) {
    weeklyMetrics[weekId] = {
    weekId,
    startDate: startDateStr,
    endDate: endDateStr,
    totalSkips: 0,
    uniqueTracksSkipped: [],
    skipsByType: {},
    skipsByDay: Array(7).fill(0), // Sunday = 0, Saturday = 6
    manualSkips: 0,
    autoSkips: 0,
    topSkipHour: 0,
    };
    }

    // Update metrics
    weeklyMetrics[weekId].totalSkips += metrics.tracksSkipped;

    // Day of week (0 = Sunday, 6 = Saturday)
    const dayOfWeek = date.getDay();
    weeklyMetrics[weekId].skipsByDay[dayOfWeek] += metrics.tracksSkipped;

    // Add unique tracks
    // First ensure metrics.uniqueTracks is an array
    const uniqueTracks = Array.isArray(metrics.uniqueTracks)
    ? metrics.uniqueTracks
    : Array.from(metrics.uniqueTracks);

    uniqueTracks.forEach((trackId) => {
    if (!weeklyMetrics[weekId].uniqueTracksSkipped.includes(trackId)) {
    weeklyMetrics[weekId].uniqueTracksSkipped.push(trackId);
    }
    });

    // Add skip types
    if (metrics.skipsByType) {
    for (const [type, count] of Object.entries(metrics.skipsByType)) {
    weeklyMetrics[weekId].skipsByType[type] =
    (weeklyMetrics[weekId].skipsByType[type] || 0) + count;
    }
    }

    // Add manual/auto skips
    if (metrics.skipsByType?.manual) {
    weeklyMetrics[weekId].manualSkips += metrics.skipsByType.manual;
    }

    if (metrics.skipsByType?.auto) {
    weeklyMetrics[weekId].autoSkips += metrics.skipsByType.auto;
    }

    // Find top skip hour by analyzing hourly skips across all days in the week
    const weekHourlySkips = Array(24).fill(0);
    for (let hour = 0; hour < 24; hour++) {
    // For hourly metrics, we directly check the numbered properties that might exist
    // This isn't a proper field of skipsByType, but we're safely accessing it anyway
    let hourlySkipCount = 0;

    if (metrics.skipsByType) {
    // Cast skipsByType to Record<string, number> for string indexing
    const skipsByType = metrics.skipsByType as Record<string, number>;
    hourlySkipCount = skipsByType[hour.toString()] || 0;
    }

    weekHourlySkips[hour] += hourlySkipCount;
    }

    // Update top skip hour if we find a new maximum
    const currentMaxHour = weeklyMetrics[weekId].topSkipHour;
    const currentMaxCount = weekHourlySkips[currentMaxHour] || 0;

    for (let hour = 0; hour < 24; hour++) {
    if (weekHourlySkips[hour] > currentMaxCount) {
    weeklyMetrics[weekId].topSkipHour = hour;
    }
    }
    } catch (err) {
    console.error("Error processing day metrics:", err, {
    dateStr,
    metrics,
    });
    }
    }

    // Store the weekly metrics in a separate file
    const weeklyMetricsFilePath = join(
    ensureStatisticsDir(),
    "weekly_skip_metrics.json",
    );
    writeJsonSync(weeklyMetricsFilePath, weeklyMetrics, { spaces: 2 });

    // Also update the statistics object with the aggregated weekly data
    for (const [weekId, metrics] of Object.entries(weeklyMetrics)) {
    if (!statistics.weeklyMetrics[weekId]) {
    continue; // Skip weeks not in the current statistics object
    }

    // Update existing weekly metrics
    statistics.weeklyMetrics[weekId].tracksSkipped = metrics.totalSkips;
    statistics.weeklyMetrics[weekId].mostActiveDay =
    metrics.skipsByDay.indexOf(Math.max(...metrics.skipsByDay));
    }

    // Save updated statistics
    await saveStatistics(statistics);

    return weeklyMetrics;
    } catch (error) {
    console.error("Error aggregating weekly skip metrics:", error);
    return {};
    }
    }