import {
  $,
  component$,
  useSignal,
  useStyles$,
  useVisibleTask$,
} from '@builder.io/qwik';
import { calendar } from '@ipohjs/calendar';
import type {
  CalendarWeekday,
  DatesGridColumn,
} from '@ipohjs/calendar/dist/typings.js';
import { fromPartsToUtcDate } from '@ipohjs/calendar/from-parts-to-utc-date';
import { getWeekdays } from '@ipohjs/calendar/get-weekdays';
import {
  useKeyboardEvent,
  type UseKeyboardEventInit,
} from '@ipohjs/qwik-hooks';

import { DataCell } from './data-cell/data-cell';
import type { DataCellProps } from './data-cell/types';
import styles from './styles.css?inline';
import type { QwikCalendarProps } from './types';
import { WeekLabel } from './week-label/week-label';
import { Weekday } from './weekday/weekday';

export const QwikCalendar = component$<QwikCalendarProps>(
  ({
    class: cls,
    date,
    disabledDates,
    disabledDays,
    firstDayOfWeek,
    locale,
    onDateChangeByKey$,
    renderDataCell$,
    renderHeader$,
    renderWeekLabel$,
    renderWeekNumber$,
    shortWeekLabel,
    showWeekNumber,
    weekLabel,
    weekNumberTemplate,
    weekNumberType,
    ...props
  }) => {
    useStyles$(styles);

    const dayFormat = new Intl.DateTimeFormat(locale, { day: 'numeric' });
    const fullDateFormat = new Intl.DateTimeFormat(locale, {
      day: 'numeric',
      month: 'short',
      weekday: 'short',
      year: 'numeric',
    });
    const longWeekdayFormat = new Intl.DateTimeFormat(locale, {
      weekday: 'long',
    });
    const narrowWeekdayFormat = new Intl.DateTimeFormat(locale, {
      weekday: 'narrow',
    });
    const max = props.max ?? fromPartsToUtcDate(2100, 11, 31);
    const min = props.min ?? fromPartsToUtcDate(1900, 0, 1);

    const calendarGrid = calendar({
      date,
      dayFormat,
      disabledDates,
      disabledDays,
      firstDayOfWeek,
      fullDateFormat,
      locale,
      max,
      min,
      showWeekNumber,
      weekNumberTemplate,
      weekNumberType,
    });
    const { datesGrid, key } = calendarGrid;
    const serializedCalendarGrid = calendarGrid.toJSON();
    const maybeWeekdaysWithWeekLabel = getWeekdays({
      firstDayOfWeek,
      longWeekdayFormat,
      narrowWeekdayFormat,
      shortWeekLabel,
      showWeekNumber,
      weekLabel,
    });
    const [weekNumber, ...weekdays] = [
      ...(showWeekNumber
        ? []
        : ([{ label: '', value: '' }] as [CalendarWeekday])),
      ...maybeWeekdaysWithWeekLabel,
    ];

    const $node = useSignal<HTMLTableSectionElement>();

    useKeyboardEvent({
      $node,
      onKeyDown$: $<UseKeyboardEventInit['onKeyDown$']>((ev) => {
        if ([' ', 'Enter', 'Tab'].every((k) => ev.key !== k)) {
          ev.preventDefault();
        }
      }),
      onKeyUp$: $<UseKeyboardEventInit['onKeyUp$']>((ev) => {
        onDateChangeByKey$?.(ev, serializedCalendarGrid);
      }),
    });

    useVisibleTask$(({ track }) => {
      track(() => date);

      if ($node.value) {
        for (const n of $node.value.children) {
          for (const o of n.children) {
            if (o.ariaSelected) {
              (o as HTMLTableCellElement).focus();
            }
          }
        }
      }
    });

    return (
      <table
        {...props}
        class={['calendar', cls]}
        is="qwik-calendar"
        key={key}
        style={{ '--_cols': maybeWeekdaysWithWeekLabel.length }}
      >
        <caption class="caption">
          Selected date is {fullDateFormat.format(date)}
        </caption>

        <thead>
          <tr>
            {showWeekNumber && (
              <WeekLabel render$={renderWeekLabel$} weekLabel={weekNumber} />
            )}
            {weekdays.map((wd, i) => {
              if (renderHeader$) {
                return (
                  <Weekday
                    key={wd.value}
                    render$={(init) => renderHeader$(init, i)}
                    weekday={wd}
                  />
                );
              }

              return <Weekday key={wd.value} weekday={wd} />;
            })}
          </tr>
        </thead>

        <tbody ref={$node}>
          {datesGrid.map((row, ri) => {
            return (
              <tr key={row.key}>
                {row.columns.map((col, ci) => {
                  const isWeekNumber = ci === 0 && showWeekNumber;
                  const baseProps: Omit<
                    DataCellProps<'td' | 'th', typeof col>,
                    'render$'
                  > = {
                    'aria-disabled': col.disabled,
                    as: isWeekNumber ? 'th' : 'td',
                    class: isWeekNumber ? 'week-number' : 'day',
                    init: { col },
                  };

                  if (isWeekNumber) {
                    if (renderWeekNumber$) {
                      return (
                        <DataCell
                          {...baseProps}
                          key={col.key}
                          render$={
                            $<
                              NonNullable<
                                DataCellProps<'th', CalendarWeekday>['render$']
                              >
                            >((init) => {
                              return (
                                renderWeekNumber$ as unknown as NonNullable<
                                  QwikCalendarProps['renderWeekNumber$']
                                >
                              )(init, ri, ci);
                            }) as unknown as DataCellProps['render$']
                          }
                        />
                      );
                    }
                  }

                  if (renderDataCell$) {
                    return (
                      <DataCell
                        {...baseProps}
                        key={col.key}
                        render$={
                          $<
                            NonNullable<
                              DataCellProps<'td', DatesGridColumn>['render$']
                            >
                          >((init) => {
                            return (
                              renderDataCell$ as unknown as NonNullable<
                                QwikCalendarProps['renderDataCell$']
                              >
                            )(init, ri, ci);
                          }) as unknown as DataCellProps['render$']
                        }
                      />
                    );
                  }

                  return <DataCell {...baseProps} key={col.key} />;
                })}
              </tr>
            );
          })}
        </tbody>

        <tfoot></tfoot>
      </table>
    );
  }
);
