import React from 'react';
import { observer } from 'mobx-react';
import { CSSProperties, css } from 'glamor';
import { Input } from '../components/input/Input';
import { View } from '../components/View';
import { Text } from '../components/Text';
import { Button } from '../components/Button';
import { ChatInput } from '../templates/ChatInput';
import { Paper } from '../components/Paper';
import { useApphouse } from '../context/useApphouse';
import { ApphouseComponent } from '../components/component.interfaces';
import { useLocalStyles } from '../styles/defaults/useLocalStyles';
import { BaseChatInputProps, ChatInputStyles, KeyDownEvent } from './ChatInput';
import { InputComponentStyles } from '../components/input/input.styles.interface';
import { toRems } from '../utils/units/toRems';
import { ChatMessage } from '../models/Chat';
import { ChatHistory } from './ChatHistory';
import { getGutterStyles } from '../styles/getGutterStyles';
import { InputStyles } from '../styles/defaults/themes.interface';

/**
 * Interface for styles to be applied to the chat box.
 */
export interface ChatBoxStyles {
  /**
   * Styles to be applied to the submit button.
   */
  button?: CSSProperties;
  /**
   * Styles to be applied to the container of the chat box.
   */
  container?: CSSProperties;
  /**
   * Styles to be applied to the wrapper of the history.
   */
  history?: CSSProperties;
  /**
   * Styles to be applied to the input.
   */
  input?: InputComponentStyles;
  /**
   * Styles to be applied to the compact input when using the compact mode.
   */
  compactInput?: ChatInputStyles;
  /**
   * Styles to be applied to the prompt when using the prompt mode.
   */
  prompt?: CSSProperties;
  /**
   * Styles to be applied to the compact input wrapper.
   */
  compactInputWrapper?: CSSProperties;
  /**
   * Styles to be applied to the title.
   */
  title?: CSSProperties;
  /**
   * Styles to be applied to the message text in the history.
   * If children is passed, this will be ignored
   */
  messageText?: CSSProperties;
}
/**
 * A Chat bot component that can be used for a Chat.
 */
export interface BaseChatBoxProps {
  /**
   * The variant of the chat box. It will be applied to the history.
   * This prop will be ignored if you pass children. The children prop will be used instead.
   * @default 'default' the history will be shown as plain text
   * @optional
   * @example 'chatBubble' the history will be shown as chat bubbles
   */
  historyVariant?: 'default' | 'chatBubble';
  /**
   * The message history of the chat box.
   * @default []
   * @optional
   */
  history?: ChatMessage[];
  /**
   * The function that will be called when the user submits a message.
   * @param message string
   * @returns
   */
  onSubmit: (message: string) => void;
  /**
   * if true, the chatbot will show a loading indicator in the submit button.
   * @default false
   * @optional
   */
  loading?: boolean;
  /**
   * The title of the chat box, it will display on top.
   * @optional
   * @default "none"
   */
  title?: string;
  /**
   * The text that will be displayed on the submit button.
   * @default 'Send'
   * @optional
   */
  submitButtonText?: string;
  /**
   * The label of the input.
   * @default none
   */
  label?: string;
  /**
   * The description of the input.
   * @default none
   */
  description?: string;
  /**
   * If true, the submit button will be disabled.
   * @default false
   */
  disabled?: boolean;
  /**
   * The max height of the chat box.
   * @default 400px
   */
  maxHeight?: string;
  /**
   * If provided, it will replace the history with this component.
   * @default none
   */
  children?: React.ReactNode;
  /**
   * If true, the chat box input will be compact with the send button.
   */
  compact?: boolean;
  /**
   * The initial value of the input.
   */
  value?: string;
  /**
   * If provided, the chat will be augmented with a prompt.
   * @default none
   */
  prompt?: React.ReactNode;
  /**
   * If provided, it will set the width of the chat box.
   * @default -webkit-fill-available
   */
  width?: number;
  /**
   * If provided, the input will not be cleared when the user submits a message.
   * @default false the input will not be cleared and the message will persist
   * @optional
   */
  persistMessageOnSend?: boolean;
  /**
   * If true, the message will be submitted when the user presses enter.
   * @default false (the message will not be submitted when the user presses enter)
   * @optional
   */
  sendMessageOnEnter?: boolean;
  /**
   * If provided, it will be applied to the size of the input.
   * @default false
   */
  inputVariant?: keyof InputStyles;
  /**
   * The max height of the input.
   * @default undefined (it will fit the content based on the space available)
   */
  maxInputHeight?: number;
}
/**
 * Interface for the chat box with compact input with upload
 */
interface ChatBoxWithCompactInputWithUpload
  extends BaseChatBoxProps,
    BaseChatInputProps,
    ApphouseComponent<ChatBoxStyles> {
  /**
   * If true, the chat box input will be compact with the send button.
   */
  compact?: true;
  /**
   * If true, the file upload button will be shown.
   * @default false
   */
  showFileUpload?: boolean;

  /**
   * 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 for the chat box with compact input without upload
 */
interface ChatBoxWithCompactInputWithoutUpload
  extends BaseChatBoxProps,
    BaseChatInputProps,
    ApphouseComponent<ChatBoxStyles> {
  compact?: false;
  showFileUpload?: never;
  sendMessageOnEnter?: never;
  handleFileUploadError?: never;
  onFileUpload?: never;
  acceptedFileTypes?: never;
}

/**
 * Interface for the chat box with input
 */
interface ChatBoxWithInput
  extends BaseChatBoxProps,
    ApphouseComponent<ChatBoxStyles> {
  compact?: false;
  id?: never;
  onChange?: never;
  onFileUpload?: never;
  onInputArrowDown?: never;
  onInputArrowUp?: never;
  onInputEnter?: never;
  onInputEscape?: never;
  showFileUpload?: never;
}

/**
 * The ChatBox Component interface.
 */
export type ChatBoxProps =
  | ChatBoxWithCompactInputWithUpload
  | ChatBoxWithCompactInputWithoutUpload
  | ChatBoxWithInput;

/**
 * Renders a chat box component with an input field and a submit button.
 * This component can be used to create a chat bot.
 * It provides built-in views for displaying chat history and user input.
 * History view can be replaced with a custom component, the children prop.
 *
 * @returns React.ReactNode
 */
export const ChatBox: React.FC<ChatBoxProps> = observer((props) => {
  const {
    theme: {
      tokens: { spacings }
    }
  } = useApphouse();

  const {
    prompt,
    children,
    compact = false,
    description,
    disabled,
    gutters,
    historyVariant = 'default',
    history = [],
    value: _value = '',
    label,
    loading,
    maxHeight = undefined,
    maxInputHeight = undefined,
    onChange,
    onFileUpload,
    onInputArrowDown,
    onInputArrowUp,
    onInputEnter,
    onInputEscape,
    onSubmit,
    showFileUpload,
    submitButtonText = 'Send',
    styleOverwrites,
    persistMessageOnSend = false,
    inputVariant = 'm',
    sendMessageOnEnter = false,
    width: _width = '-webkit-fill-available',
    title
  } = props;

  const [value, setValue] = React.useState(_value);
  const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement>(null);
  /**
   * Handles changing the input value
   * @param value the input value
   */
  const handleOnChange = (value: string) => {
    setValue(value);
    onChange && onChange(value);
  };

  /**
   * Handles submitting the message
   */
  const handleSubmit = () => {
    if (disabled) {
      return;
    }
    const message = value;
    if (persistMessageOnSend === false) {
      setValue('');
    }

    // focus back on the input
    inputRef.current?.focus();
    onSubmit && onSubmit(message);
  };

  /**
   * Here we handle clearing the input if persistMessageOnSend is false
   * @param e keyDownEvent
   */
  const handleInputEnter = (e: KeyDownEvent) => {
    if (disabled) {
      return;
    }
    const message = value;
    if (persistMessageOnSend === false) {
      setValue('');
      if (sendMessageOnEnter && !e.shiftKey && value !== '') {
        onSubmit && onSubmit(message);
      }
    }
    onInputEnter && onInputEnter(e, message);
  };

  // handle the width of the chat box
  let width = _width;
  if (typeof _width === 'number') {
    width = toRems(_width);
  }

  // define the default styles of the component
  const verticalSpacing = spacings.l;

  const componentStyles: ChatBoxStyles = {
    container: {
      padding: compact ? 0 : spacings.xxl,
      borderRadius: '12px',
      maxHeight,
      overflow: 'hidden',
      width: compact ? width : undefined,
      ...getGutterStyles(gutters)
    },
    history: {
      maxHeight: '200px',
      overflow: 'auto',
      paddingTop: verticalSpacing,
      paddingBottom: verticalSpacing
    }
  };
  const localStyles = useLocalStyles<ChatBoxStyles>(
    componentStyles,
    styleOverwrites,
    gutters
  );

  return (
    <Paper
      data-xray="ChatBox"
      styleOverwrites={localStyles.container}
      data-style="ChatBox.container|Paper"
    >
      {title && (
        <Text
          variant="title"
          data-style="ChatBox.title|Text"
          styleOverwrites={localStyles.title}
        >
          {title}
        </Text>
      )}
      <View
        orientation="vertical"
        alignItems="flex-start"
        styleOverwrites={localStyles.history}
        data-style="ChatBox.history|View"
      >
        {!children &&
          historyVariant === 'default' &&
          history?.map((item) => {
            return (
              <Text key={item.id} data-style="ChatBox.messageText|Text">
                {item.content}
              </Text>
            );
          })}
        {!children && historyVariant === 'chatBubble' && (
          <ChatHistory history={history} selfUserId="user" />
        )}
        {children && children}
      </View>
      {!compact && (
        <>
          <Input
            label={label}
            description={description}
            id="aiPrompt"
            disabled={disabled || loading}
            value={value}
            onKeyDown={(e) => {
              if (sendMessageOnEnter && !e.shiftKey && value !== '') {
                if (e.key === 'Enter') {
                  e.preventDefault();
                  handleSubmit();
                }
              }
            }}
            ref={inputRef}
            width="100%"
            variant={inputVariant}
            onChange={handleOnChange}
            styleOverwrites={localStyles.input}
            data-style="ChatBox.input|Input"
          />
          <View orientation="vertical" alignItems="flex-end">
            <Button
              disabled={disabled || loading}
              loading={loading}
              onClick={() => {
                if (disabled) {
                  return;
                }
                const message = value;
                if (!persistMessageOnSend) {
                  setValue('');
                }
                onSubmit(message);
              }}
              styleOverwrites={localStyles.button}
              data-style="ChatBox.button|Button"
            >
              {submitButtonText}
            </Button>
          </View>
        </>
      )}
      {prompt && (
        <View
          gutters={spacings.s}
          styleOverwrites={localStyles.prompt}
          data-style="ChatBox.prompt|View"
        >
          {prompt}
        </View>
      )}

      <div
        {...css({
          marginTop: spacings.m,
          marginBottom: spacings.m,
          marginRight: spacings.xs
        })}
        data-style="ChatBox.compactInputWrapper"
      >
        {compact && showFileUpload && (
          <ChatInput
            ref={inputRef}
            id="chat-input-with-file-upload"
            onChange={handleOnChange}
            disabled={disabled || loading}
            loading={loading}
            maxInputHeight={maxInputHeight}
            value={value}
            variant={inputVariant}
            onFileUpload={(file: File) => {
              onFileUpload && onFileUpload(file);
            }}
            onInputArrowDown={onInputArrowDown}
            onInputArrowUp={onInputArrowUp}
            onInputEnter={handleInputEnter}
            onInputEscape={onInputEscape}
            onSendMessage={handleSubmit}
            showFileUpload={showFileUpload}
            styleOverwrites={localStyles.compactInput}
            data-style="ChatBox.compactInput|ChatInput"
          />
        )}

        {compact && !showFileUpload && (
          <ChatInput
            ref={inputRef}
            id="chat-input-with-file-upload"
            onChange={handleOnChange}
            disabled={disabled || loading}
            value={value}
            loading={loading}
            variant={inputVariant}
            maxInputHeight={maxInputHeight}
            onInputArrowDown={onInputArrowDown}
            onInputArrowUp={onInputArrowUp}
            onInputEnter={handleInputEnter}
            onInputEscape={onInputEscape}
            onSendMessage={handleSubmit}
            styleOverwrites={localStyles.compactInput}
            data-style="ChatBox.compactInput|ChatInput"
          />
        )}
      </div>
    </Paper>
  );
});
