import { action, makeObservable, observable, reaction, computed } from 'mobx';
import * as React from 'react';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { List } from '../../../../fields/List';
import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { DocumentType } from '../../../documents/DocumentTypes';
import { CollectionView } from '../../collections/CollectionView';
import { ViewBoxAnnotatableComponent } from '../../DocComponent';
import { AspectRatioLimits } from '../../smartdraw/FireflyConstants';
import { DocumentView } from '../DocumentView';
import { FieldView, FieldViewProps } from '../FieldView';
import { DragManager } from '../../../util/DragManager';
import { RTFCast, StrCast, toList } from '../../../../fields/Types';
import { undoable } from '../../../util/UndoManager';
import ReactLoading from 'react-loading';
import { NumCast } from '../../../../fields/Types';
import { ScrapbookItemConfig, ScrapbookPreset } from './ScrapbookPreset';
import { ImageBox } from '../ImageBox';
import { FireflyImageDimensions } from '../../smartdraw/FireflyConstants';
import { SmartDrawHandler } from '../../smartdraw/SmartDrawHandler';
import { ImageCast } from '../../../../fields/Types';
import { SnappingManager } from '../../../util/SnappingManager';
import { IReactionDisposer } from 'mobx';
import { observer } from 'mobx-react';
import { runInAction } from 'mobx';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faRedoAlt }   from '@fortawesome/free-solid-svg-icons';
import { getPresetNames, createPreset } from './ScrapbookPresetRegistry';
import './ScrapbookBox.scss';


export function buildPlaceholdersFromConfigs(configs: ScrapbookItemConfig[]): Doc[] {
  const placeholders: Doc[] = [];

  for (const cfg of configs) {
    if (cfg.children && cfg.children.length) {
      const childDocs = cfg.children.map(child => {
        const doc = Docs.Create.TextDocument("[placeholder] " + child.tag);
        doc.accepts_docType = child.type;
        doc.accepts_tagType = new List<string>(child.acceptTags ?? [child.tag]);

        const ph = new Doc();
        ph.proto = doc;
        ph.original = doc;
        ph.x = child.x;
        ph.y = child.y;
        if (child.width != null) ph._width = child.width;
        if (child.height != null) ph._height = child.height;
        return ph;
      });

      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.tag,
      });

      const ph = new Doc();
      ph.proto = containerProto;
      ph.original = containerProto;
      ph.x = cfg.x;
      ph.y = cfg.y;
      if (cfg.width != null) ph._width = cfg.width;
      if (cfg.height != null) ph._height = cfg.height;
      placeholders.push(ph);
    } 
    
    else {
      const doc = Docs.Create.TextDocument("[placeholder] " + cfg.tag);
      doc.accepts_docType = cfg.type;
      doc.accepts_tagType = new List<string>(cfg.acceptTags ?? [cfg.tag]);

      const ph = new Doc();
      ph.proto = doc;
      ph.original = doc;
      ph.x = cfg.x;
      ph.y = cfg.y;
      if (cfg.width != null) ph._width = cfg.width;
      if (cfg.height != null) ph._height = cfg.height;
      placeholders.push(ph);
    }
  }

  return placeholders;
}
export function slotRealDocIntoPlaceholders(
  realDoc: Doc,
  placeholders: Doc[]
): boolean {
 const realTags = new Set<string>(
  StrListCast(realDoc.$tags_chat ?? new List<string>())
    .map(t => t.toLowerCase())
);

  // Find placeholder with most matching tags
  let bestMatch: Doc | null = null;
  let maxMatches = 0;
/*
                 (d.accepts_docType === docs[0].$type ||   // match fields based on type, or by analyzing content .. simple example of matching text in placeholder to dropped doc's type
                  RTFCast(d[Doc.LayoutDataKey(d)])?.Text.includes(StrCast(docs[0].$type)))*/

  placeholders.forEach(ph => {
    // 1) Enforce that placeholder.accepts_docType === realDoc.$type
    if (ph.accepts_docType !== realDoc.$type) {
      // Skip this placeholder entirely if types do not match.
      return;
    };
    const phTagTypes = StrListCast(ph.accepts_tagType ?? new List<string>())
  .map(t => t.toLowerCase());
    console.log({ realTags, phTagTypes });
    const matches = phTagTypes.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'),
      0
    );
    return true;
  }

  return false;
}

// Scrapbook view: a container that lays out its child items in a template
@observer
export class ScrapbookBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
    @observable selectedPreset = getPresetNames()[0];

    @observable createdDate: string;
    @observable loading = false;
    @observable src = '';
    @observable imgDoc: Doc | undefined;
    private _disposers: { [name: string]: IReactionDisposer } = {};
    private imageBoxRef = React.createRef<ImageBox>();

    // @observable configs : ScrapbookItemConfig[]

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
        const existingItems = DocListCast(this.dataDoc[this.fieldKey] as List<Doc>);
    if (!existingItems || existingItems.length === 0) {
      // Only wire up reaction/setTitle if it's truly a brand-new, empty Scrapbook
      reaction(
        () => this.selectedPreset,
        presetName => this.initScrapbook(presetName),
        { fireImmediately: true }
      );

      this.createdDate = this.getFormattedDate();
      this.setTitle();
    } else {
      // If items are already present, just preserve whatever was injected.
      // We still want `createdDate` set so that the UI title bar can show it if needed.
      this.createdDate = this.getFormattedDate();
    }
        //this.configs = 
        //ScrapbookPreset.createPreset(presetType);

        // ensure we always have a List<Doc> in dataDoc['items']
        if (!this.dataDoc[this.fieldKey]) {
            this.dataDoc[this.fieldKey] = new List<Doc>();
        }
        //this.initScrapbook(ScrapbookPresetType.Default);
        //this.setLayout(ScrapbookPreset.Spotlight);
    }

    public static LayoutString(fieldStr: string) {
        return FieldView.LayoutString(ScrapbookBox, fieldStr);
    }

    getFormattedDate(): string {
        return new Date().toLocaleDateString(undefined, {
            year: 'numeric',
            month: 'short',
            day: 'numeric',
        });
    }

    
    @action
    initScrapbook(name: string) {
        const configs = createPreset(name); 
      // 1) ensure title is set
      const title = `Scrapbook - ${this.createdDate}`;
      if (this.dataDoc.title !== title) {
        this.dataDoc.title = title;
      }
    
      // 2) build placeholders from the preset
      const placeholders = buildPlaceholdersFromConfigs(configs);

      // 3) commit them into the field
      this.dataDoc[this.fieldKey] = new List<Doc>(placeholders);
    }

    

    @action
    setTitle() {
        const title = `Scrapbook - ${this.createdDate}`;
        if (this.dataDoc.title !== title) {
            this.dataDoc.title = title;
            if (!this.dataDoc[this.fieldKey]){
            const image = Docs.Create.TextDocument('[placeholder] person image');
            image.accepts_docType = DocumentType.IMG;
            image.accepts_tagType = 'PERSON' //should i be writing fields on this doc? clarify diff between this and proto, original
            const placeholder = new Doc();
            placeholder.proto = image;
            placeholder.original = image;
            placeholder._width = 250;
            placeholder._height = 200;
            placeholder.x = 0;
            placeholder.y = -100;
            //placeholder.overrideFields = new List<string>(['x', 'y']);  // shouldn't need to do this for layout fields since the placeholder already overrides its protos
            
            const summary = Docs.Create.TextDocument('[placeholder] long summary');
            summary.accepts_docType = DocumentType.RTF;
            summary.accepts_tagType = 'lengthy description';
            //summary.$tags_chat = new List<string>(['lengthy description']); //we need to go back and set this
            const placeholder2 = new Doc();
            placeholder2.proto = summary;
            placeholder2.original = summary;
            placeholder2.x = 0;
            placeholder2.y = 200;
            placeholder2._width = 250;
            //placeholder2.overrideFields = new List<string>(['x', 'y', '_width']); // shouldn't need to do this for layout fields since the placeholder already overrides its protos
            
            const sidebar = Docs.Create.TextDocument('[placeholder] brief sidebar');
            sidebar.accepts_docType = DocumentType.RTF;
            sidebar.accepts_tagType = 'title'; //accepts_textType = 'lengthy description'
            const placeholder3 = new Doc();
            placeholder3.proto = sidebar;
            placeholder3.original = sidebar;
            placeholder3.x = 280;
            placeholder3.y = -50;
            placeholder3._width = 50; 
            placeholder3._height = 200;

          

            const internalImg = Docs.Create.TextDocument('[placeholder] landscape internal');
            internalImg.accepts_docType = DocumentType.IMG;
            internalImg.accepts_tagType = 'LANDSCAPE' //should i be writing fields on this doc? clarify diff between this and proto, original
            const placeholder5 = new Doc();
            placeholder5.proto = internalImg;
            placeholder5.original = internalImg;
            placeholder5._width = 50;
            placeholder5._height = 100;
            placeholder5.x = 0;
            placeholder5.y = -100;

            const collection = Docs.Create.StackingDocument([placeholder5], { _width: 300, _height: 300, title: "internal coll" });
            //collection.accepts_docType = DocumentType.COL; don't mark this field
            const placeholder4 = new Doc();
            placeholder4.proto = collection;
            placeholder4.original = collection;
            placeholder4.x = -200;
            placeholder4.y = -100;
            placeholder4._width = 100;
            placeholder4._height = 200;
            /*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?
           */
           this.dataDoc[this.fieldKey] = new List<Doc>([placeholder, placeholder2, placeholder3, placeholder4]);
            }
          //this.dataDoc[this.fieldKey] = this.dataDoc[this.fieldKey] ?? new List<Doc>([placeholder, placeholder2, placeholder3, placeholder4]);

          
        }
    }

    componentDidMount() {
        //this.initScrapbook(ScrapbookPresetType.Default);
        this.setTitle();
        this.generateAiImageCorrect();

        this._disposers.propagateResize = reaction(
            () => ({ w: this.layoutDoc._width, h: this.layoutDoc._height }),
            (dims, prev) => {
              // prev is undefined on the first run, so bail early
              if (!prev || !SnappingManager.ShiftKey || !this.imgDoc) return;
          
              // either guard the ref…
              const imageBox = this.imageBoxRef.current;
              if (!imageBox) return;
          
              // …or just hard-code the fieldKey if you know it’s always `"data"`
              const key = imageBox.props.fieldKey;
          
              runInAction(() => {
                if(!this.imgDoc){
                    return
                }
                // use prev.w/h (the *old* size) as your orig dims
                this.imgDoc[key + '_outpaintOriginalWidth']  = prev.w;
                this.imgDoc[key + '_outpaintOriginalHeight'] = prev.h;
                ;(this.imageBoxRef.current as any).layoutDoc._width  = dims.w
                ;(this.imageBoxRef.current as any).layoutDoc._height = dims.h
          
              });
            }
        );
    }


    @action
    async generateAiImageCorrect(prompt?: string) {
          this.loading = true;
          try {
            // 1) Default to regenPrompt if none provided
            if (!prompt) prompt = this.regenPrompt;
        
            // 2) Measure the scrapbook’s current size
            const w = NumCast(this.layoutDoc._width, 1);
            const h = NumCast(this.layoutDoc._height, 1);
            const ratio = w / h;
        
            // 3) Pick the Firefly preset that best matches the aspect ratio
            let preset = FireflyImageDimensions.Square;
            if (ratio > AspectRatioLimits[FireflyImageDimensions.Widescreen]) {
              preset = FireflyImageDimensions.Widescreen;
            } else if (ratio > AspectRatioLimits[FireflyImageDimensions.Landscape]) {
              preset = FireflyImageDimensions.Landscape;
            } else if (ratio < AspectRatioLimits[FireflyImageDimensions.Portrait]) {
              preset = FireflyImageDimensions.Portrait;
            }
        
            // 4) Call exactly the same CreateWithFirefly that ImageBox uses
            const doc = await SmartDrawHandler.CreateWithFirefly(prompt, preset);
        
            if (doc instanceof Doc) {
              // 5) Hook it into your state
              this.imgDoc = doc;
              const imgField = ImageCast(doc.data);
              this.src      = imgField?.url.href ?? '';
            } else {
              alert('Failed to generate document.');
              this.src = '';
            }
          } catch (e) {
            alert(`Generation error: ${e}`);
          } finally {
            runInAction(() => {
              this.loading = false;
            });
          }
        }
      
    childRejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
        return true; // disable dropping documents onto any child of the scrapbook.
    };
    rejectDrop = (de: DragManager.DropEvent, subView?: DocumentView) => {
        // Test to see if the dropped doc is dropped on an acceptable location (anywerhe? on a specific box).
        // const draggedDocs = de.complete.docDragData?.draggedDocuments;
        return false; // allow all Docs to be dropped onto scrapbook -- let filterAddDocument make the final decision.
    };

    filterAddDocument = (docIn: Doc | Doc[]) => {
        const docs = toList(docIn); //The docs being added to the scrapbook

        // 1) Grab all template slots:
        const slots = DocListCast(this.dataDoc[this.fieldKey]);

        // 2) recursive unwrap:
        const unwrap = (items: Doc[]): Doc[] =>
            items.flatMap(d =>
            d.$type === DocumentType.COL
                ? unwrap(DocListCast(d[Doc.LayoutDataKey(d)]))
                : [d]
            );

        // 3) produce a flat list of every doc, unwrapping any number of nested COLs
        const allDocs: Doc[] = unwrap(slots);
          if (docs?.length === 1) {
        return slotRealDocIntoPlaceholders(
            docs[0],
            allDocs,
        )
            ? false
            : false;
    }

    return false;
};
     

    @computed get regenPrompt() {
        const slots = DocListCast(this.dataDoc[this.fieldKey]);
        
        const unwrap = (items: Doc[]): Doc[] =>
            items.flatMap(d =>
                d.$type === DocumentType.COL
                    ? unwrap(DocListCast(d[Doc.LayoutDataKey(d)]))
                    : [d]
            );
    
        const allDocs: Doc[] = unwrap(slots);
        const internalTagsSet = new Set<string>();
        
        allDocs.forEach(doc => {
            const tags = StrListCast(doc.$tags_chat ?? new List<string>());
            tags.forEach(tag => 
                 {if (!tag.startsWith("ASPECT_")) {
            internalTagsSet.add(tag);
            }
    });
        });
    
        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">
        {this.loading && (
          <div className="scrapbook-box-loading-overlay">
            <ReactLoading type="spin" width={50} height={50} />
          </div>
        )}

        {this.src && this.imgDoc && (
          <ImageBox
            ref={this.imageBoxRef}
            {...this._props}
            Document={this.imgDoc}
            fieldKey="data"
          />
        )}

        <select
          className="scrapbook-box-preset-select"
          value={this.selectedPreset}
          onChange={e => (this.selectedPreset = e.currentTarget.value)}
        >
          {getPresetNames().map(name => (
            <option key={name} value={name}>
              {name}
            </option>
          ))}
        </select>

        {this._props.isContentActive() && (
          <div className="scrapbook-box-ui">
            <button
              type="button"
              title="Regenerate Background"
              onClick={() => this.generateAiImageCorrect(this.regenPrompt)}
              className="scrapbook-box-ui-button"
            >
              <FontAwesomeIcon icon={faRedoAlt} />
              <span>Regenerate Background</span>
            </button>
          </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',
    },
});
