import {
  addDays,
  endOfDay,
  endOfMonth,
  endOfWeek,
  format,
  isSameMonth,
  isSameYear,
  parse,
  startOfDay,
  startOfMonth,
  startOfWeek,
} from "date-fns"
import { fr } from "date-fns/locale"
import type { AgendaView } from "./agenda-url.ts"
import type { AgendaWeekStart } from "./agenda-settings-types.ts"

export const WEEK_OPTS = { weekStartsOn: 1 as const, locale: fr }

export type WeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6

/** Valeur stable SSR / premier rendu client quand weekStart = auto. */
export const DEFAULT_WEEK_STARTS_ON: WeekStartsOn = 1

/** Résout le premier jour de semaine (auto = locale navigateur, sinon lundi). */
export function resolveWeekStartsOn(weekStart: AgendaWeekStart = "auto"): WeekStartsOn {
  if (weekStart !== "auto") return weekStart
  if (typeof navigator === "undefined") return DEFAULT_WEEK_STARTS_ON
  try {
    const locale = new Intl.Locale(navigator.language)
    const info = (locale as Intl.Locale & { weekInfo?: { firstDay?: number } }).weekInfo
    const firstDay = info?.firstDay
    if (firstDay === 7) return 0
    if (firstDay != null && firstDay >= 0 && firstDay <= 6) return firstDay as WeekStartsOn
  } catch {
    /* locale non supportée */
  }
  return navigator.language.toLowerCase().startsWith("en-us") ? 0 : 1
}

export function getWeekOptionsFor(startsOn: WeekStartsOn) {
  return { weekStartsOn: startsOn, locale: fr }
}

export function getWeekOptions(weekStart: AgendaWeekStart = "auto") {
  return getWeekOptionsFor(resolveWeekStartsOn(weekStart))
}

export const WEEKDAY_LABELS_MON_FIRST = ["L", "M", "M", "J", "V", "S", "D"] as const
export const WEEKDAY_LABELS_SUN_FIRST = ["D", "L", "M", "M", "J", "V", "S"] as const

export function weekdayLabelsFor(startsOn: WeekStartsOn): readonly string[] {
  return startsOn === 0 ? WEEKDAY_LABELS_SUN_FIRST : WEEKDAY_LABELS_MON_FIRST
}

export function weekdayLabels(weekStart: AgendaWeekStart = "auto"): readonly string[] {
  return weekdayLabelsFor(resolveWeekStartsOn(weekStart))
}

/** Parse une valeur de date ICS (`YYYYMMDD` ou `YYYYMMDDTHHMMSS[Z]`). */
export function parseICSDate(value: string): Date | null {
  const v = value.trim()
  if (/^\d{8}$/.test(v)) {
    const y = Number(v.slice(0, 4))
    const m = Number(v.slice(4, 6))
    const d = Number(v.slice(6, 8))
    return new Date(y, m - 1, d)
  }
  const match = /^(\d{4})(\d{2})(\d{2})T(\d{2})(\d{2})(\d{2})(Z?)$/.exec(v)
  if (!match) {
    const fallback = new Date(v)
    return Number.isNaN(fallback.getTime()) ? null : fallback
  }
  const [, y, mo, d, h, mi, s, z] = match
  if (z === "Z") {
    return new Date(Date.UTC(+y, +mo - 1, +d, +h, +mi, +s))
  }
  return new Date(+y, +mo - 1, +d, +h, +mi, +s)
}

/** Format ICS UTC : `YYYYMMDDTHHMMSSZ`. */
export function formatICSDateTimeUTC(date: Date): string {
  const p = (n: number, w = 2) => String(n).padStart(w, "0")
  return (
    `${p(date.getUTCFullYear(), 4)}${p(date.getUTCMonth() + 1)}${p(date.getUTCDate())}` +
    `T${p(date.getUTCHours())}${p(date.getUTCMinutes())}${p(date.getUTCSeconds())}Z`
  )
}

/** Format ICS date locale (journée entière) : `YYYYMMDD`. */
export function formatICSDateOnly(date: Date): string {
  return format(date, "yyyyMMdd")
}

export function dateKey(date: Date): string {
  return format(date, "yyyy-MM-dd")
}

export function parseDateKey(key: string): Date | null {
  const parsed = parse(key, "yyyy-MM-dd", new Date())
  return Number.isNaN(parsed.getTime()) ? null : parsed
}

function weekOptionsFor(
  weekStart: AgendaWeekStart,
  resolvedStartsOn?: WeekStartsOn,
) {
  return resolvedStartsOn != null
    ? getWeekOptionsFor(resolvedStartsOn)
    : getWeekOptions(weekStart)
}

/** Bornes affichées pour une vue (le mois inclut les semaines débordantes). */
export function viewRange(
  view: AgendaView,
  date: Date,
  weekStart: AgendaWeekStart = "auto",
  resolvedStartsOn?: WeekStartsOn,
): { start: Date; end: Date } {
  const opts = weekOptionsFor(weekStart, resolvedStartsOn)
  switch (view) {
    case "day":
      return { start: startOfDay(date), end: endOfDay(date) }
    case "week":
      return {
        start: startOfWeek(date, opts),
        end: endOfWeek(date, opts),
      }
    case "month":
      return {
        start: startOfWeek(startOfMonth(date), opts),
        end: endOfWeek(endOfMonth(date), opts),
      }
  }
}

export function viewDays(
  view: AgendaView,
  date: Date,
  weekStart: AgendaWeekStart = "auto",
  resolvedStartsOn?: WeekStartsOn,
): Date[] {
  const { start, end } = viewRange(view, date, weekStart, resolvedStartsOn)
  const days: Date[] = []
  for (let d = start; d <= end; d = addDays(d, 1)) days.push(d)
  return days
}

function capitalize(value: string): string {
  return value.charAt(0).toUpperCase() + value.slice(1)
}

/** Titre du header façon Google Calendar. */
export function viewTitle(
  view: AgendaView,
  date: Date,
  weekStart: AgendaWeekStart = "auto",
  resolvedStartsOn?: WeekStartsOn,
): string {
  if (view === "day") {
    return capitalize(format(date, "EEEE d MMMM yyyy", { locale: fr }))
  }
  if (view === "month") {
    return capitalize(format(date, "MMMM yyyy", { locale: fr }))
  }
  const { start, end } = viewRange("week", date, weekStart, resolvedStartsOn)
  if (isSameMonth(start, end)) {
    return capitalize(format(start, "MMMM yyyy", { locale: fr }))
  }
  if (isSameYear(start, end)) {
    return `${capitalize(format(start, "MMM", { locale: fr }))} – ${format(end, "MMM yyyy", { locale: fr })}`
  }
  return `${capitalize(format(start, "MMM yyyy", { locale: fr }))} – ${format(end, "MMM yyyy", { locale: fr })}`
}

export function formatEventTime(
  date: Date,
  timeFormat: "24h" | "12h" = "24h",
): string {
  return format(date, timeFormat === "12h" ? "h:mm a" : "HH:mm", { locale: fr })
}

export function formatHourLabel(hour: number, timeFormat: "24h" | "12h" = "24h"): string {
  const d = new Date(2000, 0, 1, hour, 0, 0)
  return formatEventTime(d, timeFormat)
}

export function formatDurationMinutes(totalMinutes: number): string {
  const hours = Math.floor(totalMinutes / 60)
  const minutes = totalMinutes % 60
  if (hours === 0) return `${minutes} min`
  if (minutes === 0) return hours === 1 ? "1 h" : `${hours} h`
  return `${hours} h ${minutes}`
}

/** Libellé durée pour chips +/- agenda : largeur stable (heures toujours avec minutes). */
export function formatAgendaStepDurationMinutes(totalMinutes: number): string {
  const hours = Math.floor(totalMinutes / 60)
  const minutes = totalMinutes % 60
  if (hours === 0) return `${minutes} min`
  return `${hours} h ${String(minutes).padStart(2, "0")}`
}

export function minutesToTimeInput(date: Date): string {
  return format(date, "HH:mm")
}

export function snapMinutes(value: number, step: number): number {
  return Math.round(value / step) * step
}

/** Plage horaire lisible pour le popover de détails. */
export function formatEventRange(
  start: Date,
  end: Date,
  allDay: boolean,
  timeFormat: "24h" | "12h" = "24h",
): string {
  const sameDay = dateKey(start) === dateKey(end)
  const dayLabel = (d: Date) => capitalize(format(d, "EEEE d MMMM", { locale: fr }))
  if (allDay) {
    const endIncl = addDays(end, -1)
    if (dateKey(start) === dateKey(endIncl) || end <= start) return dayLabel(start)
    return `${dayLabel(start)} – ${dayLabel(endIncl)}`
  }
  if (sameDay) {
    return `${dayLabel(start)} ⋅ ${formatEventTime(start, timeFormat)} – ${formatEventTime(end, timeFormat)}`
  }
  return `${dayLabel(start)} ${formatEventTime(start, timeFormat)} – ${dayLabel(end)} ${formatEventTime(end, timeFormat)}`
}

export function minutesSinceMidnight(date: Date): number {
  return date.getHours() * 60 + date.getMinutes()
}

export function roundToStep(minutes: number, step = 15): number {
  return Math.round(minutes / step) * step
}
