import { Button, Colors, Size, Type } from '@dash/components';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Slider, Tooltip } from '@mui/material';
import axios from 'axios';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx';
import { observer } from 'mobx-react';
import { extname } from 'path';
import * as React from 'react';
import { AiOutlineSend } from 'react-icons/ai';
import ReactLoading from 'react-loading';
import { ClientUtils, DashColor, returnEmptyString, returnFalse, returnOne, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils';
import { Doc, DocListCast, Opt } from '../../../fields/Doc';
import { DocData } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { ObjectField } from '../../../fields/ObjectField';
import { Cast, DocCast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
import { TraceMobx } from '../../../fields/util';
import { Upload } from '../../../server/SharedMediaTypes';
import { emptyFunction } from '../../../Utils';
import { Docs } from '../../documents/Documents';
import { DocumentType } from '../../documents/DocumentTypes';
import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
import { Networking } from '../../Network';
import { DragManager } from '../../util/DragManager';
import { SettingsManager } from '../../util/SettingsManager';
import { SnappingManager } from '../../util/SnappingManager';
import { undoable, undoBatch } from '../../util/UndoManager';
import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
import { ContextMenu } from '../ContextMenu';
import { ContextMenuProps } from '../ContextMenuItem';
import { ViewBoxAnnotatableComponent } from '../DocComponent';
import { MarqueeAnnotator } from '../MarqueeAnnotator';
import { OverlayView } from '../OverlayView';
import { AnchorMenu } from '../pdf/AnchorMenu';
import { PinDocView, PinProps } from '../PinFuncs';
import { DrawingFillHandler } from '../smartdraw/DrawingFillHandler';
import { FireflyImageData, isFireflyImageData } from '../smartdraw/FireflyConstants';
import { SmartDrawHandler } from '../smartdraw/SmartDrawHandler';
import { StickerPalette } from '../smartdraw/StickerPalette';
import { StyleProp } from '../StyleProp';
import { DocumentView } from './DocumentView';
import { FieldView, FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import './ImageBox.scss';
import { OpenWhere } from './OpenWhere';
import { RichTextField } from '../../../fields/RichTextField';
import { List } from '../../../fields/List';

export class ImageEditorData {
    // eslint-disable-next-line no-use-before-define
    private static _instance: ImageEditorData;
    private static get imageData() { return (ImageEditorData._instance ?? new ImageEditorData()).imageData; } // prettier-ignore
    @observable imageData: { rootDoc: Doc | undefined; open: boolean; source: string; addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean> } = observable({ rootDoc: undefined, open: false, source: '', addDoc: undefined });
    private static set = action((open: boolean, rootDoc: Doc | undefined, source: string, addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) => {
        this._instance.imageData = { open, rootDoc, source, addDoc };
    });

    constructor() {
        makeObservable(this);
        ImageEditorData._instance = this;
    }

    public static get Open()                     { return ImageEditorData.imageData.open;  } // prettier-ignore
    public static set Open(open: boolean)        { ImageEditorData.set(open, this.imageData.rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
    public static get Source()                   { return ImageEditorData.imageData.source; } // prettier-ignore
    public static set Source(source: string)     { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, source, this.imageData.addDoc); } // prettier-ignore
    public static get RootDoc()                  { return ImageEditorData.imageData.rootDoc; } // prettier-ignore
    public static set RootDoc(rootDoc: Opt<Doc>) { ImageEditorData.set(this.imageData.open, rootDoc, this.imageData.source, this.imageData.addDoc); } // prettier-ignore
    public static get AddDoc()                   { return ImageEditorData.imageData.addDoc; } // prettier-ignore
    public static set AddDoc(addDoc: Opt<(doc: Doc | Doc[], annotationKey?: string) => boolean>) { ImageEditorData.set(this.imageData.open, this.imageData.rootDoc, this.imageData.source, addDoc); } // prettier-ignore
}

const API_URL = 'https://api.unsplash.com/search/photos';
@observer
export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(ImageBox, fieldKey);
    }
    _ffref = React.createRef<CollectionFreeFormView>();
    private _ignoreScroll = false;
    private _forcedScroll = false;
    private _dropDisposer?: DragManager.DragDropDisposer;
    private _disposers: { [name: string]: IReactionDisposer } = {};
    private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined;
    private _overlayIconRef = React.createRef<HTMLDivElement>();
    private _regenerateIconRef = React.createRef<HTMLDivElement>();
    private _mainCont: HTMLDivElement | null = null;
    private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
    imageRef: HTMLImageElement | null = null; // <video> ref
    marqueeref = React.createRef<MarqueeAnnotator>();
    @observable Loading = false; // bcz: this should be migrated into StylProviderQuiz since it's not fundamental to the imageBox

    @observable private _searchInput = '';
    @observable private _savedAnnotations = new ObservableMap<number, (HTMLDivElement & { marqueeing?: boolean })[]>();
    @observable private _curSuffix = '';
    @observable private _error = '';
    @observable private _isHovering = false; // flag to switch between primary and alternate images on hover

    // variables for AI Image Editor
    @observable private _regenInput = '';
    @observable private _canInteract = true;
    @observable private _regenerateLoading = false;
    @observable private _prevImgs: FireflyImageData[] = StrCast(this.Document.ai_firefly_history) ? JSON.parse(StrCast(this.Document.ai_firefly_history)) : [];

    // Add these observable properties to the ImageBox class
    @observable private _outpaintingInProgress = false;
    @observable private _outpaintingPrompt = '';

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
        this._props.setContentViewBox?.(this);
    }

    protected createDropTarget = (ele: HTMLDivElement) => {
        this._mainCont = ele;
        this._dropDisposer?.();
        ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.Document));
    };
    getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
        const visibleAnchor = this._getAnchor?.(this._savedAnnotations, true); // use marquee anchor, otherwise, save zoom/pan as anchor
        const anchor =
            visibleAnchor ??
            Docs.Create.ConfigDocument({
                title: 'ImgAnchor:' + this.Document.title,
                config_panX: NumCast(this.layoutDoc._freeform_panX),
                config_panY: NumCast(this.layoutDoc._freeform_panY),
                config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
                annotationOn: this.Document,
            });
        if (anchor) {
            if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
            addAsAnnotation && this.addDocument(anchor);
            PinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !visibleAnchor } }, this.Document);
            return anchor;
        }
        return this.Document;
    };

    componentDidMount() {
        super.componentDidMount?.();
        this._disposers.sizer = reaction(
            () => ({
                forceFull: this._props.renderDepth < 1 || this.layoutDoc._showFullRes,
                scrSize: (NumCast(this.layoutDoc._freeform_scale, 1) / (this._props.DocumentView?.().screenToLocalScale() ?? 1)) * this._props.PanelWidth(),
                selected: this._props.isSelected(),
            }),
            ({ forceFull, scrSize, selected }) => {
                this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 100 ? '_s' : scrSize < 400 ? '_m' : scrSize < 800 ? '_l' : '_o';
            },
            { fireImmediately: true, delay: 1000 }
        );
        const { layoutDoc } = this;
        this._disposers.path = reaction(
            () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
            ({ nativeSize, width }) => {
                if ((layoutDoc === this.layoutDoc && !this.layoutDoc._layout_nativeDimEditable) || !this.layoutDoc._height) {
                    this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
                }
            },
            { fireImmediately: true }
        );
        this._disposers.scroll = reaction(
            () => this.layoutDoc.layout_scrollTop,
            sTop => {
                this._forcedScroll = true;
                !this._ignoreScroll && this._mainCont && (this._mainCont.scrollTop = NumCast(sTop));
                this._mainCont?.scrollTo({ top: NumCast(sTop) });
                this._forcedScroll = false;
            },
            { fireImmediately: true }
        );
        this._disposers.outpainting = reaction(
            () => this.Document?._needsOutpainting,
            needsOutpainting => {
                if (needsOutpainting && this.Document?._outpaintingResize) {
                    this.processOutpainting();
                }
            }
        );
    }

    componentWillUnmount() {
        Object.values(this._disposers).forEach(disposer => disposer?.());
    }

    /**
     * Find images from the unsplash api to add to flashcards.
     */
    fetchImages = async () => {
        try {
            const { data } = await axios.get(`${API_URL}?query=${this._searchInput}&page=1&per_page=${1}&client_id=${process.env.VITE_API_KEY}`);
            const imageSnapshot = Docs.Create.ImageDocument(data.results[0].urls.small, {
                _nativeWidth: Doc.NativeWidth(this.layoutDoc),
                _nativeHeight: Doc.NativeHeight(this.layoutDoc),
                x: NumCast(this.layoutDoc.x),
                y: NumCast(this.layoutDoc.y),
                onClick: FollowLinkScript(),
                _width: 150,
                _height: 150,
                title: '--snapshot' + NumCast(this.layoutDoc._layout_currentTimecode) + ' image-',
            });
            this._props.addDocument?.(imageSnapshot);
        } catch (error) {
            console.log(error);
        }
    };

    handleSelection = async (selection: string) => {
        this._searchInput = selection;
    };

    drop = undoable(
        action((e: Event, de: DragManager.DropEvent) => {
            if (de.complete.docDragData) {
                let added: boolean | undefined;
                const hitDropTarget = (ele: HTMLElement, dropTarget: HTMLDivElement | null): boolean => {
                    if (!ele) return false;
                    if (ele === dropTarget) return true;
                    return hitDropTarget(ele.parentElement as HTMLElement, dropTarget);
                };
                if (de.metaKey || hitDropTarget(e.target as HTMLElement, this._overlayIconRef.current)) {
                    added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => {
                        this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate:hover';
                        return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', drop);
                    }, true);
                } else if (hitDropTarget(e.target as HTMLElement, this._regenerateIconRef.current)) {
                    this._regenerateLoading = true;
                    const drag = de.complete.docDragData?.draggedDocuments.lastElement();
                    const dragField = drag[Doc.LayoutFieldKey(drag)];
                    const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title));
                    const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text);
                    DrawingFillHandler.drawingToImage(this.Document, 100, newPrompt(dragField instanceof RichTextField ? dragField.Text : ''), drag)?.then(action(() => (this._regenerateLoading = false)));
                    added = false;
                } else if (de.altKey || !this.dataDoc[this.fieldKey]) {
                    const layoutDoc = de.complete.docDragData?.draggedDocuments[0];
                    const targetField = Doc.LayoutFieldKey(layoutDoc);
                    const targetDoc = layoutDoc[DocData];
                    if (targetDoc[targetField] instanceof ImageField) {
                        added = true;
                        this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField);
                        Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey);
                        Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey);
                    }
                }
                added === false && e.preventDefault();
                added !== undefined && e.stopPropagation();
                return added;
            }
            return false;
        }),
        'image drop'
    );

    @undoBatch
    resolution = () => {
        this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes;
    };

    @undoBatch
    setNativeSize = action(() => {
        const oldnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
        const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1);
        const nw = nscale / oldnativeWidth;
        this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw;
        this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw;
        this.dataDoc._freeform_panX = nw * NumCast(this.dataDoc._freeform_panX);
        this.dataDoc._freeform_panY = nw * NumCast(this.dataDoc._freeform_panY);
        this.dataDoc._freeform_panX_max = this.dataDoc._freeform_panX_max ? nw * NumCast(this.dataDoc._freeform_panX_max) : undefined;
        this.dataDoc._freeform_panX_min = this.dataDoc._freeform_panX_min ? nw * NumCast(this.dataDoc._freeform_panX_min) : undefined;
        this.dataDoc._freeform_panY_max = this.dataDoc._freeform_panY_max ? nw * NumCast(this.dataDoc._freeform_panY_max) : undefined;
        this.dataDoc._freeform_panY_min = this.dataDoc._freeform_panY_min ? nw * NumCast(this.dataDoc._freeform_panY_min) : undefined;
        const newnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
        DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => {
            doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth;
            doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth;
            if (!RTFCast(doc[Doc.LayoutFieldKey(doc)])) {
                doc.width = (NumCast(doc.width) / oldnativeWidth) * newnativeWidth;
                doc.height = (NumCast(doc.height) / oldnativeWidth) * newnativeWidth;
            }
        });
    });
    @undoBatch
    rotate = action(() => {
        const nw = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']);
        const nh = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']);
        const w = this.layoutDoc._width;
        const h = this.layoutDoc._height;
        this.dataDoc[this.fieldKey + '_rotation'] = (NumCast(this.dataDoc[this.fieldKey + '_rotation']) + 90) % 360;
        this.dataDoc[this.fieldKey + '_nativeWidth'] = nh;
        this.dataDoc[this.fieldKey + '_nativeHeight'] = nw;
        this.layoutDoc._width = h;
        this.layoutDoc._height = w;
    });

    crop = (region: Doc | undefined, addCrop?: boolean) => {
        if (!region) return undefined;
        const cropping = Doc.MakeCopy(region, true);
        const regionData = region[DocData];
        regionData.lockedPosition = true;
        regionData.title = 'region:' + this.Document.title;
        regionData.followLinkToggle = true;
        this.addDocument(region);
        const anchx = NumCast(cropping.x);
        const anchy = NumCast(cropping.y);
        const anchw = NumCast(cropping._width);
        const anchh = NumCast(cropping._height);
        const viewScale = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) / anchh;
        cropping.title = 'crop: ' + this.Document.title;
        cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
        cropping.y = NumCast(this.Document.y);
        cropping._width = anchw * (this._props.NativeDimScaling?.() || 1);
        cropping._height = anchh * (this._props.NativeDimScaling?.() || 1);
        cropping.onClick = undefined;
        const croppingProto = cropping[DocData];
        croppingProto.annotationOn = undefined;
        croppingProto.isDataDoc = true;
        croppingProto.backgroundColor = undefined;
        croppingProto.proto = Cast(this.Document.proto, Doc, null)?.proto; // set proto of cropping's data doc to be IMAGE_PROTO
        croppingProto.type = DocumentType.IMG;
        croppingProto.layout = ImageBox.LayoutString('data');
        croppingProto.data = ObjectField.MakeCopy(this.dataDoc[this.fieldKey] as ObjectField);
        croppingProto.data_nativeWidth = anchw;
        croppingProto.data_nativeHeight = anchh;
        croppingProto.freeform_scale = viewScale;
        croppingProto.freeform_panX = anchx / viewScale;
        croppingProto.freeform_panY = anchy / viewScale;
        croppingProto.freeform_scale_min = viewScale;
        croppingProto.freeform_panX_min = anchx / viewScale;
        croppingProto.freeform_panX_max = anchw / viewScale;
        croppingProto.freeform_panY_min = anchy / viewScale;
        croppingProto.freeform_panY_max = anchh / viewScale;
        if (addCrop) {
            DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' });
            cropping.x = NumCast(this.Document.x) + NumCast(this.layoutDoc._width);
            cropping.y = NumCast(this.Document.y);
            this._props.addDocTab(cropping, OpenWhere.inParent);
        }
        DocumentView.addViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200));
        this._props.bringToFront?.(cropping);
        return cropping;
    };

    docEditorView = action(() => {
        const field = Cast(this.dataDoc[this.fieldKey], ImageField);
        if (field) {
            ImageEditorData.Open = true;
            ImageEditorData.Source = this.choosePath(field.url);
            ImageEditorData.AddDoc = this._props.addDocument;
            ImageEditorData.RootDoc = this.Document;
        }
    });

    // Add this method to process outpainting when resize is complete
    @action
    processOutpainting = async () => {
        const field = Cast(this.dataDoc[this.fieldKey], ImageField);
        if (!field) return;

        const origWidth = NumCast(this.Document._outpaintingOriginalWidth);
        const origHeight = NumCast(this.Document._outpaintingOriginalHeight);

        if (!origWidth || !origHeight) {
            console.error('Original dimensions (_outpaintingOriginalWidth/_outpaintingOriginalHeight) not set. Ensure resizeViewForOutpainting was called first.');
            return;
        }

        //alert(`Original dimensions: ${origWidth} x ${origHeight}`);

        // Set flag that outpainting is in progress
        this._outpaintingInProgress = true;

        try {
            // Get the current path to the image
            const currentPath = this.choosePath(field.url);

            // Get original and new dimensions for calculating mask
            const newWidth = NumCast(this.Document._width);
            const newHeight = NumCast(this.Document._height);

            // Optional: Ask user for a prompt to guide the outpainting
            let prompt = 'Extend this image naturally with matching content';
            const customPrompt = await new Promise<string>(resolve => {
                const dialog = document.createElement('div');
                Object.assign(dialog.style, {
                    position: 'fixed',
                    top: '50%',
                    left: '50%',
                    transform: 'translate(-50%, -50%)',
                    background: 'white',
                    padding: '20px',
                    borderRadius: '8px',
                    boxShadow: '0 4px 12px rgba(0,0,0,0.2)',
                    zIndex: '10000',
                });

                const title = document.createElement('h3');
                title.style.marginTop = '0';
                title.textContent = 'Outpaint Image';

                const description = document.createElement('p');
                description.textContent = 'Enter a prompt for extending the image:';

                const input = document.createElement('input');
                input.id = 'outpaint-prompt';
                input.type = 'text';
                input.value = 'Extend this image naturally with matching content';
                Object.assign(input.style, {
                    width: '300px',
                    padding: '8px',
                    marginBottom: '10px',
                });

                const buttonContainer = document.createElement('div');
                Object.assign(buttonContainer.style, {
                    display: 'flex',
                    justifyContent: 'flex-end',
                    gap: '10px',
                });

                const cancelButton = document.createElement('button');
                cancelButton.textContent = 'Cancel';

                const confirmButton = document.createElement('button');
                confirmButton.textContent = 'Generate';
                Object.assign(confirmButton.style, {
                    background: '#0078d4',
                    color: 'white',
                    border: 'none',
                    padding: '8px 16px',
                });

                buttonContainer.appendChild(cancelButton);
                buttonContainer.appendChild(confirmButton);

                dialog.appendChild(title);
                dialog.appendChild(description);
                dialog.appendChild(input);
                dialog.appendChild(buttonContainer);

                document.body.appendChild(dialog);

                cancelButton.onclick = () => {
                    document.body.removeChild(dialog);
                    resolve('');
                };

                confirmButton.onclick = () => {
                    const promptValue = input.value;
                    document.body.removeChild(dialog);
                    resolve(promptValue);
                };
            });

            // If user cancelled, reset dimensions to original
            if (!customPrompt) {
                this.Document._width = origWidth;
                this.Document._height = origHeight;
                this._outpaintingInProgress = false;
                return;
            }

            // Show loading indicator
            const loadingOverlay = document.createElement('div');
            loadingOverlay.style.position = 'absolute';
            loadingOverlay.style.top = '0';
            loadingOverlay.style.left = '0';
            loadingOverlay.style.width = '100%';
            loadingOverlay.style.height = '100%';
            loadingOverlay.style.background = 'rgba(0,0,0,0.5)';
            loadingOverlay.style.display = 'flex';
            loadingOverlay.style.justifyContent = 'center';
            loadingOverlay.style.alignItems = 'center';
            loadingOverlay.innerHTML = '<div style="color: white; font-size: 16px;">Generating outpainted image...</div>';
            this._mainCont?.appendChild(loadingOverlay);

            // Call the outpaint API
            const response = await Networking.PostToServer('/outpaintImage', {
                imageUrl: currentPath,
                prompt: customPrompt,
                originalDimensions: {
                    width: origWidth,
                    height: origHeight,
                },
                newDimensions: {
                    width: newWidth,
                    height: newHeight,
                },
            });
            if (response && typeof response === 'object' && 'url' in response && typeof response.url === 'string') {
                console.log('Response is valid and contains URL:', response.url);
            } else {
                console.error('Unexpected API response:', response);
                alert('Failed to receive a valid image URL from server.');
            }

            if (response && 'url' in response && typeof response.url === 'string') {
                // Save the original image as an alternate
                if (!this.dataDoc[this.fieldKey + '_alternates']) {
                    this.dataDoc[this.fieldKey + '_alternates'] = new List<Doc>();
                }

                // Create a copy of the current image as an alternate
                const originalDoc = Docs.Create.ImageDocument(field.url.href, {
                    title: `Original: ${this.Document.title}`,
                    _nativeWidth: Doc.NativeWidth(this.dataDoc),
                    _nativeHeight: Doc.NativeHeight(this.dataDoc),
                });

                // Add to alternates
                Doc.AddDocToList(this.dataDoc, this.fieldKey + '_alternates', originalDoc);

                // Update the image with the outpainted version
                this.dataDoc[this.fieldKey] = new ImageField(response.url);

                // Update native dimensions
                Doc.SetNativeWidth(this.dataDoc, newWidth);
                Doc.SetNativeHeight(this.dataDoc, newHeight);

                // Add AI metadata
                this.Document.ai = true;
                this.Document.ai_outpainted = true;
                this.Document.ai_outpaint_prompt = customPrompt;
            } else {
                // If failed, revert to original dimensions
                this.Document._width = origWidth;
                this.Document._height = origHeight;
                alert('Failed to outpaint image. Please try again.');
            }

            // Remove loading overlay
            this._mainCont?.removeChild(loadingOverlay);
        } catch (error) {
            console.error('Error during outpainting:', error);
            // Revert to original dimensions on error
            this.Document._width = origWidth;
            this.Document._height = origHeight;
            alert('An error occurred while outpainting. Please try again.');
        } finally {
            // Clear the outpainting flags
            this._outpaintingInProgress = false;
            delete this.Document._originalDims;
        }
    };

    specificContextMenu = (): void => {
        const field = Cast(this.dataDoc[this.fieldKey], ImageField);
        if (field) {
            const funcs: ContextMenuProps[] = [];
            funcs.push({ description: 'Rotate Clockwise 90', event: this.rotate, icon: 'redo-alt' });
            funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
            funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
            funcs.push({
                description: 'GetImageText',
                event: () => {
                    Networking.PostToServer('/queryFireflyImageText', {
                        file: (file => {
                            const ext = extname(file);
                            return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
                        })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
                    }).then(text => alert(text));
                },
                icon: 'expand-arrows-alt',
            });
            funcs.push({
                description: 'Expand Image',
                event: () => {
                    Networking.PostToServer('/expandImage', {
                        prompt: 'sunny skies',
                        file: (file => {
                            const ext = extname(file);
                            return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
                        })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href),
                    }).then(res => {
                        const info = res as Upload.ImageInformation;
                        const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title });
                        DocUtils.assignImageInfo(info, img);
                        this._props.addDocTab(img, OpenWhere.addRight);
                    });
                },
                icon: 'expand-arrows-alt',
            });
            funcs.push({ description: 'Copy path', event: () => ClientUtils.CopyText(this.choosePath(field.url)), icon: 'copy' });
            funcs.push({ description: 'Open Image Editor', event: this.docEditorView, icon: 'pencil-alt' });
            this.layoutDoc.ai &&
                funcs.push({
                    description: 'Regenerate AI Image',
                    event: action(() => {
                        if (!SmartDrawHandler.Instance.ShowRegenerate && this.DocumentView) {
                            const [x, y] = this.DocumentView().screenToViewTransform().inverse().transformPoint(NumCast(this.Document.width), 0);
                            this._props.docViewPath().slice(-2)[0]?.ComponentView?.showSmartDraw?.(x, y, true);
                        } else {
                            SmartDrawHandler.Instance.hideRegenerate();
                        }
                    }),
                    icon: 'pen-to-square',
                });
            funcs.push({
                description: this.Document.savedAsSticker ? 'Sticker Saved!' : 'Save to Stickers',
                event: action(undoable(async () => await StickerPalette.addToPalette(this.Document), 'save to palette')),
                icon: this.Document.savedAsSticker ? 'clipboard-check' : 'file-arrow-down',
            });
            // Add new outpainting option
            funcs.push({
                description: 'Outpaint Image',
                event: () => {
                    this.processOutpainting();
                },
                icon: 'brush',
            });
            // Add outpainting history option if the image was outpainted
            this.Document.ai_outpainted &&
                funcs.push({
                    description: 'View Original Image',
                    event: action(() => {
                        const alternates = DocListCast(this.dataDoc[this.fieldKey + '_alternates']);
                        if (alternates && alternates.length) {
                            // Toggle to show the original image
                            this.layoutDoc[this.fieldKey + '_usePath'] = 'alternate';
                        }
                    }),
                    icon: 'image',
                });
            ContextMenu.Instance?.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' });
        }
    };

    // updateIcon = () => new Promise<void>(res => res());
    updateIcon = (usePanelDimensions?: boolean) => {
        const contentDiv = this._mainCont;
        return !contentDiv
            ? new Promise<void>(res => res())
            : UpdateIcon(
                  this.layoutDoc[Id] + '_icon_' + new Date().getTime(),
                  contentDiv,
                  usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width),
                  usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height),
                  this._props.PanelWidth(),
                  this._props.PanelHeight(),
                  0,
                  1,
                  false,
                  '',
                  (iconFile, nativeWidth, nativeHeight) => {
                      this.dataDoc.icon = new ImageField(iconFile);
                      this.dataDoc.icon_nativeWidth = nativeWidth;
                      this.dataDoc.icon_nativeHeight = nativeHeight;
                  }
              );
    };

    choosePath = (url: URL) => {
        if (!url?.href) return '';
        const lower = url.href.toLowerCase();
        if (url.protocol === 'data') return url.href;
        if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return ClientUtils.CorsProxy(url.href);
        if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower) || lower.endsWith('/assets/unknown-file-icon-hi.png')) return `/assets/unknown-file-icon-hi.png`;

        const ext = extname(url.href);
        return url.href.replace(ext, (this._error ? '_o' : this._curSuffix) + ext);
    };
    getScrollHeight = () => (this._props.fitWidth?.(this.Document) !== false && NumCast(this.layoutDoc._freeform_scale, 1) === NumCast(this.dataDoc.freeform_scaleMin, 1) ? this.nativeSize.nativeHeight : undefined);

    @computed get usingAlternate() {
        const usePath = StrCast(this.Document[this.fieldKey + '_usePath']);
        return 'alternate' === usePath || ('alternate:hover' === usePath && this._isHovering) || (':hover' === usePath && !this._isHovering);
    }

    @computed get nativeSize() {
        TraceMobx();
        if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0 };
        const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
        const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
        const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
        return { nativeWidth, nativeHeight, nativeOrientation };
    }
    private _sideBtnWidth = 35;
    /**
     * How much the content of the view is being scaled based on its nesting and its fit-to-width settings
     */
    @computed get viewScaling() { return this.ScreenToLocalBoxXf().Scale * ( this._props.NativeDimScaling?.() || 1); } // prettier-ignore
    /**
     * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
     */
    @computed get maxWidgetSize() { return Math.min(this._sideBtnWidth, 0.5 * Math.min(NumCast(this.Document.width)))* this.viewScaling; } // prettier-ignore
    /**
     * How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
     */
    @computed get uiBtnScaling() { return Math.min(this.maxWidgetSize / this._sideBtnWidth, 1); } // prettier-ignore

    @computed get overlayImageIcon() {
        const usePath = this.layoutDoc[`_${this.fieldKey}_usePath`];
        return (
            <Tooltip
                title={
                    <div className="dash-tooltip">
                        toggle between
                        <span style={{ color: usePath === undefined ? 'black' : undefined }}>
                            <em> primary, </em>
                        </span>
                        <span style={{ color: usePath === 'alternate' ? 'black' : undefined }}>
                            <em>alternate, </em>
                        </span>
                        <span style={{ color: usePath === 'alternate:hover' ? 'black' : undefined }}>
                            <em> alternate on hover</em>
                        </span>
                        and show
                        <span style={{ color: usePath === ':hover' ? 'black' : undefined }}>
                            <em> primary on hover</em>
                        </span>
                    </div>
                }>
                <div
                    className="imageBox-alternateDropTarget"
                    ref={this._overlayIconRef}
                    onPointerDown={e =>
                        setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, () => {
                            this.layoutDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : usePath === 'alternate:hover' ? ':hover' : undefined;
                        })
                    }
                    style={{
                        display: this._props.isContentActive() && (SnappingManager.CanEmbed || this.dataDoc[this.fieldKey + '_alternates']) ? 'block' : 'none',
                        transform: `scale(${this.uiBtnScaling})`,
                        width: this._sideBtnWidth,
                        height: this._sideBtnWidth,
                        background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray',
                        color: usePath === undefined ? 'black' : 'white',
                    }}>
                    <FontAwesomeIcon icon="circle-half-stroke" size="lg" />
                </div>
            </Tooltip>
        );
    }
    @computed get regenerateImageIcon() {
        return (
            <div
                className="imageBox-regenerateDropTarget"
                ref={this._regenerateIconRef}
                onClick={() => DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs), { openLocation: OpenWhere.addRight })}
                style={{
                    display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_firefly_generatedDocs)) || this._regenerateLoading ? 'block' : 'none',
                    transform: `scale(${this.uiBtnScaling})`,
                    width: this._sideBtnWidth,
                    height: this._sideBtnWidth,
                    background: 'transparent',
                    // color: SettingsManager.userBackgroundColor,
                }}>
                {this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width="100%" height="100%" /> : <FontAwesomeIcon icon="portrait" color={SettingsManager.userColor} size="lg" />}
            </div>
        );
    }

    @computed get paths() {
        const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); // retrieve the primary image URL that is being rendered from the data doc
        const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
        const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
        const altpaths =
            alts
                ?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl) : defaultUrl))
                .filter(url => url)
                .map(url => this.choosePath(url)) ?? []; // acc  ess the primary layout data of the alternate documents
        const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
        return paths.length ? paths.reverse() : [defaultUrl.href];
    }

    @computed get content() {
        TraceMobx();

        const backColor = DashColor((this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string) ?? Colors.WHITE);
        // allow use case where the image is transparent when the alpha value is to smallest possible value from UI (alpha = 1 out of 255)
        const backAlpha = backColor.alpha() < 0.015 && backColor.alpha() > 0 ? backColor.alpha() : 1;
        const srcpath = this.layoutDoc.hideImage ? '' : this.paths[0];
        const fadepath = this.layoutDoc.hideImage ? '' : this.paths.lastElement();
        const { nativeWidth, nativeHeight /* , nativeOrientation */ } = this.nativeSize;
        const rotation = NumCast(this.dataDoc[this.fieldKey + '_rotation']);
        const aspect = rotation % 180 ? nativeHeight / nativeWidth : 1;
        let transformOrigin = 'center center';
        let transform = `translate(0%, 0%) rotate(${rotation}deg) scale(${aspect})`;
        if (rotation === 90 || rotation === -270) {
            transformOrigin = 'top left';
            transform = `translate(100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
        } else if (rotation === 180) {
            transform = `rotate(${rotation}deg) scale(${aspect})`;
        } else if (rotation === 270 || rotation === -90) {
            transformOrigin = 'right top';
            transform = `translate(-100%, 0%) rotate(${rotation}deg) scale(${aspect})`;
        }

        return (
            <div
                className="imageBox-cont"
                onPointerEnter={action(() => {
                    this._isHovering = true;
                })}
                onPointerLeave={action(() => {
                    this._isHovering = false;
                })}
                key={this.layoutDoc[Id]}
                onPointerDown={this.marqueeDown}>
                <div className="imageBox-fader" style={{ opacity: backAlpha }}>
                    <img
                        alt=""
                        ref={action((r: HTMLImageElement | null) => (this.imageRef = r))}
                        key="paths"
                        src={srcpath}
                        style={{ transform, transformOrigin }}
                        onError={action(e => {
                            this._error = e.toString();
                        })}
                        draggable={false}
                        width={nativeWidth}
                    />
                    {fadepath === srcpath ? null : (
                        <div className={`imageBox-fadeBlocker${this.usingAlternate ? '-hover' : ''}`} style={{ transition: StrCast(this.layoutDoc.viewTransition, 'opacity 1000ms') }}>
                            <img alt="" className="imageBox-fadeaway" key="fadeaway" src={fadepath} style={{ transform, transformOrigin }} draggable={false} width={20} />
                        </div>
                    )}
                </div>
            </div>
        );
    }

    protected _btnWidth = 50;
    protected _inputWidth = 50;
    protected _sideBtnMaxPanelPct = 0.12;
    @observable _filterFunc: ((doc: Doc) => boolean) | undefined = undefined;
    @observable private _fireflyRefStrength = 0;

    componentAIViewHistory = () => (
        <div className="imageBox-aiView-history">
            <Button text="Clear History" type={Type.SEC} size={Size.XSMALL} />
            {this._prevImgs.map(img => (
                <div key={img.pathname}>
                    <img
                        className="imageBox-aiView-img"
                        src={ClientUtils.prepend(img.pathname.replace(extname(img.pathname), '_s' + extname(img.pathname)))}
                        onClick={() => {
                            this.dataDoc[this.fieldKey] = new ImageField(img.pathname);
                            this.dataDoc.ai_firefly_prompt = img.prompt;
                            this.dataDoc.ai_firefly_seed = img.seed;
                        }}
                    />
                    <span>{img.prompt}</span>
                </div>
            ))}
        </div>
    );

    componentAIView = () => {
        const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey]));
        return (
            <div className="imageBox-aiView">
                <div className="imageBox-aiView-regenerate">
                    <span className="imageBox-aiView-firefly" style={{ color: SnappingManager.userColor }}>
                        Firefly:
                    </span>
                    <input
                        style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
                        className="imageBox-aiView-input"
                        aria-label="Edit instructions input"
                        type="text"
                        value={this._regenInput || StrCast(this.Document.title)}
                        onChange={action(e => this._canInteract && (this._regenInput = e.target.value))}
                        placeholder={this._regenInput || StrCast(this.Document.title)}
                    />
                    <div className="imageBox-aiView-regenerate-createBtn">
                        <Button
                            text="Create"
                            type={Type.TERT}
                            color={SnappingManager.userColor}
                            background={SnappingManager.userBackgroundColor}
                            // style={{ alignSelf: 'flex-end' }}
                            icon={this._regenerateLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
                            iconPlacement="right"
                            onClick={action(async () => {
                                this._regenerateLoading = true;
                                if (this._fireflyRefStrength) {
                                    DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._regenInput || StrCast(this.Document.title), this.Document)?.then(action(() => (this._regenerateLoading = false)));
                                } else {
                                    SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput || StrCast(this.Document.title), true).then(
                                        action(newImgs => {
                                            const firstImg = newImgs[0];
                                            if (isFireflyImageData(firstImg)) {
                                                const url = firstImg.pathname;
                                                const imgField = new ImageField(url);
                                                this._prevImgs.length === 0 &&
                                                    this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field.url.pathname });
                                                this._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url });
                                                this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs);
                                                this.dataDoc.ai_firefly_prompt = firstImg.prompt;
                                                this.dataDoc[this.fieldKey] = imgField;
                                                this._regenerateLoading = false;
                                                this._regenInput = '';
                                            }
                                        })
                                    );
                                }
                            })}
                        />
                    </div>
                </div>
                <div className="imageBox-aiView-strength">
                    <span className="imageBox-aiView-similarity" style={{ color: SnappingManager.userColor }}>
                        Similarity
                    </span>
                    <Slider
                        className="imageBox-aiView-slider"
                        sx={{
                            '& .MuiSlider-track': { color: SettingsManager.userColor },
                            '& .MuiSlider-rail': { color: SettingsManager.userBackgroundColor },
                            '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10` } },
                        }}
                        min={0}
                        max={100}
                        step={1}
                        size="small"
                        value={this._fireflyRefStrength}
                        onChange={action((e, val) => this._canInteract && (this._fireflyRefStrength = val as number))}
                        valueLabelDisplay="auto"
                    />
                </div>
            </div>
        );
    };

    @computed get annotationLayer() {
        TraceMobx();
        return <div className="imageBox-annotationLayer" style={{ height: this._props.PanelHeight() }} ref={this._annotationLayer} />;
    }
    screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop) * this.ScreenToLocalBoxXf().Scale);
    marqueeDown = (e: React.PointerEvent) => {
        if (!this.dataDoc[this.fieldKey]) {
            this.chooseImage();
        } else if (!e.altKey && e.button === 0 && NumCast(this.layoutDoc._freeform_scale, 1) <= NumCast(this.dataDoc.freeform_scaleMin, 1) && this._props.isContentActive() && Doc.ActiveTool !== InkTool.Ink) {
            setupMoveUpEvents(
                this,
                e,
                action(moveEv => {
                    MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
                    this.marqueeref.current?.onInitiateSelection([moveEv.clientX, moveEv.clientY]);
                    return true;
                }),
                returnFalse,
                () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations),
                false
            );
        }
    };
    @action
    finishMarquee = () => {
        this._getAnchor = AnchorMenu.Instance?.GetAnchor;
        this._props.styleProvider?.(this.Document, this._props, StyleProp.AnchorMenuItems);
        AnchorMenu.Instance.addToCollection = this._props.DocumentView?.()._props.addDocument;
        AnchorMenu.Instance.marqueeWidth = this.marqueeref.current?.Width ?? 0;
        AnchorMenu.Instance.marqueeHeight = this.marqueeref.current?.Height ?? 0;
        this.marqueeref.current?.onTerminateSelection();
        this._props.select(false);
    };
    focus = (anchor: Doc, options: FocusViewOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options));

    renderedPixelDimensions = async () => {
        const res = await Networking.PostToServer('/inspectImage', { source: this.paths[0] });
        const { nativeWidth: width, nativeHeight: height } = res as { nativeWidth: number; nativeHeight: number };
        return { width, height };
    };
    savedAnnotations = () => this._savedAnnotations;
    render() {
        TraceMobx();
        const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding) as string;
        const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
        const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']);
        const doc = this.usingAlternate ? (alts.lastElement() ?? this.Document) : this.Document;
        return (
            <>
                <div
                    className="imageBox"
                    onContextMenu={this.specificContextMenu}
                    ref={this.createDropTarget}
                    onScroll={action(() => {
                        if (!this._forcedScroll) {
                            if (this.layoutDoc._layout_scrollTop || this._mainCont?.scrollTop) {
                                this._ignoreScroll = true;
                                this.layoutDoc._layout_scrollTop = this._mainCont?.scrollTop;
                                this._ignoreScroll = false;
                            }
                        }
                    })}
                    style={{
                        width: this._props.PanelWidth() ? undefined : `100%`,
                        height: this._props.PanelHeight() ? undefined : `100%`,
                        pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                        borderRadius,
                        overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : 'hidden',
                    }}>
                    <CollectionFreeFormView
                        ref={this._ffref}
                        {...this._props}
                        Document={doc}
                        setContentViewBox={emptyFunction}
                        NativeWidth={returnZero}
                        NativeHeight={returnZero}
                        renderDepth={this._props.renderDepth + 1}
                        fieldKey={this.annotationKey}
                        styleProvider={this._props.styleProvider}
                        isAnnotationOverlay
                        annotationLayerHostsContent
                        PanelWidth={this._props.PanelWidth}
                        PanelHeight={this._props.PanelHeight}
                        ScreenToLocalTransform={this.screenToLocalTransform}
                        select={emptyFunction}
                        focus={this.focus}
                        getScrollHeight={this.getScrollHeight}
                        NativeDimScaling={returnOne}
                        isAnyChildContentActive={returnFalse}
                        whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}
                        removeDocument={this.removeDocument}
                        moveDocument={this.moveDocument}
                        addDocument={this.addDocument}>
                        {this.content}
                    </CollectionFreeFormView>
                    {this.Loading ? (
                        <div className="loading-spinner" style={{ position: 'absolute' }}>
                            <ReactLoading type="spin" height={50} width={50} color={'blue'} />
                        </div>
                    ) : null}
                    {this.regenerateImageIcon}
                    {this.overlayImageIcon}
                    {this.annotationLayer}
                    {!this._mainCont || !this.DocumentView || !this._annotationLayer.current ? null : (
                        <MarqueeAnnotator
                            Document={this.Document}
                            ref={this.marqueeref}
                            scrollTop={0}
                            annotationLayerScrollTop={0}
                            scaling={returnOne}
                            annotationLayerScaling={this._props.NativeDimScaling}
                            screenTransform={this.DocumentView().screenToViewTransform}
                            docView={this.DocumentView}
                            addDocument={this.addDocument}
                            finishMarquee={this.finishMarquee}
                            savedAnnotations={this.savedAnnotations}
                            selectionText={returnEmptyString}
                            annotationLayer={this._annotationLayer.current}
                            marqueeContainer={this._mainCont}
                            highlightDragSrcColor=""
                            anchorMenuCrop={this.crop}
                            // anchorMenuFlashcard={() => this.getImageDesc()}
                        />
                    )}
                </div>
                {this._outpaintingInProgress && (
                    <div className="imageBox-outpaintingSpinner">
                        <ReactLoading type="spin" color="#666" height={60} width={60} />
                    </div>
                )}
            </>
        );
    }

    public chooseImage = () => {
        const input = document.createElement('input');
        input.type = 'file';
        input.multiple = true;
        input.accept = 'image/*';
        input.onchange = async () => {
            const file = input.files?.[0];
            if (file) {
                const disposer = OverlayView.ShowSpinner();
                const [{ result }] = await Networking.UploadFilesToServer({ file });
                if (result instanceof Error) {
                    alert('Error uploading files - possibly due to unsupported file types');
                } else {
                    this.dataDoc[this.fieldKey] = new ImageField(result.accessPaths.agnostic.client);
                    !(result instanceof Error) && DocUtils.assignImageInfo(result, this.dataDoc);
                }
                disposer();
            } else {
                console.log('No file selected');
            }
        };
        input.click();
    };
}

Docs.Prototypes.TemplateMap.set(DocumentType.IMG, {
    layout: { view: ImageBox, dataField: 'data' },
    options: { acl: '', freeform: '', _layout_nativeDimEditable: true, systemIcon: 'BsFileEarmarkImageFill' },
});
