import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useClickOutside, useDidUpdateEffect, useMemoImmer } from '@devesharp/react-hooks';
import { OnlyNumbers, Calendar, GetScrollParent } from '@devesharp/react-utils';
import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
import { faAngleLeft, faAngleRight, faMinusCircle, faPlusCircle, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useField } from '@unform/core';
import * as dateFns from 'date-fns';
import { useTheme } from 'styled-components';
import { Checkbox } from '../Checkbox';
import { FormStyle } from '../styles';
import { IPropsDatepicker } from './Datepicker.interface';
import Style from './Datepicker.style';

function InputDatePicker({
   date,
   error,
   setOpen,
   withTimePicker,
   removeDate,
   allDay,
   formatShowDate,
   formatTimeDate,
}): JSX.Element {
   const viewDateStringView = date ? dateFns.format(date, formatShowDate) : '';
   const viewTimeStringView = date ? dateFns.format(date, formatTimeDate) : '';

   return (
      <>
         {
            // ----- Start Block Input Empty -----
         }
         {!date && (
            <div className={`input-border ${error && 'has-error'}`} data-testid="datepicker-input">
               <div className="prefix calendar-icon">
                  <FontAwesomeIcon icon={faCalendarAlt} />
               </div>
               <div className="input pl-3">Selecione um data</div>

               <div className="container-cursor" data-testid="open-popup" onClick={() => setOpen(true)} />
            </div>
         )}
         {
            // ----- Start Block Input Empty -----
         }

         {
            // ----- Start Block View Date  -----
         }
         {!!date && (
            <div className="input-border" data-cy="view-date">
               <div className="table calendar-input">
                  <div className="text-primary calendar-icon">
                     <FontAwesomeIcon icon={faCalendarAlt} />
                  </div>
                  <div
                     className="pl-3 text-primary font-weight-bold flex-fill date-input-container"
                     data-testid="datepicker-input"
                  >
                     <div className="date" data-testid="date-string">
                        {viewDateStringView}
                     </div>
                     {withTimePicker && !allDay && <div className="time">{viewTimeStringView}</div>}
                     {withTimePicker && allDay && <div className="time">O dia todo</div>}
                  </div>
                  <div
                     className="font-weight-bold align-self-center remove-date"
                     data-testid="datepicker-remove-date"
                     onClick={removeDate}
                  >
                     <FontAwesomeIcon icon={faTimes} />
                  </div>
               </div>

               <div className="container-cursor" data-testid="open-popup" onClick={() => setOpen(true)} />
            </div>
         )}
         {
            // ----- Start Block View Date  -----
         }
      </>
   );
}

function DatePickerCalendar({
   calendarDate,
   setCalendarDate,
   selectedDate,
   setSelectedDate,
   disablePastDates,
}): JSX.Element {
   const monthName = useMemoImmer(() => Calendar.monthsName[dateFns.getMonth(calendarDate)], [calendarDate]);
   const calendarYear = dateFns.getYear(calendarDate);
   const weekNames = useMemo(() => Calendar.weeksName, []);
   const month = useMemo(() => Calendar.getMonth(dateFns.getMonth(calendarDate), dateFns.getYear(calendarDate)), [
      calendarDate,
   ]);

   const NextMonth = useCallback(function NextMonth(): void {
      setCalendarDate((d) => dateFns.addMonths(d, 1));
   }, []);

   const PreviousMonth = useCallback(function PreviousMonth(): void {
      setCalendarDate((d) => dateFns.subMonths(d, 1));
   }, []);

   return (
      <>
         {
            // ----- Start Block Title -----
         }
         <div className="month">
            <span className="right" onClick={NextMonth}>
               <FontAwesomeIcon icon={faAngleRight} data-testid="datepicker-next-month" />
            </span>

            <span className="left" onClick={PreviousMonth}>
               <FontAwesomeIcon icon={faAngleLeft} data-testid="datepicker-previous-month" />
            </span>

            <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'} ${disablePastDates && day.past && 'disable-past'} ${
                        dateFns.isSameDay(day.dateInstance, selectedDate) && 'select'
                     } ${!day.currentMonth && 'other-month'}`}
                     onClick={() => setSelectedDate(day.dateInstance)}
                  >
                     {day.day}
                  </div>
               ))}
            </div>
         ))}
      </>
   );
}

function TimePicker({ date, setDate, allDayValue, onAllDayChange, enableAllDay }): JSX.Element {
   const theme: any = useTheme();
   const { datePicker = {} } = theme ?? {};
   const { defaultColor } = datePicker;

   const hourInputRef = useRef<HTMLInputElement>();
   const minuteInputRef = useRef<HTMLInputElement>();

   useEffect(() => {
      hourInputRef.current.value = dateFns.format(date ?? new Date(), 'HH');
      minuteInputRef.current.value = dateFns.format(date ?? new Date(), 'mm');
   }, [date]);

   const SubHour = useCallback(
      function SubHour(): void {
         setDate(dateFns.subHours(date ?? new Date(), 1));
      },
      [date],
   );

   const AddHour = useCallback(
      function AddHour(): void {
         setDate(dateFns.addHours(date ?? new Date(), 1));
      },
      [date],
   );

   const SubMinute = useCallback(
      function SubMinute(): void {
         // define hora também, para não incrementar/decrementar quando passar do 59
         setDate(dateFns.setHours(dateFns.subMinutes(date ?? new Date(), 1), dateFns.getHours(date ?? new Date())));
      },
      [date],
   );

   const AddMinute = useCallback(
      function AddMinute(): void {
         // define hora também, para não incrementar/decrementar quando passar do 59
         setDate(dateFns.setHours(dateFns.addMinutes(date ?? new Date(), 1), dateFns.getHours(date ?? new Date())));
      },
      [date],
   );

   const OnChangeHour = useCallback(
      function OnChangeHour(): void {
         if (hourInputRef.current.value.length >= 2) {
            minuteInputRef.current.focus();
            minuteInputRef.current.select();
         }
      },
      [date],
   );

   const OnBlurHours = useCallback(
      function OnBlurHours(): void {
         let newDate = dateFns.setHours(date ?? new Date(), Number(OnlyNumbers(hourInputRef.current.value)));
         newDate = dateFns.setDate(newDate, dateFns.getDate(date ?? new Date()));
         newDate = dateFns.setMonth(newDate, dateFns.getMonth(date ?? new Date()));
         setDate(newDate);
      },
      [date],
   );

   const OnChangeMinutes = useCallback(function OnChangeMinutes(): void {}, [date]);

   const OnBlurMinutes = useCallback(
      function OnBlurMinutes(): void {
         let newDate = dateFns.setMinutes(date ?? new Date(), Number(OnlyNumbers(minuteInputRef.current.value)));
         newDate = dateFns.setHours(newDate, dateFns.getHours(date ?? new Date()));
         newDate = dateFns.setDate(newDate, dateFns.getDate(date ?? new Date()));
         newDate = dateFns.setMonth(newDate, dateFns.getMonth(date ?? new Date()));
         setDate(newDate);
      },
      [date],
   );

   return (
      <div>
         <hr />

         {enableAllDay && (
            <Checkbox
               title="Dia todo"
               checked={allDayValue}
               onChange={onAllDayChange}
               data-testid="datepicker-all-day"
               color={defaultColor}
            />
         )}

         <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}>
                     <FontAwesomeIcon icon={faMinusCircle} />
                  </div>
                  <div className="input-time">
                     <input
                        type="text"
                        defaultValue="00"
                        ref={hourInputRef}
                        onFocus={() => hourInputRef.current.select()}
                        onChange={OnChangeHour}
                        onBlur={OnBlurHours}
                        maxLength={3}
                        data-testid="datepicker-input-hour"
                     />
                  </div>
                  <div className="plus flex-fill align-self-center" onClick={AddHour}>
                     <FontAwesomeIcon icon={faPlusCircle} />
                  </div>
               </div>
               <span className="division">:</span>
               <div className="d-flex flex-fill">
                  <div className="plus flex-fill align-self-center" onClick={SubMinute}>
                     <FontAwesomeIcon icon={faMinusCircle} />
                  </div>
                  <div className="input-time">
                     <input
                        type="text"
                        defaultValue="00"
                        ref={minuteInputRef}
                        onFocus={() => minuteInputRef.current.select()}
                        onChange={OnChangeMinutes}
                        onBlur={OnBlurMinutes}
                        maxLength={2}
                        data-testid="datepicker-input-minute"
                     />
                  </div>
                  <div className="plus flex-fill align-self-center" onClick={AddMinute}>
                     <FontAwesomeIcon icon={faPlusCircle} />
                  </div>
               </div>
            </div>
         </div>
      </div>
   );
}

function DatePickerPopup({
   enableAllDay,
   date,
   setDate,
   allDayValue,
   onAllDayChange,
   calendarDate,
   setCalendarDate,
   withTimePicker,
   applyOnSelect,
   cleanDatePicker,
   disablePastDates,
   onApply,
}): JSX.Element {
   /**
    * fechar popup se clicar fora dele
    */
   const datePickerPopupRef = useRef(null);
   useClickOutside(() => {
      cleanDatePicker();
   }, datePickerPopupRef);

   return (
      <div className="datepicker-popup" ref={datePickerPopupRef} data-testid="datepicker-popup">
         <DatePickerCalendar
            calendarDate={calendarDate}
            selectedDate={date}
            setSelectedDate={setDate}
            setCalendarDate={setCalendarDate}
            disablePastDates={disablePastDates}
         />

         {withTimePicker && (
            <TimePicker
               date={date}
               setDate={setDate}
               allDayValue={allDayValue}
               onAllDayChange={onAllDayChange}
               enableAllDay={enableAllDay}
            />
         )}

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

/**
 * Todo: adicionar suporte para usar sem input, apenas com clique
 * Todo: verificar qual a melhor posição para aparecer o datepicker em cima ou em baixo
 */
export const DatepickerWithoutUnform: React.FunctionComponent<IPropsDatepicker> = ({
   name,
   title,
   withTimePicker = false,
   value = null,
   onValueChange,
   allDay: allDayValue = false,
   onAllDayChange = () => {},
   error,
   applyOnSelect = false,
   formattedString,
   formatShowDate = 'd MMM yyyy',
   formatTimeDate = "'Ás' HH:mm",
   enableAllDay = false,
   disablePastDates,
   asPopup,
   children,
   ...rest
}: IPropsDatepicker) => {
   const datepickerRef = useRef<HTMLDivElement>();

   const [calendarDate, setCalendarDate] = useState<Date>(value ?? new Date());
   const [dateTmp, setDateTmp] = useState<Date>(value); // data selecionada temporaria para quando applyOnSelect = true
   const [allDay, setAllDay] = useState<boolean>(allDayValue);
   const [openDatePicker, setOpenDatePicker] = useState<boolean>(false);

   useDidUpdateEffect(() => {
      setAllDay(allDayValue);
   }, [allDayValue]);

   useDidUpdateEffect(() => {
      setDateTmp(value);
   }, [value]);

   const OpenDatePicker = useCallback(function OpenDatePicker(): void {
      setOpenDatePicker(true);
   }, []);

   const CloseDatePicker = useCallback(function CloseDatePicker(): void {
      setOpenDatePicker(false);
   }, []);

   const CleanDatePicker = useCallback(
      function CleanDatePicker(): void {
         setDateTmp(value);
         setCalendarDate(value ?? new Date());
         setOpenDatePicker(false);
         setAllDay(allDayValue);
      },
      [value, allDayValue],
   );

   const RemoveDateInput = useCallback(
      function RemoveDateInput(): void {
         if (onValueChange) onValueChange(null, null);
         setCalendarDate(new Date());
         if (onAllDayChange) onAllDayChange(false);
         setOpenDatePicker(false);
      },
      [onValueChange],
   );

   const SetSelectedDate = useCallback(
      function SetSelectedDate(d: Date): void {
         // Não pode selecionar datas anteriores
         if (disablePastDates && dateFns.differenceInDays(d, new Date()) < 0) {
            return;
         }

         // Não pode selecionar horas passadas
         if (disablePastDates && dateFns.differenceInMinutes(d, new Date()) < 0) {
            d = dateFns.setMinutes(d, dateFns.getMinutes(new Date()));
            d = dateFns.setHours(d, dateFns.getHours(new Date()));
         }

         // Sem time normaliza tempo para poder ser usado nos testes
         if (!withTimePicker) {
            d = dateFns.setMinutes(d, 0);
            d = dateFns.setHours(d, 0);
         }

         // normaliza valores
         d = dateFns.setSeconds(d, 0);
         d = dateFns.setMilliseconds(d, 0);

         if (!applyOnSelect) {
            if (onValueChange) onValueChange(d, formattedString ? dateFns.format(d, formattedString) : null);
            CloseDatePicker();
         } else {
            setDateTmp(d);
         }
      },
      [onValueChange],
   );

   const OnApply = useCallback(
      function OnApply(): void {
         if (onValueChange)
            onValueChange(dateTmp, dateTmp && formattedString ? dateFns.format(dateTmp, formattedString) : null);
         if (onAllDayChange) onAllDayChange(allDay);
         CloseDatePicker();
      },
      [dateTmp, onValueChange, allDay],
   );

   /**
    * Envia para posição do datepicker
    */
   useEffect(() => {
      if (openDatePicker) {
         const el = document.getElementsByClassName('datepicker-popup')[0];

         const scrollParent = GetScrollParent(datepickerRef.current.parentNode);

         const { top: topMain, height: heightMain } = datepickerRef.current.getBoundingClientRect();
         const { height: heightScroll } = scrollParent.getBoundingClientRect();

         if (topMain + heightMain + el.getBoundingClientRect().height > heightScroll) {
            if (el.scrollIntoView) el.scrollIntoView({ block: 'end', behavior: 'smooth' });
         }
      }
   }, [openDatePicker]);

   // Se tiver hora, deve ser aplicavel
   if (withTimePicker) applyOnSelect = true;

   return (
      <FormStyle>
         <Style data-testid={`datepicker-${name}`} ref={datepickerRef}>
            {title && <div className="input-title">{title}</div>}

            {!asPopup && (
               <InputDatePicker
                  date={value}
                  error={!!error}
                  allDay={allDayValue}
                  setOpen={OpenDatePicker}
                  withTimePicker={withTimePicker}
                  removeDate={RemoveDateInput}
                  formatShowDate={formatShowDate}
                  formatTimeDate={formatTimeDate}
               />
            )}

            {asPopup && (
               <div className="container-cursor" data-testid="open-popup" onClick={OpenDatePicker}>
                  {children}
               </div>
            )}

            {openDatePicker && (
               <DatePickerPopup
                  enableAllDay={enableAllDay}
                  date={dateTmp}
                  setDate={SetSelectedDate}
                  calendarDate={calendarDate}
                  setCalendarDate={setCalendarDate}
                  withTimePicker={withTimePicker}
                  applyOnSelect={applyOnSelect}
                  onApply={OnApply}
                  cleanDatePicker={CleanDatePicker}
                  allDayValue={allDay}
                  onAllDayChange={setAllDay}
                  disablePastDates={disablePastDates}
               />
            )}

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

function AllDayUnform({ name, allDay, onChange }): null {
   const { fieldName, registerField, defaultValue = false } = useField(name);
   const inputRef = useRef(defaultValue);

   useLayoutEffect(() => {
      onChange(defaultValue);
   }, []);

   useEffect(() => {
      inputRef.current = allDay;
   }, [allDay]);

   useEffect(() => {
      registerField({
         name: fieldName,
         ref: inputRef,
         path: 'current' as any,
         clearValue(ref) {
            ref.current = false;
            onChange(false);
         },
         setValue(ref, v) {
            ref.current = v;
            onChange(v);
         },
         getValue(ref) {
            return ref.current;
         },
      });
   }, [fieldName, registerField, onChange]);

   return null;
}

export const DatepickerWithUnform: React.FunctionComponent<IPropsDatepicker> = (props: IPropsDatepicker) => {
   const { fieldName, registerField, defaultValue = null, error, clearError } = useField(props.name);

   if (props.formattedString && typeof defaultValue?.getMonth === 'function') {
      throw new Error("Se formattedString for definido 'value' tem ser string");
   }

   if (!props.formattedString && typeof defaultValue?.getMonth !== 'function' && defaultValue != null) {
      throw new Error("'value' deve ser um Date ou null");
   }

   const inputDateRef = useRef(
      defaultValue && props.formattedString
         ? dateFns.parse(defaultValue, props.formattedString, new Date())
         : defaultValue,
   );
   const inputFormattedRef = useRef(typeof defaultValue === 'string' ? defaultValue : null);

   const [value, setValue] = useState<Date>(inputDateRef.current);
   const [allDay, setAllDay] = useState<boolean>();

   useEffect(() => {
      registerField<any>({
         name: fieldName,
         ref: inputDateRef,
         path: 'current' as any,
         clearValue(ref) {
            if (props.formattedString) {
               inputFormattedRef.current = null;
            }
            ref.current = null;
            setValue(null);
         },
         setValue(ref, v) {
            if (props.formattedString && typeof v?.getMonth === 'function') {
               throw new Error("Se formattedString for definido 'value' tem ser string");
            }

            if (!props.formattedString && typeof v?.getMonth !== 'function' && v != null) {
               throw new Error("'value' deve ser um Date ou null");
            }

            if (typeof v === 'string') {
               ref.current = v && props.formattedString ? dateFns.parse(v, props.formattedString, new Date()) : v;
               inputFormattedRef.current = v;
            } else {
               inputFormattedRef.current = null;
               ref.current = v;
               setValue(v);
            }
         },
         getValue(ref) {
            if (props.formattedString) return inputFormattedRef.current;
            return ref.current;
         },
      });
   }, [fieldName, registerField, props.formattedString]);

   const onValueChange = useCallback(function onSelectItem(d: Date, f: string) {
      clearError();
      setValue(d);
      inputDateRef.current = d;
      inputFormattedRef.current = f;
      if (props.onValueChange) props.onValueChange(d, f);
   }, []);

   return (
      <>
         {props.nameAllDay && <AllDayUnform name={props.nameAllDay} allDay={allDay} onChange={setAllDay} />}

         <DatepickerWithoutUnform
            {...props}
            value={value}
            onValueChange={onValueChange}
            allDay={allDay}
            onAllDayChange={setAllDay}
            error={error}
         />
      </>
   );
};

export const Datepicker: React.FunctionComponent<IPropsDatepicker> = (props: IPropsDatepicker) => {
   if (props.name) {
      return <DatepickerWithUnform {...props} />;
   }

   return <DatepickerWithoutUnform {...props} />;
};
