import type {Reward} from '@/features/lyts/types/reward';

/**
 * Domain rules for LYT rewards.
 *
 * The presentation mapping (status → badge tone) stays in utils/rewardStatus —
 * it knows about badges. These are the calculations behind it, which know only
 * about rewards, and which the expiry panel, the performance table and any
 * future export all have to agree on.
 */

/** Whole days until expiry; negative once lapsed, null if it never expires. */
export function daysUntil(
  expiresAt: string | null,
  nowMs: number,
): number | null {
  if (!expiresAt) return null;
  return Math.ceil((new Date(expiresAt).getTime() - nowMs) / 86400000);
}

/**
 * Claimed → used conversion, as a percentage.
 *
 * Zero claims is 0%, not NaN: a reward nobody has taken up has a usage rate,
 * and it is zero. Rounded to one decimal because the figure is read, not
 * computed against.
 */
export function usageRate(claimed: number, used: number): number {
  return claimed === 0 ? 0 : Number(((used / claimed) * 100).toFixed(1));
}

/**
 * Rewards that need a decision, soonest deadline first.
 *
 * `withinDays` is the merchant's attention window rather than a fixed
 * constant — the expiry panel asks for 7, a daily digest would ask for 1.
 * Already-expired rewards are excluded: they are history, and mixing them into
 * an alert list is how a panel about acting becomes a panel about reading.
 */
export function selectExpiring(
  rewards: Reward[],
  nowMs: number,
  withinDays: number,
): Reward[] {
  return rewards
    .map((reward) => ({reward, days: daysUntil(reward.expiresAt, nowMs)}))
    .filter(
      (entry): entry is {reward: Reward; days: number} =>
        entry.days !== null && entry.days >= 0 && entry.days <= withinDays,
    )
    .sort((a, b) => a.days - b.days)
    .map((entry) => entry.reward);
}
