import dayjs from 'dayjs';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';

import * as styles from './calendar.css';
import { DATE_MAX, DATE_MIN, YEAR_MAX, YEAR_MIN } from './constants';
import { CalendarLayout, NavButtons } from './items';
import type { DatePickerModePanelProps } from './types';

const ROW_SIZE = 3;
const DECADE = 12;

export const YearPicker = memo(function YearPicker(
  props: DatePickerModePanelProps
) {
  const { value, cursor, onModeChange, onCursorChange } = props;
  const dayPickerRootRef = useRef<HTMLDivElement>(null);
  const [yearCursor, setYearCursor] = useState(dayjs(cursor).startOf('year'));

  const closeYearPicker = useCallback(
    () => onModeChange?.('day'),
    [onModeChange]
  );

  const onYearChange = useCallback(
    (y: dayjs.Dayjs) => {
      closeYearPicker();
      onCursorChange?.(y);
    },
    [closeYearPicker, onCursorChange]
  );

  const nextDecade = useCallback(() => {
    setYearCursor(prev => prev.add(DECADE, 'year').startOf('year'));
  }, []);

  const prevDecade = useCallback(() => {
    setYearCursor(prev => prev.subtract(DECADE, 'year').startOf('year'));
  }, []);

  const decadeIndex = useMemo(
    () => Math.floor((yearCursor.year() - YEAR_MIN) / DECADE),
    [yearCursor]
  );
  const decadeStart = useMemo(
    () => dayjs(DATE_MIN).add(decadeIndex * DECADE, 'year'),
    [decadeIndex]
  );
  const decadeEnd = useMemo(
    () => decadeStart.add(DECADE - 1, 'year'),
    [decadeStart]
  );
  const nextDecadeDisabled = useMemo(
    () => yearCursor.add(DECADE, 'year').isAfter(`${YEAR_MAX}-01-01`),
    [yearCursor]
  );
  const prevDecadeDisabled = useMemo(() => decadeIndex <= 0, [decadeIndex]);

  const matrix = useMemo(() => {
    const matrix = [];

    let currentYear = decadeStart.clone();
    while (currentYear.isBefore(decadeEnd.add(1, 'year'))) {
      const row = [];
      for (let i = 0; i < ROW_SIZE; i++) {
        row.push(currentYear.clone().startOf('year'));
        currentYear = currentYear.add(1, 'year');
      }
      matrix.push(row);
    }
    return matrix;
  }, [decadeEnd, decadeStart]);

  const focusCursor = useCallback(() => {
    const div = dayPickerRootRef.current;
    if (!div) return;
    const focused = div.querySelector('[data-is-year-cell][tabindex="0"]');
    focused && (focused as HTMLElement).focus();
  }, []);

  // keyboard navigation
  useEffect(() => {
    const div = dayPickerRootRef.current;
    if (!div) return;

    const onKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.preventDefault();
        e.stopPropagation();
        closeYearPicker();
        return;
      }

      if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key))
        return;

      e.preventDefault();
      e.stopPropagation();

      if (e.key === 'ArrowUp') setYearCursor(c => c.subtract(ROW_SIZE, 'year'));
      if (e.key === 'ArrowDown') setYearCursor(c => c.add(ROW_SIZE, 'year'));
      if (e.key === 'ArrowLeft') setYearCursor(c => c.subtract(1, 'year'));
      if (e.key === 'ArrowRight') setYearCursor(c => c.add(1, 'year'));
      setTimeout(focusCursor);
    };

    div.addEventListener('keydown', onKeyDown);

    return () => {
      div.removeEventListener('keydown', onKeyDown);
    };
  }, [closeYearPicker, focusCursor]);

  const HeaderLeft = useMemo(() => {
    return (
      <button
        onClick={closeYearPicker}
        className={styles.calendarHeaderTriggerButton}
      >
        {decadeStart.year()}-{decadeEnd.year()}
      </button>
    );
  }, [closeYearPicker, decadeEnd, decadeStart]);
  const HeaderRight = useMemo(
    () => (
      <NavButtons
        onNext={nextDecade}
        onPrev={prevDecade}
        nextDisabled={nextDecadeDisabled}
        prevDisabled={prevDecadeDisabled}
      />
    ),
    [nextDecade, nextDecadeDisabled, prevDecade, prevDecadeDisabled]
  );
  const Body = useMemo(() => {
    return (
      <div className={styles.decadeViewBody}>
        {matrix.map((row, i) => {
          return (
            <div key={i} className={styles.decadeViewRow}>
              {row.map((year, j) => {
                const isDisabled =
                  year.isAfter(DATE_MAX) || year.isBefore(DATE_MIN);
                return (
                  <div key={j} className={styles.decadeViewBodyCell}>
                    <button
                      aria-disabled={isDisabled}
                      data-value={year.format('YYYY')}
                      data-is-year-cell
                      className={styles.decadeViewBodyCellInner}
                      data-selected={value && year.isSame(value, 'year')}
                      data-current-year={year.isSame(dayjs(), 'year')}
                      tabIndex={year.isSame(yearCursor, 'year') ? 0 : -1}
                      onClick={
                        isDisabled ? undefined : () => onYearChange(year)
                      }
                    >
                      {year.year()}
                    </button>
                  </div>
                );
              })}
            </div>
          );
        })}
      </div>
    );
  }, [matrix, onYearChange, value, yearCursor]);

  return (
    <CalendarLayout
      mode="year"
      ref={dayPickerRootRef}
      length={ROW_SIZE}
      headerLeft={HeaderLeft}
      headerRight={HeaderRight}
      body={Body}
    />
  );
});