export function sampleTrendPoints(points, limit = 240) {
  if (!Array.isArray(points) || points.length <= limit) return Array.isArray(points) ? points : [];
  const step = Math.ceil((points.length - 1) / (limit - 1));
  const sampled = [];
  for (let index = 0; index < points.length - 1; index += step) sampled.push(points[index]);
  sampled.push(points.at(-1));
  return sampled;
}

const LIMIT_WINDOW_SHIFT_MS = 5 * 60 * 1000;

export function buildTrendPolylines(points, field, resetField, options) {
  const { since, until, width, height, inset } = options;
  const duration = Math.max(1, until - since);
  const validPoints = (Array.isArray(points) ? points : []).flatMap((point) => {
    const at = Date.parse(point.at);
    const value = point[field];
    if (!Number.isFinite(at) || !Number.isFinite(value) || at > until) return [];
    return [{ at, value, resetsAt: point[resetField] }];
  });
  const previousPoint = validPoints.filter((point) => point.at < since).at(-1);
  const visiblePoints = validPoints.filter((point) => point.at >= since);
  const sampled = sampleTrendPoints(previousPoint ? [previousPoint, ...visiblePoints] : visiblePoints);
  const segments = [];
  let segment = [];
  let previous = null;

  for (const point of sampled) {
    const x = point.at < since ? inset : trendX(point.at, since, duration, width, inset);
    const y = trendY(point.value, height, inset);
    if (!previous) {
      segment.push(`${x},${y}`);
      previous = { ...point, x, y };
      continue;
    }

    if (limitWindowChanged(previous, point)) {
      appendCoordinate(segment, `${x},${previous.y}`);
      segments.push(segment.join(" "));
      segment = [`${x},${y}`];
    } else {
      appendCoordinate(segment, `${x},${previous.y}`);
      appendCoordinate(segment, `${x},${y}`);
    }
    previous = { ...point, x, y };
  }

  if (previous && previous.at < until) appendCoordinate(segment, `${width - inset},${previous.y}`);
  if (segment.length > 0) segments.push(segment.join(" "));
  return segments;
}

const DAY_MS = 24 * 60 * 60 * 1000;
const HOUR_MS = 60 * 60 * 1000;

export function buildTrendAxisTicks(since, until) {
  const duration = Math.max(1, until - since);
  const minimumInterval = duration >= 2 * DAY_MS ? DAY_MS : HOUR_MS;
  const intervals = [HOUR_MS, 2 * HOUR_MS, 3 * HOUR_MS, 4 * HOUR_MS, 6 * HOUR_MS, 12 * HOUR_MS, DAY_MS, 2 * DAY_MS, 3 * DAY_MS, 7 * DAY_MS]
    .filter((value) => value >= minimumInterval);
  const targetInterval = duration / 6;
  const interval = intervals.find((value) => value >= targetInterval) ?? intervals.at(-1);
  const firstTick = Math.ceil(since / interval) * interval;
  const ticks = [];
  for (let at = firstTick; at <= until; at += interval) ticks.push({ at, position: (at - since) / duration });
  return ticks;
}

export function trendHistoryStart(points) {
  if (!Array.isArray(points)) return null;
  return points.reduce((earliest, point) => {
    const at = Date.parse(point?.at);
    return Number.isFinite(at) && (earliest === null || at < earliest) ? at : earliest;
  }, null);
}

export function panTrendViewport(until, days, deltaX, width, now = Date.now(), historyDays = 30, historyStart = null) {
  const safeWidth = Math.max(1, width);
  const nextUntil = until - deltaX / safeWidth * days * DAY_MS;
  return clampTrendViewport(days, nextUntil, now, historyDays, historyStart).until;
}

function clampTrendViewport(days, until, now, historyDays, historyStart) {
  const safeDays = clamp(days, 0.125, historyDays);
  const retainedHistoryUntil = now - (historyDays - safeDays) * DAY_MS;
  const recordedHistoryUntil = Number.isFinite(historyStart) ? historyStart + safeDays * DAY_MS : retainedHistoryUntil;
  const earliestUntil = Math.min(now, Math.max(retainedHistoryUntil, recordedHistoryUntil));
  return { days: safeDays, until: clamp(until, earliestUntil, now) };
}

export function sortUsageAccounts(accounts, entries, mode = "availability", direction = "descending") {
  if (!Array.isArray(accounts)) return [];
  if (mode === "configured") return direction === "ascending" ? [...accounts] : [...accounts].reverse();
  const multiplier = direction === "ascending" ? 1 : -1;
  return accounts.map((account, index) => ({ account, index, score: usageSortScore(entries?.[account.id]?.usage, mode) }))
    .sort((left, right) => {
      if (left.score === null && right.score === null) return left.index - right.index;
      if (left.score === null) return 1;
      if (right.score === null) return -1;
      return (left.score - right.score) * multiplier || left.index - right.index;
    })
    .map(({ account }) => account);
}

export function sortWeeklySummaryAccounts(accounts, entries) {
  if (!Array.isArray(accounts)) return [];
  return accounts.map((account, index) => ({ account, index, remaining: entries?.[account.id]?.usage?.secondary?.remainingPercent }))
    .sort((left, right) => {
      const leftAvailable = Number.isFinite(left.remaining);
      const rightAvailable = Number.isFinite(right.remaining);
      if (leftAvailable !== rightAvailable) return leftAvailable ? -1 : 1;
      return right.remaining - left.remaining || left.index - right.index;
    })
    .map(({ account }) => account);
}

function usageSortScore(usage, mode) {
  const primary = usage?.primary?.remainingPercent;
  const secondary = usage?.secondary?.remainingPercent;
  if (mode === "primary") return Number.isFinite(primary) ? primary : null;
  if (mode === "secondary") return Number.isFinite(secondary) ? secondary : null;
  return Number.isFinite(primary) && Number.isFinite(secondary) ? Math.min(primary, secondary) : null;
}

function limitWindowChanged(previous, current) {
  const previousReset = Date.parse(previous.resetsAt);
  const currentReset = Date.parse(current.resetsAt);
  if (!Number.isFinite(previousReset) || !Number.isFinite(currentReset)) return false;
  if (previous.value >= 100 && current.value >= 100) return false;
  return Math.abs(currentReset - previousReset) > LIMIT_WINDOW_SHIFT_MS;
}

function trendX(at, since, duration, width, inset) {
  return round(inset + (at - since) / duration * (width - inset * 2));
}

function trendY(value, height, inset) {
  return round(inset + (100 - Math.max(0, Math.min(100, value))) / 100 * (height - inset * 2));
}

function appendCoordinate(segment, coordinate) {
  if (segment.at(-1) !== coordinate) segment.push(coordinate);
}

function round(value) {
  return Math.round(value * 10) / 10;
}

function clamp(value, minimum, maximum) {
  return Math.max(minimum, Math.min(maximum, value));
}
