import type { ChoreSchedule, DayConfig } from './models'

/**
 * Returns the JS day-of-week index for a given Date.
 * 0 = Sunday, 6 = Saturday (matches Python / our DayConfig.day convention).
 */
function getLocalWeekday(d: Date): number {
  return d.getDay()
}

/**
 * Returns true if the interval schedule hits on the given localDate.
 *
 * Anchor: the ISO date string from `anchor_date` (e.g. "2026-02-26").
 * An empty string means "use localDate as anchor" (backward compat) which
 * hits on today (diffDays=0) and every intervalDays days after.
 *
 * Dates before the anchor always return false (scheduling hasn't started yet).
 */
export function intervalHitsToday(
  anchorDate: string,
  intervalDays: number,
  localDate: Date,
): boolean {
  // Parse anchor: use localDate itself when anchor is empty (backward compat)
  let anchor: Date
  if (anchorDate) {
    const [y, m, d] = anchorDate.split('-').map(Number)
    anchor = new Date(y, m - 1, d, 0, 0, 0, 0)
  } else {
    anchor = new Date(localDate)
    anchor.setHours(0, 0, 0, 0)
  }

  const target = new Date(localDate)
  target.setHours(0, 0, 0, 0)

  const diffDays = Math.round((target.getTime() - anchor.getTime()) / 86_400_000)
  if (diffDays < 0) return false // before anchor — not started yet
  return diffDays % intervalDays === 0
}

/**
 * Returns true if the schedule applies today (localDate).
 * - 'days' mode: today's weekday is in day_configs
 * - 'interval' mode: intervalHitsToday
 * - paused (enabled === false): always returns true (chore shows every day, like no schedule)
 */
export function isScheduledToday(schedule: ChoreSchedule, localDate: Date): boolean {
  if (schedule.enabled === false) return true // paused = show always, like an unscheduled chore
  if (schedule.mode === 'days') {
    const todayWeekday = getLocalWeekday(localDate)
    return schedule.day_configs.some((dc: DayConfig) => dc.day === todayWeekday)
  } else {
    return intervalHitsToday(schedule.anchor_date ?? '', schedule.interval_days, localDate)
  }
}

/**
 * Returns the due time {hour, minute} for today, or null if:
 * - the schedule is paused (enabled === false) → no deadline, acts like Anytime
 * - the day is not scheduled, OR
 * - the schedule has no deadline (interval_has_deadline === false → "Anytime")
 *
 * Callers treat null as "active all day with no expiry".
 */
export function getDueTimeToday(
  schedule: ChoreSchedule,
  localDate: Date,
): { hour: number; minute: number } | null {
  if (schedule.enabled === false) return null // paused = no deadline, no expiry
  if (schedule.mode === 'days') {
    // default_has_deadline === false means 'Anytime' — no expiry for scheduled days
    if (schedule.default_has_deadline === false) return null
    const todayWeekday = getLocalWeekday(localDate)
    const dayConfig = schedule.day_configs.find((dc: DayConfig) => dc.day === todayWeekday)
    if (!dayConfig) return null
    return { hour: dayConfig.hour, minute: dayConfig.minute }
  } else {
    if (!intervalHitsToday(schedule.anchor_date ?? '', schedule.interval_days, localDate))
      return null
    // interval_has_deadline === false means "Anytime" — no expiry time
    if (schedule.interval_has_deadline === false) return null
    return { hour: schedule.interval_hour, minute: schedule.interval_minute }
  }
}

/**
 * Returns true if the given due time has already passed given the current local time.
 */
export function isPastTime(dueHour: number, dueMinute: number, localNow: Date): boolean {
  const nowMinutes = localNow.getHours() * 60 + localNow.getMinutes()
  const dueMinutes = dueHour * 60 + dueMinute
  return nowMinutes >= dueMinutes
}

/**
 * Returns true if extensionDate matches today's local ISO date string.
 */
export function isExtendedToday(
  extensionDate: string | null | undefined,
  localDate: Date,
): boolean {
  if (!extensionDate) return false
  return extensionDate === toLocalISODate(localDate)
}

/**
 * Formats a 24h hour/minute pair into a 12h "X:XX AM/PM" label.
 */
export function formatDueTimeLabel(hour: number, minute: number): string {
  const period = hour < 12 ? 'AM' : 'PM'
  const h12 = hour % 12 === 0 ? 12 : hour % 12
  const mm = String(minute).padStart(2, '0')
  return `${h12}:${mm} ${period}`
}

/**
 * Returns the number of milliseconds from localNow until the due time fires today.
 * Returns 0 if it has already passed.
 */
export function msUntilExpiry(dueHour: number, dueMinute: number, localNow: Date): number {
  const nowMs =
    localNow.getHours() * 3600000 +
    localNow.getMinutes() * 60000 +
    localNow.getSeconds() * 1000 +
    localNow.getMilliseconds()
  const dueMs = dueHour * 3600000 + dueMinute * 60000
  return Math.max(0, dueMs - nowMs)
}

/**
 * Returns the local date as an ISO date string (YYYY-MM-DD) without timezone conversion.
 */
export function toLocalISODate(d: Date): string {
  const year = d.getFullYear()
  const month = String(d.getMonth() + 1).padStart(2, '0')
  const day = String(d.getDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}
