import {
  ChangeDetectionStrategy,
  Component,
  computed,
  Signal,
  inject,
} from '@angular/core';
import { DatePipe } from '@angular/common';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import {
  isaActionChevronDown,
  isaActionChevronLeft,
  isaActionChevronRight,
} from '@isa/icons';
import {
  format,
  startOfMonth,
  endOfMonth,
  startOfWeek,
  endOfWeek,
  eachDayOfInterval,
  isDate,
  isValid,
  parse,
} from 'date-fns';
import { CalendarBodyCellDirective } from './calendar-body-cell.directive';
import { DatepickerViewState } from '../../datepicker-view.state';
import { DatepickerView } from '../../types/datepicker-view.type';
import { DaysOfWeek } from '../../types/days-of-week.type';
import { injectDatepicker } from '../../inject-datepicker';
import { enUS } from 'date-fns/locale';

/**
 * Component that renders the calendar body for the datepicker.
 * It computes the calendar days, selected dates, and handles navigation
 * between months.
 */
@Component({
  selector: 'ui-calendar-body',
  templateUrl: 'calendar-body.component.html',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class]': "['ui-calendar-body']",
  },
  imports: [DatePipe, NgIconComponent, CalendarBodyCellDirective],
  providers: [
    provideIcons({
      isaActionChevronLeft,
      isaActionChevronRight,
      isaActionChevronDown,
    }),
  ],
})
export class CalendarBodyComponent {
  /**
   * The injected Datepicker instance.
   */
  datepicker = injectDatepicker();

  /**
   * The state of the Datepicker view.
   */
  viewState = inject(DatepickerViewState);

  /**
   * An alias to the DatepickerView constant.
   */
  DatepickerView = DatepickerView;

  /**
   * A computed property that returns the selected dates from the Datepicker.
   * If the value is an array, only valid Date objects are returned.
   */
  selectedDates = computed(() => {
    const selected = this.datepicker.value();
    if (!selected) return [];

    if (Array.isArray(selected)) {
      return selected.filter((date) => isDate(date));
    }

    return [selected];
  });

  /**
   * Computes an array of abbreviated day names (first letter in uppercase) based on the DaysOfWeek enum.
   *
   * It iterates over the values of DaysOfWeek, parses each full day name using the 'EEEE' pattern,
   * and then formats the parsed date to extract the first character in uppercase.
   *
   * @returns {string[]} The array of abbreviated day names.
   */
  daysOfWeek = computed(() => {
    const dates: string[] = [];

    for (const day of Object.values(DaysOfWeek)) {
      const parsed = parse(day, 'EEEE', new Date(), { locale: enUS });
      dates.push(format(parsed, 'EEEE').charAt(0).toUpperCase());
    }

    return dates;
  });

  /**
   * A signal that computes the array of days to be displayed in the calendar.
   * The interval is determined based on the displayed month and week boundaries.
   */
  calendarDays: Signal<Date[]> = computed(() => {
    const selectedMonth = this.viewState.displayedDate();
    const start = startOfWeek(startOfMonth(selectedMonth), { weekStartsOn: 1 });
    const end = endOfWeek(endOfMonth(selectedMonth), { weekStartsOn: 1 });
    return eachDayOfInterval({ start, end });
  });

  /**
   * A computed property that indicates whether the Datepicker has a valid value.
   * Checks both singular and array values to ensure at least one valid date exists.
   */
  hasValue = computed(() => {
    const value = this.datepicker.value();
    if (!value) return false;

    if (Array.isArray(value)) {
      return value.some((date) => !!date && isValid(date));
    }

    return !!value && isValid(value);
  });

  /**
   * Navigates to the previous month by updating the displayed date.
   */
  previous() {
    const date = this.viewState.displayedDate();
    this.viewState.displayedDate.set(
      new Date(date.getFullYear(), date.getMonth() - 1),
    );
  }

  /**
   * Navigates to the next month by updating the displayed date.
   */
  next() {
    const date = this.viewState.displayedDate();
    this.viewState.displayedDate.set(
      new Date(date.getFullYear(), date.getMonth() + 1),
    );
  }
}
