import { StructureSchema } from '@ephox/boulder';
import { Arr, Fun, Optional, Optionals } from '@ephox/katamari';
import { Css, SugarLocation } from '@ephox/sugar';
import * as AriaFocus from '../../aria/AriaFocus';
import * as Anchor from '../../positioning/layout/Anchor';
import * as Origins from '../../positioning/layout/Origins';
import * as Placement from '../../positioning/layout/Placement';
import * as SimpleLayout from '../../positioning/layout/SimpleLayout';
import { PlacementSchema } from './PositionSchema';
const getFixedOrigin = () => {
    // Don't use window.innerWidth/innerHeight here, as we don't want to include scrollbars
    // since the right/bottom position is based on the edge of the scrollbar not the window
    const html = document.documentElement;
    return Origins.fixed(0, 0, html.clientWidth, html.clientHeight);
};
const getRelativeOrigin = (component) => {
    const position = SugarLocation.absolute(component.element);
    const bounds = component.element.dom.getBoundingClientRect();
    // We think that this just needs to be kept consistent with Boxes.win. If we remove the scroll values from Boxes.win, we
    // should change this to just bounds.left and bounds.top from getBoundingClientRect
    return Origins.relative(position.left, position.top, bounds.width, bounds.height);
};
const place = (origin, anchoring, optBounds, placee, lastPlace, transition) => {
    const anchor = Anchor.box(anchoring.anchorBox, origin);
    return SimpleLayout.simple(anchor, placee.element, anchoring.bubble, anchoring.layouts, lastPlace, optBounds, anchoring.overrides, transition);
};
const position = (component, posConfig, posState, placee, placementSpec) => {
    const optWithinBounds = Optional.none();
    positionWithinBounds(component, posConfig, posState, placee, placementSpec, optWithinBounds);
};
const positionWithinBounds = (component, posConfig, posState, placee, placementSpec, optWithinBounds) => {
    const placeeDetail = StructureSchema.asRawOrDie('placement.info', StructureSchema.objOf(PlacementSchema), placementSpec);
    const anchorage = placeeDetail.anchor;
    const element = placee.element;
    const placeeState = posState.get(placee.uid);
    // Preserve the focus as IE 11 loses it when setting visibility to hidden
    AriaFocus.preserve(() => {
        // We set it to be fixed, so that it doesn't interfere with the layout of anything
        // when calculating anchors
        Css.set(element, 'position', 'fixed');
        const oldVisibility = Css.getRaw(element, 'visibility');
        Css.set(element, 'visibility', 'hidden');
        // We need to calculate the origin (esp. the bounding client rect) *after* we have done
        // all the preprocessing of the component and placee. Otherwise, the relative positions
        // (bottom and right) will be using the wrong dimensions
        const origin = posConfig.useFixed() ? getFixedOrigin() : getRelativeOrigin(component);
        anchorage.placement(component, anchorage, origin).each((anchoring) => {
            // If "within bounds" is specified, it overrides any Positioning config. Otherwise, we
            // use the Positioning config. We don't try to combine automatically here because they are
            // sometimes serving different purposes. If the Positioning config getBounds needs to be
            // combined with the optWithinBounds bounds, then it is the responsibility of the calling
            // code to combine them, and pass in the combined value as optWithinBounds. The optWithinBounds
            // will *always* override the Positioning config.
            const optBounds = optWithinBounds.orThunk(() => posConfig.getBounds.map(Fun.apply));
            // Place the element and then update the state for the placee
            const newState = place(origin, anchoring, optBounds, placee, placeeState, placeeDetail.transition);
            posState.set(placee.uid, newState);
        });
        oldVisibility.fold(() => {
            Css.remove(element, 'visibility');
        }, (vis) => {
            Css.set(element, 'visibility', vis);
        });
        // We need to remove position: fixed put on by above code if it is not needed.
        if (Css.getRaw(element, 'left').isNone() &&
            Css.getRaw(element, 'top').isNone() &&
            Css.getRaw(element, 'right').isNone() &&
            Css.getRaw(element, 'bottom').isNone() &&
            Optionals.is(Css.getRaw(element, 'position'), 'fixed')) {
            Css.remove(element, 'position');
        }
    }, element);
};
const getMode = (component, pConfig, _pState) => pConfig.useFixed() ? 'fixed' : 'absolute';
const reset = (component, pConfig, posState, placee) => {
    const element = placee.element;
    Arr.each(['position', 'left', 'right', 'top', 'bottom'], (prop) => Css.remove(element, prop));
    Placement.reset(element);
    posState.clear(placee.uid);
};
export { position, positionWithinBounds, getMode, reset };
//# sourceMappingURL=PositionApis.js.map