import { Button } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Slider, Switch } from '@mui/material';
import { action, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import ReactLoading from 'react-loading';
import { returnEmptyFilter, returnFalse, returnTrue } from '../../../ClientUtils';
import { emptyFunction, numberRange } from '../../../Utils';
import { Doc, DocListCast, returnEmptyDoclist } from '../../../fields/Doc';
import { ImageCast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { DocumentType } from '../../documents/DocumentTypes';
import { Docs } from '../../documents/Documents';
import { makeUserTemplateButtonOrImage } from '../../util/DropConverter';
import { SettingsManager } from '../../util/SettingsManager';
import { Transform } from '../../util/Transform';
import { undoBatch } from '../../util/UndoManager';
import { ObservableReactComponent } from '../ObservableReactComponent';
import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider';
import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView';
import { FieldView } from '../nodes/FieldView';
import { DrawingOptions, SmartDrawHandler } from './SmartDrawHandler';
import './StickerPalette.scss';

interface StickerPaletteProps {
    Doc: Doc;
}

enum StickerPaletteMode {
    create,
    view,
}

/**
 * The StickerPalette can be toggled in the lightbox view of a document. The goal of the palette
 * is to offer an easy way for users to create stickers and  drag and drop them onto a document.
 * These stickers can technically be of any document type and operate similarly to user templates.
 * However, the palette is designed to be geared toward ink stickers and image stickers.
 *
 * On the "add" side of the palette, there is a way to create a drawing sticker with GPT. Users can
 * enter the item to draw, toggle different settings, then GPT will generate three versions of the drawing
 * to choose from. These drawings can then be saved to the palette as stickers.
 */
@observer
export class StickerPalette extends ObservableReactComponent<StickerPaletteProps> {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(StickerPalette, fieldKey);
    }
    /**
     * Adds a doc to the sticker palette. Gets a snapshot of the document to use as a preview in the palette. When this
     * preview is dragged onto a parent document, a copy of that document is added as a sticker.
     */
    public static addToPalette = async (doc: Doc) => {
        if (!doc.savedAsSticker) {
            const docView = DocumentView.getDocumentView(doc);
            await docView?.ComponentView?.updateIcon?.(true);
            const { clone } = Doc.MakeClone(doc);
            clone.title = doc.title;
            const image = ImageCast(doc.icon, ImageCast(clone[Doc.LayoutDataKey(clone)]))?.url?.href;
            Doc.MyStickers && Doc.AddDocToList(Doc.MyStickers, 'data', makeUserTemplateButtonOrImage(clone, image));
            doc.savedAsSticker = true;
        }
    };

    public static getIcon(group: Doc) {
        const docView = DocumentView.getDocumentView(group);
        docView?.ComponentView?.updateIcon?.(true);
        return !docView ? undefined : new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 1000));
    }

    private _gptRes: string[] = [];

    @observable private _paletteMode = StickerPaletteMode.view;
    @observable private _userInput: string = '';
    @observable private _isLoading: boolean = false;
    @observable private _canInteract: boolean = true;
    @observable private _showRegenerate: boolean = false;
    @observable private _docView: DocumentView | null = null;
    @observable private _docCarouselView: DocumentView | null = null;
    @observable private _opts: DrawingOptions = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };

    constructor(props: StickerPaletteProps) {
        super(props);
        makeObservable(this);
    }

    componentWillUnmount() {
        this.resetPalette(true);
    }

    Contains = (view: DocumentView) =>
        (this._docView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docView)) || //
        (this._docCarouselView && (view.containerViewPath?.() ?? []).concat(view).includes(this._docCarouselView));

    return170 = () => 170;

    handleKeyPress = (event: React.KeyboardEvent) => {
        if (event.key === 'Enter') {
            this.generateDrawings();
        }
    };

    setPaletteMode = action((mode: StickerPaletteMode) => {
        this._paletteMode = mode;
    });

    setUserInput = action((input: string) => {
        if (!this._isLoading) this._userInput = input;
    });

    setDetail = action((detail: number) => {
        if (this._canInteract) this._opts.complexity = detail;
    });

    setColor = action((autoColor: boolean) => {
        if (this._canInteract) this._opts.autoColor = autoColor;
    });

    setSize = action((size: number) => {
        if (this._canInteract) this._opts.size = size;
    });

    resetPalette = action((changePaletteMode: boolean) => {
        if (changePaletteMode) this.setPaletteMode(StickerPaletteMode.view);
        this.setUserInput('');
        this.setDetail(5);
        this.setColor(true);
        this.setSize(200);
        this._showRegenerate = false;
        this._canInteract = true;
        this._opts = { text: '', complexity: 5, size: 200, autoColor: true, x: 0, y: 0 };
        this._gptRes = [];
        this._props.Doc.$data = undefined;
    });

    /**
     * Calls the draw with AI functions in SmartDrawHandler to allow users to generate drawings straight from
     * the sticker palette.
     */
    @undoBatch
    generateDrawings = action(() => {
        this._isLoading = true;
        const prevDrawings = DocListCast(this._props.Doc.$data);
        this._props.Doc.$data = undefined;
        SmartDrawHandler.Instance.AddDrawing = this.addDrawing;
        this._canInteract = false;
        Promise.all(
            numberRange(3).map(() => {
                return this._showRegenerate
                    ? SmartDrawHandler.Instance.regenerate(prevDrawings, this._userInput)
                    : SmartDrawHandler.Instance.drawWithGPT({ X: 0, Y: 0 }, this._userInput, this._opts.complexity || 0, this._opts.size || 0, !!this._opts.autoColor);
            })
        ).then(() => {
            this._opts.text !== '' ? (this._opts.text = `${this._opts.text} ~~~ ${this._userInput}`) : (this._opts.text = this._userInput);
            this._userInput = '';
            this._isLoading = false;
            this._showRegenerate = true;
        });
    });

    @action
    addDrawing = (drawing: Doc) => {
        this._gptRes.push(StrCast(drawing.$ai_drawing_data));
        drawing.$freeform_fitContentsToBox = true;
        Doc.AddDocToList(this._props.Doc, 'data', drawing);
    };

    /**
     * Saves the currently showing, newly generated drawing to the sticker palette and sets the metadata.
     * AddToPalette() is generically used to add any document to the palette, while this defines the behavior for when a user
     * presses the "save drawing" button.
     */
    saveDrawing = () => {
        const cIndex = NumCast(this._props.Doc.carousel_index);
        const focusedDrawing = DocListCast(this._props.Doc.data)[cIndex];
        focusedDrawing.$title = this._opts.text?.match(/^(.*?)~~~.*$/)?.[1] || this._opts.text;
        focusedDrawing.$ai_prompt = this._opts.text;
        focusedDrawing.$ai_drawing_complexity = this._opts.complexity;
        focusedDrawing.$ai_drawing_colored = this._opts.autoColor;
        focusedDrawing.$ai_drawing_size = this._opts.size;
        focusedDrawing.$ai_drawing_data = this._gptRes[cIndex];
        focusedDrawing.$ai = 'gpt';
        focusedDrawing.width = this._opts.size;
        focusedDrawing.x = this._opts.x;
        focusedDrawing.y = this._opts.y;
        StickerPalette.addToPalette(focusedDrawing).then(() => this.resetPalette(true));
    };

    renderCreateInput = () => (
        <div className="palette-create">
            <input
                className="palette-create-input"
                aria-label="label-input"
                id="new-label"
                type="text"
                value={this._userInput}
                onChange={e => this.setUserInput(e.target.value)}
                placeholder={this._showRegenerate ? '(Optional) Enter edits' : 'Enter item to draw'}
                onKeyDown={this.handleKeyPress}
            />
            <Button
                style={{ alignSelf: 'flex-end' }}
                tooltip={this._showRegenerate ? 'Regenerate' : 'Send'}
                icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : this._showRegenerate ? <FontAwesomeIcon icon={'rotate'} /> : <AiOutlineSend />}
                iconPlacement="right"
                color={SettingsManager.userColor}
                onClick={this.generateDrawings}
            />
        </div>
    );
    renderCreateOptions = () => (
        <div className="palette-create-options">
            <div className="palette-color">
                Color
                <Switch
                    sx={{
                        '& .MuiSwitch-switchBase.Mui-checked': {
                            color: SettingsManager.userColor,
                        },
                        '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
                            backgroundColor: SettingsManager.userVariantColor,
                        },
                    }}
                    defaultChecked={true}
                    value={this._opts.autoColor}
                    size="small"
                    onChange={() => this.setColor(!this._opts.autoColor)}
                />
            </div>
            <div className="palette-detail">
                Detail
                <Slider
                    sx={{
                        '& .MuiSlider-thumb': {
                            color: SettingsManager.userColor,
                            '&.Mui-focusVisible, &:hover, &.Mui-active': {
                                boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10`,
                            },
                        },
                        '& .MuiSlider-track': {
                            color: SettingsManager.userVariantColor,
                        },
                        '& .MuiSlider-rail': {
                            color: SettingsManager.userColor,
                        },
                    }}
                    style={{ width: '80%' }}
                    min={1}
                    max={10}
                    step={1}
                    size="small"
                    value={this._opts.complexity}
                    onChange={(e, val) => typeof val === 'number' && this.setDetail(val)}
                    valueLabelDisplay="auto"
                />
            </div>
            <div className="palette-size">
                Size
                <Slider
                    sx={{
                        '& .MuiSlider-thumb': {
                            color: SettingsManager.userColor,
                            '&.Mui-focusVisible, &:hover, &.Mui-active': {
                                boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20`,
                            },
                        },
                        '& .MuiSlider-track': {
                            color: SettingsManager.userVariantColor,
                        },
                        '& .MuiSlider-rail': {
                            color: SettingsManager.userColor,
                        },
                    }}
                    style={{ width: '80%' }}
                    min={50}
                    max={500}
                    step={10}
                    size="small"
                    value={this._opts.size}
                    onChange={(e, val) => typeof val === 'number' && this.setSize(val)}
                    valueLabelDisplay="auto"
                />
            </div>
        </div>
    );
    renderDoc = (doc: Doc, refFunc: (r: DocumentView) => void) => {
        return (
            <DocumentView
                ref={refFunc}
                Document={doc}
                addDocument={undefined}
                addDocTab={DocumentViewInternal.addDocTabFunc}
                pinToPres={DocumentView.PinDoc}
                containerViewPath={returnEmptyDocViewList}
                styleProvider={DefaultStyleProvider}
                removeDocument={returnFalse}
                ScreenToLocalTransform={Transform.Identity}
                PanelWidth={this.return170}
                PanelHeight={this.return170}
                renderDepth={1}
                isContentActive={returnTrue}
                focus={emptyFunction}
                whenChildContentsActiveChanged={emptyFunction}
                childFilters={returnEmptyFilter}
                childFiltersByRanges={returnEmptyFilter}
                searchFilterDocs={returnEmptyDoclist}
            />
        );
    };
    renderPaletteCreate = () => (
        <>
            {this.renderCreateInput()}
            {this.renderCreateOptions()}
            {this.renderDoc(this._props.Doc, (r: DocumentView) => {
                this._docCarouselView = r;
            })}
            <div className="palette-buttons">
                <Button text="Back" tooltip="Back to All Stickers" icon={<FontAwesomeIcon icon="reply" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(true)} />
                <div className="palette-save-reset">
                    <Button tooltip="Save" icon={<FontAwesomeIcon icon="file-arrow-down" />} color={SettingsManager.userColor} onClick={this.saveDrawing} />
                    <Button tooltip="Reset" icon={<FontAwesomeIcon icon="rotate-left" />} color={SettingsManager.userColor} onClick={() => this.resetPalette(false)} />
                </div>
            </div>
        </>
    );
    renderPaletteView = () => (
        <>
            {Doc.MyStickers &&
                this.renderDoc(Doc.MyStickers, (r: DocumentView) => {
                    this._docView = r;
                })}
            <Button text="Add" icon={<FontAwesomeIcon icon="square-plus" />} color={SettingsManager.userColor} onClick={() => this.setPaletteMode(StickerPaletteMode.create)} />
        </>
    );

    render() {
        return (
            <div className="sticker-palette" style={{ zIndex: 1000 }} onClick={e => e.stopPropagation()}>
                {this._paletteMode === StickerPaletteMode.view ? this.renderPaletteView() : null}
                {this._paletteMode === StickerPaletteMode.create ? this.renderPaletteCreate() : null}
            </div>
        );
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.ANNOPALETTE, {
    layout: { view: StickerPalette, dataField: 'data' },
    options: { acl: '' },
});
