import {
  ChangeDetectionStrategy,
  Component,
  computed,
  inject,
} from '@angular/core';
import { format, getMonth, getYear, startOfMonth } from 'date-fns';
import { RangeDatepicker } from '../../range-datepicker';
import { ButtonComponent } from '@isa/ui/buttons';
import { isaActionCheck } from '@isa/icons';
import { NgIconComponent, provideIcons } from '@ng-icons/core';
import { DatepickerViewState } from '../../datepicker-view.state';

@Component({
  selector: 'ui-month-year-body',
  templateUrl: 'month-year-body.component.html',
  standalone: true,
  changeDetection: ChangeDetectionStrategy.OnPush,
  host: {
    '[class]': "['ui-month-year-body']",
  },
  imports: [ButtonComponent, NgIconComponent],
  providers: [
    provideIcons({
      isaActionCheck,
    }),
  ],
})
export class MonthYearBodyComponent {
  /**
   * The injected Datepicker instance.
   */
  datepicker = inject(RangeDatepicker);

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

  /**
   * The current displayed month derived from the DatepickerViewState.
   */
  currentDisplayedMonth = getMonth(this.viewState.displayedDate());

  /**
   * The current displayed year derived from the DatepickerViewState.
   */
  currentDisplayedYear = getYear(this.viewState.displayedDate());

  /**
   * A computed property that returns an array of month objects for the current year.
   *
   * Each month object contains a formatted label and the corresponding Date value.
   * Months outside the min and max bounds are represented as null.
   */
  months = computed(() => {
    const year = this.currentDisplayedYear;
    const min = this.datepicker.min();
    const max = this.datepicker.max();

    return Array.from({ length: 12 }, (_, i) => {
      const date = new Date(year, i, 1);

      const beforeMin = min && date < startOfMonth(min);
      const afterMax = max && date > startOfMonth(max);

      if (beforeMin || afterMax) return null;

      return {
        label: format(date, 'LLLL'),
        value: date,
      };
    });
  });

  /**
   * A computed property that returns an array of year objects within the allowed range.
   *
   * Each year object contains a string label and the corresponding Date value.
   */
  years = computed(() => {
    const min = this.datepicker.min();
    const max = this.datepicker.max();

    const currentYear = getYear(new Date());

    const minYear = min ? getYear(min) : currentYear - 5; // Default: up to 5 years back
    const maxYear = max ? getYear(max) : currentYear;

    return Array.from({ length: maxYear - minYear + 1 }, (_, i) => {
      const year = maxYear - i;
      const date = new Date(year, 0, 1);

      return {
        label: year.toString(),
        value: date,
      };
    });
  });

  /**
   * A computed property that indicates if the currently displayed month and year
   * match the displayed date in the DatepickerViewState.
   */
  canSave = computed(() => {
    const currentMonth = this.currentDisplayedMonth;
    const currentYear = this.currentDisplayedYear;

    const displayedDate = this.viewState.displayedDate();
    const displayedMonth = getMonth(displayedDate);
    const displayedYear = getYear(displayedDate);

    return currentMonth === displayedMonth && currentYear === displayedYear;
  });

  /**
   * Updates the displayed month in the DatepickerViewState.
   *
   * @param newValue - A Date object representing the new month.
   */
  updateMonth(newValue: Date) {
    const month = getMonth(newValue);
    const year = getYear(this.viewState.displayedDate());
    this.viewState.displayedDate.set(new Date(year, month));
  }

  /**
   * Updates the displayed year in the DatepickerViewState.
   *
   * @param newValue - A Date object representing the new year.
   */
  updateYear(newValue: Date) {
    const month = getMonth(this.viewState.displayedDate());
    const year = getYear(newValue);
    this.viewState.displayedDate.set(new Date(year, month));
  }

  /**
   * Rolls back the displayed month and year in the DatepickerViewState to the original values.
   */
  rollbackDisplayedMonthYear() {
    this.viewState.displayedDate.set(
      new Date(this.currentDisplayedYear, this.currentDisplayedMonth),
    );
  }

  /**
   * Checks if the provided date's month matches the currently displayed month.
   *
   * @param {Date} date - The date to check.
   * @returns {boolean} True if the month matches, false otherwise.
   */
  isSelectedMonth(date: Date): boolean {
    return getMonth(this.viewState.displayedDate()) === getMonth(date);
  }

  /**
   * Checks if the provided date's year matches the currently displayed year.
   *
   * @param {Date} date - The date to check.
   * @returns {boolean} True if the year matches, false otherwise.
   */
  isSelectedYear(date: Date): boolean {
    return getYear(this.viewState.displayedDate()) === getYear(date);
  }
}
