import { computed, Directive, input } from '@angular/core';
import {
  isAfter,
  isBefore,
  isDate,
  isSameDay,
  isToday,
  isWithinInterval,
  startOfDay,
} from 'date-fns';
import { injectDatepicker } from '../../inject-datepicker';

@Directive({
  selector: '[uiCalendarBodyCell]',
  host: {
    '[class.selected]': 'isSelected()',
    '[class.today]': 'isToday()',
    '[disabled]': 'isDisabled()',
    '[class.start]': "isDateInRange() === 'start'",
    '[class.end]': "isDateInRange() === 'end'",
    '[class.in-range]': "isDateInRange() === 'in-range'",
    '(click)': 'setValue()',
  },
})
export class CalendarBodyCellDirective {
  /**
   * The injected Datepicker instance.
   */
  datepicker = injectDatepicker();

  /**
   * A required input property representing a day as a Date object.
   */
  day = input.required<Date>({ alias: 'uiCalendarBodyCell' });

  /**
   * Computed property that returns true if the current day is today.
   */
  isToday = computed<boolean>(() => isToday(this.day()));

  /**
   * Computed property that determines if the current day should be disabled.
   * Returns true if the day is before the minimum allowed date or after the maximum.
   */
  isDisabled = computed<boolean>(() => {
    const currentDay = this.day();
    const min = this.datepicker.min();
    const max = this.datepicker.max();

    if (min && isBefore(currentDay, startOfDay(min))) {
      return true;
    }

    if (max && isAfter(currentDay, startOfDay(max))) {
      return true;
    }

    return false;
  });

  /**
   * Computed property that indicates whether the current day is selected.
   * Checks if the day matches the selected date or is part of the selected range.
   */
  isSelected = computed<boolean>(() => {
    const currentDay = this.day();
    const value = this.datepicker.value();

    if (Array.isArray(value)) {
      return value.some((date) => isDate(date) && isSameDay(currentDay, date));
    }

    return false;
  });

  /**
   * Computed property that determines the role of the current day in a date range.
   *
   * Returns:
   * - 'start' if the day is the start of the range and the range spans multiple days,
   * - 'end' if the day is the end of the range and the range spans multiple days,
   * - 'in-range' if the day lies between the start and end,
   * - An empty string if the day is outside the range or if start and end are the same.
   */
  isDateInRange = computed<'start' | 'end' | 'in-range' | ''>(() => {
    const currentDay = this.day();
    const value = this.datepicker.value();

    const [start, end] = value ?? [undefined, undefined];

    if (start && end) {
      if (isSameDay(currentDay, start) && !isSameDay(start, end)) {
        return 'start';
      }

      if (isSameDay(currentDay, end) && !isSameDay(start, end)) {
        return 'end';
      }

      if (isWithinInterval(currentDay, { start, end })) {
        return 'in-range';
      }
    }

    return '';
  });

  /**
   * Sets the date selection based on the current day.
   *
   * If the datepicker is in range mode, then sets the start or end of the range.
   * Otherwise, sets the selected date to the current day.
   */
  setValue(): void {
    const currentDay = this.day();
    this.datepicker.setDateRange(currentDay);
  }
}
