import {
  addMinutes,
  differenceInCalendarDays,
  differenceInHours,
  endOfWeek,
  format,
  formatDistanceToNowStrict,
  formatDuration,
  hoursToMinutes,
  intervalToDuration,
  isBefore,
  isSameDay,
  isToday,
  isTomorrow,
  isYesterday,
  minutesToSeconds,
  parseISO,
  secondsToMilliseconds,
  set,
  startOfDay,
  startOfWeek,
} from 'date-fns';
import i18next, { type TFunction } from 'i18next';
import { isString } from 'lodash-es';
import { decodeTime } from 'ulid';

import { MILISECONDS, MINUTES } from 'src/constants/date';
import { Languages } from 'src/constants/languages';
import { LOCALE_MAP } from 'src/constants/locale';
import { ChatStatus } from 'src/types/chats';
import { type Duration } from 'src/types/date';
import { type MeUser } from 'src/types/user';
import { type ParamsDatesVacationCell } from 'src/types/vacations';

import { type LokaliseTFunction } from './i18n';
import { getCurrentLocale } from './locale';
import { capitalizeFirstLetter } from './text';

export const formatRelativeDate = (date: Date) => {
  try {
    if (isToday(date)) return format(date, 'HH:mm');
    if (isYesterday(date)) return i18next.t('YESTERDAY');

    return format(date, 'dd/MM/yy');
  } catch (_err) {
    return '';
  }
};

export const getCompletedDate = (date: Date | string, user: MeUser) => {
  const dateToFormat = new Date(date);
  const correctLocale = getCurrentLocale(user);
  try {
    return format(dateToFormat, 'EEEE PPP', { locale: correctLocale });
  } catch (_err) {
    return '';
  }
};

export const getCompletedDateWithTimeAndDay = (
  date: Date | string,
  user: MeUser,
) => {
  const dateToFormat = new Date(date);
  const correctLocale = getCurrentLocale(user);
  try {
    return format(dateToFormat, 'EEEE PPPp', {
      locale: correctLocale,
    });
  } catch (_err) {
    return '';
  }
};

export const getCompletedDateWithTime = (date: Date | string, user: MeUser) => {
  const dateToFormat = new Date(date);
  const correctLocale = getCurrentLocale(user);
  try {
    return format(dateToFormat, 'PPPp', {
      locale: correctLocale,
    });
  } catch (_err) {
    return '';
  }
};

export const getTimeFormat = (date: string) => {
  const dateToFormat = new Date(date);
  try {
    return format(dateToFormat, 'HH:mm a');
  } catch (_err) {
    return '';
  }
};

export const formatFullDate = (dateString: string, user: MeUser) => {
  if (!dateString) return '';

  const [year, month, day] = dateString.split('-').map(Number);
  // Use the multi-argument Date constructor to avoid timezone shifts - new Date(val) is UTC
  const dateObj = new Date(year, month - 1, day);
  const languageSymbol = user?.language;
  const dd = String(dateObj.getDate()).padStart(2, '0');
  const mm = String(dateObj.getMonth() + 1).padStart(2, '0');
  const yyyy = dateObj.getFullYear();

  return languageSymbol === Languages.ENGLISH
    ? `${mm}/${dd}/${yyyy}`
    : `${dd}/${mm}/${yyyy}`;
};

export const formatDateToMonthYear = (dateString: string, user: MeUser) => {
  const [year, month] = dateString.split('-').map(Number);
  // Use the multi-argument Date constructor to avoid timezone shifts - default date is 1
  const dateObj = new Date(year, month - 1, 1);
  const languageSymbol = user?.language || 'en';
  const monthStr = dateObj.toLocaleString(languageSymbol, { month: 'short' });
  const yyyy = dateObj.getFullYear();

  return `${monthStr.toUpperCase()} ${yyyy}`;
};

export const getFormatedDateTime = (date: string, user?: MeUser) => {
  try {
    const isEnglish = user?.language === Languages.ENGLISH;
    return format(
      parseISO(date),
      isEnglish ? 'MM/dd/yyyy HH:mm' : 'dd/MM/yyyy HH:mm',
    );
  } catch (_err) {
    return '';
  }
};

export const formatToTime = (date: Date | string) => {
  try {
    return format(new Date(date), 'HH:mm');
  } catch (_err) {
    return '';
  }
};

export const formatDistanceToNow = (
  date: Date,
  user: MeUser | null,
  onlyDistance = false,
) => {
  try {
    const correctLocale = getCurrentLocale(user);
    const distance = formatDistanceToNowStrict(date, { locale: correctLocale });

    if (!onlyDistance && date.getTime() > Date.now()) {
      // For future dates (e.g. scheduled posts), rely on date-fns suffix
      // so we don't force "ago" translations on upcoming content.
      return formatDistanceToNowStrict(date, {
        locale: correctLocale,
        addSuffix: true,
      });
    }

    return i18next.t(
      onlyDistance ? 'general:distance' : 'general:time_distance',
      {
        // Some locales still interpolate with {time}; passing both prevents
        // raw placeholder leaks in UI while keeping current keys compatible.
        distance,
        time: distance,
        defaultValue: distance,
      },
    );
  } catch (_err) {
    return '';
  }
};

export const formatDistanceNotifications = (
  date: Date,
  user: MeUser | null,
) => {
  try {
    const correctLocale = getCurrentLocale(user);
    if (isYesterday(date)) return i18next.t('general:yesterday');
    return i18next.t('general:time_distance', {
      distance: formatDistanceToNowStrict(date, { locale: correctLocale }),
    });
  } catch (_err) {
    return '';
  }
};

export const formatDateToLongLocalizedDate = (
  date: Date | string,
  user: MeUser,
) => {
  const dateToFormat = new Date(date);
  const correctLocale = getCurrentLocale(user);
  try {
    return format(dateToFormat, 'PPP', { locale: correctLocale });
  } catch (_err) {
    return '';
  }
};

export const formatDateInChatMessages = (date: Date, user: MeUser) => {
  const dateToFormat = new Date(date);
  const correctLocale = getCurrentLocale(user);
  try {
    if (isToday(dateToFormat)) return i18next.t('TODAY');
    if (isYesterday(dateToFormat)) return i18next.t('YESTERDAY');

    return format(dateToFormat, 'MMMM d', { locale: correctLocale });
  } catch (_err) {
    return '';
  }
};

export const formatUTCLocalDate = (
  date: string | Date,
  user: MeUser,
  pattern = user?.language === Languages.ENGLISH ? 'MM/dd/yyyy' : 'dd/MM/yyyy',
) => {
  const dateToFormat = new Date(date);
  const locale = getCurrentLocale(user);
  try {
    return format(dateToFormat, pattern, { locale });
  } catch (_err) {
    return '';
  }
};

export const formatUTCDate = (
  date: string | number | Date,
  pattern = 'dd/MM/yyyy',
): string => {
  try {
    return format(new Date(date), pattern);
  } catch (_err) {
    return '';
  }
};

export const isSameUTCDate = (a: Date, b: Date): boolean =>
  formatUTCDate(a) === formatUTCDate(b);

export const addOneDay = (date: Date) =>
  new Date(date.setDate(date.getDate() + 1));

export const addRemoveDay = (date: string, add = true) => {
  const d = new Date(date);
  return add ? d.setDate(d.getDate() + 1) : d.setDate(d.getDate() - 1);
};

export const simpleDateStringToLocalDate = (utcISOdate: string | Date) => {
  if (typeof utcISOdate !== 'string') {
    return utcISOdate;
  }
  const dateInUTC = parseISO(utcISOdate);

  const newDate = new Date();
  const calendarLocal = set(newDate, {
    year: dateInUTC.getUTCFullYear(),
    month: dateInUTC.getUTCMonth(),
    date: dateInUTC.getUTCDate(),
  });
  // `set` keeps hours/min/sec/ms from `newDate`, so identical ISO strings would
  // otherwise yield different timestamps on each call (breaks date-only comparisons).
  return startOfDay(calendarLocal);
};

export const orderByDate = (
  a: { createdAt: Date | string },
  b: { createdAt: Date | string },
) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();

export const formatDateInVacations = ({
  date,
  user,
  type,
  isUtc,
}: ParamsDatesVacationCell) => {
  let dateToFormat: Date;
  if (isString(date)) {
    if (isUtc) {
      dateToFormat = new Date(date);
    } else {
      dateToFormat = new Date(parseISO(date));
    }
  } else {
    dateToFormat = date;
  }
  const correctLocale = getCurrentLocale(user);
  try {
    return format(dateToFormat, type, { locale: correctLocale });
  } catch (_err) {
    return '';
  }
};

const getDifferenceInHours = (dateLeft?: Date, dateRight?: Date): number => {
  const difference = dateLeft
    ? differenceInHours(
        new Date(dateLeft),
        dateRight ? new Date(dateRight) : new Date(),
      )
    : undefined;
  return Math.max(difference || 0, 0);
};

export const getResolutionTime = (dueDate: Date, createdAt?: Date) => {
  const hoursToResolve = getDifferenceInHours(dueDate, createdAt);
  if (!hoursToResolve) {
    return '0h';
  }

  const resolutionTimeDays = Math.trunc(hoursToResolve / 24);
  const resolutionTimeHours = Math.trunc(hoursToResolve % 24);

  let formattedResolutionTime = '';
  if (resolutionTimeDays > 0) {
    formattedResolutionTime += `${resolutionTimeDays}d `;
  }
  if (resolutionTimeHours > 0) {
    formattedResolutionTime += `${resolutionTimeHours}h`;
  }

  return formattedResolutionTime;
};

export const getColorByTime = (
  createdAt: Date,
  dueDate: Date,
  tabSelected: ChatStatus,
) => {
  if (tabSelected === ChatStatus.UNASSIGNED) {
    return 'secondary';
  } else if (tabSelected === ChatStatus.CLOSED) {
    return 'primary';
  }

  const hoursTotalTime = getDifferenceInHours(dueDate, createdAt);
  const hoursToResolve = getDifferenceInHours(dueDate);

  if (hoursToResolve > hoursTotalTime * 0.66) {
    return 'success';
  }

  if (hoursToResolve > hoursTotalTime * 0.33) {
    return 'warning';
  }

  return 'error';
};

export const getTotalDuration = (duration: Duration) => {
  const newDuration = {
    hours: 0,
    minutes: 0,
    seconds: 0,
    milliseconds: 0,
  };

  newDuration.hours += duration?.hours || 0;
  newDuration.minutes += hoursToMinutes(newDuration.hours);

  newDuration.minutes += duration?.minutes || 0;
  newDuration.seconds += minutesToSeconds(newDuration.minutes);

  newDuration.seconds += duration?.seconds || 0;
  newDuration.milliseconds += secondsToMilliseconds(newDuration.seconds);

  return newDuration;
};

export const formatDateDifference = (
  lastUpdateDate: string,
  t: TFunction | LokaliseTFunction,
) => {
  if (!lastUpdateDate) return null;

  const now = new Date();
  const nowUTC = now.getTime();

  const lastUpdateUTC = new Date(lastUpdateDate).getTime();

  const diffDate = Math.abs(nowUTC - lastUpdateUTC);

  const minutes = Math.floor(diffDate / MILISECONDS.MINUTE);
  const hours = Math.floor(diffDate / MILISECONDS.HOUR);
  const days = Math.floor(diffDate / MILISECONDS.DAY);
  const years = Math.floor(diffDate / MILISECONDS.YEAR);
  const remainingDays = Math.floor(
    (diffDate % MILISECONDS.YEAR) / MILISECONDS.DAY,
  );

  if (minutes < MINUTES.MINUTE) {
    return t('LESS_THAN_A_MINUTE', { ns: 'general' });
  } else if (minutes < MINUTES.HOUR) {
    return t('MINUTES_AGO', { count: minutes, ns: 'general' });
  } else if (minutes < MINUTES.DAY) {
    return t('HOURS_AGO', { count: hours, ns: 'general' });
  } else if (days < 365) {
    return t('DAYS_AGO', { count: days, ns: 'general' });
  }

  // Construct the time string with years and days
  const yearsPart = t('years', { years, count: years, ns: 'general' });
  const daysPart =
    remainingDays > 0
      ? ` ${t('and', { ns: 'general' })} ${t('days', { days: remainingDays, count: remainingDays, ns: 'general' })}`
      : '';
  const timeString = `${yearsPart}${daysPart}`;

  // Example return: "1 year and 365 days ago"
  return t('time_ago', { time: timeString, ns: 'general' });
};

export const dateIsBefore = (
  date: Date | string,
  dateToCompare: Date | string,
) => isBefore(new Date(date), new Date(dateToCompare));

export const getBanBajioCurrentDate = () => {
  const currentDate = new Date();
  const isEs = i18next.language === 'es';
  let day: string;
  if (isEs) {
    day = format(currentDate, 'dd', { locale: LOCALE_MAP.es });
  } else {
    day = format(currentDate, 'do', { locale: LOCALE_MAP.en });
  }
  const month = format(currentDate, 'MMM', {
    locale: isEs ? LOCALE_MAP.es : LOCALE_MAP.en,
  }).toUpperCase();
  const year = format(currentDate, 'yyyy', {
    locale: isEs ? LOCALE_MAP.es : LOCALE_MAP.en,
  });
  return { day, month, year };
};

/**
 * Formats a date consistently with localization support and optional time inclusion.
 * This function normalizes timezone handling to ensure consistent date representation.
 *
 * @param {string | Date} date - The date to format
 * @param {Object} labels - Configuration options
 * @param {boolean} [labels.includeTime=false] - Whether to include time in the formatted output
 * @param {string} [labels.language] - Language code override (defaults to i18next.language)
 * @returns {string | null} - Formatted date string or null if invalid
 */
export const formatLocalizedDate = (
  date: string | Date,
  options: { includeTime?: boolean; language?: string } = {},
) => {
  if (!date) return null;

  // Create a consistent date representation
  let localDate: Date;
  if (typeof date === 'string') {
    const parsedDate = parseISO(date);
    // Set to noon to avoid day boundary issues with timezone conversions
    localDate = new Date(
      parsedDate.getFullYear(),
      parsedDate.getMonth(),
      parsedDate.getDate(),
      12,
      0,
      0,
      0,
    );
  } else {
    // For Date objects, normalize time to noon
    localDate = new Date(date);
    localDate.setHours(12, 0, 0, 0);
  }

  if (localDate.toString() === 'Invalid Date') return null;

  const isEs = options.language
    ? options.language === 'es'
    : i18next.language === 'es';

  // Determine format string based on language and time inclusion
  let formatString = '';
  if (isEs) {
    formatString = options.includeTime ? 'dd/MMM/yyyy HH:mm' : 'dd/MMM/yyyy';
  } else {
    formatString = options.includeTime ? 'MMM/dd/yyyy HH:mm' : 'MMM/dd/yyyy';
  }

  // If we need time and had a date object with specific time, restore it
  if (options.includeTime && date instanceof Date) {
    localDate.setHours(date.getHours(), date.getMinutes(), 0, 0);
  } else if (options.includeTime && typeof date === 'string') {
    const originalDate = new Date(date);
    if (originalDate.toString() !== 'Invalid Date') {
      localDate.setHours(
        originalDate.getHours(),
        originalDate.getMinutes(),
        0,
        0,
      );
    }
  }

  return format(localDate, formatString, {
    locale: isEs ? LOCALE_MAP.es : LOCALE_MAP.en,
  }).toUpperCase();
};

export const formatTime = (date: Date, pattern = 'HH:mm') => {
  const hours = date.getHours().toString().padStart(2, '0');
  const minutes = date.getMinutes().toString().padStart(2, '0');
  if (pattern === 'HH:mm') {
    return `${hours}:${minutes}`;
  }
  return `${hours}:${minutes}`;
};

export const formatDateEvents = (date: string, user: MeUser) => {
  const dateToFormat = new Date(date);
  const correctLocale = getCurrentLocale(user);
  try {
    if (isToday(dateToFormat)) return i18next.t('today');
    if (isTomorrow(dateToFormat)) return i18next.t('tomorrow');

    return format(dateToFormat, 'PPP', { locale: correctLocale });
  } catch (_err) {
    return '';
  }
};

export const formatDurationEvents = (
  starDate: Date | string,
  endDate: Date | string,
  user: MeUser | null,
) => {
  const correctLocale = getCurrentLocale(user);

  const duration = intervalToDuration({
    start: new Date(starDate),
    end: new Date(endDate),
  });

  return formatDuration(duration, {
    format:
      duration.days && duration.days >= 1
        ? ['months', 'days']
        : ['hours', 'minutes'],
    locale: correctLocale,
  });
};

export const setTimeString = (date: Date, timeString: string) => {
  const [hours, minutes] = timeString.split(':').map(Number);
  date.setHours(hours, minutes, 0, 0);
  return date;
};

export const getHHMMstring = (date: string | Date) => {
  const hours = getHHString(date);
  const minutes = getMMString(date);

  return `${hours}:${minutes}`;
};

export const formatMessageDate = (ts: string, user: MeUser | null) => {
  const correctLocale = getCurrentLocale(user);

  const tsFormated = decodeTime(ts);
  const date = new Date(tsFormated);

  return format(date, 'HH:mm', { locale: correctLocale });
};

export const formatMessageShortDate = (ts: string, user: MeUser) => {
  const tsFormated = decodeTime(ts);
  return formatUTCLocalDate(new Date(tsFormated), user);
};

export const formatPillsDate = (
  date: Date,
  user: MeUser | null,
  t: LokaliseTFunction,
) => {
  const today = new Date();

  if (isSameDay(date, today)) return t('general:today');
  if (isYesterday(date)) return t('general:yesterday');

  const correctLocale = getCurrentLocale(user);

  const daysDifference = differenceInCalendarDays(today, date);

  if (daysDifference >= 1 && daysDifference <= 6) {
    const dayOfWeek = format(date, 'EEEE', { locale: correctLocale });
    return capitalizeFirstLetter(dayOfWeek);
  }
  const dayOfWeek = format(date, 'EEE', { locale: correctLocale });
  const month = format(date, 'MMM', { locale: correctLocale }).toLowerCase();
  const day = format(date, 'd', { locale: correctLocale });
  return `${capitalizeFirstLetter(dayOfWeek)}, ${month} ${day}`;
};

export const formateDateForMessageRead = (
  ts: number,
  user: MeUser | null,
  t: LokaliseTFunction,
) => {
  if (!ts) return;

  const date = new Date(ts);
  const today = new Date();

  const correctLocale = getCurrentLocale(user);

  const hourObject = {
    hour: format(date, 'HH:mm', { locale: correctLocale }),
  };

  if (isSameDay(date, today)) {
    return {
      date: t('general:today'),
      ...hourObject,
    };
  }

  if (isYesterday(date)) {
    return {
      date: t('general:yesterday'),
      ...hourObject,
    };
  }

  return {
    date: format(date, 'dd/MM/yyyy', { locale: correctLocale }),
    ...hourObject,
  };
};

export const formatTimestampInChatList = (
  ts: string,
  user: MeUser | null,
  t: LokaliseTFunction,
) => {
  if (!ts) return;

  const tsFormated = decodeTime(ts);
  const date = new Date(tsFormated);
  const today = new Date();

  const correctLocale = getCurrentLocale(user);

  if (isToday(date)) {
    return format(date, 'HH:mm', { locale: correctLocale });
  }

  if (isYesterday(date)) {
    return t('general:yesterday');
  }

  const daysDifference = differenceInCalendarDays(today, date);

  if (daysDifference >= 1 && daysDifference <= 6) {
    return format(date, 'EEEE', { locale: correctLocale });
  }

  return format(date, 'dd/MM/yyyy', { locale: correctLocale });
};

/**
 * Formats a given date into a string format that includes the abbreviated weekday,
 * day of the month, abbreviated month name, and time, according to the user's locale.
 *
 * @param {Date} date - The date object to format.
 * @param {User} user - The user object containing locale information.
 * @returns {string} Formatted date string, such as 'Wed, 19 Jun - 16:20'.
 *
 * @example
 * // Usage example:
 * const user = { language: 'es' }; // Assuming the user object has a language property like this
 * formatPublicationDate(new Date('2024-06-19T16:20:00'), user);
 * // Returns: 'mié, 19 jun - 16:20' (assuming the user locale is Spanish)
 *
 * const user = { language: 'en' }; // Another example with English locale
 * formatPublicationDate(new Date('2024-06-19T16:20:00'), user);
 * // Returns: 'Wed, 19 Jun - 16:20'
 */
export const formatPublicationDate = (date: Date, user: MeUser) => {
  const correctLocale = getCurrentLocale(user);
  return format(new Date(date), 'EEE, d MMM - HH:mm', {
    locale: correctLocale,
  });
};

/**
 * Calculates the UTC offset, including Daylight Saving Time (DST) adjustments, for a specific date in a given timezone.
 *
 * @param {Date} date - The date for which the offset is to be calculated.
 * @param {string} timezone - The IANA timezone identifier (e.g., "America/New_York").
 * @returns {number} - The UTC offset in minutes for the specified date and timezone.
 *
 * @example
 * // Get the UTC offset for "America/New_York" on a DST date
 * const date = new Date("2025-06-01T12:00:00Z");
 * const offset = getTimezoneOffsetWithDST(date, "America/New_York");
 * console.log(offset); // Output: -240 (UTC-4 during DST)
 *
 * @example
 * // Get the UTC offset for "America/New_York" during standard time
 * const date = new Date("2025-01-01T12:00:00Z");
 * const offset = getTimezoneOffsetWithDST(date, "America/New_York");
 * console.log(offset); // Output: -300 (UTC-5 during standard time)
 *
 * @example
 * // Get the UTC offset for "Asia/Tokyo" on a specific date
 * const date = new Date("2025-06-01T12:00:00Z");
 * const offsetTokyo = getTimezoneOffsetWithDST(date, "Asia/Tokyo");
 * console.log(offsetTokyo); // Output: 540 (UTC+9, no DST in Tokyo)
 *
 * @note
 * This function uses the `Intl.DateTimeFormat` API with the "en-SE" locale to format the date in a reliable ISO-like format.
 * The offset is calculated as the difference between the given date and the UTC representation of the same date in the target timezone.
 */
export const getTimezoneOffsetWithDST = (
  date: Date,
  timezone: string,
): number => {
  // Using English Swedish locale
  const timeStampFormatter = new Intl.DateTimeFormat('en-SE', {
    timeZone: timezone,
    year: 'numeric',
    month: 'numeric',
    day: 'numeric',
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric',
  });

  // Local time in the specified timezone
  const timeString = timeStampFormatter
    .formatToParts(date)
    .map(part => part.value)
    .join('');

  // Convert local time back to a Date object in UTC
  const localDateInTimezone = new Date(`${timeString.replace(', ', 'T')}Z`);

  // Calculate the offset in minutes
  return (localDateInTimezone.getTime() - date.getTime()) / (1000 * 60);
};

// Takes an ISO dateString an returns a Date object of the same date
const parseDateISOString = (dateString: string) => {
  const parsedDate = dateString.split(/\D/).map(s => parseInt(s));
  const [year, month, day, hour, minute, second] = parsedDate;
  return new Date(year, month - 1, day, hour, minute, second);
};

/**
 * Parses a `Date` object and adjusts it between the local timezone and a specified target timezone.
 *
 * @param {Date} date - The `Date` object to be adjusted.
 * @param {string} timezone - The IANA timezone identifier (e.g., "America/New_York").
 * @param {'from' | 'to'} [way='to'] - Specifies the direction of the adjustment:
 *  - `'to'`: Converts the local `Date` to the target timezone.
 *  - `'from'`: Converts a `Date` from the target timezone back to the local timezone.
 * @returns {Date} - A new `Date` object representing the adjusted time.
 *
 * @example
 * // Convert a local date (UTC-3) to its equivalent in the "America/New_York" timezone (UTC-5)
 * const localDate = new Date("2025-01-02T12:00:00"); // Tue Jan 07 2025 12:00:00 GMT-0300 (Argentina Standard Time)
 * const convertedDate = parseLocalDateWithTimezone(localDate, "America/New_York", "to"); // Adjusted date in New York's timezone
 * console.log(convertedDate); // Output: Tue Jan 07 2025 10:00:00 GMT-0300 (Argentina Standard Time)
 *
 * @example
 * // Convert a date from the "America/New_York" timezone (UTC-5) back to the local timezone (UTC-3)
 * const localDate = new Date("2025-01-02T12:00:00"); // Tue Jan 07 2025 12:00:00 GMT-0300 (Argentina Standard Time)
 * const adjustedDate = parseLocalDateWithTimezone(localDate, "America/New_York", "from"); // Date adjusted back to the local timezone
 * console.log(adjustedDate); // Output: Tue Jan 07 2025 14:00:00 GMT-0300 (Argentina Standard Time)
 */
export const parseLocalDateWithTimezone = (
  date: Date,
  timezone: string,
  way: 'from' | 'to' = 'to',
): Date => {
  const targetTimezoneOffset = getTimezoneOffsetWithDST(date, timezone);
  const currentTimezoneOffset = getTimezoneOffsetWithDST(
    date,
    Intl.DateTimeFormat().resolvedOptions().timeZone,
  );

  // Calculate the difference in offsets
  const offsetDifference = targetTimezoneOffset - currentTimezoneOffset;

  return way === 'to'
    ? new Date(date.getTime() + offsetDifference * 60000)
    : new Date(date.getTime() - offsetDifference * 60000);
};

/**
 * Formats a given string date into another string date which after being used under the constructor Date() will represent
 * the date in the given timezone as a local date
 *
 * @param {string} dateString - Date to parse into another timezone.
 * @param {string} timezone - An ANIA compliant timezone (ex: "America/New_York").
 * @returns {string} - string representing the given date in the given timezone.
 *
 * @example
 * const timestamp = '2025-01-01T12:00:00.000Z' // (date given in UTC-0)
 * const timezone = 'America/New_York' // UTC-5
 * const parsedTimeStamp = formatDateInTimezone(timestamp, timezone) // Output: 2025-01-01T10:00:00.000Z
 * new Date(parsedTimeStamp) // Output: Wed Jan 01 2025 07:00:00 GMT-0300 (Argentina Standard Time)
 */
export const formatDateInTimezone = (dateString: string, timezone: string) => {
  // obtain offset of timezone
  const offsetMinutes = getTimezoneOffsetWithDST(
    new Date(dateString),
    timezone,
  );
  const utcDate = parseDateISOString(dateString);
  const adjustedDate = addMinutes(utcDate, offsetMinutes); // Date adjusted to match timezone's offset
  return adjustedDate.toISOString();
};

export const getHHString = (date: string | Date) => {
  const localeDate = new Date(date);
  const hours = localeDate.getHours().toString().padStart(2, '0');
  return hours;
};

export const getMMString = (date: string | Date) => {
  const localeDate = new Date(date);
  const minutes = localeDate.getMinutes().toString().padStart(2, '0');
  return minutes;
};

/**
 * Checks if a Unix timestamp in milliseconds is almost equal to the current time.
 *
 * @param unixTimestampMs - The Unix timestamp to compare (in milliseconds).
 * @param toleranceSeconds - Max allowed difference in seconds (default is 5).
 * @returns True if the timestamp is within the tolerance range of the current time.
 */
export const isUnixTimestampAlmostEqualToNow = (
  unixTimestampMs: number,
  toleranceSeconds: number = 5,
): boolean => Math.abs(Date.now() - unixTimestampMs) <= toleranceSeconds * 1000;

export const getWeekRange = (
  date: Date,
  weekStartsOn: 'MONDAY' | 'SUNDAY' = 'MONDAY',
) => ({
  startDate: startOfWeek(date, {
    weekStartsOn: weekStartsOn === 'MONDAY' ? 1 : 0,
  }),
  endDate: endOfWeek(date, { weekStartsOn: weekStartsOn === 'MONDAY' ? 1 : 0 }),
});

export const getTimeZoneInfo = (timeZone: string) => {
  try {
    const date = new Date();

    // Get GMT offset
    const offsetFormatter = new Intl.DateTimeFormat(i18next.language, {
      timeZone,
      timeZoneName: 'longOffset',
    });

    const offsetParts = offsetFormatter.formatToParts(date);
    const offsetPart = offsetParts.find(part => part.type === 'timeZoneName');

    // Get descriptive timezone name
    const descriptiveFormatter = new Intl.DateTimeFormat(i18next.language, {
      timeZone,
      timeZoneName: 'long',
    });

    const descriptiveParts = descriptiveFormatter.formatToParts(date);
    const descriptivePart = descriptiveParts.find(
      part => part.type === 'timeZoneName',
    );

    if (offsetPart && descriptivePart) {
      return `(${offsetPart.value}) ${descriptivePart.value}`;
    }

    return timeZone;
  } catch (_error) {
    return timeZone;
  }
};

/**
 * Formats a date from ISO string preserving the exact date value from backend
 * without timezone conversion. Extracts YYYY-MM-DD from ISO string and formats it.
 * @param date - Date in ISO format (e.g., "2025-11-19T00:00:00.000Z")
 * @returns Formatted date string in DD/MM/YYYY format
 * @example
 * formatDateFromISO('2025-11-19T00:00:00.000Z')
 * returns: '19/11/2025'
 */
export const formatDateFromISO = (date: string | Date): string => {
  if (!date) return '';

  const dateStr =
    typeof date === 'string'
      ? date.split('T')[0]
      : date.toISOString().split('T')[0];
  const [year, month, day] = dateStr.split('-');

  return `${day}/${month}/${year}`;
};
