import { change, getMonthNames, getMonthNamesStartingWith, today } from '../Calendar/utils';
import { rule, MaskType } from '../hooks/useMask';

interface NumericMaskProps {
  maximumFractionDigits?: number;
  minimumFractionDigits?: number;
  minValue?: number;
  maxValue?: number;
  separator?: string | false;
}

export const numericMask: MaskType = (props: NumericMaskProps = {}) => {
  const { maximumFractionDigits, minimumFractionDigits, minValue, maxValue, separator } = props;

  return {
    mask: [
      rule('-', ({ nextValue, cursorIndex, target }) => {
        if (Number.isFinite(minValue) && minValue >= 0) return false;
        if (nextValue.match(/-/g).length > 1 || cursorIndex !== 0) return false;
        return true;
      }),
      rule('.', ({ nextValue, cursorIndex }) => {
        if (nextValue[cursorIndex] === '-') return false;
        if (Number.isInteger(maximumFractionDigits) && maximumFractionDigits <= 0) return false;
        return nextValue.match(/\./g).length <= 1;
      }),
      rule(',', ({ nextValue, previousValue, cursorIndex }) => {
        if (nextValue[cursorIndex] === '-') return false;
        if (previousValue[cursorIndex - 1] === (separator || ',')) return false;
        if (previousValue.slice(0, cursorIndex).split('.')[1]) return false;
        return true;
      }),
      rule(/^[0-9]$/, ({ cursorIndex, nextValue }) => {
        if (nextValue[cursorIndex] === '-') return false;
        const numberValue = parseFloat(nextValue.replace(/,/g, ''));
        if (Number.isFinite(maxValue) && numberValue > maxValue) return false;
        if (Number.isFinite(minValue) && numberValue < minValue) return false;
        if (Number.isInteger(maximumFractionDigits) && (nextValue.split('.')[1]?.length || 0) > maximumFractionDigits) return false;
        return true;
      }),
    ],
    formatter: (value) => {
      if (!value || value.length === 0) return value;
      const numberValue = parseFloat(value.replace(/,/g, ''));
      if (isNaN(numberValue)) return '';
      const numberString = isFinite(maximumFractionDigits) ? numberValue.toFixed(maximumFractionDigits) : numberValue.toString();
      const parts = numberString.split('.');
      if (separator !== false) {
        parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, (separator || ','));
      }

      if (isFinite(minimumFractionDigits)) {
        const decimalNumber = parseFloat(`0.${parts[1] || '0'}`);
        parts[1] = isNaN(decimalNumber) ? '0' : (`${decimalNumber}`.split('.')[1] || '0');
        parts[1] = parts[1].padEnd(minimumFractionDigits, '0')
      } else if (parseInt(parts[1]) === 0) {
        parts.pop();
      }

      return parts.join('.');
    }
  }
}

export const calendarDayMask: MaskType = ({ dateValue = today() } = {}) => {
  const performRule = (nextInputValue: string) => {
    if (nextInputValue === '0') return true;
    if (nextInputValue.length > 2) return false; // max of 2 digits for day
    const nextDateValue = change(dateValue, nextInputValue, 'day');
    if (dateValue.getMonth() !== nextDateValue.getMonth()) return false;
    if (dateValue.getFullYear() !== nextDateValue.getFullYear()) return false;
    if (nextDateValue.getDate() !== parseInt(nextInputValue, 10)) return false;
    return true;
  }

  return {
    shiftFocusIf: (nextValue, key) => {
      if (key == '/') return true;
      if (nextValue === '0') return false;
      return performRule(nextValue) && !performRule(`${nextValue}0`);
    },
    mask: [
      rule(/^[0-9]$/, ({ nextValue }) => {
        return performRule(nextValue)
      }),
    ],
    formatter: (value) => {
      if (!value || value.length === 0) return value;
      if (value === '0') return '01'; // Special case for zero
      return `${value}`.padStart(2, '0');
    },
    aria: {
      'aria-valuemin': 1,
      'aria-valuemax': 31
    }
  }
}

export const calendarMonthMask: MaskType = ({ dateValue = today() } = {}) => {

  const performRule = (nextInputValue: string) => {
    if (nextInputValue === '0') return true;
    if (nextInputValue.length > 2) return false; // max of 2 digits for month
    const monthNumber = parseInt(nextInputValue, 10) - 1; // Convert to zero-based month index
    const nextDateValue = change(dateValue, monthNumber, 'monthIndex');
    if (nextDateValue.getMonth() !== monthNumber) return false;
    if (dateValue.getFullYear() !== nextDateValue.getFullYear()) return false;
    if (dateValue.getDate() !== nextDateValue.getDate()) return false;
    return true;
  }

  return {
    shiftFocusIf: (nextValue, key) => {
      if (key == '/') return true;
      if (nextValue === '0') return false;
      return performRule(nextValue) && !performRule(`${nextValue}0`);
    },
    mask: [
      rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)),
    ],
    formatter: (value) => {
      if (!value || value.length === 0) return value;
      if (value === '0') return '01'; // Special case for zero
      return `${value}`.padStart(2, '0');
    },
    aria: {
      'aria-valuemin': 1,
      'aria-valuemax': 12
    }
  }
}

export const calendarYearMask: MaskType = (props = {}) => {
  const performRule = (nextInputValue: string) => {
    if (nextInputValue.length <= 4) return true; // needs at least 4 digits for year

    return false;
  }
  return {
    shiftFocusIf: (nextValue, key) => key == '/' || (performRule(nextValue) && !performRule(`${nextValue}0`)),
    mask: [
      rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)),
    ],
    formatter: (value) => {
      if (!value) return value;
      return `${value}`.padStart(3, '0').padStart(4, '2');;
    },
    aria: {
      'aria-valuemin': 1,
      'aria-valuemax': 9999
    }
  }
}

export const calendarHourMask: MaskType = ({ militaryTime = false } = {}) => {
  const performRule = (nextInputValue: string) => {
    if (nextInputValue === '0') return true;
    if (nextInputValue.length > 2) return false; // max of 2 digits for hour
    const numberValue = parseInt(nextInputValue, 10);
    if (militaryTime) {
      if (numberValue >= 0 && numberValue <= 23) return true; // 00-23 for military time
    } else {
      if (numberValue >= 1 && numberValue <= 12) return true; // 01-12 for standard time
    }

    return false;
  }
  return {
    shiftFocusIf: (nextValue, key) => {
      if (key == ':') return true;
      if (nextValue === '0') return false;
      return (performRule(nextValue) && !performRule(`${nextValue}0`))
    },
    mask: [
      rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)),
    ],
    formatter: (value) => {
      if (!value || value.length === 0) return value;
      if (value === '0' && !militaryTime) return '01'; // Special case for zero
      return `${value}`.padStart(2, '0');
    },
    aria: {
      'aria-valuemin': 1,
      'aria-valuemax': 23
    }
  }
}

export const calendarMinuteMask: MaskType = (props = {}) => {
  const performRule = (nextInputValue: string) => {
    if (nextInputValue === '0') return true;
    if (nextInputValue.length > 2) return false; // max of 2 digits for minute
    const numberValue = parseInt(nextInputValue, 10);
    if (numberValue >= 0 && numberValue <= 59) return true;
    return false;
  }
  return {
    shiftFocusIf: (nextValue, key) => {
      if (key == ':') return true;
      if (nextValue === '0') return false;
      return (performRule(nextValue) && !performRule(`${nextValue}0`))
    },
    mask: [
      rule(/^[0-9]$/, ({ nextValue }) => performRule(nextValue)),
    ],
    formatter: (value) => {
      if (!value || value.length === 0) return value;
      return `${value}`.padStart(2, '0');
    },
    aria: {
      'aria-valuemin': 0,
      'aria-valuemax': 59
    }
  }
}

export const calendarTimeOfDayMask: MaskType = (props = {}) => {
  return {
    shiftFocusIf: (_nextValue, key) => {
      const lowerKey = key.toLowerCase();
      if (lowerKey == 'a' || lowerKey == 'p') return true;
      return false;
    },
    mask: [
      rule(/^[aApP]$/)
    ],
    autoComplete: (value, key) => {
      if (key.toLowerCase() === 'a') return 'AM';
      if (key.toLowerCase() === 'p') return 'PM';
      return value;
    },
    formatter: (value) => {
      if (!value || value.length === 0) return value;
      if (value.toLowerCase().startsWith('a')) return 'AM';
      if (value.toLowerCase().startsWith('p')) return 'PM';
      return value;
    },
    aria: {
      'aria-valuemin': 0,
      'aria-valuemax': 1
    }
  }
}

// For english for example, this will return 'Jul' for july (since June also starts with "Ju"), and "Oct" for "o" since it is unique.
function autoCompleteKeyForAbbreviation(abbrev: string, characterCount = 1) {
  if (abbrev.length === characterCount) return abbrev;

  const matches = getMonthNamesStartingWith(abbrev.slice(0, characterCount), 'default', 'short');
  if (matches.length === 1) {
    return abbrev.toLowerCase().slice(0, characterCount);
  } else if (matches.length === 0) {
    return 'noclue';
  } else {
    return autoCompleteKeyForAbbreviation(abbrev, characterCount + 1);
  }
}

export const calendarMonthNameMask: MaskType = (props = {}) => {
  const monthAbbrevs = getMonthNames('default', 'short');

  const autoCompleteKeys = monthAbbrevs.map((abbrev) => [autoCompleteKeyForAbbreviation(abbrev), abbrev]);

  const autoComplete = (value: string) => {
    const foundAutoCompleteKey = autoCompleteKeys.find((keyMatch) => value.startsWith(keyMatch[0]));
    if (!foundAutoCompleteKey) return value;
    return foundAutoCompleteKey[1];
  }
  return {
    shiftFocusIf: (nextValue) => {
      if (autoComplete(nextValue) !== nextValue) return true;
      if (nextValue.length >= 3) return true;
      return false;
    },
    mask: [
      rule(/^[a-zA-Z]$/, ({ nextValue }) => {
        if (nextValue.length > 3) return false; // max of 3 letters for month name

        for (let i = 0; i < monthAbbrevs.length; i++) {
          if (monthAbbrevs[i].toLowerCase().startsWith(nextValue.toLowerCase())) return true; // Check if the next value starts with a valid month abbreviation
        }
        return false; // If it doesn't match any month abbreviation, return false
      })
    ],
    autoComplete,
    formatter: (value) => {
      if (!value || value.length === 0) return value;
      for (let i = 0; i < monthAbbrevs.length; i++) {
        const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
        if (monthAbbrevs[i].toLowerCase().startsWith(value.toLowerCase())) return capitalize(monthAbbrevs[i]); // Check if the next value starts with a valid month abbreviation
      }
      return value;
    },
    aria: {
      'aria-valuemin': 1,
      'aria-valuemax': 12
    }
  }
}

export default {
  'number': numericMask,
  'calendar-day': calendarDayMask,
  'calendar-month': calendarMonthMask,
  'calendar-year': calendarYearMask,
  'time-hour': calendarHourMask,
  'time-minute': calendarMinuteMask,
  'time-tod': calendarTimeOfDayMask
}
