import type { AgendaItem, TimeString, DateString } from './types'

/**
 * Pure time-arithmetic for the agenda editor.
 *
 * All times are `HH:MM` 24-hour strings handled as integer minutes since
 * midnight — no `Date` objects, so the logic is timezone-safe and trivially
 * testable. Date helpers use UTC math for the same reason.
 */

const MINUTES_PER_HOUR = 60
const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR
const MS_PER_DAY = 24 * 60 * 60 * 1000

const TIME_PATTERN = /^(\d{1,2}):(\d{2})$/
const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/

/** `"08:30"` → `510`. Returns `0` for unparseable input. */
export function parseTime(time: TimeString): number {
  const match = time.trim().match(TIME_PATTERN)
  if (!match) return 0
  const hours = Number(match[1])
  const minutes = Number(match[2])
  if (Number.isNaN(hours) || Number.isNaN(minutes)) return 0
  return hours * MINUTES_PER_HOUR + minutes
}

/**
 * `510` → `"08:30"`. Overflow past midnight is shown verbatim (e.g. `"25:30"`)
 * rather than wrapped, so an over-booked day stays visible to the user.
 */
export function formatTime(totalMinutes: number): TimeString {
  const safe = Math.max(0, Math.round(totalMinutes))
  const hours = Math.floor(safe / MINUTES_PER_HOUR)
  const minutes = safe % MINUTES_PER_HOUR
  return `${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')}`
}

export function addMinutes(time: TimeString, minutes: number): TimeString {
  return formatTime(parseTime(time) + minutes)
}

/**
 * Recompute `start_time` / `end_time` for every item of a day.
 *
 * The first item starts at `dayStart`; each subsequent item starts when the
 * previous one ends. An item with `is_time_fixed` and a `start_time` acts as a
 * manual anchor: it starts at its fixed time and the automatic chain resumes
 * from its end. Non-positive durations are treated as zero-length so the chain
 * never runs backwards (the condition is surfaced separately as a warning).
 *
 * Returns a new array of new item objects; the input is never mutated.
 */
export function recomputeDayItems(items: AgendaItem[], dayStart: TimeString): AgendaItem[] {
  let cursor = parseTime(dayStart)
  return items.map((item) => {
    const fixedStart = item.is_time_fixed && item.start_time ? parseTime(item.start_time) : null
    const start = fixedStart ?? cursor
    const safeDuration = Math.max(0, item.duration_minutes)
    const end = start + safeDuration
    cursor = end
    return { ...item, start_time: formatTime(start), end_time: formatTime(end) }
  })
}

export type WarningCode =
  | 'duration_non_positive'
  | 'exceeds_day_end'
  | 'missing_title'
  | 'overlap'
  | 'end_date_before_start'

export interface AgendaWarning {
  code: WarningCode
  dayId?: string
  itemId?: string
  /** Extra context for the i18n message (e.g. the offending time). */
  data?: Record<string, string | number>
}

/**
 * Collect validation warnings for a recomputed day. Expects items that have
 * already been through {@link recomputeDayItems} (so start/end are populated).
 */
export function computeDayWarnings(
  dayId: string,
  items: AgendaItem[],
  dayEnd: TimeString,
): AgendaWarning[] {
  const warnings: AgendaWarning[] = []
  const dayEndMinutes = parseTime(dayEnd)
  let previousEnd: number | null = null

  for (const item of items) {
    if (item.duration_minutes <= 0) {
      warnings.push({ code: 'duration_non_positive', dayId, itemId: item.id })
    }
    if (!item.title.trim()) {
      warnings.push({ code: 'missing_title', dayId, itemId: item.id })
    }
    const start = item.start_time ? parseTime(item.start_time) : null
    if (start !== null && previousEnd !== null && start < previousEnd) {
      warnings.push({
        code: 'overlap',
        dayId,
        itemId: item.id,
        data: { start: item.start_time ?? '', previousEnd: formatTime(previousEnd) },
      })
    }
    previousEnd = item.end_time ? parseTime(item.end_time) : previousEnd
  }

  if (previousEnd !== null && previousEnd > dayEndMinutes && dayEndMinutes < MINUTES_PER_DAY) {
    warnings.push({
      code: 'exceeds_day_end',
      dayId,
      data: { end: formatTime(previousEnd), dayEnd },
    })
  }

  return warnings
}

function parseDateUtc(date: DateString): number | null {
  const match = date.trim().match(DATE_PATTERN)
  if (!match) return null
  return Date.UTC(Number(match[1]), Number(match[2]) - 1, Number(match[3]))
}

function formatDateUtc(ms: number): DateString {
  const d = new Date(ms)
  const year = d.getUTCFullYear()
  const month = String(d.getUTCMonth() + 1).padStart(2, '0')
  const day = String(d.getUTCDate()).padStart(2, '0')
  return `${year}-${month}-${day}`
}

/** Inclusive list of dates from `start` to `end`. Empty if `end` < `start`. */
export function enumerateDates(start: DateString, end: DateString): DateString[] {
  const startMs = parseDateUtc(start)
  const endMs = parseDateUtc(end)
  if (startMs === null || endMs === null || endMs < startMs) return []
  const dates: DateString[] = []
  for (let ms = startMs; ms <= endMs; ms += MS_PER_DAY) {
    dates.push(formatDateUtc(ms))
  }
  return dates
}

/** Inclusive day count between two dates (`1` for the same day, `0` if inverted). */
export function daysBetween(start: DateString, end: DateString): number {
  const startMs = parseDateUtc(start)
  const endMs = parseDateUtc(end)
  if (startMs === null || endMs === null) return 0
  return Math.max(0, Math.round((endMs - startMs) / MS_PER_DAY) + 1)
}
