import { CSSProperties, css } from 'glamor';
import React, { forwardRef } from 'react';
import { useApphouse } from '../../context/useApphouse';
import { observer } from 'mobx-react';
import { mergeStyles } from '../../styles/mergeStyles';
import { removeHiddenCharacters } from '../../utils/string/removeHiddenCharacters';
import { InputProps } from './input.interface';
import { omit } from '../../utils/obj/omit';
import { merge } from '../..';

/**
 * Resizes the input height element according to the content
 * @param inputElement the input element to be resized
 * @param withWidth width of the input element set by the user, if any
 */
const resizeInputHeight = (
  /**
   * the input element to be resized
   */
  inputElement?: HTMLTextAreaElement | null,
  /**
   * overwrite style of the input element set by the user, if any
   */
  styles?: CSSProperties
) => {
  if (!inputElement) return;
  let inputHeight: string | number = inputElement.scrollHeight;

  const lines = inputElement.value.split('\n');
  if (lines.length > 1) {
    // render a fake multiline input to get the height
    const tempInput = document.createElement('textarea');
    tempInput.style.position = 'absolute';
    tempInput.rows = lines.length;
    document.body.appendChild(tempInput);
    tempInput.textContent = `${inputElement.value || inputElement.placeholder}`;
    document.body.appendChild(tempInput);
    inputHeight = tempInput.offsetHeight;
    // Remove the temporary span element
    tempInput.remove();
  }

  const borderFromStyles = styles?.border ? styles.border.split(' ') : [0];

  const borderWidth =
    parseFloat(borderFromStyles[0] || styles?.borderWidth || '0') * 2;
  const finalHeight = inputHeight + borderWidth + 'px';
  inputElement.style.height = finalHeight;
};

/**
 * Resizes the input height element according to the content
 * @param inputElement the input element to be resized
 * @param styles styles of the input element set by the user, if any - we will extract the borders from it
 */
const resizeInputWidth = (
  inputElement: HTMLTextAreaElement,
  hasSetWidth: boolean
) => {
  // Create a temporary span element to measure the width
  const tempSpan = document.createElement('span');
  // Set the same font styles as the input element
  tempSpan.style.fontFamily = getComputedStyle(inputElement).fontFamily;
  tempSpan.style.fontSize = getComputedStyle(inputElement).fontSize;
  tempSpan.style.padding = getComputedStyle(inputElement).padding;
  tempSpan.style.border = getComputedStyle(inputElement).border;
  tempSpan.style.paddingLeft = getComputedStyle(inputElement).paddingLeft;
  tempSpan.style.paddingRight = getComputedStyle(inputElement).paddingRight;

  const elemWidth = getComputedStyle(inputElement).width;
  const elemMinWidth = getComputedStyle(inputElement).minWidth;
  const elemMaxWidth = getComputedStyle(inputElement).maxWidth;

  let _setWidth = hasSetWidth ? elemWidth : undefined;
  if (hasSetWidth) {
    tempSpan.style.width = elemWidth;
    _setWidth = elemWidth;
  }
  const hasMaxWidth = elemMaxWidth !== 'none' && parseInt(elemMaxWidth) > 0;

  // setting max width to the input element
  if (hasMaxWidth && elemWidth < elemMaxWidth) {
    if (!hasSetWidth) {
      tempSpan.style.maxWidth = elemMaxWidth;
    }
  }

  tempSpan.style.minWidth = elemMinWidth;

  const PLACEHOLDER_CHAR_FOR_WIDTH = 'a';
  // Get the width of the input value in pixels
  tempSpan.textContent = `${
    inputElement.value || inputElement.placeholder
  }${PLACEHOLDER_CHAR_FOR_WIDTH}`;
  document.body.appendChild(tempSpan);
  const inputWidth = _setWidth || tempSpan.offsetWidth;

  // Set the input width in pixels
  inputElement.style.width = `${inputWidth}px`;

  // Remove the temporary span element
  tempSpan.remove();
};

const resetInputHeight = (
  /**
   * the input element to be resized
   */
  inputElement?: HTMLTextAreaElement | null
) => {
  if (inputElement) {
    inputElement.style.height = 'unset';
  }
};

interface ResizableInputProps extends Omit<InputProps, 'styleOverwrites'> {
  /**
   * The styles to be applied to the textarea
   */
  styleOverwrites?: any;
  /**
   * If provided, the input will stop resizing when it reaches this height
   * @default undefined - the input will resize to fit the content
   */
  maxHeight?: number;
}
/**
 * This is an internal component and it should not be used directly
 * (although it can be used) as it doesn't provide labels, descriptions, etc.
 * It is used by the Input component to provide a resizable textarea.
 * @param props
 * @returns
 */
export const ResizableInput = observer(
  forwardRef<HTMLTextAreaElement, ResizableInputProps>((props, ref) => {
    const {
      onChange,
      id,
      placeholder,
      variant = 'm',
      onKeyDown,
      fullWidth,
      maxHeight,
      styleOverwrites,
      resizableWidth = false,
      resizableHeight = true
    } = props;
    const { theme } = useApphouse();
    const textareaRef = React.useRef<HTMLTextAreaElement>(null);
    const inputRef =
      ref || (textareaRef as React.RefObject<HTMLTextAreaElement>);

    const componentStyles: CSSProperties = merge(
      {},
      {
        color: 'inherit',
        resize: 'none'
      },
      fullWidth ? { width: '100%' } : {},
      maxHeight ? { maxHeight: maxHeight } : {}
    );
    let localStyles: CSSProperties =
      mergeStyles(theme?.styles.input[variant], styleOverwrites) || {};
    localStyles = mergeStyles(localStyles, componentStyles);

    const hasSetWidth = !!localStyles.width || fullWidth || false;
    const resizeTextarea = () => {
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      const textInput = inputRef.current;
      if (!textInput) {
        return;
      }
      if (textInput.value === '') {
        resetInputHeight(textInput);
      }
      // Call resize function initially to set the input field's width
      resizableWidth &&
        textInput &&
        // eslint-disable-next-line @typescript-eslint/ban-ts-comment
        // @ts-ignore
        resizeInputWidth(inputRef?.current, hasSetWidth);

      resizableHeight && textInput && resizeInputHeight(textInput, localStyles);
    };

    React.useEffect(() => {
      resizeTextarea();
    });

    const inputProps = omit(props, [
      'variant',
      'styleOverwrites',
      'loading',
      'children',
      'ref',
      'resizableWidth',
      'resizableHeight',
      'fullWidth',
      'multiline',
      'maxHeight',
      'password',
      'description',
      'onKeyDown',
      'onChange',
      'value',
      'input'
    ]);
    const handleOnChange = (inputValue: string) => {
      const v = inputValue;
      const updatedInput = removeHiddenCharacters(v);

      if (updatedInput.trim() === '') {
        resetInputHeight(textareaRef?.current);
      } else {
        resizeTextarea();
      }
      onChange && onChange(v);
    };
    return (
      <textarea
        {...omit(inputProps, ['children'])}
        id={id}
        data-xray="ResizableInput|textarea"
        placeholder={placeholder}
        ref={inputRef}
        value={props.value}
        onChange={(e) => {
          handleOnChange(e.target.value);
        }}
        rows={1}
        onKeyDown={(e) => {
          const code = e.code;
          const target = e.target as HTMLTextAreaElement;
          const value = target.value;
          const updatedInput = removeHiddenCharacters(value);
          const reset = code === 'Backspace' && updatedInput.trim() === '';

          const shiftKey = e.shiftKey;
          if (shiftKey && e.key === 'Enter') {
            e.preventDefault();
            e.stopPropagation();

            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            if (inputRef && inputRef.current) {
              // eslint-disable-next-line @typescript-eslint/ban-ts-comment
              // @ts-ignore
              inputRef.current.value = value + '\n';
            }
          }

          onKeyDown && onKeyDown(e);
          if (reset) {
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            resetInputHeight(inputRef?.current);
          } else {
            resizeTextarea();
          }
        }}
        {...css(localStyles)}
      />
    );
  })
);
