import {
  addMilliseconds,
  addMonths,
  isFirstDayOfMonth,
  isLastDayOfMonth,
  differenceInMilliseconds,
  differenceInMonths,
  endOfMonth,
  startOfDay,
  endOfDay,
  differenceInDays,
  addDays,
} from "date-fns";
import { TZDate } from "@date-fns/tz";
import type { HassConfig } from "home-assistant-js-websocket";
import type { FrontendLocaleData } from "../../data/translation";
import { TimeZone } from "../../data/translation";

const calcZonedDate = (
  date: Date,
  tz: string,
  fn: (date: Date, options?: any) => Date | number | boolean,
  options?
) => {
  const tzDate = new TZDate(date, tz);
  const fnResult = fn(tzDate, options);
  if (fnResult instanceof Date) {
    // Convert back to regular Date in the specified timezone
    return new Date(fnResult.getTime());
  }
  return fnResult;
};

export const calcDate = (
  date: Date,
  fn: (date: Date, options?: any) => Date,
  locale: FrontendLocaleData,
  config: HassConfig,
  options?
) =>
  locale.time_zone === TimeZone.server
    ? (calcZonedDate(date, config.time_zone, fn, options) as Date)
    : fn(date, options);

export const calcDateProperty = (
  date: Date,
  fn: (date: Date, options?: any) => boolean | number,
  locale: FrontendLocaleData,
  config: HassConfig,
  options?
) =>
  locale.time_zone === TimeZone.server
    ? (calcZonedDate(date, config.time_zone, fn, options) as number | boolean)
    : fn(date, options);

export const calcDateDifferenceProperty = (
  endDate: Date,
  startDate: Date,
  fn: (date: Date, options?: any) => boolean | number,
  locale: FrontendLocaleData,
  config: HassConfig
) =>
  calcDateProperty(
    endDate,
    fn,
    locale,
    config,
    locale.time_zone === TimeZone.server
      ? new TZDate(startDate, config.time_zone)
      : startDate
  );

export const shiftDateRange = (
  startDate: Date,
  endDate: Date,
  forward: boolean,
  locale: FrontendLocaleData,
  config: any
): { start: Date; end: Date } => {
  let start: Date;
  let end: Date;
  if (
    (calcDateProperty(
      startDate,
      isFirstDayOfMonth,
      locale,
      config
    ) as boolean) &&
    (calcDateProperty(endDate, isLastDayOfMonth, locale, config) as boolean)
  ) {
    const difference =
      ((calcDateDifferenceProperty(
        endDate,
        startDate,
        differenceInMonths,
        locale,
        config
      ) as number) +
        1) *
      (forward ? 1 : -1);
    start = calcDate(startDate, addMonths, locale, config, difference);
    end = calcDate(
      calcDate(endDate, addMonths, locale, config, difference),
      endOfMonth,
      locale,
      config
    );
  } else if (
    calcDateProperty(
      startDate,
      (date) => startOfDay(date).getMilliseconds() === date.getMilliseconds(),
      locale,
      config
    ) &&
    calcDateProperty(
      endDate,
      (date) => endOfDay(date).getMilliseconds() === date.getMilliseconds(),
      locale,
      config
    )
  ) {
    const difference =
      ((calcDateDifferenceProperty(
        endDate,
        startDate,
        differenceInDays,
        locale,
        config
      ) as number) +
        1) *
      (forward ? 1 : -1);
    start = calcDate(startDate, addDays, locale, config, difference);
    end = calcDate(endDate, addDays, locale, config, difference);
  } else {
    const difference =
      (calcDateDifferenceProperty(
        endDate,
        startDate,
        differenceInMilliseconds,
        locale,
        config
      ) as number) * (forward ? 1 : -1);
    start = calcDate(startDate, addMilliseconds, locale, config, difference);
    end = calcDate(endDate, addMilliseconds, locale, config, difference);
  }
  return { start, end };
};

/**
 * @description Parses a date in browser display timezone
 * @param date - The date to parse
 * @param timezone - The timezone to parse the date in
 * @returns The parsed date as a Date object
 */
export const parseDate = (date: string, timezone: string): Date => {
  const tzDate = new TZDate(date, timezone);
  return new Date(tzDate.getTime());
};

/**
 * @description Formats a date in browser display timezone
 * @param date - The date to format
 * @param timezone - The timezone to format the date in
 * @returns The formatted date in YYYY-MM-DD format
 */
export const formatDate = (date: Date, timezone: string): string => {
  const tzDate = new TZDate(date, timezone);
  return tzDate.toISOString().split("T")[0];
};

/**
 * @description Formats a time in browser display timezone
 * @param date - The date to format
 * @param timezone - The timezone to format the time in
 * @returns The formatted time in HH:mm:ss format
 */
export const formatTime = (date: Date, timezone: string): string => {
  const tzDate = new TZDate(date, timezone);
  return tzDate.toISOString().split("T")[1].split(".")[0];
};
