Source: diurnalStats.js

import { trimDate } from "./trimDate.js";
import { roundAndUseNull, useNull } from "./utils.js";

/**
 * Calculate hourly (diurnal) statistics over recent days.
 *
 * Returns five arrays of hourly values:
 * * hour  -- Local-time hour of day [0–23]
 * * count -- Number of non-missing values per hour
 * * min   -- Minimum value at each hour
 * * mean  -- Mean value at each hour
 * * max   -- Maximum value at each hour
 *
 * By default, the most recent 7 full days are used.
 *
 * @param {Array.<DateTime>} datetime - Hourly UTC timestamps as Luxon DateTime objects.
 * @param {Array.<number>} x - Matching array of values (e.g. PM2.5).
 * @param {string} timezone - Olson timezone string (e.g. "America/Los_Angeles").
 * @param {number} dayCount - Number of days to include (default: 7).
 * @returns {object} - Object with hour, count, min, mean, and max arrays.
 */
export function diurnalStats(datetime, x, timezone, dayCount = 7) {
  // Trim to full local days
  const trimmed = trimDate(datetime, x, timezone);

  // Return empty result if no full days exist
  if (
    !Array.isArray(trimmed.datetime) ||
    trimmed.datetime.length < 24 ||
    trimmed.datetime.length !== trimmed.x.length
  ) {
    return {
      hour: [],
      count: [],
      min: [],
      mean: [],
      max: [],
    };
  }

  // Use as many recent full days as possible, up to `dayCount`
  const fullDayCount = trimmed.datetime.length / 24;
  dayCount = fullDayCount < dayCount ? fullDayCount : dayCount;
  const startIndex = trimmed.datetime.length - dayCount * 24;

  // Clean data and mark valid values
  const values = useNull(trimmed.x);
  const validFlags = values.map((v) => (v === null ? 0 : 1));

  const hour = [];
  const hourlyCount = [];
  const hourlyMin = [];
  const hourlyMean = [];
  const hourlyMax = [];

  // For each hour of the day (0–23), compute stats across days
  for (let h = 0; h < 24; h++) {
    let min = Number.MAX_VALUE;
    let max = Number.MIN_VALUE;
    let count = 0;
    let sum = null;

    for (let d = 0; d < dayCount; d++) {
      const index = startIndex + h + d * 24;

      if (validFlags[index] === 1) {
        if (sum === null) sum = 0;

        min = values[index] < min ? values[index] : min;
        max = values[index] > max ? values[index] : max;
        sum += values[index];
        count += 1;
      }
    }

    hour[h] = h;
    hourlyMin[h] = min === Number.MAX_VALUE ? null : min;
    hourlyMax[h] = max === Number.MIN_VALUE ? null : max;
    hourlyCount[h] = count;
    hourlyMean[h] = sum === null ? null : sum / count;
  }

  // Round all numeric outputs and use null for missing
  const roundedMin = roundAndUseNull(hourlyMin);
  const roundedMean = roundAndUseNull(hourlyMean);
  const roundedMax = roundAndUseNull(hourlyMax);

  return {
    hour: hour,
    count: hourlyCount,
    min: roundedMin,
    mean: roundedMean,
    max: roundedMax,
  };
}