import { Arr, Fun, Future, Optional, Result } from '@ephox/katamari';
import { Css, Width } from '@ephox/sugar';
import * as ComponentStructure from '../alien/ComponentStructure';
import { Composing } from '../api/behaviour/Composing';
import { Coupling } from '../api/behaviour/Coupling';
import { Focusing } from '../api/behaviour/Focusing';
import { Positioning } from '../api/behaviour/Positioning';
import { Receiving } from '../api/behaviour/Receiving';
import { Representing } from '../api/behaviour/Representing';
import { Sandboxing } from '../api/behaviour/Sandboxing';
import { SketchBehaviours } from '../api/component/SketchBehaviours';
import { tieredMenu as TieredMenu } from '../api/ui/TieredMenu';
import * as AriaControls from '../aria/AriaControls';
import * as InternalSink from '../parts/InternalSink';
import * as Tagger from '../registry/Tagger';
import * as Dismissal from '../sandbox/Dismissal';
import * as Reposition from '../sandbox/Reposition';
const getAnchor = (detail, component) => {
    const hotspot = detail.getHotspot(component).getOr(component);
    const type = 'hotspot';
    const overrides = detail.getAnchorOverrides();
    return detail.layouts.fold(() => ({ type, hotspot, overrides }), (layouts) => ({ type, hotspot, overrides, layouts }));
};
const fetch = (detail, mapFetch, component) => {
    const fetcher = detail.fetch;
    return fetcher(component).map(mapFetch);
};
const openF = (detail, mapFetch, anchor, component, sandbox, externals, highlightOnOpen) => {
    const futureData = fetch(detail, mapFetch, component);
    const getLazySink = getSink(component, detail);
    // TODO: Make this potentially a single menu also
    return futureData.map((tdata) => tdata.bind((data) => Optional.from(TieredMenu.sketch({
        // Externals are configured by the "menu" part. It's called external because it isn't contained
        // within the DOM descendants of the dropdown. You can configure things like `fakeFocus` here.
        ...externals.menu(),
        uid: Tagger.generate(''),
        data,
        highlightOnOpen,
        onOpenMenu: (tmenu, menu) => {
            const sink = getLazySink().getOrDie();
            Positioning.position(sink, menu, { anchor });
            Sandboxing.decloak(sandbox);
        },
        onOpenSubmenu: (tmenu, item, submenu) => {
            const sink = getLazySink().getOrDie();
            Positioning.position(sink, submenu, {
                anchor: {
                    type: 'submenu',
                    item
                }
            });
            Sandboxing.decloak(sandbox);
        },
        onRepositionMenu: (tmenu, primaryMenu, submenuTriggers) => {
            const sink = getLazySink().getOrDie();
            Positioning.position(sink, primaryMenu, { anchor });
            Arr.each(submenuTriggers, (st) => {
                Positioning.position(sink, st.triggeredMenu, {
                    anchor: { type: 'submenu', item: st.triggeringItem }
                });
            });
        },
        onEscape: () => {
            // Focus the triggering component after escaping the menu
            Focusing.focus(component);
            Sandboxing.close(sandbox);
            return Optional.some(true);
        }
    }))));
};
// onOpenSync is because some operations need to be applied immediately, not wrapped in a future
// It can avoid things like flickering due to asynchronous bouncing
const open = (detail, mapFetch, hotspot, sandbox, externals, onOpenSync, highlightOnOpen) => {
    const anchor = getAnchor(detail, hotspot);
    const processed = openF(detail, mapFetch, anchor, hotspot, sandbox, externals, highlightOnOpen);
    return processed.map((tdata) => {
        // If we have data, display a menu. Else, close the menu if it was open
        tdata.fold(() => {
            if (Sandboxing.isOpen(sandbox)) {
                Sandboxing.close(sandbox);
            }
        }, (data) => {
            Sandboxing.cloak(sandbox);
            Sandboxing.open(sandbox, data);
            onOpenSync(sandbox);
        });
        return sandbox;
    });
};
const close = (detail, mapFetch, component, sandbox, _externals, _onOpenSync, _highlightOnOpen) => {
    Sandboxing.close(sandbox);
    return Future.pure(sandbox);
};
const togglePopup = (detail, mapFetch, hotspot, externals, onOpenSync, highlightOnOpen) => {
    const sandbox = Coupling.getCoupled(hotspot, 'sandbox');
    const showing = Sandboxing.isOpen(sandbox);
    const action = showing ? close : open;
    return action(detail, mapFetch, hotspot, sandbox, externals, onOpenSync, highlightOnOpen);
};
const matchWidth = (hotspot, container, useMinWidth) => {
    const menu = Composing.getCurrent(container).getOr(container);
    const buttonWidth = Width.get(hotspot.element);
    if (useMinWidth) {
        Css.set(menu.element, 'min-width', buttonWidth + 'px');
    }
    else {
        Width.set(menu.element, buttonWidth);
    }
};
const getSink = (anyInSystem, sinkDetail) => anyInSystem
    .getSystem()
    .getByUid(sinkDetail.uid + '-' + InternalSink.suffix())
    .map((internalSink) => () => Result.value(internalSink))
    .getOrThunk(() => sinkDetail.lazySink.fold(() => () => Result.error(new Error('No internal sink is specified, nor could an external sink be found')), (lazySinkFn) => () => lazySinkFn(anyInSystem)));
const doRepositionMenus = (sandbox) => {
    Sandboxing.getState(sandbox).each((tmenu) => {
        TieredMenu.repositionMenus(tmenu);
    });
};
const makeSandbox = (detail, hotspot, extras) => {
    const ariaControls = AriaControls.manager();
    const onOpen = (component, menu) => {
        const anchor = getAnchor(detail, hotspot);
        ariaControls.link(hotspot.element);
        if (detail.matchWidth) {
            matchWidth(anchor.hotspot, menu, detail.useMinWidth);
        }
        detail.onOpen(anchor, component, menu);
        if (extras !== undefined && extras.onOpen !== undefined) {
            extras.onOpen(component, menu);
        }
    };
    const onClose = (component, menu) => {
        ariaControls.unlink(hotspot.element);
        if (extras !== undefined && extras.onClose !== undefined) {
            extras.onClose(component, menu);
        }
    };
    const lazySink = getSink(hotspot, detail);
    return {
        dom: {
            tag: 'div',
            classes: detail.sandboxClasses,
            // TODO: Add aria-selected attribute
            attributes: {
                id: ariaControls.id,
                role: 'listbox'
            }
        },
        behaviours: SketchBehaviours.augment(detail.sandboxBehaviours, [
            Representing.config({
                store: {
                    mode: 'memory',
                    initialValue: hotspot
                }
            }),
            Sandboxing.config({
                onOpen,
                onClose,
                isPartOf: (container, data, queryElem) => {
                    return ComponentStructure.isPartOf(data, queryElem) || ComponentStructure.isPartOf(hotspot, queryElem);
                },
                getAttachPoint: () => {
                    return lazySink().getOrDie();
                }
            }),
            // The Composing of the dropdown here is the the active menu of the TieredMenu
            // inside the sandbox.
            Composing.config({
                find: (sandbox) => {
                    return Sandboxing.getState(sandbox).bind((menu) => Composing.getCurrent(menu));
                }
            }),
            Receiving.config({
                channels: {
                    ...Dismissal.receivingChannel({
                        isExtraPart: Fun.never
                    }),
                    ...Reposition.receivingChannel({
                        doReposition: doRepositionMenus
                    })
                }
            })
        ])
    };
};
const repositionMenus = (comp) => {
    const sandbox = Coupling.getCoupled(comp, 'sandbox');
    doRepositionMenus(sandbox);
};
export { makeSandbox, togglePopup, open, repositionMenus, getSink };
//# sourceMappingURL=DropdownUtils.js.map