import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { ColorState } from 'react-color';
import { Doc, Opt } from '../../../fields/Doc';
import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
import { SelectionManager } from '../../util/SelectionManager';
import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
import { LinkPopup } from '../linking/LinkPopup';
import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
import { LightboxView } from '../LightboxView';
import { EditorView } from 'prosemirror-view';
import './AnchorMenu.scss';
import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
import { StrCast } from '../../../fields/Types';

@observer
export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
    static Instance: AnchorMenu;

    private _disposer: IReactionDisposer | undefined;
    private _disposer2: IReactionDisposer | undefined;
    private _commentCont = React.createRef<HTMLButtonElement>();
    private _palette = [
        'rgba(208, 2, 27, 0.8)',
        'rgba(238, 0, 0, 0.8)',
        'rgba(245, 166, 35, 0.8)',
        'rgba(248, 231, 28, 0.8)',
        'rgba(245, 230, 95, 0.616)',
        'rgba(139, 87, 42, 0.8)',
        'rgba(126, 211, 33, 0.8)',
        'rgba(65, 117, 5, 0.8)',
        'rgba(144, 19, 254, 0.8)',
        'rgba(238, 169, 184, 0.8)',
        'rgba(224, 187, 228, 0.8)',
        'rgba(225, 223, 211, 0.8)',
        'rgba(255, 255, 255, 0.8)',
        'rgba(155, 155, 155, 0.8)',
        'rgba(0, 0, 0, 0.8)',
    ];

    @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';

    @observable public Status: 'marquee' | 'annotation' | '' = '';

    // GPT additions
    @observable private GPTpopupText: string = '';
    @observable private loadingGPT: boolean = false;
    @observable private showGPTPopup: boolean = false;
    @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY;
    @observable private selectedText: string = '';
    @observable private editorView?: EditorView;
    @observable private textDoc?: Doc;
    @observable private highlightRange: number[] | undefined;
    private selectionRange: number[] | undefined;

    @action
    setGPTPopupVis = (vis: boolean) => {
        this.showGPTPopup = vis;
    };
    @action
    setGPTMode = (mode: GPTPopupMode) => {
        this.GPTMode = mode;
    };

    @action
    setGPTPopupText = (txt: string) => {
        this.GPTpopupText = txt;
    };

    @action
    setLoading = (loading: boolean) => {
        this.loadingGPT = loading;
    };

    @action
    setHighlightRange(r: number[] | undefined) {
        this.highlightRange = r;
    }

    @action
    public setSelectedText = (txt: string) => {
        this.selectedText = txt;
    };

    @action
    public setEditorView = (editor: EditorView) => {
        this.editorView = editor;
    };

    @action
    public setTextDoc = (textDoc: Doc) => {
        this.textDoc = textDoc;
    };

    public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search

    public OnCrop: (e: PointerEvent) => void = unimplementedFunction;
    public OnClick: (e: PointerEvent) => void = unimplementedFunction;
    public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
    public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
    public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
    public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
    public Delete: () => void = unimplementedFunction;
    public PinToPres: () => void = unimplementedFunction;
    public MakeTargetToggle: () => void = unimplementedFunction;
    public ShowTargetTrail: () => void = unimplementedFunction;
    public IsTargetToggler: () => boolean = returnFalse;
    public get Active() {
        return this._left > 0;
    }

    constructor(props: Readonly<{}>) {
        super(props);

        AnchorMenu.Instance = this;
        AnchorMenu.Instance._canFade = false;
    }

    componentWillUnmount() {
        this._disposer?.();
        this._disposer2?.();
    }

    componentDidMount() {
        this._disposer2 = reaction(
            () => this._opacity,
            opacity => {
                if (!opacity) {
                    this.setGPTPopupVis(false);
                    this.setGPTPopupText('');
                }
            },
            { fireImmediately: true }
        );
        this._disposer = reaction(
            () => SelectionManager.Views().slice(),
            selected => {
                this.setGPTPopupVis(false);
                this.setGPTPopupText('');
                AnchorMenu.Instance.fadeOut(true);
            }
        );
    }

    /**
     * Invokes the API with the selected text and stores it in the summarized text.
     * @param e pointer down event
     */
    gptSummarize = async (e: React.PointerEvent) => {
        this.setHighlightRange(undefined);
        this.setGPTPopupVis(true);
        this.setGPTMode(GPTPopupMode.SUMMARY);
        this.setLoading(true);

        try {
            const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
            if (res) {
                this.setGPTPopupText(res);
            } else {
                this.setGPTPopupText('Something went wrong.');
            }
        } catch (err) {
            console.error(err);
        }

        this.setLoading(false);
    };

    /**
     * Makes a GPT call to edit selected text.
     * @returns nothing
     */
    gptEdit = async () => {
        if (!this.editorView) return;
        this.setHighlightRange(undefined);
        const state = this.editorView.state;
        const sel = state.selection;
        const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n');
        const selectedText = state.doc.textBetween(sel.from, sel.to);

        this.setGPTPopupVis(true);
        this.setGPTMode(GPTPopupMode.EDIT);
        this.setLoading(true);

        try {
            let res = await gptAPICall(selectedText, GPTCallType.EDIT);
            // let res = await this.mockGPTCall();
            if (!res) return;
            res = res.trim();
            const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1);

            if (res) {
                this.setGPTPopupText(resultText);
                this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]);
            } else {
                this.setGPTPopupText('Something went wrong.');
            }
        } catch (err) {
            console.error(err);
        }

        this.setLoading(false);
    };

    /**
     * Replaces text suggestions from GPT.
     */
    replaceText = (replacement: string) => {
        if (!this.editorView || !this.textDoc) return;
        this.textDoc.text = replacement;
    };

    pointerDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            (e: PointerEvent) => {
                this.StartDrag(e, this._commentCont.current!);
                return true;
            },
            returnFalse,
            e => this.OnClick?.(e)
        );
    };

    audioDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(this, e, returnFalse, returnFalse, e => this.OnAudio?.(e));
    };

    cropDown = (e: React.PointerEvent) => {
        setupMoveUpEvents(
            this,
            e,
            (e: PointerEvent) => {
                this.StartCropDrag(e, this._commentCont.current!);
                return true;
            },
            returnFalse,
            e => this.OnCrop?.(e)
        );
    };

    @action
    highlightClicked = (e: React.MouseEvent) => {
        this.Highlight(this.highlightColor, false, undefined, true);
        AnchorMenu.Instance.fadeOut(true);
    };

    @computed get highlighter() {
        return (
            <Group>
                <IconButton
                    icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
                    tooltip={'Click to Highlight'}
                    onClick={this.highlightClicked}
                    colorPicker={this.highlightColor}
                    color={StrCast(Doc.UserDoc().userColor)}
                />
                <ColorPicker colorPickerType={'github'} selectedColor={this.highlightColor} setSelectedColor={color => this.changeHighlightColor(color)} size={Size.XSMALL} />
            </Group>
        );
    }

    @action changeHighlightColor = (color: string) => {
        const col: ColorState = {
            hex: color,
            hsl: { a: 0, h: 0, s: 0, l: 0, source: '' },
            hsv: { a: 0, h: 0, s: 0, v: 0, source: '' },
            rgb: { a: 0, r: 0, b: 0, g: 0, source: '' },
            oldHue: 0,
            source: '',
        };
        this.highlightColor = Utils.colorString(col);
    };

    /**
     * Returns whether the selected text can be summarized. The goal is to have
     * all selected text available to summarize but its only supported for pdf and web ATM.
     * @returns Whether the GPT icon for summarization should appear
     */
    canSummarize = (): boolean => {
        const docs = SelectionManager.Docs();
        if (docs.length > 0) {
            return docs.some(doc => doc.type === 'pdf' || doc.type === 'web');
        }
        return false;
    };

    /**
     * Returns whether the selected text can be edited.
     * @returns Whether the GPT icon for summarization should appear
     */
    canEdit = (): boolean => {
        const docs = SelectionManager.Docs();
        if (docs.length > 0) {
            return docs.some(doc => doc.type === 'rtf');
        }
        return false;
    };

    render() {
        const buttons =
            this.Status === 'marquee' ? (
                <>
                    {this.highlighter}
                    <IconButton tooltip={'Drag to Place Annotation'} onPointerDown={this.pointerDown} icon={<FontAwesomeIcon icon="comment-alt" />} color={StrCast(Doc.UserDoc().userColor)} />
                    {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
                    {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
                        <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}>
                            <button className="antimodeMenu-button annotate" onPointerDown={this.gptSummarize} style={{ cursor: 'grab' }}>
                                <FontAwesomeIcon icon="comment-dots" size="lg" />
                            </button>
                        </Tooltip>
                    )}
                    <GPTPopup
                        key="gptpopup"
                        visible={this.showGPTPopup}
                        text={this.GPTpopupText}
                        highlightRange={this.highlightRange}
                        loading={this.loadingGPT}
                        callSummaryApi={this.gptSummarize}
                        callEditApi={this.gptEdit}
                        replaceText={this.replaceText}
                        mode={this.GPTMode}
                    />
                    {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
                        <IconButton tooltip={'Click to Record Annotation'} onPointerDown={this.audioDown} icon={<FontAwesomeIcon icon="microphone" />} color={StrCast(Doc.UserDoc().userColor)} />
                    )}
                    {this.canEdit() && <IconButton tooltip={'AI edit suggestions'} onPointerDown={this.gptEdit} icon={<FontAwesomeIcon icon="pencil-alt" />} color={StrCast(Doc.UserDoc().userColor)} />}
                    <Popup tooltip="Find document to link to selected text" type={Type.PRIM} icon={<FontAwesomeIcon icon={'search'} />} popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} color={StrCast(Doc.UserDoc().userColor)} />
                    {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
                        <IconButton tooltip={'Click/Drag to create cropped image'} onPointerDown={this.cropDown} icon={<FontAwesomeIcon icon="image" />} color={StrCast(Doc.UserDoc().userColor)} />
                    )}
                </>
            ) : (
                <>
                    {this.Delete !== returnFalse && <IconButton tooltip={'Remove Link Anchor'} onPointerDown={this.Delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={StrCast(Doc.UserDoc().userColor)} />}
                    {this.PinToPres !== returnFalse && <IconButton tooltip={'Pin to Presentation'} onPointerDown={this.PinToPres} icon={<FontAwesomeIcon icon="map-pin" />} color={StrCast(Doc.UserDoc().userColor)} />}
                    {this.ShowTargetTrail !== returnFalse && <IconButton tooltip={'Show Linked Trail'} onPointerDown={this.ShowTargetTrail} icon={<FontAwesomeIcon icon="taxi" />} color={StrCast(Doc.UserDoc().userColor)} />}
                    {this.IsTargetToggler !== returnFalse && (
                        <Toggle
                            tooltip={'Make target visibility toggle on click'}
                            type={Type.PRIM}
                            toggleType={ToggleType.BUTTON}
                            toggleStatus={this.IsTargetToggler()}
                            onClick={this.MakeTargetToggle}
                            icon={<FontAwesomeIcon icon="thumbtack" />}
                            color={StrCast(Doc.UserDoc().userColor)}
                        />
                    )}
                </>
            );

        return this.getElement(buttons);
    }
}
