import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import css from 'styled-jsx/css';

import type { WithSpacingProps } from '~/src/lib/hocs/withSpacing';
import type {
  ChangeEvent,
  ChangeEventHandler,
  CSSProperties,
  InputHTMLAttributes,
  RefObject,
} from 'react';

import { isFirefox } from '~/lib/device/utils';
import Box from '~/src/components/Box';
import useTheme from '~/src/hooks/useTheme';
import withSpacing from '~/src/lib/hocs/withSpacing';
import debounce from '~/src/lib/utils/debounce';
import { stripNonDomProps } from '~/src/lib/utils/stripNonDomProps';
import DateIcon from '../Icon/DateIcon';
import {
  TEXT_INPUT_DEFAULT_FONT_SIZE,
  TEXT_INPUT_DEFAULT_HEIGHT,
  TEXT_INPUT_HORIZONTAL_PADDING,
} from '../TextInput';

const styles = css.resolve`
  .root {
    position: relative;
    align-items: center;
    color-scheme: light dark;
  }

  .root:focus-within {
    border-color: #666 !important;
  }

  .input {
    position: relative;
    height: 100%;
    flex: 1;
  }

  input {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;

    padding: 0;
    background: none;
    border-radius: 0;
    border: 0;
    width: 100%;
    height: 100%;
    color: inherit;
    font-family: inherit;
    font-weight: 300;
    letter-spacing: 0.02em;
  }

  input::placeholder {
    color: inherit;
    opacity: 0.4;
    letter-spacing: 0.03em;
  }

  input[type='date']::-webkit-calendar-picker-indicator {
    background: transparent;
    color: transparent;
    cursor: pointer;
    position: absolute;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
    height: auto;
    width: auto;
    margin-inline-start: 0;
  }
`;

const DEBOUNCE_TIMEOUT = 300;

export interface DateInputProps
  extends Omit<
      InputHTMLAttributes<HTMLInputElement>,
      'onChange' | 'size' | 'value' | 'defaultValue'
    >,
    WithSpacingProps {
  borderColor?: string;
  value?: string | Date | number;
  defaultValue?: string | Date | number;
  fontSize?: number | string;
  centerText?: boolean;
  withBorder?: boolean;
  withBackground?: boolean;
  withShadow?: boolean;
  isDisabled?: boolean;
  inputRef?: RefObject<HTMLInputElement | null>;
  testId?: string;

  onChange?: (params: {
    value: string;
    date: Date | undefined;
    event: ChangeEvent<HTMLInputElement>;
  }) => void;

  onInputEnd?: (params: {
    value: string;
    date: Date | undefined;
    event: ChangeEvent<HTMLInputElement>;
  }) => void;
}

const DateInput = withSpacing<DateInputProps>(
  ({
    borderColor,
    value,
    defaultValue,
    onChange,
    onInputEnd,
    placeholder,
    centerText,
    withBorder = true,
    withBackground = true,
    withShadow = true,
    testId,
    inputRef,
    className = '',
    isDisabled,
    fontSize = TEXT_INPUT_DEFAULT_FONT_SIZE,
    style,
    autoFocus,
    min,
    required,
    children,
    ...inputProps
  }) => {
    const hasValue = !!(value ?? defaultValue);
    const [showPlaceholder, setShowPlaceholder] = useState<boolean>(!hasValue);
    const theme = useTheme();
    const height = style?.height || TEXT_INPUT_DEFAULT_HEIGHT;
    const defaultRef = useRef<HTMLInputElement>(null);

    inputRef = inputRef || defaultRef;

    useEffect(() => {
      if (autoFocus) inputRef!.current?.focus();
    }, []);

    useEffect(() => {
      setShowPlaceholder(!hasValue);
    }, [hasValue]);

    const onChangeDebounced = useMemo(
      () =>
        debounce(
          (params: {
            value: string;
            date: Date | undefined;
            event: ChangeEvent<HTMLInputElement>;
          }) => {
            if (onInputEnd) onInputEnd(params);
          },
          DEBOUNCE_TIMEOUT
        ),
      [onInputEnd]
    );

    const onChangeInternal = useCallback<ChangeEventHandler<HTMLInputElement>>(
      (event) => {
        const { value } = event.currentTarget;
        const date = value ? new Date(value) : undefined;

        if (onChange) {
          onChange({
            value,
            date,
            event,
          });
        }

        onChangeDebounced({
          date,
          value,
          event,
        });

        setShowPlaceholder(!value);
      },
      [onChange]
    );

    const rootStyle: CSSProperties = {
      ...style,
      background: withBackground ? theme.textInputBackground : '#000',
      border: withBorder
        ? `solid 1px ${borderColor || theme.textInputBorderColor}`
        : 'none',
      borderRadius: withBorder ? `${theme.borderRadius}px` : undefined,
      boxShadow: withShadow ? '0 1px 3px rgba(0,0,0,1)' : undefined,
      fontSize,
      height,
    };

    const inputStyle: CSSProperties = {
      textAlign: centerText ? 'center' : 'left',
      fontSize: '1em',
      color: theme.textColor90,
      padding: `0 ${TEXT_INPUT_HORIZONTAL_PADDING}`,
      opacity: showPlaceholder ? '0' : '1',
    };

    const placeholderStyle: CSSProperties = {
      textAlign: centerText ? 'center' : 'left',
      opacity: showPlaceholder ? '1' : '0',
      pointerEvents: 'none',
      color: 'rgba(230, 230, 230, 0.4)',
    };

    if (isDisabled) {
      inputStyle.opacity = 0.3;
    }

    // HACK: defining `value` and `defaultValue` props seemed to cause
    // dom state to keep being overwritten by `defaultValue`.
    const valueProps = value
      ? { value: toDateInputValue(value) }
      : { defaultValue: toDateInputValue(defaultValue) };

    return (
      <Box
        flexRow
        className={`${styles.className} ${className} root`}
        style={rootStyle}
      >
        <div className={`${styles.className} input`}>
          <input
            {...stripNonDomProps(inputProps)}
            {...valueProps}
            ref={inputRef}
            // NOTE: required prevents native 'Clear' button working in Chrome
            required={required}
            min={min}
            type="date"
            onChange={onChangeInternal}
            className={styles.className}
            data-testid={testId}
            style={inputStyle}
            disabled={isDisabled}
            // Enforce a pattern for browsers that don't support date inputs
            pattern="\d{4}-\d{2}-\d{2}"
            // Force the Date picker to be displayed on click
            // because it's not automatically displayed in Firefox.
            onClick={() => {
              if (
                isFirefox(navigator.userAgent) &&
                inputRef &&
                inputRef.current
              ) {
                inputRef.current.showPicker();
              }
            }}
          />
          <Box
            coverParent
            padding={`1.4rem ${TEXT_INPUT_HORIZONTAL_PADDING} 0`}
            style={placeholderStyle}
          >
            {placeholder}
          </Box>
          <DateIcon
            color="#444"
            size="2.5rem"
            style={{
              position: 'absolute',
              right: TEXT_INPUT_HORIZONTAL_PADDING,
              top: 0,
              bottom: 0,
              margin: 'auto',
              pointerEvents: 'none',
            }}
          />
        </div>
        {styles.styles}
      </Box>
    );
  }
);

/**
 * Takes any date string and returns it in the format that <DateInput> expects.
 *
 * REVIEW: this might be better internal to <DateInput> so we can just throw
 * any date at it.
 *
 * NOTE: this date is going to be in user's local time so there may be some
 * timezone gotchas here.
 */
const toDateInputValue = (value: Date | string | number | undefined) => {
  if (!value) return;

  // ensure Date
  const date = value instanceof Date ? value : new Date(value);

  return date.toISOString().substring(0, 10);
};

export default DateInput;
