import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { Dropdown, DropdownType, IconButton, IListItemProps, Shadows, Size, Type } from 'browndash-components';
import { action, runInAction, untracked } from 'mobx';
import { extname } from 'path';
import * as React from 'react';
import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs';
import { FaFilter } from 'react-icons/fa';
import { Doc, Opt, StrListCast } from '../../fields/Doc';
import { DocViews } from '../../fields/DocSymbols';
import { BoolCast, Cast, DocCast, ImageCast, NumCast, StrCast } from '../../fields/Types';
import { DashColor, lightOrDark, Utils } from '../../Utils';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager';
import { IsFollowLinkScript } from '../util/LinkFollower';
import { LinkManager } from '../util/LinkManager';
import { SettingsManager } from '../util/SettingsManager';
import { SnappingManager } from '../util/SnappingManager';
import { undoBatch, UndoManager } from '../util/UndoManager';
import { TreeSort } from './collections/TreeSort';
import { Colors } from './global/globalEnums';
import { DocumentViewInternalProps, DocumentViewProps } from './nodes/DocumentView';
import { FieldViewProps } from './nodes/FieldView';
import { KeyValueBox } from './nodes/KeyValueBox';
import { PropertiesView } from './PropertiesView';
import './StyleProvider.scss';

export enum StyleProp {
    TreeViewIcon = 'treeView_Icon',
    TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items
    DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view
    Opacity = 'opacity', // opacity of the document view
    BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views
    BorderRounding = 'borderRounding', // border radius of the document view
    Color = 'color', // foreground color of Document view items
    BackgroundColor = 'backgroundColor', // background color of a document view
    FillColor = 'fillColor', // fill color of an ink stroke or shape
    WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note
    PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified
    Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background
    HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that
    ShowCaption = 'layout_showCaption',
    TitleHeight = 'titleHeight', // Height of Title area
    ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix)
    BorderPath = 'customBorder', // border path for document view
    FontSize = 'fontSize', // size of text font
    FontFamily = 'fontFamily', // font family of text
    FontWeight = 'fontWeight', // font weight of text
    Highlighting = 'highlighting', // border highlighting
}

function toggleLockedPosition(doc: Doc) {
    UndoManager.RunInBatch(
        () =>
            runInAction(() => {
                doc._lockedPosition = !doc._lockedPosition;
                doc._pointerEvents = doc._lockedPosition ? 'none' : undefined;
            }),
        'toggleBackground'
    );
}

export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps {
    return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
}
export function testFieldProps(toBeDetermined: any): toBeDetermined is FieldViewProps {
    return toBeDetermined?.isContentActive ? toBeDetermined : undefined;
}

export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
    return `M ${pw * 0.5} ${ph * inset}   C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset}   C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3}   C ${
        pw * (1 - inset)
    } ${ph * 0.4} ${pw} ${ph * (1 - 2 * inset)} ${pw * (1 - inset)} ${ph * (1 - inset)}  C ${pw * (1 - 2 * inset)} ${ph} ${pw * 0.6} ${ph * (1 - inset)} ${pw * 0.5} ${ph * (1 - inset)}   C ${pw * 0.3} ${ph * (1 - inset)} ${pw * (2 * inset)} ${ph} ${
        pw * inset
    } ${ph * (1 - inset)}  C 0 ${ph * (1 - 2 * inset)} ${pw * inset} ${ph * 0.8} ${pw * inset} ${ph * 0.75}   C ${pw * inset} ${ph * 0.7} 0 ${ph * (2 * inset)} ${pw * inset} ${ph * inset}   C ${pw * (2 * inset)} 0 ${pw * 0.25} ${ph * inset} ${
        pw * 0.5
    } ${ph * inset}`;
}

// a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab
//
export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewInternalProps | FieldViewProps>, property: string): any {
    const remoteDocHeader = 'author;author_date;noMargin';
    const docProps = testDocProps(props) ? props : undefined;
    const fieldProps = testFieldProps(props) ? props : undefined;
    const isCaption = property.includes(':caption');
    const isAnchor = property.includes(':anchor');
    const isContent = property.includes(':content');
    const isAnnotated = property.includes(':annotated');
    const isInk = () => doc?._layout_isSvg && !docProps?.LayoutTemplateString;
    const isOpen = property.includes(':open');
    const isEmpty = property.includes(':empty');
    const boxBackground = property.includes(':box');
    const fieldKey = fieldProps?.fieldKey ? fieldProps.fieldKey + '_' : isCaption ? 'caption_' : '';
    const lockedPosition = () => doc && BoolCast(doc._lockedPosition);
    const titleHeight = () => props?.styleProvider?.(doc, props, StyleProp.TitleHeight);
    const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor);
    const color = () => props?.styleProvider?.(doc, props, StyleProp.Color);
    const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity);
    const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle);
    // prettier-ignore
    switch (property.split(':')[0]) {
        case StyleProp.TreeViewIcon:
            const img = ImageCast(doc?.icon, ImageCast(doc?.data));
            if (img) {
                const ext = extname(img.url.href);
                const url = doc?.icon ? img.url.href : img.url.href.replace(ext, '_s' + ext);
                return <img src={url} width={20} height={15} style={{ margin: 'auto', display: 'block', objectFit: 'contain' }} />;
            }
            return Doc.toIcon(doc, isOpen);
        case StyleProp.TreeViewSortings:
            const allSorts: { [key: string]: { color: string; icon: JSX.Element | string } | undefined } = {};
            allSorts[TreeSort.AlphaDown] = { color: Colors.MEDIUM_BLUE, icon: <BsArrowDown/> };
            allSorts[TreeSort.AlphaUp] = { color: 'crimson', icon: <BsArrowUp/> };
            if (doc?._type_collection === CollectionViewType.Freeform) allSorts[TreeSort.Zindex] = { color: 'green', icon: 'Z' };
            allSorts[TreeSort.WhenAdded] = { color: 'darkgray', icon: <BsArrowDownUp/> };
            return allSorts;
        case StyleProp.Highlighting:
            if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined;            
            if (doc && !doc.layout_disableBrushing && !props?.disableBrushing) {
                const selected = Array.from(doc?.[DocViews]??[]).filter(dv => dv.IsSelected).length;
                const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0);
                const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex];
                const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex];
                if (highlightIndex) {
                    return {
                        highlightStyle: doc.isGroup ? "dotted": highlightStyle,
                        highlightColor,
                        highlightIndex,
                        highlightStroke: doc.layout_isSvg,
                    };
                }
            }
            return  undefined;
        case StyleProp.DocContents:return undefined;
        case StyleProp.WidgetColor:return isAnnotated ? Colors.LIGHT_BLUE : 'dimgrey';
        case StyleProp.Opacity:    return docProps?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : doc?.text_inlineAnnotations ? 0 :  Cast(doc?._opacity, "number", Cast(doc?.opacity, 'number', null));
        case StyleProp.FontSize:   return StrCast(doc?.[fieldKey + 'fontSize'],   StrCast(doc?._text_fontSize,   StrCast(Doc.UserDoc().fontSize)));
        case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + 'fontFamily'], StrCast(doc?._text_fontFamily, StrCast(Doc.UserDoc().fontFamily)));
        case StyleProp.FontWeight: return StrCast(doc?.[fieldKey + 'fontWeight'], StrCast(doc?._text_fontWeight, StrCast(Doc.UserDoc().fontWeight)));
        case StyleProp.FillColor:  return StrCast(doc?._fillColor, StrCast(doc?.fillColor, 'transparent'));
        case StyleProp.ShowCaption:return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption);
        case StyleProp.TitleHeight:
            return (props?.DocumentView?.().screenToViewTransform().Scale ?? 1) * NumCast(Doc.UserDoc().headerHeight,30)
        case StyleProp.ShowTitle:
            return (
                (doc &&
                    !docProps?.LayoutTemplateString &&
                    !doc.presentation_targetDoc &&
                    !docProps?.LayoutTemplateString?.includes(KeyValueBox.name) &&
                    props?.layout_showTitle?.() !== '' &&
                    StrCast(
                        doc._layout_showTitle,
                        props?.layout_showTitle?.() ||
                            (!Doc.IsSystem(doc) && [DocumentType.COL, DocumentType.FUNCPLOT, DocumentType.LABEL, DocumentType.RTF, DocumentType.IMG, DocumentType.VID].includes(doc.type as any)
                                ? doc.author === Doc.CurrentUserEmail
                                    ? StrCast(Doc.UserDoc().layout_showTitle)
                                    : remoteDocHeader
                                : '')
                    )) ||
                ''
            );
        case StyleProp.Color:
            if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userBackgroundColor;
            if (Doc.IsSystem(doc!)) return SettingsManager.userColor;
            if (doc?.type === DocumentType.FONTICON) return SettingsManager.userColor;
            const docColor: Opt<string> = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color));
            if (docColor) return docColor;
            const docView = props?.DocumentView?.();
            const backColor = backgroundCol() || docView?._props.styleProvider?.(docView._props.treeViewDoc, docView.docView?._props, StyleProp.BackgroundColor);
            return backColor ? lightOrDark(backColor) : undefined;
        case StyleProp.BorderRounding:
            return StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
        case StyleProp.BorderPath:
            const borderPath = Doc.IsComicStyle(doc) &&
                props?.renderDepth &&
                !doc?.layout_isSvg && { path: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0), fill: wavyBorderPath(props?.PanelWidth?.() || 0, props?.PanelHeight?.() || 0, 0.08), width: 3 };
            return !borderPath
                ? null
                : {
                      clipPath: `path('${borderPath.path}')`,
                      jsx: (
                          <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
                              <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${props.PanelWidth()} ${props.PanelHeight()}`}>
                                  <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
                              </svg>
                          </div>
                      ),
                  };
        case StyleProp.HeaderMargin:
            return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._type_collection as any) ||
                (doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) ||
                doc?.type === DocumentType.LABEL) &&
                layout_showTitle() &&
                !StrCast(doc?.layout_showTitle).includes(':hover')
                ? titleHeight()
                : 0;
        case StyleProp.BackgroundColor: {
            if (SettingsManager.Instance.LastPressedBtn === doc) return SettingsManager.userColor; // hack to indicate active menu panel item
            let docColor: Opt<string> = StrCast(doc?.[fieldKey + '_backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : ''));
            // prettier-ignore
            switch (doc?.type) {
                case DocumentType.PRESELEMENT: docColor = docColor || ""; break;
                case DocumentType.PRES:        docColor = docColor || 'transparent'; break;
                case DocumentType.FONTICON:    docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break;
                case DocumentType.RTF:         docColor = docColor ||  Colors.LIGHT_GRAY; break;
                case DocumentType.LINK:        docColor = (isAnchor ? docColor : undefined); break;
                case DocumentType.INK:         docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break;
                case DocumentType.EQUATION:    docColor = docColor || 'transparent'; break;
                case DocumentType.LABEL:       docColor = docColor || Colors.LIGHT_GRAY; break;
                case DocumentType.BUTTON:      docColor = docColor ||  Colors.LIGHT_GRAY; break;
                case DocumentType.IMG:
                case DocumentType.WEB:
                case DocumentType.PDF:
                case DocumentType.MAP:
                case DocumentType.SCREENSHOT:
                case DocumentType.VID:         docColor = docColor || (Colors.LIGHT_GRAY); break;
                case DocumentType.COL:
                    docColor = docColor || (Doc.IsSystem(doc)
                            ? SettingsManager.userBackgroundColor
                            : doc.annotationOn
                            ? '#00000010' // faint interior for collections on PDFs, images, etc
                            : doc?.isGroup
                            ? undefined
                            : doc._type_collection === CollectionViewType.Stacking ?
                            (Colors.DARK_GRAY)
                            : Cast((props?.renderDepth || 0) > 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (Colors.MEDIUM_GRAY));
                    break;
                //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)";
                default: docColor = docColor || (Colors.WHITE);
            }
            return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor;
        }
        case StyleProp.BoxShadow: {
            if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow)
            if (doc.layout_boxShadow === 'standard') return Shadows.STANDARD_SHADOW;
            if (IsFollowLinkScript(doc?.onClick) && LinkManager.Links(doc).length && !doc.layout_isSvg) return StrCast(doc?._linkButtonShadow, 'lightblue 0em 0em 1em');
            switch (doc?.type) {
                case DocumentType.COL:
                    return StrCast(
                        doc?.layout_boxShadow,
                        doc?._type_collection === CollectionViewType.Pile
                            ? '4px 4px 10px 2px'
                            : lockedPosition() || doc?.isGroup || docProps?.LayoutTemplateString
                            ? undefined // groups have no drop shadow -- they're supposed to be "invisible".  LayoutString's imply collection is being rendered as something else (e.g., title of a Slide)
                            : `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                    );

                case DocumentType.LABEL:
                    if (doc?.annotationOn !== undefined) return 'black 2px 2px 1px';
                default:
                    return doc.z
                        ? `#9c9396  ${StrCast(doc?.layout_boxShadow, '10px 10px 0.9vw')}` // if it's a floating doc, give it a big shadow
                        : props?.docViewPath().lastElement()?.Document._freeform_useClusters
                        ? `${backgroundCol()} ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                        : NumCast(doc.group, -1) !== -1
                        ? `gray ${StrCast(doc.layout_boxShadow, `0vw 0vw ${(lockedPosition() ? 100 : 50) / (docProps?.NativeDimScaling?.() || 1)}px`)}` // if it's just in a cluster, make the shadown roughly match the cluster border extent
                        : lockedPosition()
                        ? undefined // if it's a background & has a cluster color, make the shadow spread really big
                        : StrCast(doc.layout_fieldKey).includes('_inline')  // if doc is an inline document in a text box
                        ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0vw 0vw 0.1vw')}`
                        : DocCast(doc.embedContainer)?.type=== DocumentType.RTF // if doc is embedded in a text document (but not an inline)
                        ? `${Colors.DARK_GRAY} ${StrCast(doc.layout_boxShadow, '0.2vw 0.2vw 0.8vw')}`
                        : StrCast(doc.layout_boxShadow, '');
            }
        }
        case StyleProp.PointerEvents:
            if (StrCast(doc?.pointerEvents) && !docProps?.LayoutTemplateString?.includes(KeyValueBox.name)) return StrCast(doc!.pointerEvents); // honor pointerEvents field (set by lock button usually) if it's not a keyValue view of the Doc
            if (docProps?.DocumentView?.()._props.LayoutTemplateString?.includes(KeyValueBox.name)) return 'all';
            if (SnappingManager.ExploreMode || doc?.layout_unrendered) return isInk() ? 'visiblePainted' : 'all';
            if (props?.pointerEvents?.() === 'none') return 'none';
            if (opacity() === 0) return 'none';
            if (props?.isGroupActive?.() ) return isInk()  ? 'visiblePainted':  (doc?.
                isGroup )? undefined: 'all' 
            if (props?.isDocumentActive?.() && !props.treeViewDoc) return isInk() ? 'visiblePainted' : 'all';
            return undefined; // fixes problem with tree view elements getting pointer events when the tree view is not active
        case StyleProp.Decorations:
            const lock = () => {
                if (props?.docViewPath().lastElement()?.Document?._type_collection === CollectionViewType.Freeform) {
                    return doc?.pointerEvents !== 'none' ? null : (
                        <div className="styleProvider-lock" onClick={() => toggleLockedPosition(doc)}>
                            <FontAwesomeIcon icon='lock' size="lg" />
                        </div>
                    );
                }
            };
            const filter = () => {
                const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard));
                const showFilterIcon =
                    StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length
                        ? 'green' // #18c718bd' //'hasFilter'
                        : docProps?.childFilters?.().filter(f => Utils.IsRecursiveFilter(f) && f !== Utils.noDragDocsFilter).length || docProps?.childFiltersByRanges().length
                        ? 'orange' //'inheritsFilter'
                        : undefined;
                return !showFilterIcon ? null : (
                    <div className="styleProvider-filter">
                        <Dropdown
                            type={Type.TERT}
                            dropdownType={DropdownType.CLICK}
                            fillWidth
                            iconProvider={(active:boolean) => <div className='styleProvider-filterShift'><FaFilter/></div>}
                            closeOnSelect={true}
                            setSelectedVal={
                                action((dv) => {
                                    (dv as any).select(false);
                                    (SettingsManager.Instance.propertiesWidth = 250);
                                    setTimeout(action(() => {
                                        if (PropertiesView.Instance) {
                                            PropertiesView.Instance.CloseAll();
                                            PropertiesView.Instance.openFilters = true;
                                        }
                                    }));
                                })
                            }
                            size={Size.XSMALL}
                            width={15}
                            height={15}
                            title={showFilterIcon === 'green' ? 
                                "This view is filtered.  Click to view/change filters":
                                "this view inherits filters from one of its parents"}
                            color={SettingsManager.userColor}
                            background={showFilterIcon}
                            items={[ ...(dashView ? [dashView]: []), ...(props?.docViewPath?.()??[]), ...(props?.DocumentView?[props?.DocumentView?.()]:[])]
                                    .filter(dv => StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length)
                                    .map(dv => ({
                                text: StrCast(dv?.Document.title),
                                val: dv as any,
                                style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor},
                            } as IListItemProps)) }
                        />
                    </div>
                );
            };
            const audio = () => {
                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
                const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length;
                if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
                const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
                return (
                    <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
                        <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.docView?.playAnnotation()}>
                            <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
                        </div>
                    </Tooltip>
                );
            };
            return (
                <>
                    {lock()}
                    {filter()}
                    {audio()}
                </>
            );
    }
}

export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, offIcon: IconProp, clickFunc?: () => void) {
    const color = SettingsManager.userColor;
    return (
        <IconButton
            size={Size.XSMALL}
            color={color}
            icon={<FontAwesomeIcon icon={(doc[field] ? (onIcon as any) : offIcon) as IconProp} />}
            onClick={undoBatch(
                action((e: React.MouseEvent) => {
                    e.stopPropagation();
                    clickFunc ? clickFunc() : (doc[field] = doc[field] ? undefined : true);
                })
            )}
        />
    );
}
/**
 * add hide button decorations for the "Dashboards" flyout TreeView
 */
export function DashboardStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewInternalProps>, property: string) {
    if (doc && property.split(':')[0] === StyleProp.Decorations) {
        return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc)
            ? null
            : DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.embedContainer ?? doc?.annotationOn)));
    }
    return DefaultStyleProvider(doc, props, property);
}
