import { observer } from 'mobx-react';
import { CSSProperties, css } from 'glamor';

import { BsFillFileEarmarkMusicFill } from 'react-icons/bs';
import { BiSend } from 'react-icons/bi';
import React, { Ref } from 'react';
import { BoxSizeStyles } from '../styles/defaults/themes.interface';
import { ApphouseComponent } from '../components/component.interfaces';
import { useLocalStyles } from '../styles/defaults/useLocalStyles';

import { Button, ButtonStyles } from '../components/Button';
import {
  ButtonGetFile,
  ButtonGetFileStyle
} from '../components/fileDropper/ButtonGetFile';
import { ResizableInput, Text, useApphouse } from '..';

export type KeyDownEvent = React.KeyboardEvent<HTMLTextAreaElement>;
/**
 * Maximum number of characters allowed in the chat input.
 * This is a limitation from ChatGPT3.
 */
const MAX_CHARACTERS_ALLOWED = 4097;

export interface BaseChatInputProps {
  /**
   * Unique id for the input.
   */
  id: string;
  /**
   * If true, the file upload button will be shown.
   * @default false
   */
  showFileUpload?: boolean;
  /**
   * The current value in the input.
   * @default ''
   */
  value?: string;
  /**
   * Callback for when the value changes.
   * @param value
   * @returns
   */
  onChange?: (value: string) => void;
  /**
   * Callback for when the user clicks the send button.
   * @param value
   * @returns
   */
  onSendMessage?: (value: string) => void;
  /**
   * If true, a message will be shown in the footer.
   * @default none
   */
  footerMessage?: string;

  /**
   * Maximum number of characters allowed in the chat input.
   * @default 4097
   */
  maxCharactersAllowed?: number;
  /**
   * Disables all inputs and buttons for the chat box
   * @default false
   */
  disabled?: boolean;
  /**
   * Callback for when the user presses enter.
   * if provided, it will be called when the user presses enter.
   * @returns
   */
  onInputEnter?: (e: KeyDownEvent, value: string) => void;
  /**
   * If provided, it will be called when the user presses escape.
   * @returns
   */
  onInputEscape?: (e: KeyDownEvent, value: string) => void;
  /**
   * If provided, it will be called when the user presses arrow up.
   * @returns
   */
  onInputArrowUp?: (e: KeyDownEvent, value: string) => void;
  /**
   * If provided, it will be called when the user presses arrow down.
   * @returns
   */
  onInputArrowDown?: (e: KeyDownEvent, value: string) => void;
  /**
   * if true, it will show the loading indicator in the send button.
   * @default false
   */
  loading?: boolean;
  /**
   * The variant of input
   * @default 'm'
   */
  variant?: keyof BoxSizeStyles;
  /**
   * If provided, this will be the max height of the input
   * @default undefined
   * @optional
   */
  maxInputHeight?: number;
}

interface ChatInputWithUploadProps
  extends BaseChatInputProps,
    BaseChatInputProps,
    ApphouseComponent<ChatInputStyles> {
  /**
   * If true, the file upload button will be shown.
   * @default false
   */
  showFileUpload: true;

  /**
   * Callback for when the file upload fails.
   * It will be ignored if `showFileUpload` is false.
   * @param error
   * @returns
   */
  handleFileUploadError?: (error: Error) => void;
  /**
   * Callback for when the file upload succeeds.
   * It will be ignored if `showFileUpload` is false.
   * @param file
   * @returns
   */
  onFileUpload: (file: File) => void;
  /**
   * The accepted file types for the file upload.
   * It will be ignored if `showFileUpload` is false.
   * @default ['m4a', 'mp3', 'mp4', 'wav', 'webm']
   * @optional
   */
  acceptedFileTypes?: string[];
}

interface ChatInputWithoutUploadProps
  extends BaseChatInputProps,
    ApphouseComponent<ChatInputStyles> {
  showFileUpload?: false;
  sendMessageOnEnter?: never;
  handleFileUploadError?: never;
  onFileUpload?: never;
  acceptedFileTypes?: never;
}

export type ChatInputProps =
  | ChatInputWithUploadProps
  | ChatInputWithoutUploadProps;

export interface ChatInputStyles {
  container?: CSSProperties;
  wrapper?: CSSProperties;
  inputWrapper?: CSSProperties;
  input?: CSSProperties;
  fileUploadWrapper?: CSSProperties;
  footer?: CSSProperties;
  footerMessage?: CSSProperties;
  footerMessageText?: CSSProperties;
  fileIcon?: CSSProperties;
  sendButtonWrapper?: CSSProperties;
  sendButton?: ButtonStyles;
  buttonGetFile?: ButtonGetFileStyle;
}

/**
 * ChatInput, controlled component for a chat input.
 * @beta
 */
export const ChatInput = observer(
  React.forwardRef(
    (
      props: ChatInputProps,
      ref: React.Ref<HTMLInputElement | HTMLTextAreaElement>
    ) => {
      const {
        id,
        value = '',
        onChange,
        variant = 'm',
        onSendMessage,
        loading,
        onFileUpload,
        acceptedFileTypes,
        onInputEscape,
        showFileUpload = false,
        onInputEnter = false,
        footerMessage,
        maxInputHeight,
        onInputArrowDown,
        onInputArrowUp,
        disabled = false,
        maxCharactersAllowed = MAX_CHARACTERS_ALLOWED,
        handleFileUploadError,
        styleOverwrites
      } = props;

      const hasMaxCharacters = (value?.length || 0) > maxCharactersAllowed;
      const disableSend =
        hasMaxCharacters || value?.length === 0 || value === '' || disabled;

      const {
        theme: { colors, styles, tokens }
      } = useApphouse();
      const { spacings } = tokens;

      const handleKeyDown = (e: KeyDownEvent) => {
        const code = e.key;
        const shift = e.shiftKey;
        // if shift is pressed, we don't want to do anything, let the browser handle it
        if (shift) {
          return;
        }
        if (code === 'Enter') {
          if (onInputEnter) {
            e.preventDefault();
            onInputEnter && onInputEnter(e, value);
          }
        }
        if (code === 'Escape') {
          if (onInputEscape) {
            e.preventDefault();
            onInputEscape(e, value);
          }
        }

        if (code === 'ArrowUp') {
          if (onInputArrowUp) {
            e.preventDefault();
            onInputArrowUp(e, value);
          }
        }

        if (code === 'ArrowDown') {
          if (onInputArrowDown) {
            e.preventDefault();
            onInputArrowDown(e, value);
          }
        }
      };

      const componentStyles: ChatInputStyles = {
        container: {
          width: 'inherit',
          bottom: 0,
          display: 'contents',
          flexDirection: 'column',
          padding: '6px',
          boxSizing: 'border-box'
        },
        wrapper: {
          display: 'flex',
          flexDirection: 'column',
          position: 'relative',
          alignItems: 'center',
          boxSizing: 'border-box'
        },
        inputWrapper: {
          width: '100%',
          paddingLeft: showFileUpload ? '40px' : spacings.s,
          paddingRight: 0,
          display: 'flex',
          boxSizing: 'border-box'
        },
        input: {
          backgroundColor: styles.input[variant].backgroundColor,
          borderColor: hasMaxCharacters
            ? colors.error
            : styles.input[variant].borderColor,
          paddingRight: '40px'
        },
        footer: {
          paddingLeft: showFileUpload ? '60px' : spacings.s,
          paddingRight: spacings.s,
          marginTop: spacings.s,
          boxSizing: 'border-box',
          display: 'flex',
          width: '100%',
          justifyContent: 'space-between'
        },
        fileUploadWrapper: { position: 'absolute', top: 0, left: 0 },
        sendButtonWrapper: { position: 'absolute', top: 0, right: 0 },
        footerMessageText: { opacity: 0.5, fontWeight: 'bold' }
      };
      const localStyles = useLocalStyles(componentStyles, styleOverwrites);

      return (
        <div
          {...css(localStyles.container)}
          data-xray="ChatInput"
          data-style="container"
        >
          <div {...css(localStyles.wrapper)} data-style="wrapper">
            <div {...css({ width: '100%' })}>
              {showFileUpload && (
                <div
                  {...css(localStyles.fileUploadWrapper)}
                  data-style="fileUploadWrapper"
                >
                  <ButtonGetFile
                    label={<BsFillFileEarmarkMusicFill size={16} />}
                    onError={handleFileUploadError}
                    size="m"
                    variant="brandClear"
                    parseFileContentOnLoad={false}
                    acceptedFileTypes={acceptedFileTypes}
                    onFileAvailable={(file: File) => {
                      onFileUpload && onFileUpload(file);
                    }}
                    styleOverwrites={localStyles.buttonGetFile}
                  />
                </div>
              )}
              <div {...css(localStyles.inputWrapper)} data-style="inputWrapper">
                <ResizableInput
                  id={id}
                  maxHeight={maxInputHeight}
                  ref={ref as Ref<HTMLTextAreaElement>}
                  fullWidth
                  variant={variant}
                  styleOverwrites={localStyles.input}
                  value={value}
                  disabled={loading || disabled}
                  placeholder="message"
                  onChange={(value) => {
                    onChange && onChange(value);
                  }}
                  onKeyDown={handleKeyDown}
                />
              </div>

              <div
                {...css(localStyles.sendButtonWrapper)}
                data-style="sendButtonWrapper"
              >
                <Button
                  variant="clear"
                  size={variant}
                  loading={loading}
                  loadingSize={6}
                  disabled={disableSend}
                  onClick={() => {
                    if (!disableSend && onSendMessage) {
                      onSendMessage(value);
                    }
                  }}
                  styleOverwrites={localStyles.sendButton}
                >
                  <BiSend
                    // we make the size a bit smaller than the default because this icon is a little bigger than the others
                    size={adjustIconSize(tokens.iconSize[variant])}
                  />
                </Button>
              </div>
            </div>
            <div {...css(localStyles.footer)} data-style="footer">
              <Text
                variant="caption"
                styleOverwrites={localStyles.footerMessageText}
                data-style="footerMessageText"
              >
                {value.length}/{maxCharactersAllowed}
              </Text>
              {footerMessage && (
                <Text
                  variant="caption"
                  styleOverwrites={localStyles.footerMessageText}
                  data-style="footerMessageText"
                >
                  {footerMessage}
                </Text>
              )}
            </div>
          </div>
        </div>
      );
    }
  )
);

const adjustIconSize = (size?: string | number) => {
  if (typeof size === 'number') {
    return size - 6;
  }
  return size;
};
