import { addDays, addMonths, addWeeks, addYears, format, startOfWeek } from "date-fns"
import { fr } from "date-fns/locale"
import { parseICSDate, WEEK_OPTS } from "./agenda-date.ts"

export type RRuleFreq = "DAILY" | "WEEKLY" | "MONTHLY" | "YEARLY"

export interface ParsedRRule {
  freq: RRuleFreq
  interval: number
  count?: number
  until?: Date
  /** Jours (0 = dimanche … 6 = samedi), uniquement pour WEEKLY. */
  byday: number[]
}

const BYDAY_TO_INDEX: Record<string, number> = {
  SU: 0,
  MO: 1,
  TU: 2,
  WE: 3,
  TH: 4,
  FR: 5,
  SA: 6,
}

const INDEX_TO_BYDAY = ["SU", "MO", "TU", "WE", "TH", "FR", "SA"]

export function parseRRule(rrule: string): ParsedRRule | null {
  const value = rrule.trim().replace(/^RRULE:/i, "")
  if (!value) return null
  const parts: Record<string, string> = {}
  for (const seg of value.split(";")) {
    const [k, v] = seg.split("=")
    if (k && v) parts[k.toUpperCase()] = v.toUpperCase()
  }
  const freq = parts.FREQ as RRuleFreq | undefined
  if (!freq || !["DAILY", "WEEKLY", "MONTHLY", "YEARLY"].includes(freq)) return null
  const interval = Math.max(1, Number(parts.INTERVAL ?? "1") || 1)
  const count = parts.COUNT ? Math.max(1, Number(parts.COUNT) || 1) : undefined
  const until = parts.UNTIL ? (parseICSDate(parts.UNTIL) ?? undefined) : undefined
  const byday =
    freq === "WEEKLY" && parts.BYDAY
      ? parts.BYDAY.split(",")
          .map((d) => BYDAY_TO_INDEX[d.trim()])
          .filter((d): d is number => d !== undefined)
      : []
  return { freq, interval, count, until, byday }
}

/**
 * Développe les occurrences d'une règle dans une fenêtre donnée.
 * COUNT est compté depuis la première occurrence, même hors fenêtre.
 */
export function expandOccurrences(
  masterStart: Date,
  rule: ParsedRRule,
  exdates: Set<number>,
  rangeStart: Date,
  rangeEnd: Date,
  maxOccurrences = 400,
): Date[] {
  const out: Date[] = []
  let produced = 0

  const push = (occurrence: Date): boolean => {
    if (rule.until && occurrence > rule.until) return false
    produced++
    if (rule.count && produced > rule.count) return false
    if (
      occurrence >= rangeStart &&
      occurrence <= rangeEnd &&
      !exdates.has(occurrence.getTime())
    ) {
      out.push(occurrence)
    }
    return out.length < maxOccurrences
  }

  if (rule.freq === "WEEKLY" && rule.byday.length > 0) {
    const days = [...rule.byday].sort((a, b) => a - b)
    let weekStart = startOfWeek(masterStart, WEEK_OPTS)
    // Garde-fou : 1 000 semaines ≈ 19 ans.
    for (let w = 0; w < 1000; w++) {
      for (const day of days) {
        // weekStart est un lundi (index 1) — offset vers le jour demandé.
        const offset = (day - 1 + 7) % 7
        const occurrence = new Date(addDays(weekStart, offset))
        occurrence.setHours(
          masterStart.getHours(),
          masterStart.getMinutes(),
          masterStart.getSeconds(),
          0,
        )
        if (occurrence < masterStart) continue
        if (!push(occurrence)) return out
      }
      weekStart = addWeeks(weekStart, rule.interval)
      if (weekStart > rangeEnd && (!rule.count || produced > rule.count)) break
      if (weekStart > rangeEnd && !rule.count) break
    }
    return out
  }

  let occurrence = new Date(masterStart)
  for (let i = 0; i < 5000; i++) {
    if (!push(occurrence)) return out
    switch (rule.freq) {
      case "DAILY":
        occurrence = addDays(occurrence, rule.interval)
        break
      case "WEEKLY":
        occurrence = addWeeks(occurrence, rule.interval)
        break
      case "MONTHLY":
        occurrence = addMonths(occurrence, rule.interval)
        break
      case "YEARLY":
        occurrence = addYears(occurrence, rule.interval)
        break
    }
    if (occurrence > rangeEnd && !rule.count && !rule.until) break
    if (occurrence > rangeEnd && rule.until && rule.until <= rangeEnd) break
  }
  return out
}

export interface RecurrenceOption {
  value: string
  label: string
}

/** Options de récurrence proposées dans l'éditeur, dépendantes de la date. */
export function recurrenceOptionsFor(start: Date): RecurrenceOption[] {
  const weekday = format(start, "EEEE", { locale: fr })
  const dayOfMonth = format(start, "d", { locale: fr })
  const monthDay = format(start, "d MMMM", { locale: fr })
  return [
    { value: "", label: "Ne se répète pas" },
    { value: "FREQ=DAILY", label: "Tous les jours" },
    {
      value: `FREQ=WEEKLY;BYDAY=${INDEX_TO_BYDAY[start.getDay()]}`,
      label: `Toutes les semaines le ${weekday}`,
    },
    { value: "FREQ=MONTHLY", label: `Tous les mois le ${dayOfMonth}` },
    { value: "FREQ=YEARLY", label: `Tous les ans le ${monthDay}` },
    {
      value: "FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR",
      label: "Tous les jours ouvrés (lun.–ven.)",
    },
  ]
}

const FREQ_LABELS: Record<RRuleFreq, [string, string]> = {
  DAILY: ["Tous les jours", "Tous les {n} jours"],
  WEEKLY: ["Toutes les semaines", "Toutes les {n} semaines"],
  MONTHLY: ["Tous les mois", "Tous les {n} mois"],
  YEARLY: ["Tous les ans", "Tous les {n} ans"],
}

const DAY_LABELS = ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."]

/** Libellé français d'une règle de récurrence arbitraire. */
export function describeRRule(rrule: string): string {
  const rule = parseRRule(rrule)
  if (!rule) return "Récurrence personnalisée"
  const [singular, plural] = FREQ_LABELS[rule.freq]
  let label =
    rule.interval === 1 ? singular : plural.replace("{n}", String(rule.interval))
  if (rule.freq === "WEEKLY" && rule.byday.length > 0) {
    const sorted = [...rule.byday].sort((a, b) => ((a + 6) % 7) - ((b + 6) % 7))
    if (sorted.length === 5 && sorted.every((d) => d >= 1 && d <= 5)) {
      label = "Tous les jours ouvrés (lun.–ven.)"
    } else {
      label += ` le ${sorted.map((d) => DAY_LABELS[d]).join(", ")}`
    }
  }
  if (rule.count) label += `, ${rule.count} fois`
  if (rule.until) {
    label += `, jusqu'au ${format(rule.until, "d MMM yyyy", { locale: fr })}`
  }
  return label
}
