import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { action, computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ColorState, SketchPicker } from 'react-color';
import { Doc, StrListCast } from '../../../../fields/Doc';
import { Height, Width } from '../../../../fields/DocSymbols';
import { InkTool } from '../../../../fields/InkField';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { WebField } from '../../../../fields/URLField';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
import { aggregateBounds, Utils } from '../../../../Utils';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { LinkManager } from '../../../util/LinkManager';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
import { SelectionManager } from '../../../util/SelectionManager';
import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager';
import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
import { ContextMenu } from '../../ContextMenu';
import { DocComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { GestureOverlay } from '../../GestureOverlay';
import { Colors } from '../../global/globalEnums';
import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke';
import { InkTranscription } from '../../InkTranscription';
import { StyleProp } from '../../StyleProvider';
import { FieldView, FieldViewProps } from '.././FieldView';
import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView';
import { OpenWhere } from '../DocumentView';
import { RichTextMenu } from '../formattedText/RichTextMenu';
import { WebBox } from '../WebBox';
import { FontIconBadge } from './FontIconBadge';
import './FontIconBox.scss';

export enum ButtonType {
    TextButton = 'textBtn',
    MenuButton = 'menuBtn',
    DropdownList = 'drpdownList',
    DropdownButton = 'drpdownBtn',
    ClickButton = 'clickBtn',
    DoubleButton = 'dblBtn',
    ToggleButton = 'tglBtn',
    ColorButton = 'colorBtn',
    ToolButton = 'toolBtn',
    NumberSliderButton = 'numSliderBtn',
    NumberDropdownButton = 'numDropdownBtn',
    NumberInlineButton = 'numInlineBtn',
    EditableText = 'editableText',
}

export interface ButtonProps extends FieldViewProps {
    type?: ButtonType;
}
@observer
export class FontIconBox extends DocComponent<ButtonProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(FontIconBox, fieldKey);
    }
    @observable noTooltip = false;
    showTemplate = (): void => {
        const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null);
        dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight);
    };
    dragAsTemplate = (): void => {
        this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
    };
    useAsPrototype = (): void => {
        this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)');
    };

    specificContextMenu = (): void => {
        if (!Doc.noviceMode) {
            const cm = ContextMenu.Instance;
            cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' });
            cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' });
            cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' });
        }
    };

    static GetShowLabels() {
        return BoolCast(Doc.UserDoc()._showLabel);
    }
    static SetShowLabels(show: boolean) {
        Doc.UserDoc()._showLabel = show;
    }
    static GetRecognizeGestures() {
        return BoolCast(Doc.UserDoc()._recognizeGestures);
    }
    static SetRecognizeGestures(show: boolean) {
        Doc.UserDoc()._recognizeGestures = show;
    }

    // Determining UI Specs
    @computed get label() {
        return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title));
    }
    Icon = (color: string) => {
        const icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any;
        const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />;
        return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
    };
    @computed get dropdown() {
        return BoolCast(this.rootDoc.dropDownOpen);
    }
    @computed get buttonList() {
        return StrListCast(this.rootDoc.btnList);
    }
    @computed get type() {
        return StrCast(this.rootDoc.btnType);
    }

    /**
     * Types of buttons in dash:
     * - Main menu button (LHS)
     * - Tool button
     * - Expandable button (CollectionLinearView)
     * - Button inside of CollectionLinearView vs. outside of CollectionLinearView
     * - Action button
     * - Dropdown button
     * - Color button
     * - Dropdown list
     * - Number button
     **/

    _batch: UndoManager.Batch | undefined = undefined;
    /**
     * Number button
     */
    @computed get numberSliderButton() {
        const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined });
        // Script for checking the outcome of the toggle
        const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));
        const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>;

        const dropdown = (
            <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()}>
                <input
                    className="menu-slider"
                    type="range"
                    step="1"
                    min={NumCast(this.rootDoc.numBtnMin, 0)}
                    max={NumCast(this.rootDoc.numBtnMax, 100)}
                    //readOnly={true}
                    value={checkResult}
                    onPointerDown={() => (this._batch = UndoManager.StartBatch('num slider changing'))}
                    onPointerUp={() => this._batch?.end()}
                    onChange={undoable(e => {
                        e.stopPropagation();
                        numScript(Number(e.target.value));
                    }, 'set num value')}
                />
            </div>
        );
        return (
            <div
                className="menuButton numBtn slider"
                onPointerDown={e => e.stopPropagation()}
                onClick={action(() => {
                    this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
                    this.noTooltip = this.rootDoc.dropDownOpen;
                    Doc.UnBrushAllDocs();
                })}>
                {checkResult}
                {label}
                {this.rootDoc.dropDownOpen ? dropdown : null}
            </div>
        );
    }
    /**
     * Number button
     */
    @computed get numberDropdownButton() {
        const numScript = (value?: number) => ScriptCast(this.rootDoc.script)?.script.run({ self: this.rootDoc, value, _readOnly_: value === undefined });

        const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3));

        const items: number[] = [];
        for (let i = 0; i < 100; i += 2) items.push(i);

        const list = items.map(value => {
            return (
                <div
                    className="list-item"
                    key={`${value}`}
                    style={{
                        backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined,
                    }}
                    onClick={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}>
                    {value}
                </div>
            );
        });
        return (
            <div className="menuButton numBtn list">
                <div className="button" onClick={undoable(e => numScript(Number(checkResult) - 1), `${this.rootDoc.title} decrement value`)}>
                    <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon="minus" />
                </div>
                <div
                    className={`button ${'number'}`}
                    onPointerDown={e => {
                        e.stopPropagation();
                        e.preventDefault();
                    }}
                    onClick={action(() => {
                        this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
                        this.noTooltip = this.rootDoc.dropDownOpen;
                        Doc.UnBrushAllDocs();
                    })}>
                    <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} readOnly={true} onChange={undoable(e => numScript(Number(e.target.value)), `${this.rootDoc.title} button set value`)} />
                </div>
                <div className={`button`} onClick={undoable(e => numScript(Number(checkResult) + 1), `${this.rootDoc.title} increment value`)}>
                    <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} />
                </div>

                {this.rootDoc.dropDownOpen ? (
                    <div>
                        <div className="menuButton-dropdownList" style={{ left: '25%' }}>
                            {list}
                        </div>
                        <div
                            className="dropbox-background"
                            onClick={action(e => {
                                e.stopPropagation();
                                this.rootDoc.dropDownOpen = false;
                                this.noTooltip = false;
                                Doc.UnBrushAllDocs();
                            })}
                        />
                    </div>
                ) : null}
            </div>
        );
    }
    /**
     * Number button
     */
    @computed get numberInlineButton() {
        return <div />;
    }

    /**
     * Dropdown button
     */
    @computed get dropdownButton() {
        const active: string = StrCast(this.rootDoc.dropDownOpen);
        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
        return (
            <div
                className={`menuButton ${this.type} ${active}`}
                style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
                onClick={action(() => {
                    this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
                    this.noTooltip = this.rootDoc.dropDownOpen;
                    Doc.UnBrushAllDocs();
                })}>
                {this.Icon(color)}
                {!this.label || !FontIconBox.GetShowLabels() ? null : (
                    <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
                        {' '}
                        {this.label}{' '}
                    </div>
                )}
                <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}>
                    <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" />
                </div>
                {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null}
            </div>
        );
    }

    /**
     * Dropdown list
     */
    @computed get dropdownListButton() {
        const active: string = StrCast(this.rootDoc.dropDownOpen);
        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);

        const script = ScriptCast(this.rootDoc.script);

        let noviceList: string[] = [];
        let text: string | undefined;
        let dropdown = true;
        let icon: IconProp = 'caret-down';
        try {
            if (script?.script.originalScript.startsWith('setView')) {
                const selected = SelectionManager.Docs().lastElement();
                if (selected) {
                    if (StrCast(selected.type) === DocumentType.COL) {
                        text = StrCast(selected._type_collection);
                    } else {
                        dropdown = false;
                        text = selected.type === DocumentType.RTF ? 'Text' : StrCast(selected.type);
                        icon = Doc.toIcon(selected);
                    }
                } else {
                    dropdown = false;
                    icon = 'globe-asia';
                    text = 'User Default';
                }
                noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
            } else text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
        } catch (e) {
            console.log(e);
        }

        // Get items to place into the list
        const list = this.buttonList
            .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value))
            .map(value => (
                <div
                    className="list-item"
                    key={value}
                    style={{
                        fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined,
                        backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined,
                    }}
                    onClick={undoable(() => script.script.run({ self: this.rootDoc, value }), value)}>
                    {value[0].toUpperCase() + value.slice(1)}
                </div>
            ));

        const label =
            !this.label || !FontIconBox.GetShowLabels() ? null : (
                <div className="fontIconBox-label" style={{ bottom: 0, position: 'absolute', color: color, backgroundColor: backgroundColor }}>
                    {this.label}
                </div>
            );

        return (
            <div
                className={`menuButton ${this.type} ${active}`}
                style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : 'flex' }}
                onClick={
                    dropdown
                        ? action(() => {
                              this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen;
                              this.noTooltip = this.rootDoc.dropDownOpen;
                              Doc.UnBrushAllDocs();
                          })
                        : undefined
                }>
                {dropdown ? null : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />}
                <div className="menuButton-dropdown-header">{text && text[0].toUpperCase() + text.slice(1)}</div>
                {label}
                {!dropdown ? null : (
                    <div className="menuButton-dropDown">
                        <FontAwesomeIcon icon={icon} color={color} size="sm" />
                    </div>
                )}
                {this.rootDoc.dropDownOpen ? (
                    <div>
                        <div className="menuButton-dropdownList" style={{ left: 0 }}>
                            {list}
                        </div>
                        <div
                            className="dropbox-background"
                            onClick={action(e => {
                                e.stopPropagation();
                                this.rootDoc.dropDownOpen = false;
                                this.noTooltip = false;
                                Doc.UnBrushAllDocs();
                            })}
                        />
                    </div>
                ) : null}
            </div>
        );
    }

    @observable colorPickerClosed: boolean = true;
    @computed get colorScript() {
        return ScriptCast(this.rootDoc.script);
    }

    colorPicker = (curColor: string) => {
        const change = (value: ColorState, ev: MouseEvent) => {
            ev.preventDefault();
            ev.stopPropagation();
            const s = this.colorScript;
            s && undoBatch(() => s.script.run({ self: this.rootDoc, value: Utils.colorString(value), _readOnly_: false }).result)();
        };
        const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent'];
        return <SketchPicker onChange={change as any /* SketchPicker passes the mouse event to the callback, but the type system doesn't know that */} color={curColor} presetColors={presets} />;
    };
    /**
     * Color button
     */
    @computed get colorButton() {
        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
        const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent';

        const label =
            !this.label || !FontIconBox.GetShowLabels() ? null : (
                <div className="fontIconBox-label" style={{ color, backgroundColor }}>
                    {this.label}
                </div>
            );

        return (
            <div
                className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')} ${this.colorPickerClosed}`}
                style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }}
                onClick={action(e => {
                    this.colorPickerClosed = !this.colorPickerClosed;
                    this.noTooltip = !this.colorPickerClosed;
                    setTimeout(() => Doc.UnBrushAllDocs());
                    e.stopPropagation();
                })}
                onPointerDown={e => e.stopPropagation()}>
                {this.Icon(color)}
                <div className="colorButton-color" style={{ backgroundColor: curColor }} />
                {label}
                {/* {dropdownCaret} */}
                {this.colorPickerClosed ? null : (
                    <div>
                        <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={e => e.stopPropagation()}>
                            {this.colorPicker(curColor)}
                        </div>
                        <div
                            className="dropbox-background"
                            onPointerDown={action(e => {
                                e.preventDefault();
                                e.stopPropagation();
                                this.colorPickerClosed = true;
                                this.noTooltip = false;
                                Doc.UnBrushAllDocs();
                            })}
                        />
                    </div>
                )}
            </div>
        );
    }

    @computed get toggleButton() {
        // Determine the type of toggle button
        const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle);
        const buttonText: string = StrCast(this.rootDoc.buttonText);
        // Colors
        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);

        // Button label
        const label =
            !this.label || !FontIconBox.GetShowLabels() ? null : (
                <div className="fontIconBox-label" style={{ color, backgroundColor }}>
                    {this.label}
                </div>
            );

        if (switchToggle) {
            return (
                <div className={`menuButton ${this.type} ${'switch'}`}>
                    {buttonText ? buttonText : null}
                    <label className="switch">
                        <input type="checkbox" checked={backgroundColor === Colors.MEDIUM_BLUE} />
                        <span className="slider round" />
                    </label>
                </div>
            );
        } else {
            return (
                <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}>
                    {this.Icon(color)}
                    {label}
                </div>
            );
        }
    }

    /**
     * Default
     */
    @computed get defaultButton() {
        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
        return (
            <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
                <div className="menuButton-wrap">
                    {this.Icon(color)}
                    {!this.label || !FontIconBox.GetShowLabels() ? null : (
                        <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}>
                            {' '}
                            {this.label}{' '}
                        </div>
                    )}
                </div>
            </div>
        );
    }

    @computed get editableText() {
        // Script for running the toggle
        const script = ScriptCast(this.rootDoc.script);
        // Function to run the script
        const checkResult = script?.script.run({ value: '', _readOnly_: true }).result;

        const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result;
        return (
            <div className="menuButton editableText">
                <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} />
                <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}>
                    <EditableView GetValue={() => script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} />
                </div>
            </div>
        );
    }

    render() {
        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
        const label = (noBackground: boolean = false) =>
            !this.label || !FontIconBox.GetShowLabels() ? null : (
                <div className="fontIconBox-label" style={{ color, backgroundColor: noBackground ? 'transparent' : backgroundColor }}>
                    {this.label}
                </div>
            );
        // TODO:glr Add label of button type
        let button: JSX.Element = this.defaultButton;

        // prettier-ignore
        switch (this.type) {
            case ButtonType.EditableText: return this.editableText;
            case ButtonType.DropdownList: button = this.dropdownListButton; break;
            case ButtonType.ColorButton: button = this.colorButton; break;
            case ButtonType.NumberDropdownButton: button = this.numberDropdownButton; break;
            case ButtonType.NumberInlineButton: button = this.numberInlineButton; break;
            case ButtonType.NumberSliderButton: button = this.numberSliderButton; break;
            case ButtonType.DropdownButton: button = this.dropdownButton; break;
            case ButtonType.ToggleButton: button = this.toggleButton; break;
            case ButtonType.TextButton: 
                    // Script for checking the outcome of the toggle
                    const script = ScriptCast(this.rootDoc.script);
                    const checkResult = script?.script.run({ _readOnly_: true }).result;
                    button = (
                    <div className={`menuButton ${this.type}`} style={{ color, backgroundColor:checkResult ?? backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}>
                        {this.Icon(color)}
                        {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null}
                        {label()}
                    </div>
                );
                break;
            case ButtonType.ClickButton:
            case ButtonType.ToolButton: button = (
                    <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}>
                        {this.Icon(color)}
                        {label()}
                    </div>
                );
                break;
            case ButtonType.MenuButton: button = (
                    <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}>
                        {this.Icon(color)}
                        {label(true)}
                        <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} />
                    </div>
                );
                break;
        }

        return !this.layoutDoc.toolTip || this.noTooltip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>;
    }
}

// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setView(view: string) {
    const selected = SelectionManager.Docs().lastElement();
    selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed');
});

// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) {
    const selectedViews = SelectionManager.Views();
    if (Doc.ActiveTool !== InkTool.None) {
        if (checkResult) {
            return ActiveFillColor();
        }
        SetActiveFillColor(color ?? 'transparent');
    } else if (selectedViews.length) {
        if (checkResult) {
            const selView = selectedViews.lastElement();
            const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
            const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
            const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
            return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent';
        }
        selectedViews.forEach(dv => {
            const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor';
            const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values
            const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
            if (contentFrameNumber !== undefined) {
                CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color });
            } else {
                dv.dataDoc[fieldKey] = color;
            }
        });
    } else {
        const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : [];
        if (checkResult) {
            return selected.lastElement()?._backgroundColor ?? 'transparent';
        }
        selected.forEach(doc => (doc._backgroundColor = color));
    }
});

// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) {
    if (checkResult) {
        return Doc.SharingDoc().userColor;
    }
    Doc.SharingDoc().userColor = undefined;
    Doc.GetProto(Doc.SharingDoc()).userColor = color;
    Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'author_date');
});

// toggle: Set overlay status of selected document
ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
    const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined;
    if (checkResult) {
        if (NumCast(selected?.Document.z) >= 1) return Colors.MEDIUM_BLUE;
        return 'transparent';
    }
    selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
});

ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
    const selected = SelectionManager.Docs().lastElement();
    // prettier-ignore
    const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
        ['grid', {
            checkResult: (doc:Doc) => doc._freeform_backgroundGrid,
            setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
        }],
        ['snaplines', {
            checkResult: (doc:Doc) => doc._freeform_snapLines,
            setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines,
        }],
        ['viewAll', {
            checkResult: (doc:Doc) => doc._freeform_fitContentsToBox,
            setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
        }],
        ['clusters', {
            waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
            checkResult: (doc:Doc) => doc._freeform_useClusters,
            setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters,
        }],
        ['arrange', {
            waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
            checkResult: (doc:Doc) => doc._autoArrange,
            setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange,
        }],
        ['flashcards', {
            checkResult: (doc:Doc) => Doc.UserDoc().defaultToFlashcards,
            setDoc: (doc:Doc) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards,
        }],
   ]);

    if (checkResult) {
        return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent';
    }
    const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
    SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv));
    setTimeout(() => batch.end(), 100);
});
ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: any, checkResult?: boolean) {
    const editorView = RichTextMenu.Instance?.TextView?.EditorView;
    const selected = SelectionManager.Docs().lastElement();
    // prettier-ignore
    const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([
        ['font', {
            checkResult: () => RichTextMenu.Instance?.fontFamily,
            setDoc: () => value && RichTextMenu.Instance.setFontFamily(value),
        }],
        ['highlight', {
            checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight,
            setDoc: () => value  && RichTextMenu.Instance.setHighlight(value),
        }],
        ['fontColor', {
            checkResult: () => RichTextMenu.Instance?.fontColor,
            setDoc: () => value && RichTextMenu.Instance.setColor(value),
        }],
        ['fontSize', {
            checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''),
            setDoc: () => {
                if (typeof value === 'number') value = value.toString();
                if (value && Number(value).toString() === value) value += 'px';
                RichTextMenu.Instance.setFontSize(value);
            },
        }],
    ]);

    if (checkResult) {
        return map.get(attr)?.checkResult();
    }
    map.get(attr)?.setDoc?.();
});

type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal';
type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }];
ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) {
    const textView = RichTextMenu.Instance?.TextView;
    const editorView = textView?.EditorView;
    // prettier-ignore
    const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) =>
        [ where, {      checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false),
                        toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]);
    // prettier-ignore
    const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list =>
        [ list, {       checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false),
                        toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
    // prettier-ignore
    const attrs:attrfuncs[] = [
        ['dictation', { checkResult: () => textView?._recording ? true:false,
                        toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }],
        ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
                        toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
        ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
                        toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}],
        ['italics',   { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false),
                        toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}],
        ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false),
                        toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]]

    const map = new Map(attrs.concat(alignments).concat(listings));
    if (checkResult) return map.get(charStyle)?.checkResult() ? Colors.MEDIUM_BLUE : 'transparent';
    map.get(charStyle)?.toggle();
});

export function checkInksToGroup() {
    // console.log("getting here to inks group");
    if (Doc.ActiveTool === InkTool.Write) {
        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
            // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
            // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other
            const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => {
                // console.log(inkDoc.x, inkDoc.y);
            });
        });
    }
}

export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) {
    // TODO nda - if document being added to is a inkGrouping then we can just add to that group
    if (Doc.ActiveTool === InkTool.Write) {
        CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => {
            // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those
            const selected = ffView.unprocessedDocs;
            // loop through selected an get the bound
            const bounds: { x: number; y: number; width?: number; height?: number }[] = [];

            selected.map(
                action(d => {
                    const x = NumCast(d.x);
                    const y = NumCast(d.y);
                    const width = d[Width]();
                    const height = d[Height]();
                    bounds.push({ x, y, width, height });
                })
            );

            const aggregBounds = aggregateBounds(bounds, 0, 0);
            const marqViewRef = ffView._marqueeViewRef.current;

            // set the vals for bounds in marqueeView
            if (marqViewRef) {
                marqViewRef._downX = aggregBounds.x;
                marqViewRef._downY = aggregBounds.y;
                marqViewRef._lastX = aggregBounds.r;
                marqViewRef._lastY = aggregBounds.b;
            }

            selected.map(
                action(d => {
                    const dx = NumCast(d.x);
                    const dy = NumCast(d.y);
                    delete d.x;
                    delete d.y;
                    delete d.activeFrame;
                    delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
                    delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
                    // calculate pos based on bounds
                    if (marqViewRef?.Bounds) {
                        d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2;
                        d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2;
                    }
                    return d;
                })
            );
            ffView.props.removeDocument?.(selected);
            // TODO: nda - this is the code to actually get a new grouped collection
            const newCollection = marqViewRef?.getCollection(selected, undefined, true);
            if (newCollection) {
                newCollection.height = newCollection[Height]();
                newCollection.width = newCollection[Width]();
            }

            // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs
            newCollection && ffView.props.addDocument?.(newCollection);
            // TODO: nda - will probably need to go through and only remove the unprocessed selected docs
            ffView.unprocessedDocs = [];

            InkTranscription.Instance.transcribeInk(newCollection, selected, false);
        });
    }
    CollectionFreeFormView.collectionsWithUnprocessedInk.clear();
}

function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) {
    InkTranscription.Instance?.createInkGroup();
    if (checkResult) {
        return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool
            ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures)
                ? Colors.MEDIUM_BLUE
                : Colors.MEDIUM_BLUE_ALT
            : 'transparent';
    }
    runInAction(() => {
        if (GestureOverlay.Instance) {
            GestureOverlay.Instance.KeepPrimitiveMode = keepPrim;
        }
        if (Object.values(GestureUtils.Gestures).includes(tool as any)) {
            if (GestureOverlay.Instance.InkShape === tool && !keepPrim) {
                Doc.ActiveTool = InkTool.None;
                GestureOverlay.Instance.InkShape = undefined;
            } else {
                Doc.ActiveTool = InkTool.Pen;
                GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures;
            }
        } else if (tool) {
            // pen or eraser
            if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) {
                Doc.ActiveTool = InkTool.None;
            } else {
                Doc.ActiveTool = tool as any;
                GestureOverlay.Instance.InkShape = undefined;
            }
        } else {
            Doc.ActiveTool = InkTool.None;
        }
    });
}

ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode');

// toggle: Set overlay status of selected document
ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) {
    const selected = SelectionManager.Docs().lastElement();
    // prettier-ignore
    const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([
        ['inkMask', {
            checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask()) ? Colors.MEDIUM_BLUE : 'transparent'),
            setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask),
            setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()),
        }],
        ['fillColor', {
            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"),
            setInk: (doc: Doc) => (doc.fillColor = StrCast(value)),
            setMode: () => SetActiveFillColor(StrCast(value)),
        }],
        [ 'strokeWidth', {
            checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()),
            setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)),
            setMode: () => SetActiveInkWidth(value.toString()),
        }],
        ['strokeColor', {
            checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()),
            setInk: (doc: Doc) => (doc.color = String(value)),
            setMode: () => SetActiveInkColor(StrCast(value)),
        }],
    ]);

    if (checkResult) {
        return map.get(option)?.checkResult();
    }
    map.get(option)?.setMode();
    SelectionManager.Docs()
        .filter(doc => doc.type === DocumentType.INK)
        .map(doc => map.get(option)?.setInk(doc));
});

/** WEB
 *      webSetURL
 **/
ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) {
    const selected = SelectionManager.Views().lastElement();
    if (selected?.rootDoc.type === DocumentType.WEB) {
        if (checkResult) {
            return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href);
        }
        selected.ComponentView?.setData?.(url);
        //selected.rootDoc.data = new WebField(url);
    }
});
ScriptingGlobals.add(function webForward(checkResult?: boolean) {
    const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
    if (checkResult) {
        return selected?.forward(checkResult) ? undefined : 'lightGray';
    }
    selected?.forward();
});
ScriptingGlobals.add(function webBack(checkResult?: boolean) {
    const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox;
    if (checkResult) {
        return selected?.back(checkResult) ? undefined : 'lightGray';
    }
    selected?.back();
});

/** Schema
 *      toggleSchemaPreview
 **/
ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) {
    const selected = SelectionManager.Docs().lastElement();
    if (checkResult && selected) {
        const result: boolean = NumCast(selected.schema_previewWidth) > 0;
        if (result) return Colors.MEDIUM_BLUE;
        else return 'transparent';
    } else if (selected) {
        if (NumCast(selected.schema_previewWidth) > 0) {
            selected.schema_previewWidth = 0;
        } else {
            selected.schema_previewWidth = 200;
        }
    }
});
ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) {
    const selected = SelectionManager.Docs().lastElement();
    if (checkResult && selected) {
        return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent';
    }
    if (selected) {
        selected._schema_singleLine = !selected._schema_singleLine;
    }
});

/** STACK
 *      groupBy
 */
ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) {
    SelectionManager.Docs().map(doc => (doc._text_fontFamily = key));
    const editorView = RichTextMenu.Instance.TextView?.EditorView;
    if (checkResult) {
        return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
    }
    if (editorView) RichTextMenu.Instance.setFontFamily(key);
    else Doc.UserDoc().fontFamily = key;
});
