// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { useMsal } from '@azure/msal-react';
import { Button, Spinner, Textarea, makeStyles, mergeClasses, shorthands, tokens } from '@fluentui/react-components';
import { AttachRegular, MicRegular, SendRegular } from '@fluentui/react-icons';
import debug from 'debug';
import * as speechSdk from 'microsoft-cognitiveservices-speech-sdk';
import React, { useRef, useState } from 'react';
import { Constants } from '../../Constants';
import { COPY } from '../../assets/strings';
import { AuthHelper } from '../../libs/auth/AuthHelper';
import { useFile } from '../../libs/hooks';
import { GetResponseOptions } from '../../libs/hooks/useChat';
import { AlertType } from '../../libs/models/AlertType';
import { ChatMessageType } from '../../libs/models/ChatMessage';
import { useAppDispatch, useAppSelector } from '../../redux/app/hooks';
import { RootState } from '../../redux/app/store';
import { FeatureKeys } from '../../redux/features/app/AppState';
import { addAlert } from '../../redux/features/app/appSlice';
import { editConversationInput, updateBotResponseStatus } from '../../redux/features/conversations/conversationsSlice';
import { Alerts } from '../shared/Alerts';
import { getErrorDetails } from '../utils/TextUtils';
import { SpeechService } from './../../libs/services/SpeechService';
import { updateUserIsTyping } from './../../redux/features/conversations/conversationsSlice';
import { ChatStatus } from './ChatStatus';

const log = debug(Constants.debug.root).extend('chat-input');

const useClasses = makeStyles({
    root: {
        display: 'flex',
        flexDirection: 'column',
        ...shorthands.margin(0, '72px'),
    },
    typingIndicator: {
        maxHeight: '28px',
    },
    content: {
        ...shorthands.gap(tokens.spacingHorizontalM),
        display: 'flex',
        flexDirection: 'row',
        width: '100%',
    },
    input: {
        width: '100%',
    },
    textarea: {
        maxHeight: '80px',
    },
    controls: {
        display: 'flex',
        flexDirection: 'row',
    },
    essentials: {
        display: 'flex',
        flexDirection: 'row',
        marginLeft: 'auto', // align to right
    },
    functional: {
        display: 'flex',
        flexDirection: 'row',
    },
    dragAndDrop: {
        ...shorthands.border(tokens.strokeWidthThick, ' solid', tokens.colorBrandStroke1),
        ...shorthands.padding('8px'),
        textAlign: 'center',
        backgroundColor: tokens.colorNeutralBackgroundInvertedDisabled,
        fontSize: tokens.fontSizeBase300,
        color: tokens.colorBrandForeground1,
        caretColor: 'transparent',
    },
});

export enum ChatInputState {
    Ready = 0,
    Sending,
    IsLocked,
    MaxTurnReached,
}

interface ChatInputProps {
    isDraggingOver?: boolean;
    onDragLeave: React.DragEventHandler<HTMLDivElement | HTMLTextAreaElement>;
    onSubmit: (options: GetResponseOptions) => Promise<void>;
    chatInputState: ChatInputState;
}



export const ChatInput: React.FC<ChatInputProps> = ({ isDraggingOver, onDragLeave, onSubmit, chatInputState }) => {
    const classes = useClasses();
    const { instance, inProgress } = useMsal();
    const dispatch = useAppDispatch();
    const { conversations, selectedId } = useAppSelector((state: RootState) => state.conversations);
    const { activeUserInfo, features } = useAppSelector((state: RootState) => state.app);
    const fileHandler = useFile();

    const [value, setValue] = useState('');
    const [recognizer, setRecognizer] = useState<speechSdk.SpeechRecognizer>();
    const [isListening, setIsListening] = useState(false);
    const { importingDocuments } = conversations[selectedId];

    const documentFileRef = useRef<HTMLInputElement | null>(null);
    const textAreaRef = React.useRef<HTMLTextAreaElement>(null);
    React.useEffect(() => {
        // Focus on the text area when the selected conversation changes
        textAreaRef.current?.focus();
    }, [selectedId]);

    React.useEffect(() => {
        async function initSpeechRecognizer() {
            const speechService = new SpeechService();
            const response = await speechService.getSpeechTokenAsync(
                await AuthHelper.getSKaaSAccessToken(instance, inProgress),
            );
            if (response.isSuccess) {
                const recognizer = speechService.getSpeechRecognizerAsyncWithValidKey(response);
                setRecognizer(recognizer);
            }
        }

        initSpeechRecognizer().catch((e) => {
            const errorDetails = getErrorDetails(e);
            const errorMessage = `Unable to initialize speech recognizer. Details: ${errorDetails}`;
            dispatch(addAlert({ message: errorMessage, type: AlertType.Error }));
        });
    }, [dispatch, instance, inProgress]);

    React.useEffect(() => {
        const chatState = conversations[selectedId];
        setValue(chatState.disabled ? COPY.CHAT_DELETED_MESSAGE() : chatState.input);
    }, [conversations, selectedId]);

    const handleSpeech = () => {
        setIsListening(true);
        if (recognizer) {
            recognizer.recognizeOnceAsync((result) => {
                if (result.reason === speechSdk.ResultReason.RecognizedSpeech) {
                    if (result.text && result.text.length > 0) {
                        handleSubmit(result.text);
                    }
                }
                setIsListening(false);
            });
        }
    };

    const handleSubmit = (value: string, messageType: ChatMessageType = ChatMessageType.Message) => {
        if (value.trim() === '') {
            return; // only submit if value is not empty
        }
        setValue('');
        dispatch(editConversationInput({ id: selectedId, newInput: '' }));
        dispatch(updateBotResponseStatus({ chatId: selectedId, status: 'Calling the kernel' }));
        onSubmit({ value, messageType, chatId: selectedId }).catch((error) => {
            const message = `Error submitting chat input: ${(error as Error).message}`;
            log(message);
            dispatch(
                addAlert({
                    type: AlertType.Error,
                    message,
                }),
            );
        });
    };

    const handleDrop = (e: React.DragEvent<HTMLTextAreaElement>) => {
        onDragLeave(e);
        void fileHandler.handleImport(selectedId, documentFileRef, undefined, e.dataTransfer.files);
    };

    const getTextAreaValue = (): string => {
        switch (chatInputState) {
            case ChatInputState.IsLocked:
                return 'This conversation is being reviewed. Wait for the review to complete before continuing this conversation.';
            case ChatInputState.MaxTurnReached:
                return 'This conversation has reached the maximum number of turns.';
            case ChatInputState.Sending:
                return 'Sending message please wait...';
            default:
                return isDraggingOver
                    ? features[FeatureKeys.Documents].enabled
                        ? 'Drop your files here'
                        : 'File upload is not enabled for this challenge.'
                    : value;
        }
    };

    const chatInputLocked = (): boolean => conversations[selectedId].disabled
        || chatInputState == ChatInputState.IsLocked
        || chatInputState == ChatInputState.MaxTurnReached
        || chatInputState == ChatInputState.Sending

    return (
        <div className={classes.root}>
            <div className={classes.typingIndicator}>
                <ChatStatus />
            </div>
            <Alerts />
            <div className={classes.content}>
                <Textarea
                    title="Chat input"
                    aria-label="Chat input field. Click enter to submit input."
                    ref={textAreaRef}
                    id="chat-input"
                    resize="vertical"
                    disabled={chatInputLocked()}
                    textarea={{
                        className: !(chatInputState == ChatInputState.IsLocked || chatInputState == ChatInputState.MaxTurnReached) ? (
                            isDraggingOver ? mergeClasses(classes.dragAndDrop, classes.textarea) : classes.textarea
                        ) : mergeClasses(classes.dragAndDrop, classes.textarea),
                    }}
                    className={classes.input}
                    value={getTextAreaValue()}
                    onDrop={handleDrop}
                    onFocus={() => {
                        // update the locally stored value to the current value
                        const chatInput = document.getElementById('chat-input');
                        if (chatInput) {
                            setValue((chatInput as HTMLTextAreaElement).value);
                        }
                        // User is considered typing if the input is in focus
                        if (activeUserInfo) {
                            dispatch(
                                updateUserIsTyping({ userId: activeUserInfo.id, chatId: selectedId, isTyping: true }),
                            );
                        }
                    }}
                    onChange={(_event, data) => {
                        if (isDraggingOver) {
                            return;
                        }

                        setValue(data.value);
                        dispatch(editConversationInput({ id: selectedId, newInput: data.value }));
                    }}
                    onKeyDown={(event) => {
                        if (event.key === 'Enter' && !event.shiftKey) {
                            event.preventDefault();
                            handleSubmit(value);
                        }
                    }}
                    onBlur={() => {
                        // User is considered not typing if the input is not  in focus
                        if (activeUserInfo) {
                            dispatch(
                                updateUserIsTyping({ userId: activeUserInfo.id, chatId: selectedId, isTyping: false }),
                            );
                        }
                    }}
                />
            </div>
            <div className={classes.controls}>
                {features[FeatureKeys.Documents].enabled && (
                    <>
                        <div className={classes.functional}>
                            {/* Hidden input for file upload. Only accept .txt and .pdf files for now. */}
                            <input
                                type="file"
                                ref={documentFileRef}
                                style={{ display: 'none' }}
                                accept={Constants.app.importTypes}
                                multiple={true}
                                onChange={() => {
                                    void fileHandler.handleImport(selectedId, documentFileRef);
                                }}
                            />
                            <Button
                                disabled={
                                    conversations[selectedId].disabled || (importingDocuments && importingDocuments.length > 0)
                                }
                                appearance="transparent"
                                icon={<AttachRegular />}
                                onClick={() => documentFileRef.current?.click()}
                                title="Attach file"
                                aria-label="Attach file button"
                            />
                            {importingDocuments && importingDocuments.length > 0 && <Spinner size="tiny" />}
                        </div>
                    </>
                )}
                <div className={classes.essentials}>
                    {recognizer && (
                        <Button
                            appearance="transparent"
                            disabled={conversations[selectedId].disabled || isListening}
                            icon={<MicRegular />}
                            onClick={handleSpeech}
                        />
                    )}
                    <Button
                        title="Submit"
                        aria-label="Submit message"
                        appearance="transparent"
                        icon={<SendRegular />}
                        onClick={() => {
                            handleSubmit(value);
                        }}
                        disabled={chatInputLocked()}
                    />
                </div>
            </div>
        </div>
    );
};
