import { Button, ColorPicker, Dropdown, DropdownType, IconButton, IListItemProps, MultiToggle, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from '@dash/components';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ClientUtils, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils';
import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
import { InkTool } from '../../../../fields/InkField';
import { ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { emptyFunction } from '../../../../Utils';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { SnappingManager } from '../../../util/SnappingManager';
import { undoable, UndoManager } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { ViewBoxBaseComponent } from '../../DocComponent';
import { EditableView } from '../../EditableView';
import { SelectedDocView } from '../../selectedDoc';
import { StyleProp } from '../../StyleProp';
import { FieldView, FieldViewProps } from '../FieldView';
import { OpenWhere } from '../OpenWhere';
import './FontIconBox.scss';
import TrailsIcon from './TrailsIcon';

export enum ButtonType {
    TextButton = 'textBtn',
    MenuButton = 'menuBtn',
    DropdownList = 'dropdownList',
    ClickButton = 'clickBtn',
    ToggleButton = 'toggleBtn',
    ColorButton = 'colorBtn',
    ToolButton = 'toolBtn',
    MultiToggleButton = 'multiToggleBtn',
    NumberSliderButton = 'numSliderBtn',
    NumberDropdownButton = 'numDropdownBtn',
    NumberInlineButton = 'numInlineBtn',
    EditText = 'editableText',
}

export interface ButtonProps extends FieldViewProps {
    type?: ButtonType;
}
@observer
export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(FontIconBox, fieldKey);
    }

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

    @observable noTooltip = false;

    showTemplate = (dragFactory: Doc) => this._props.addDocTab(dragFactory, OpenWhere.addRight);
    specificContextMenu = (): void => {
        const dragFactory = DocCast(this.layoutDoc.dragFactory);
        if (!Doc.noviceMode && dragFactory) {
            ContextMenu.Instance.addItem({ description: 'Show Template', event: () => this.showTemplate(dragFactory), icon: 'tag' });
        }
    };

    /**
     * this chooses the appropriate title for the label
     * if the Document is a template, then we use the title of the data doc that it renders
     * otherwise, we use the Document's title itself.
     */
    @computed get label() {
        return StrCast(this.Document.isTemplateDoc ? this.dataDoc.title : this.Document.title);
    }
    Icon = (color: string, iconFalse?: boolean) => {
        let icon;
        if (iconFalse) {
            icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as IconProp;
            if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
            return null;
        }
        icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as IconProp;
        return !icon ? null : icon === ('pres-trail' as IconProp) ? TrailsIcon(color) : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />;
    };
    @computed get dropdown() {
        return BoolCast(this.Document.dropDownOpen);
    }
    @computed get buttonList() {
        return StrListCast(this.Document.btnList);
    }
    @computed get type() {
        return StrCast(this.Document.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 numberDropdown() {
        let type: NumberDropdownType;
        switch (this.type) {
            case ButtonType.NumberDropdownButton:  type = 'dropdown'; break;
            case ButtonType.NumberInlineButton:    type = 'input';  break;
            case ButtonType.NumberSliderButton:
            default:                               type = 'slider';
                break;
        } // prettier-ignore
        const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined });
        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
        // Script for checking the outcome of the toggle
        const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)));

        return (
            <NumberDropdown
                color={color}
                background={SnappingManager.userBackgroundColor}
                numberDropdownType={type}
                showPlusMinus={false}
                formLabel={(StrCast(this.Document.title).startsWith(' ') ? '\u00A0' : '') + StrCast(this.Document.title)}
                tooltip={StrCast(this.Document.toolTip, this.label)}
                type={Type.PRIM}
                min={NumCast(this.dataDoc.numBtnMin, 0)}
                max={NumCast(this.dataDoc.numBtnMax, 100)}
                number={checkResult}
                size={Size.XXSMALL}
                setNumber={undoable(value => numScript(value), `${this.Document.title} button set from list`)}
                fillWidth
            />
        );
    }

    dropdownItemDown = (e: React.PointerEvent, value: string | number) => {
        setupMoveUpEvents(
            this,
            e,
            () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, value: { doc: value, e } }).result as boolean,
            emptyFunction,
            emptyFunction
        ); // prettier-ignore
        return false;
    };

    /**
     * Displays custom dropdown menu for fonts -- this is a HACK -- fix for generality, don't copy
     */
    handleFontDropdown = (script: () => string, buttonList: string[]) => {
        // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
        return {
            buttonList,
            jsx: undefined,
            selectedVal: script(),
            toolTip: 'Set text font',
            getStyle: (val: string) => ({ fontFamily: val }),
        };
    };
    /**
     * Displays custom dropdown menu for view selection -- this is a HACK -- fix for generality, don't copy
     */
    handleViewDropdown = (script: ScriptField, buttonList: string[]) => {
        const selected = Array.from(script?.script.run({ _readOnly_: true }).result as Doc[]);
        const noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Card, CollectionViewType.Carousel3D, CollectionViewType.Carousel, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
        return selected.length === 1 && selected[0].type === DocumentType.COL
            ? {
                  buttonList: buttonList.filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value as CollectionViewType)),
                  getStyle: undefined,
                  selectedVal: StrCast(selected[0]._type_collection),
                  toolTip: 'change view type (press Shift to add as a new view)',
              }
            : {
                  jsx: selected.length ? (
                      <Popup
                          icon={<FontAwesomeIcon size="1x" icon={selected.length > 1 ? 'caret-down' : (Doc.toIcon(selected.lastElement()) as IconProp)} />}
                          text={selected.length === 1 ? ClientUtils.cleanDocumentType(StrCast(selected[0].type) as DocumentType) : selected.length + ' selected'}
                          type={Type.TERT}
                          color={SnappingManager.userColor}
                          background={SnappingManager.userVariantColor}
                          popup={<SelectedDocView selectedDocs={selected} />}
                          fillWidth
                      />
                  ) : (
                      <Button
                          text={`${Doc.ActiveTool === InkTool.None ? 'Text box' : Doc.ActiveInk} defaults`} //
                          type={Type.TERT}
                          color={SnappingManager.userColor}
                          background={SnappingManager.userVariantColor}
                          fillWidth
                          inactive
                      />
                  ),
              };
    };

    /**
     * Dropdown list
     */
    @computed get dropdownListButton() {
        const script = ScriptCast(this.Document.script);
        const selectedFunc = () => script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;
        const { buttonList, selectedVal, getStyle, jsx, toolTip } = (() => {
            switch (this.Document.title) {
                case 'Font':         return this.handleFontDropdown(selectedFunc, this.buttonList);
                case 'Perspective':  return this.handleViewDropdown(script, this.buttonList);
                default:             return { buttonList: this.buttonList, selectedVal: selectedFunc(), toolTip: undefined, jsx: undefined, getStyle: undefined };
            } // prettier-ignore
        })();
        if (jsx) return jsx;

        // Get items to place into the list
        const list: IListItemProps[] = buttonList.map(value => ({
            text: typeof value === 'string' ? value.charAt(0).toUpperCase() + value.slice(1) : StrCast(DocCast(value)?.title),
            val: value,
            style: getStyle?.(value),
            // shortcut: '#',
        }));

        return (
            <Dropdown
                selectedVal={selectedVal}
                setSelectedVal={undoable((value, e) => script.script.run({ this: this.Document, value, shiftKey: e.shiftKey }), `dropdown select ${this.label}`)}
                color={SnappingManager.userColor}
                background={SnappingManager.userVariantColor}
                toolTip={toolTip}
                type={Type.TERT}
                closeOnSelect={false}
                dropdownType={DropdownType.SELECT}
                onItemDown={this.dropdownItemDown}
                items={list}
                tooltip={StrCast(this.Document.toolTip, this.label)}
                fillWidth
            />
        );
    }

    @computed get colorScript() {
        return ScriptCast(this.Document.script);
    }

    colorBatch: UndoManager.Batch | undefined;
    /**
     * Color button
     */
    @computed get colorButton() {
        const color = SnappingManager.userColor;
        const pickedColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
        const background = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor) as string;
        const curColor = (this.colorScript?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result as string) ?? 'transparent';
        const tooltip: string = StrCast(this.Document.toolTip);

        return (
            <div onPointerDown={e => e.stopPropagation()}>
                <ColorPicker
                    setSelectedColor={value => {
                        if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`);
                        this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
                    }}
                    setFinalColor={value => {
                        this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false });
                        this.colorBatch?.end();
                        this.colorBatch = undefined;
                    }}
                    defaultPickerType="Classic"
                    selectedColor={curColor}
                    type={Type.TERT}
                    color={color}
                    background={background}
                    icon={this.Icon(pickedColor) ?? undefined}
                    tooltip={tooltip}
                    label={this.label}
                />
            </div>
        );
    }
    @computed get multiToggleButton() {
        const tooltip = StrCast(this.Document.toolTip);

        const script = ScriptCast(this.Document.onClick)?.script;
        const toggleStatus = script?.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean;

        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
        const background = this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string;
        const items = DocListCast(this.dataDoc.data);
        const selectedItems = items.filter(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result).map(item => StrCast(item.toolType));

        return (
            <MultiToggle
                tooltip={`Click to Toggle ${tooltip} or select new option`}
                type={Type.TERT}
                color={color}
                background={background}
                multiSelect={true}
                onPointerDown={e => script && !toggleStatus && setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => script.run({ this: this.Document, value: undefined, _readOnly_: false }))}
                toggleStatus={toggleStatus}
                showUntilToggle={BoolCast(this.Document.showUntilToggle)} // allow the toggle to be clickable unless ignoreClick is set on the Document
                label={selectedItems.length === 1 ? selectedItems[0] : this.label}
                items={items.map(item => ({
                    icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as IconProp} color={color} />,
                    tooltip: StrCast(item.toolTip),
                    val: StrCast(item.toolType),
                }))}
                selectedItems={selectedItems}
                onSelectionChange={(val: (string | number) | (string | number)[], added: boolean) => {
                    // note: the multitoggle is telling us whether the selection was toggled on or off, but we ignore this since we know the state of all the buttons
                    //   and control it through the selectedItems prop.  Therefore, the callback script will have to re-determine the toggle information.
                    //   it would be better to pas the 'added' flag to the callback script, but our script generator from currentUserUtils makes it hard to define
                    //   arbitrary parameter variables (but it could be done as a special case or with additional effort when creating the sript)
                    const itemsChanged = items.filter(item => (val instanceof Array ? val.includes(item.toolType as string | number) : item.toolType === val));
                    itemsChanged.forEach(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, _added_: added, value: toggleStatus, itemDoc, _readOnly_: false }));
                }}
            />
        );
    }

    @observable _hackToRecompute = 0; // bcz: ugh ... <Toggle>'s toggleStatus initializes but doesn't track its value after a click.  so a click that does nothing to the toggle state will toggle the button anyway.  this forces the Toggle to re-read the ToggleStatus value.

    @computed get toggleButton() {
        // Determine the type of toggle button
        const buttonText = StrCast(this.dataDoc.buttonText);
        const tooltip = StrCast(this.Document.toolTip);

        const script = ScriptCast(this.Document.onClick);
        const double = ScriptCast(this.Document.onDoubleClick);
        const toggleStatus = (script?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result as boolean) ?? false;
        // Colors
        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;

        // bcz: ink shapes are tri-state - off, one-shot, and on.  Need to update Toggle buttons to allow this and update currentUserUtils to set the tri-state on the Doc
        // in the meantime, if the button matches a tool type that is not locked, we want to set the background color to something distinct.
        const inkShapeHack = ((this.Document.toolType && this.Document.toolType === SnappingManager.InkShape) || this.Document.toolType === Doc.ActiveTool) && !SnappingManager.KeepGestureMode;
        return (
            <Toggle
                tooltip={`Toggle ${tooltip}`}
                toggleType={ToggleType.BUTTON}
                type={Type.TERT}
                toggleStatus={toggleStatus}
                text={buttonText}
                color={color}
                triState={inkShapeHack}
                background={color}
                icon={this.Icon(color)!}
                label={this.label}
                onPointerDown={e =>
                    setupMoveUpEvents(
                        this,
                        e,
                        returnTrue,
                        emptyFunction,
                        action((clickEv, doubleTap) => {
                            (!doubleTap || !double) && script?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false });
                            doubleTap && double?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false });
                            this._hackToRecompute += 1;
                        })
                    )
                }
            />
        );
    }

    /**
     * Default
     */
    @computed get defaultButton() {
        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
        const tooltip = StrCast(this.Document.toolTip);

        return <IconButton tooltip={tooltip} icon={this.Icon(color) ?? undefined} label={this.label} />;
    }

    @computed get editableText() {
        const script = ScriptCast(this.Document.script);
        const checkResult = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string;

        const setValue = (value: string) => script?.script.run({ this: this.Document, value, _readOnly_: false }).result as boolean;

        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({ this: this.Document, value: '', _readOnly_: true }).result as string} SetValue={setValue} oneLine contents={checkResult} />
                </div>
            </div>
        );
    }

    renderButton = () => {
        const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string;
        const tooltip = StrCast(this.Document.toolTip);
        const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, _readOnly_: false });
        const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label };
        // prettier-ignore
        switch (this.type) {
            case ButtonType.NumberDropdownButton: 
            case ButtonType.NumberInlineButton:
            case ButtonType.NumberSliderButton: return this.numberDropdown;
            case ButtonType.EditText:           return this.editableText; 
            case ButtonType.DropdownList:       return this.dropdownListButton;
            case ButtonType.ColorButton:        return this.colorButton; 
            case ButtonType.MultiToggleButton:  return this.multiToggleButton;
            case ButtonType.ToggleButton:       return this.toggleButton;
            case ButtonType.ClickButton:        return <IconButton {...btnProps} size={Size.MEDIUM} color={color} background={color} />;
            case ButtonType.ToolButton:         return <IconButton {...btnProps} size={Size.LARGE}  color={color} background={color} />;
            case ButtonType.TextButton:         return <Button     {...btnProps}                    color={color} background={color} 
                                                                   text={StrCast(this.dataDoc.buttonText)}/>;
            case ButtonType.MenuButton:         return <IconButton size={Size.LARGE}  {...btnProps} color={color} background={color} 
                                                                   tooltipPlacement='right' onPointerDown={scriptFunc} />; 
            default: 
        }
        return this.defaultButton;
    };

    render() {
        return (
            <div className="fonticonbox" onContextMenu={this.specificContextMenu}>
                {this.renderButton()}
            </div>
        );
    }
}

Docs.Prototypes.TemplateMap.set(DocumentType.FONTICON, {
    layout: { view: FontIconBox, dataField: 'icon' },
    options: { acl: '', defaultDoubleClick: 'ignore', waitForDoubleClickToClick: 'never', layout_hideContextMenu: true, layout_hideLinkButton: true, _width: 40, _height: 40 },
});
