import React, { Reducer, useCallback, useEffect, useLayoutEffect, useMemo, useReducer, useRef, useState } from 'react';
import { useField } from '@unform/core';
import * as dateFns from 'date-fns';
import { useImmer } from 'use-immer';
import { useClickOutside } from '../../../hooks/useClickOutside';
import { OnlyNumbers } from '../../../services';
import { Calendar, IMonthCalendar, IDayCalendar } from '../../../services/calendar.service';
import { Checkbox } from '../../Checkbox/web';
import { UnformVirtual } from '../../UnformVirtual';
import { getDateValue, IPropsDatepicker, IValueDatePicker, transformerValue } from '../datepicker.interface';
import Style from './datepicker.style';

export const Datepicker: React.FunctionComponent<IPropsDatepicker> = ({
   name,
   title,
   onChange,
   withTimePicker = false,
   theme,
   applyOnSelect = false,
   ...rest
}: IPropsDatepicker) => {
   // Unform
   const { registerField, defaultValue, fieldName, error, clearError } = useField(name);

   // Data do calendário
   const [calendarDate, setCalendarDate] = useImmer(getDateValue(defaultValue) || new Date());
   const [currentAuxValue, setCurrentAuxValue] = useImmer<Date | null>(getDateValue(defaultValue));
   const [currentValue, setCurrentValue] = useImmer<Date | null>(getDateValue(defaultValue));

   // DatePicker toggle
   const [open, setOpen] = useState(false);
   // Evento dia todo
   const [allDay, setAllDay] = useState(transformerValue(defaultValue)?.allDay || false);
   // Ref Input Hour
   const hourInputRef = useRef(null);
   // Ref Input Minute
   const minuteInputRef = useRef(null);
   // Se tiver hora, deve ser aplicavel
   if (withTimePicker) applyOnSelect = true;

   // Ref popup
   const calendarPopupRef = useRef(null);
   // Fechar Popup se clicar fora dele
   useClickOutside(() => {
      setOpen(false);
      setCalendarDate(() => currentValue || new Date());
   }, calendarPopupRef);

   // Envia para posição do datepicker
   useEffect(() => {
      if (open) {
         const el = document.getElementsByClassName('datepicker-popup')[0];
         if (el) {
            if (el.scrollIntoView) el.scrollIntoView({ block: 'end' });
         }
      }
   }, [open]);

   /**
    * Unform
    */
   useEffect(() => {
      registerField({
         name: fieldName,
         clearValue: () => {
            setCalendarDate(() => new Date());
            setCurrentAuxValue(() => null);
            setCurrentValue(() => null);
         },
         setValue: (ref: any, v: any) => {
            setCurrentAuxValue(() => getDateValue(v));
            setCurrentValue(() => getDateValue(v));
         },
         getValue: (i) => {
            return transformerValue(currentValue, { allDay });
         },
      });
   }, [fieldName, registerField, currentValue, allDay]);

   /**
    * Avançar 1 més
    */
   //--
   const nextMonth = useCallback(function nextMonth(): void {
      setCalendarDate((draft) => dateFns.addMonths(draft, 1));
   }, []);

   /**
    * Voltar 1 més
    */
   //--
   const previousMonth = useCallback(function previousMonth(): void {
      setCalendarDate((draft) => dateFns.subMonths(draft, 1));
   }, []);

   /**
    * Aplicar alterações
    * @param date
    */

   const apply = useCallback(
      function apply(): void {
         setOpen(false);
         if (!currentAuxValue) return;

         clearError();

         setCurrentAuxValue((draft) => {
            let date = draft;

            // Atualiza hora
            if (!allDay && withTimePicker) {
               date = dateFns.setHours(date, Number(hourInputRef.current.value));
               date = dateFns.setMinutes(date, Number(minuteInputRef.current.value));
            }
            return getDateValue(date);
         });

         setCurrentValue(() => {
            let date = currentAuxValue;

            // Atualiza hora
            if (!allDay && withTimePicker) {
               date = dateFns.setHours(date, Number(hourInputRef.current.value));
               date = dateFns.setMinutes(date, Number(minuteInputRef.current.value));
            }

            return getDateValue(date);
         });

         if (onChange) {
            let date = currentAuxValue;

            // Atualiza hora
            if (!allDay) {
               date = dateFns.setHours(date, Number(hourInputRef.current.value));
               date = dateFns.setMinutes(date, Number(minuteInputRef.current.value));
            }

            onChange(transformerValue(date, { allDay }));
         }
      },
      [currentAuxValue, allDay],
   );

   useLayoutEffect(() => {
      if (open) {
         if (hourInputRef.current) hourInputRef.current.value = Number(dateFns.getHours(currentAuxValue)) || 0;
         if (minuteInputRef.current) minuteInputRef.current.value = Number(dateFns.getMinutes(currentAuxValue)) || 0;
      }
   }, [open]);

   /**
    * Cancelar seleção
    */
   const cancel = useCallback(
      function cancel(): void {
         setOpen(false);
         setCurrentAuxValue(() => currentValue);
         setCalendarDate(() => currentValue || new Date());
      },
      [currentValue],
   );

   /**
    * Seleciona data datepicker
    * @param date
    */
   const setDate = useCallback(function setDate(date: Date): void {
      setCurrentAuxValue((drat) => {
         if (!drat) return date;

         return dateFns.set(drat, {
            year: dateFns.getYear(date),
            month: dateFns.getMonth(date),
            date: dateFns.getDate(date),
         });
      });

      if (!applyOnSelect) {
         clearError();
         setOpen(false);
         setCurrentValue(() => date);
      }
   }, []);

   function onChangeHour(): void {
      hourInputRef.current.value = OnlyNumbers(hourInputRef.current.value);

      if (Number(hourInputRef.current.value) > 23) {
         hourInputRef.current.value = '23';
      }

      if (hourInputRef.current.value.length >= 2) {
         minuteInputRef.current.focus();
         minuteInputRef.current.select();
      }
   }

   function onChangeMinutes(): void {
      minuteInputRef.current.value = OnlyNumbers(minuteInputRef.current.value);

      if (Number(minuteInputRef.current.value) > 59) {
         minuteInputRef.current.value = '59';
      }
   }

   function onBlurHours(): void {
      if (!minuteInputRef.current.value) {
         minuteInputRef.current.value = '00';
      }

      if (!hourInputRef.current.value) {
         hourInputRef.current.value = '00';
      }
   }

   /**
    * Remover data
    */
   const removeDate = useCallback(function removeDate(): void {
      setCalendarDate(() => new Date());
      setCurrentAuxValue(() => null);
      setCurrentValue(() => null);
      setOpen(false);
      if (onChange) onChange(null);
   }, []);

   const addHour = useCallback(function addHour(): void {
      hourInputRef.current.value = Number(OnlyNumbers(hourInputRef.current.value)) + 1;
      if (hourInputRef.current.value > 23) {
         hourInputRef.current.value = 0;
      }
   }, []);

   const subHour = useCallback(function subHour(): void {
      hourInputRef.current.value = Number(OnlyNumbers(hourInputRef.current.value)) - 1;
      if (hourInputRef.current.value < 0) {
         hourInputRef.current.value = 23;
      }
   }, []);

   const addMinute = useCallback(function addMinute(): void {
      minuteInputRef.current.value = Number(OnlyNumbers(minuteInputRef.current.value)) + 1;
      if (minuteInputRef.current.value > 59) {
         minuteInputRef.current.value = 0;
      }
   }, []);

   const subMinute = useCallback(function subMinute(): void {
      minuteInputRef.current.value = Number(OnlyNumbers(minuteInputRef.current.value)) - 1;
      if (minuteInputRef.current.value < 0) {
         minuteInputRef.current.value = 59;
      }
   }, []);

   return (
      <Style themeInput={theme} data-cy={`datepicker-${name}`}>
         <div className="input-title">{title}</div>

         <InputDatePicker
            currentValue={currentValue}
            error={error}
            allDay={allDay}
            setOpen={setOpen}
            withTimePicker={withTimePicker}
            removeDate={removeDate}
         />

         {open && (
            <div className="datepicker-popup" ref={calendarPopupRef} data-testid="datepicker-popup">
               <DatePickerCalendar
                  nextMonth={nextMonth}
                  previousMonth={previousMonth}
                  date={calendarDate}
                  selectedDate={currentAuxValue}
                  setDate={setDate}
               />

               {withTimePicker && (
                  <div>
                     <hr />
                     <UnformVirtual onSubmit={() => {}} initialData={{ allDay }}>
                        <Checkbox
                           name="allDay"
                           title="Dia todo"
                           onChange={(checked) => setAllDay(!!checked)}
                           data-testid="datepicker-all-day"
                        />
                     </UnformVirtual>

                     {!allDay && (
                        <div className="timepicker">
                           <div className="d-flex w-100">
                              <div className="d-flex flex-fill">
                                 <div className="plus flex-fill align-self-center" onClick={subHour}>
                                    <i className="fas fa-minus-circle" />
                                 </div>
                                 <div className="input-time">
                                    <input
                                       type="text"
                                       defaultValue="00"
                                       ref={hourInputRef}
                                       onFocus={() => hourInputRef.current.select()}
                                       onChange={onChangeHour}
                                       onBlur={onBlurHours}
                                       maxLength={2}
                                       data-testid="datepicker-input-hour"
                                    />
                                 </div>
                                 <div className="plus flex-fill align-self-center" onClick={addHour}>
                                    <i className="fas fa-plus-circle" />
                                 </div>
                              </div>
                              <span className="division">:</span>
                              <div className="d-flex flex-fill">
                                 <div className="plus flex-fill align-self-center" onClick={subMinute}>
                                    <i className="fas fa-minus-circle" />
                                 </div>
                                 <div className="input-time">
                                    <input
                                       type="text"
                                       defaultValue="00"
                                       ref={minuteInputRef}
                                       onFocus={() => minuteInputRef.current.select()}
                                       onChange={onChangeMinutes}
                                       onBlur={onBlurHours}
                                       maxLength={2}
                                       data-testid="datepicker-input-minute"
                                    />
                                 </div>
                                 <div className="plus flex-fill align-self-center" onClick={addMinute}>
                                    <i className="fas fa-plus-circle" />
                                 </div>
                              </div>
                           </div>
                        </div>
                     )}
                  </div>
               )}

               {applyOnSelect && (
                  <div className="actions">
                     <div className="btn-cancel" onClick={cancel} data-testid="datepicker-cancel">
                        Cancelar
                     </div>
                     <div className="btn-apply" onClick={() => apply()} data-testid="datepicker-apply">
                        Aplicar
                     </div>
                  </div>
               )}
            </div>
         )}

         {error && (
            <div data-testid="input-error" className="error-input">
               {error}
            </div>
         )}
      </Style>
   );
};

function InputDatePicker({ currentValue, error, setOpen, withTimePicker, removeDate, allDay }): JSX.Element {
   const viewDateStringView = currentValue ? dateFns.format(currentValue, 'd MMM yyyy') : '';
   const value = transformerValue(currentValue);
   const hour = currentValue ? dateFns.format(currentValue, 'HH') : '';
   const minute = currentValue ? dateFns.format(currentValue, 'mm') : '';

   return (
      <>
         {
            // ----- Start Block Input Empty -----
         }
         {!value && (
            <div
               className={`input-border ${error && 'has-error'}`}
               onClick={() => setOpen(true)}
               data-testid="datepicker-input"
            >
               <div className="prefix">
                  <i className="far fa-calendar-alt" />
               </div>
               <div className="input pl-3">Selecione um data</div>
            </div>
         )}
         {
            // ----- Start Block Input Empty -----
         }

         {
            // ----- Start Block View Date  -----
         }
         {!!value && (
            <div className="input-border" data-cy="view-date">
               <div className="table calendar-input">
                  <div className="text-primary calendar-icon" onClick={() => setOpen(true)}>
                     <i className="far fa-calendar-alt" />
                  </div>
                  <div
                     className="pl-3 text-primary font-weight-bold flex-fill"
                     data-testid="datepicker-input"
                     onClick={() => setOpen(true)}
                  >
                     <div className="date">{viewDateStringView}</div>
                     {withTimePicker && !allDay && (
                        <div className="time">
                           Ás {hour}:{minute}
                        </div>
                     )}
                     {withTimePicker && allDay && <div className="time">O dia todo</div>}
                  </div>
                  <div
                     className="font-weight-bold align-self-center"
                     data-testid="datepicker-remove-date"
                     onClick={removeDate}
                  >
                     <i className="fas fa-times" />
                  </div>
               </div>
            </div>
         )}
         {
            // ----- Start Block View Date  -----
         }
      </>
   );
}

function DatePickerCalendar({ nextMonth, previousMonth, date, selectedDate, setDate }): JSX.Element {
   const monthName = Calendar.monthsName[dateFns.getMonth(date)];
   const calendarYear = dateFns.getYear(date);
   const weekNames = useMemo(() => Calendar.weeksName, []);
   const month = useMemo(() => Calendar.getMonth(dateFns.getMonth(date), dateFns.getYear(date)), [date]);

   return (
      <>
         {
            // ----- Start Block Title -----
         }
         <div className="month">
            <i className="fas fa-angle-right right" onClick={nextMonth} data-testid="datepicker-next-month" />
            <i className="fas fa-angle-left left" onClick={previousMonth} data-testid="datepicker-previous-month" />

            <div className="month-name" data-testid="datepicker-month-name">
               {monthName}, {calendarYear}
            </div>
         </div>
         {
            // ----- End Block Title -----
         }

         {weekNames.map((nameWeek, i) => (
            <div key={`${nameWeek}-${i}`} className="weekName">
               {nameWeek}
            </div>
         ))}

         {month.map((week, weekI) => (
            <div key={weekI} className="week">
               {week.map((day) => (
                  <div
                     key={day.dateYYYYMMDD}
                     data-testid={day.dateYYYYMMDD}
                     className={`day ${day.today && 'today'} ${
                        dateFns.isSameDay(day.dateInstance, selectedDate) && 'select'
                     } ${!day.currentMonth && 'other-month'}`}
                     onClick={() => setDate(day.dateInstance)}
                  >
                     {day.day}
                  </div>
               ))}
            </div>
         ))}
      </>
   );
}
