import {
  addDays,
  addHours,
  addMonths,
  addWeeks,
  eachDayOfInterval,
  endOfMonth,
  endOfWeek,
  format,
  isSameMonth,
  startOfDay,
  startOfMonth,
  startOfWeek,
  subMonths,
  subWeeks,
  type Locale,
} from "date-fns";
import { ar, enUS } from "date-fns/locale";

import type { CalendarView } from "../types";

export {
  isSameDay,
  isSameMonth,
  isToday,
  setHours,
  startOfDay,
} from "date-fns";

export type WeekStart = 0 | 1 | 2 | 3 | 4 | 5 | 6;

/** Map the app language to a date-fns locale (Gregorian calendar, localized names). */
export function getDateLocale(language: string): Locale {
  return language === "ar" ? ar : enUS;
}

/** Explicit week start wins; otherwise fall back to the locale's convention. */
export function resolveWeekStartsOn(
  explicit: WeekStart | undefined,
  locale: Locale,
): WeekStart {
  return (explicit ?? locale.options?.weekStartsOn ?? 0) as WeekStart;
}

/** Move the focused date by one period in the given direction (-1 = back, 1 = forward). */
export function navigateDate(
  date: Date,
  view: CalendarView,
  direction: -1 | 1,
): Date {
  if (view === "month") {
    return direction === 1 ? addMonths(date, 1) : subMonths(date, 1);
  }
  if (view === "week") {
    return direction === 1 ? addWeeks(date, 1) : subWeeks(date, 1);
  }
  return addDays(date, direction);
}

/** Weeks (each an array of 7 days) fully covering the month that contains `date`. */
export function getMonthWeeks(date: Date, weekStartsOn: WeekStart): Date[][] {
  const start = startOfWeek(startOfMonth(date), { weekStartsOn });
  const end = endOfWeek(endOfMonth(date), { weekStartsOn });
  const days = eachDayOfInterval({ start, end });
  const weeks: Date[][] = [];
  for (let i = 0; i < days.length; i += 7) weeks.push(days.slice(i, i + 7));
  return weeks;
}

/** The 7 days of the week containing `date`, ordered from `weekStartsOn`. */
export function getWeekDays(date: Date, weekStartsOn: WeekStart): Date[] {
  const start = startOfWeek(date, { weekStartsOn });
  return Array.from({ length: 7 }, (_, i) => addDays(start, i));
}

/** Localized weekday headers (short + narrow), ordered from `weekStartsOn`. */
export function getWeekdayLabels(
  locale: Locale,
  weekStartsOn: WeekStart,
): { short: string; narrow: string }[] {
  // Any fixed reference week works — only the day-of-week names matter.
  const base = startOfWeek(new Date(2021, 7, 1), { weekStartsOn });
  return Array.from({ length: 7 }, (_, i) => {
    const day = addDays(base, i);
    return {
      short: format(day, "EEE", { locale }),
      narrow: format(day, "EEEEE", { locale }),
    };
  });
}

/** Hour rows for the time grid, with localized 12-hour labels. */
export function getHourRows(
  locale: Locale,
  startHour: number,
  endHour: number,
): { hour: number; label: string }[] {
  const base = startOfDay(new Date(2021, 0, 1));
  const rows: { hour: number; label: string }[] = [];
  for (let hour = startHour; hour < endHour; hour++) {
    rows.push({ hour, label: format(addHours(base, hour), "h a", { locale }) });
  }
  return rows;
}

/** The heading shown in the toolbar for the current period. */
export function getPeriodLabel(
  date: Date,
  view: CalendarView,
  locale: Locale,
  weekStartsOn: WeekStart,
): string {
  if (view === "month") return format(date, "LLLL yyyy", { locale });
  if (view === "day") return format(date, "EEEE, d LLLL yyyy", { locale });

  const start = startOfWeek(date, { weekStartsOn });
  const end = endOfWeek(date, { weekStartsOn });
  if (isSameMonth(start, end)) {
    return `${format(start, "d", { locale })} – ${format(end, "d LLLL yyyy", { locale })}`;
  }
  if (start.getFullYear() === end.getFullYear()) {
    return `${format(start, "d LLLL", { locale })} – ${format(end, "d LLLL yyyy", { locale })}`;
  }
  return `${format(start, "d LLLL yyyy", { locale })} – ${format(end, "d LLLL yyyy", { locale })}`;
}

/** A compact localized time range, e.g. "9:00 AM – 10:30 AM". */
export function formatTimeRange(
  start: Date,
  end: Date,
  locale: Locale,
): string {
  return `${format(start, "h:mm a", { locale })} – ${format(end, "h:mm a", { locale })}`;
}

/** A single localized time, e.g. "9:00 AM". */
export function formatTime(date: Date, locale: Locale): string {
  return format(date, "h:mm a", { locale });
}

/** Localized day-of-month number (e.g. header/cell digits). */
export function formatDayNumber(date: Date, locale: Locale): string {
  return format(date, "d", { locale });
}

/** Localized weekday + day-of-month, for time-grid column headers. */
export function formatColumnHeader(
  date: Date,
  locale: Locale,
): { weekday: string; day: string } {
  return {
    weekday: format(date, "EEE", { locale }),
    day: format(date, "d", { locale }),
  };
}
