import {
  isToday,
  isAfter,
  parse,
  compareAsc,
  addDays,
  isSameDay,
  startOfDay,
} from "date-fns";
import { nb } from "date-fns/locale";
import { toZonedTime, format } from "date-fns-tz";
import { unique } from "./common";

const timeZone = "Europe/Oslo";

export const compactDateTimeFormat = "dd.MM.yyyy 'kl.' HH:mm";
export const compactDateFormat = "dd.MM.yyyy";

export function toLocalTime(date: Date | string | number) {
  return toZonedTime(date, timeZone);
}

export function formatDate(date: Date | string | number, formatStr: string) {
  return format(toLocalTime(date), formatStr, { timeZone, locale: nb });
}

export function formatYearMonth(yearMonth: string) {
  // full name of month if year is current year, otherwise name of month + year
  const parsed = parse(yearMonth, "yyyy-MM", new Date());
  if (parsed.getFullYear === new Date().getFullYear) {
    return formatDate(parsed, "MMMM");
  }
  return formatDate(parsed, "MMMM yyyy");
}

export function formatExtendedDateTime(
  date: Date | string | number,
  dateOnly: boolean = false,
  alwaysIncludeYear: boolean = false
) {
  // wide date with weekday and month name
  // year included if not current year
  const parsed = toLocalTime(date);
  const timePart = dateOnly ? "" : " 'kl.' HH:mm";
  const isCurrentYear = parsed.getFullYear() === new Date().getFullYear();
  const yearPart = (!isCurrentYear || alwaysIncludeYear) ? " yyyy" : "";
  const formatStr = `EEEE d. MMMM${yearPart}${timePart}`;
  const formatted = format(parsed, formatStr, { timeZone, locale: nb });
  return formatted;
}

export function isTodayOrFuture(
  date: Date | string | number,
  timeZone = "UTC"
) {
  const now = new Date();
  const zonedNow = toZonedTime(now, timeZone);
  const zonedDate = toLocalTime(date);
  return isToday(zonedDate) || isAfter(zonedDate, zonedNow);
}

export function compareDates(a: Date | string, b: Date | string) {
  return compareAsc(new Date(a), new Date(b));
}

export function isConsecutiveDays(a: Date, b: Date): boolean {
  const nextDay = addDays(a, 1);
  return isSameDay(nextDay, b);
}

export function groupConsecutiveDates(dates: string[]): string[][] {
  const groupedDates: string[][] = [];
  const uniqueDays = unique(
    dates.map((date) => format(startOfDay(toLocalTime(date)), "yyyy-MM-dd"))
  ).sort();
  let group: string[] = [];

  for (let i = 0; i < uniqueDays.length; i++) {
    if (
      i === 0 ||
      isSameDay(uniqueDays[i - 1], uniqueDays[i]) ||
      isConsecutiveDays(uniqueDays[i - 1], uniqueDays[i])
    ) {
      group.push(uniqueDays[i]);
    } else {
      groupedDates.push(group);
      group = [uniqueDays[i]];
    }
  }

  if (group.length > 0) {
    groupedDates.push(group);
  }

  return groupedDates;
}

export function formatOccurrenceMonths(starts: string[]): string {
  if (starts.length === 0) return "";

  const months = unique(
    starts.map((s) => format(toLocalTime(s), "yyyy-MM"))
  ).sort() as string[];

  const monthIndex = (ym: string) => {
    const [y, m] = ym.split("-").map(Number);
    return y * 12 + (m - 1);
  };

  const groups: string[][] = [];
  for (const ym of months) {
    const last = groups[groups.length - 1];
    if (last && monthIndex(ym) === monthIndex(last[last.length - 1]) + 1) {
      last.push(ym);
    } else {
      groups.push([ym]);
    }
  }

  return groups
    .map((g) => {
      const first = parse(g[0], "yyyy-MM", new Date());
      if (g.length === 1) {
        return formatDate(first, "MMMM yyyy");
      }
      const last = parse(g[g.length - 1], "yyyy-MM", new Date());
      const firstFmt =
        first.getFullYear() === last.getFullYear()
          ? formatDate(first, "MMMM")
          : formatDate(first, "MMMM yyyy");
      return `${firstFmt} – ${formatDate(last, "MMMM yyyy")}`;
    })
    .join(", ");
}

export function formatDateRange(dates: string[]): string {
  if (dates.length === 1) {
    return formatDate(dates[0], "d. MMMM");
  }

  const firstDate = toLocalTime(dates[0]);
  const lastDate = toLocalTime(dates[dates.length - 1]);

  if (firstDate.getMonth() === lastDate.getMonth()) {
    // same month
    return `${firstDate.getDate()}.—${lastDate.getDate()}. ${formatDate(
      firstDate,
      "MMMM"
    )}`;
  }

  // continues into next month
  const formattedFirstDate = formatDate(firstDate, "d. MMMM");
  const formattedLastDate = formatDate(lastDate, "d. MMMM");
  return `${formattedFirstDate}—${formattedLastDate}`;
}
