import type { DurationInput } from '@formatjs/intl-durationformat/src/types';
import memoizeOne from 'memoize-one';

import type { FrontendLocaleData } from '../../data/translation';
import { round } from '../number/round';

export interface ForDict {
  days?: number;
  hours?: number;
  minutes?: number;
  seconds?: number;
  milliseconds?: number;
}

export interface HaDurationData {
  days?: number;
  hours?: number;
  minutes?: number;
  seconds?: number;
  milliseconds?: number;
}

const leftPad = (num: number) => (num < 10 ? `0${num}` : num);

export const formatNumericDuration = (locale: FrontendLocaleData, duration: HaDurationData) => {
  const d = duration.days || 0;
  const h = duration.hours || 0;
  const m = duration.minutes || 0;
  const s = duration.seconds || 0;
  const ms = duration.milliseconds || 0;

  if (d > 0) {
    return `${Intl.NumberFormat(locale.language, {
      style: 'unit',
      unit: 'day',
      unitDisplay: 'long',
    }).format(d)} ${h}:${leftPad(m)}:${leftPad(s)}`;
  }
  if (h > 0) {
    return `${h}:${leftPad(m)}:${leftPad(s)}`;
  }
  if (m > 0) {
    return `${m}:${leftPad(s)}`;
  }
  if (s > 0) {
    return Intl.NumberFormat(locale.language, {
      style: 'unit',
      unit: 'second',
      unitDisplay: 'long',
    }).format(s);
  }
  if (ms > 0) {
    return Intl.NumberFormat(locale.language, {
      style: 'unit',
      unit: 'millisecond',
      unitDisplay: 'long',
    }).format(ms);
  }
  return null;
};

const formatDurationLongMem = memoizeOne(
  (locale: FrontendLocaleData) =>
    new Intl.DurationFormat(locale.language, {
      style: 'long',
    })
);

export const formatDurationLong = (locale: FrontendLocaleData, duration: HaDurationData) =>
  formatDurationLongMem(locale).format(duration);

const formatDigitalDurationMem = memoizeOne(
  (locale: FrontendLocaleData) =>
    new Intl.DurationFormat(locale.language, {
      style: 'digital',
      hoursDisplay: 'auto',
    })
);

export const formatDurationDigital = (locale: FrontendLocaleData, duration: HaDurationData) =>
  formatDigitalDurationMem(locale).format(duration);

export const DURATION_UNITS = ['min', 'h', 'd'] as const;

type DurationUnit = (typeof DURATION_UNITS)[number];

const formatDurationDayMem = memoizeOne(
  (locale: FrontendLocaleData) =>
    new Intl.DurationFormat(locale.language, {
      style: 'narrow',
      daysDisplay: 'always',
    })
);

const formatDurationHourMem = memoizeOne(
  (locale: FrontendLocaleData) =>
    new Intl.DurationFormat(locale.language, {
      style: 'narrow',
      hoursDisplay: 'always',
    })
);

const formatDurationMinuteMem = memoizeOne(
  (locale: FrontendLocaleData) =>
    new Intl.DurationFormat(locale.language, {
      style: 'narrow',
      minutesDisplay: 'always',
    })
);

export const formatDuration = (
  locale: FrontendLocaleData,
  duration: string,
  unit: DurationUnit,
  precision?: number | undefined
): string => {
  const value = precision !== undefined ? round(parseFloat(duration), precision) : parseFloat(duration);

  switch (unit) {
    case 'd': {
      const days = Math.floor(value);
      const hours = Math.floor((value - days) * 24);
      const input: DurationInput = {
        days,
        hours,
      };
      return formatDurationDayMem(locale).format(input);
    }
    case 'h': {
      const hours = Math.floor(value);
      const minutes = Math.floor((value - hours) * 60);
      const input: DurationInput = {
        hours,
        minutes,
      };
      return formatDurationHourMem(locale).format(input);
    }
    case 'min': {
      const minutes = Math.floor(value);
      const seconds = Math.floor((value - minutes) * 60);
      const input: DurationInput = {
        minutes,
        seconds,
      };
      return formatDurationMinuteMem(locale).format(input);
    }
    default:
      throw new Error('Invalid duration unit');
  }
};

export const createDurationData = (duration: string | number | ForDict | undefined): HaDurationData | undefined => {
  if (duration === undefined) {
    return undefined;
  }
  if (typeof duration !== 'object') {
    if (typeof duration === 'string' || isNaN(duration)) {
      const parts = duration?.toString().split(':') || [];
      if (parts.length === 1) {
        return { seconds: Number(parts[0]) };
      }
      if (parts.length > 3) {
        return undefined;
      }
      const seconds = Number(parts[2]) || 0;
      const seconds_whole = Math.floor(seconds);
      return {
        hours: Number(parts[0]) || 0,
        minutes: Number(parts[1]) || 0,
        seconds: seconds_whole,
        milliseconds: Math.floor(Number((seconds - seconds_whole).toFixed(4)) * 1000),
      };
    }
    return { seconds: duration };
  }
  if (!('days' in duration)) {
    return duration;
  }
  const { days, minutes, seconds, milliseconds } = duration;
  let hours = duration.hours || 0;
  hours = (hours || 0) + (days || 0) * 24;
  return {
    hours,
    minutes,
    seconds,
    milliseconds,
  };
};
