import { Assertions, Pipeline, Step, TestLogs, TestStore } from '@ephox/agar';
import { Fun, Global, Merger, Obj, Optional, Type } from '@ephox/katamari';
import { DomEvent, Html, Insert, Remove, SugarBody, SugarDocument, SugarElement, SugarShadowDom, Traverse } from '@ephox/sugar';
import * as Attachment from '../system/Attachment';
import * as Gui from '../system/Gui';
const setupIn = (root, createComponent, f, success, failure) => {
    const store = TestStore();
    const gui = Gui.create();
    const contentContainer = SugarShadowDom.getContentContainer(root);
    Attachment.attachSystem(contentContainer, gui);
    const component = createComponent(store, root, contentContainer);
    gui.add(component);
    try {
        const steps = f(root, contentContainer, gui, component, store);
        Pipeline.async({}, steps, () => {
            Attachment.detachSystem(gui);
            success();
        }, (e, logs) => {
            failure(e, logs);
        }, TestLogs.init());
    }
    catch (e) {
        failure(e);
    }
};
const bddSetupIn = (setupRoot, createComponent, createGui, skip = Fun.never) => {
    let state = {};
    let teardown = Fun.noop;
    let hasFailure = false;
    // Note: Don't use bedrock imports here so as to avoid requiring bedrock as a
    // dependency. It'll still work the same, but we'll be missing the types.
    Global.before(function () {
        if (skip()) {
            this.skip();
        }
        const store = TestStore();
        const setup = setupRoot();
        teardown = setup.teardown;
        const root = setup.root;
        const contentContainer = SugarShadowDom.getContentContainer(root);
        const gui = Type.isNullable(createGui) ? Gui.create() : createGui();
        // Attach the gui if needed
        if (Traverse.parent(gui.element).isNone()) {
            Attachment.attachSystem(contentContainer, gui);
        }
        const component = createComponent(store, root, contentContainer);
        gui.add(component);
        state = {
            root,
            body: contentContainer,
            gui,
            component,
            store
        };
    });
    Global.afterEach(function () {
        var _a;
        if (((_a = this.currentTest) === null || _a === void 0 ? void 0 : _a.isFailed()) === true) {
            hasFailure = true;
        }
    });
    Global.after(() => {
        if (!hasFailure) {
            Obj.get(state, 'gui').each(Attachment.detachSystem);
            teardown();
        }
        state = {};
    });
    const lazyGet = (name) => () => Obj.get(state, name).getOrDie('The setup hooks have not run yet');
    return {
        root: lazyGet('root'),
        body: lazyGet('body'),
        gui: lazyGet('gui'),
        component: lazyGet('component'),
        store: lazyGet('store')
    };
};
/**
 * Setup in the body, run list of untyped Steps, then tear down.
 * Use #guiSetup for type-safe steps.
 *
 * @param createComponent
 * @param f
 * @param success
 * @param failure
 */
const setup = (createComponent, f, success, failure) => {
    setupIn(SugarDocument.getDocument(), createComponent, f, success, failure);
};
const bddSetup = (createComponent, createGui) => bddSetupIn(() => ({
    root: SugarDocument.getDocument(),
    teardown: Fun.noop
}), createComponent, createGui);
/**
 * Setup in a Shadow Root, run list of untyped Steps, then tear down.
 *
 * @param createComponent
 * @param f
 * @param success
 * @param failure
 */
const setupInShadowRoot = (createComponent, f, success, failure) => {
    if (!SugarShadowDom.isSupported()) {
        return success();
    }
    const sh = SugarElement.fromTag('div');
    Insert.append(SugarBody.body(), sh);
    const sr = SugarElement.fromDom(sh.dom.attachShadow({ mode: 'open' }));
    setupIn(sr, createComponent, f, () => {
        Remove.remove(sh);
        success();
    }, failure);
};
const bddSetupInShadowRoot = (createComponent, createGui) => {
    return bddSetupIn(() => {
        const sh = SugarElement.fromTag('div');
        Insert.append(SugarBody.body(), sh);
        const sr = SugarElement.fromDom(sh.dom.attachShadow({ mode: 'open' }));
        return {
            root: sr,
            teardown: () => Remove.remove(sh)
        };
    }, createComponent, createGui, () => !SugarShadowDom.isSupported());
};
const setupInBodyAndShadowRoot = (createComponent, f, success, failure) => {
    setup(createComponent, f, () => {
        setupInShadowRoot(createComponent, f, success, failure);
    }, failure);
};
/**
 * Setup in the body, run a typed Step, then tear down.
 * If you need to run multiple Steps, compose them using the functions in StepSequence.
 *
 * @param createComponent
 * @param f
 * @param success
 * @param failure
 */
const guiSetup = (createComponent, f, success, failure) => {
    setup(createComponent, (doc, body, gui, component, store) => [
        f(doc, body, gui, component, store)
    ], success, failure);
};
const setupKeyLogger = (body) => {
    const onKeydown = DomEvent.bind(body, 'keydown', (event) => {
        newState.log.push('keydown.to.body: ' + event.raw.which);
    });
    const log = [];
    const newState = {
        log,
        onKeydown
    };
    return newState;
};
const teardownKeyLogger = (state, expected) => {
    Assertions.assertEq('Checking key log outside context (on teardown)', expected, state.log);
    state.onKeydown.unbind();
};
const addStyles = (dos, styles) => {
    const style = SugarElement.fromTag('style');
    const head = SugarShadowDom.getStyleContainer(dos);
    Insert.append(head, style);
    Html.set(style, styles.join('\n'));
    return style;
};
const removeStyles = (style) => Remove.remove(style);
const bddAddStyles = (dos, styles) => {
    let style = Optional.none();
    // Note: Don't use bedrock imports here so as to avoid requiring bedrock as a
    // dependency. It'll still work the same, but we'll be missing the types.
    Global.before(() => {
        style = Optional.some(addStyles(dos, styles));
    });
    Global.after(() => {
        style.each(Remove.remove);
        style = Optional.none();
    });
};
const mSetupKeyLogger = (body) => Step.stateful((oldState, next, _die) => {
    next({
        ...oldState,
        ...setupKeyLogger(body)
    });
});
const mTeardownKeyLogger = (body, expected) => Step.stateful((state, next, _die) => {
    teardownKeyLogger(state, expected);
    const { onKeydown, log, ...rest } = state;
    next(rest);
});
const mAddStyles = (dos, styles) => Step.stateful((value, next, _die) => {
    next(Merger.deepMerge(value, {
        style: addStyles(dos, styles)
    }));
});
const mRemoveStyles = Step.stateful((value, next, _die) => {
    removeStyles(value.style);
    next(value);
});
export { setup, setupInShadowRoot, setupInBodyAndShadowRoot, guiSetup, bddSetup, bddSetupInShadowRoot, setupKeyLogger, teardownKeyLogger, addStyles, removeStyles, bddAddStyles, mSetupKeyLogger, mTeardownKeyLogger, mAddStyles, mRemoveStyles };
//# sourceMappingURL=GuiSetup.js.map