import { IconButton, Size } from '@dash/components';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import ReactLoading from 'react-loading';
import { Doc, DocListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { DateCast, DocCast, NumCast, toList } from '../../../../fields/Types';
import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { DragManager } from '../../../util/DragManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { undoable } from '../../../util/UndoManager';
import { CollectionView } from '../../collections/CollectionView';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { AspectRatioLimits, FireflyImageDimensions } from '../../smartdraw/FireflyConstants';
import { SmartDrawHandler } from '../../smartdraw/SmartDrawHandler';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { ImageBox } from '../ImageBox';
import './ScrapbookBox.scss';
import { ScrapbookItemConfig } from './ScrapbookPreset';
import { createPreset, getPresetNames } from './ScrapbookPresetRegistry';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { DocUtils } from '../../../documents/DocUtils';
import { returnTrue } from '../../../../ClientUtils';

function createPlaceholder(cfg: ScrapbookItemConfig, doc: Doc) {
    const placeholder = new Doc();
    placeholder.proto = doc;
    placeholder.original = doc;
    placeholder.x = cfg.x;
    placeholder.y = cfg.y;
    if (cfg.width !== null) placeholder._width = cfg.width;
    if (cfg.height !== null) placeholder._height = cfg.height;
    return placeholder;
}

function createMessagePlaceholder(cfg: ScrapbookItemConfig) {
    return createPlaceholder(cfg, 
        Docs.Create.TextDocument(cfg.message ?? ('[placeholder] ' + cfg.acceptTags?.[0]), { placeholder: "", placeholder_docType: cfg.type, placeholder_acceptTags: new List<string>(cfg.acceptTags) })
    ); // prettier-ignore
}
export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]) {
    return configs.map(cfg => {
        if (cfg.children?.length) {
            const childDocs = cfg.children.map(createMessagePlaceholder);
            const protoW = cfg.containerWidth ?? cfg.width;
            const protoH = cfg.containerHeight ?? cfg.height;
            // Create a stacking document with the child placeholders
            const containerProto = Docs.Create.StackingDocument(childDocs, {
                ...(protoW !== null ? { _width: protoW } : {}),
                ...(protoH !== null ? { _height: protoH } : {}),
                title: cfg.message,
            });
            return createPlaceholder(cfg, containerProto);
        }
        return createMessagePlaceholder(cfg);
    });
}
export async function slotRealDocIntoPlaceholders(realDoc: Doc, placeholders: Doc[]) {
    if (!realDoc.$tags_chart) {
        await DocumentView.getFirstDocumentView(realDoc)?.ComponentView?.autoTag?.();
    }
    const realTags = new Set<string>(StrListCast(realDoc.$tags_chat).map(t => t.toLowerCase?.() ?? ''));

    // Find placeholder with most matching tags
    let bestMatch: Doc | null = null;
    let maxMatches = 0;

    // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type
    placeholders
        .filter(ph => ph.placeholder_docType === realDoc.$type) // Skip this placeholder entirely if types do not match.
        .forEach(ph => {
            const matches = StrListCast(ph.placeholder_acceptTags)
                .map(t => t.toLowerCase?.())
                .filter(tag => realTags.has(tag));

            if (matches.length > maxMatches) {
                maxMatches = matches.length;
                bestMatch = ph;
            }
        });

    if (bestMatch && maxMatches > 0) {
        setTimeout(undoable(() => (bestMatch!.proto = realDoc), 'Scrapbook add'));
        return true;
    }

    return false;
}

// Scrapbook view: a container that lays out its child items in a template
@observer
export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    public static LayoutString(fieldStr: string) {
        return FieldView.LayoutString(ScrapbookBox, fieldStr);
    }
    private _disposers: { [name: string]: IReactionDisposer } = {};
    private _imageBoxRef = React.createRef<ImageBox>();

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

    @observable _selectedPreset = getPresetNames()[0];
    @observable _loading = false;

    @computed get createdDate() {
        return DateCast(this.dataDoc.$author_date)?.date.toLocaleDateString(undefined, {
            year: 'numeric',
            month: 'short',
            day: 'numeric',
        });
    }
    @computed get ScrapbookLayoutDocs()  { return DocListCast(this.dataDoc[this.fieldKey]); } // prettier-ignore
    @computed get BackgroundDoc()        { return DocCast(this.dataDoc[this.fieldKey + '_background']); } // prettier-ignore
    set ScrapbookLayoutDocs(doc: Doc[])  { this.dataDoc[this.fieldKey] = new List(doc); } // prettier-ignore
    set BackgroundDoc(doc: Opt<Doc>)     { this.dataDoc[this.fieldKey + '_background'] = doc; } // prettier-ignore

    @action
    setDefaultPlaceholder = () => {
        this.ScrapbookLayoutDocs = [
            createMessagePlaceholder({
                message: 'To create a scrapbook from existing documents, marquee select. For existing scrapbook arrangements, select a preset from the dropdown.',
                type: DocumentType.RTF,
                width: 250,
                height: 200,
                x: 0,
                y: 0,
            }),
        ];

        const placeholder1 = createMessagePlaceholder({ acceptTags: ['PERSON'], type: DocumentType.IMG, width: 250, height: 200, x: 0, y: -100 });
        const placeholder2 = createMessagePlaceholder({ acceptTags: ['lengthy description'], type: DocumentType.RTF, width: 250, height: undefined, x: 0, y: 200 });
        const placeholder3 = createMessagePlaceholder({ acceptTags: ['title'], type: DocumentType.RTF, width: 50, height: 200, x: 280, y: -50 });
        const placeholder4 = createPlaceholder( { width: 100, height: 200, x: -200, y: -100 }, Docs.Create.StackingDocument([
                             createMessagePlaceholder({ acceptTags: ['LANDSCAPE'], type: DocumentType.IMG, width: 50, height: 100, x: 0, y: -100 })
                             ], { _width: 300, _height: 300, title: 'internal coll' })); // prettier-ignore
        console.log('UNUSED', placeholder4, placeholder3, placeholder2, placeholder1);
        /* note-to-self
                    would doing:
                        const collection = Docs.Create.ScrapbookDocument([placeholder, placeholder2, placeholder3]);
                        create issues with references to the same object? */

        /*note-to-self
                    Should we consider that there are more collections than just COL type collections?
                    when spreading */

        /*note-to-self
                    difference between passing a new List<Doc> versus just the raw array? */
    };

    selectPreset = action((presetName: string) => (this.ScrapbookLayoutDocs = buildPlaceholdersFromConfigs(createPreset(presetName))));

    componentDidMount() {
        const title = `Scrapbook - ${this.createdDate}`;
        if (!this.ScrapbookLayoutDocs.length) this.setDefaultPlaceholder();
        if (!this.BackgroundDoc) this.generateAiImage(this.regenPrompt);
        if (this.dataDoc.title !== title) this.dataDoc.title = title; // ensure title is set

        this._disposers.propagateResize = reaction(
            () => ({ w: this.layoutDoc._width, h: this.layoutDoc._height }),
            (dims, prev) => {
                const imageBox = this._imageBoxRef.current;
                // prev is undefined on the first run
                if (prev && SnappingManager.ShiftKey && this.BackgroundDoc && imageBox) {
                    this.BackgroundDoc[imageBox.fieldKey + '_outpaintOriginalWidth'] = prev.w;
                    this.BackgroundDoc[imageBox.fieldKey + '_outpaintOriginalHeight'] = prev.h;
                    imageBox.layoutDoc._width = dims.w;
                    imageBox.layoutDoc._height = dims.h;
                }
            }
        );
    }

    isOutpaintable = returnTrue;
    showBorderRounding = returnTrue;

    @action
    generateAiImage = (prompt: string) => {
        this._loading = true;

        const ratio = NumCast(this.layoutDoc._width, 1) / NumCast(this.layoutDoc._height, 1); // Measure the scrapbook’s current aspect
        const choosePresetForDimensions = (() => { // Pick the Firefly preset that best matches the aspect ratio
            if (ratio > AspectRatioLimits[FireflyImageDimensions.Widescreen]) return FireflyImageDimensions.Widescreen;
            if (ratio > AspectRatioLimits[FireflyImageDimensions.Landscape]) return FireflyImageDimensions.Landscape;
            if (ratio < AspectRatioLimits[FireflyImageDimensions.Portrait]) return FireflyImageDimensions.Portrait;
            return FireflyImageDimensions.Square;
        })(); // prettier-ignore

        SmartDrawHandler.CreateWithFirefly(prompt, choosePresetForDimensions)  // Call exactly the same CreateWithFirefly that ImageBox uses
            .then(action(doc => {
                if (doc instanceof Doc) {
                    this.BackgroundDoc = doc; // set the background image directly on the scrapbook 
                } else {
                    alert('Failed to generate document.');
                }
            }))
            .catch(e => alert(`Generation error: ${e}`))
            .finally(action(() => (this._loading = false))); // prettier-ignore
    };

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => true; // disable dropping documents onto any child of the scrapbook.

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision.

    /**
     * Filter function to determine if a document can be added to the scrapbook.
     * This checks if the document matches any of the placeholder slots in the scrapbook.
     * @param docs - The document(s) being added to the scrapbook.
     * @returns true if the document can be added, false otherwise.
     */
    filterAddDocument = (docs: Doc | Doc[]) => {
        toList(docs).forEach(doc => slotRealDocIntoPlaceholders(doc, DocUtils.unwrapPlaceholders(this.ScrapbookLayoutDocs)));
        return false;
    };

    @computed get regenPrompt() {
        const allDocs = DocUtils.unwrapPlaceholders(this.ScrapbookLayoutDocs); // find all non-collections in scrapbook (e.g., placeholder content docs)
        const internalTagsSet = new Set<string>(allDocs.flatMap(doc => StrListCast(doc.$tags_chat).filter(tag => !tag.startsWith?.('ASPECT_'))));
        const internalTags = Array.from(internalTagsSet).join(', ');

        return internalTags ? `Create a new scrapbook background featuring: ${internalTags}` : 'A serene mountain landscape at sunrise, ultra-wide, pastel sky, abstract, scrapbook background';
    }

    render() {
        return (
            <div className="scrapbook-box">
                <div style={{ display: this._loading ? undefined : 'none' }} className="scrapbook-box-loading-overlay">
                    <ReactLoading type="spin" width={50} height={50} />
                </div>

                {this.BackgroundDoc && <ImageBox ref={this._imageBoxRef} {...this._props} Document={this.BackgroundDoc} fieldKey="data" />}
                <div style={{ display: this._props.isContentActive() ? 'flex' : 'none', alignItems: 'center', justifyContent: 'space-between', padding: '0 10px' }}>
                    <select className="scrapbook-box-preset-select" value={this._selectedPreset} onChange={action(e => this.selectPreset((this._selectedPreset = e.currentTarget.value)))}>
                        {getPresetNames().map(name => (
                            <option key={name} value={name}>
                                {name}
                            </option>
                        ))}
                    </select>
                    <div className="scrapbook-box-ui" style={{ opacity: this._loading ? 0.5 : 1 }}>
                        <IconButton size={Size.SMALL} tooltip="regenerate a new background" label="back-ground" icon={<FontAwesomeIcon icon="redo-alt" size="sm" />} onClick={() => !this._loading && this.generateAiImage(this.regenPrompt)} />
                    </div>
                </div>

                <CollectionView {...this._props} setContentViewBox={emptyFunction} rejectDrop={this.rejectDrop} childRejectDrop={this.childRejectDrop} filterAddDocument={this.filterAddDocument} />
            </div>
        );
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.SCRAPBOOK, {
    layout: { view: ScrapbookBox, dataField: 'items' },
    options: {
        acl: '',
        _height: 200,
        _xMargin: 10,
        _yMargin: 10,
        _layout_fitWidth: false,
        _layout_autoHeight: true,
        _layout_reflowVertical: true,
        _layout_reflowHorizontal: true,
        _freeform_fitContentsToBox: true,
        defaultDoubleClick: 'ignore',
        systemIcon: 'BsImages',
    },
});
