import { FieldSchema, StructureSchema } from '@ephox/boulder';
import { Fun, Obj, Optional, Optionals, Thunk } from '@ephox/katamari';
import * as AlloyEvents from '../../api/events/AlloyEvents';
import * as FunctionAnnotator from '../../debugging/FunctionAnnotator';
import * as DomModification from '../../dom/DomModification';
const executeEvent = (bConfig, bState, executor) => AlloyEvents.runOnExecute((component) => {
    executor(component, bConfig, bState);
});
const loadEvent = (bConfig, bState, f) => AlloyEvents.runOnInit((component, _simulatedEvent) => {
    f(component, bConfig, bState);
});
const create = (schema, name, active, apis, extra, state) => {
    const configSchema = StructureSchema.objOfOnly(schema);
    const schemaSchema = FieldSchema.optionObjOf(name, [
        FieldSchema.optionObjOfOnly('config', schema)
    ]);
    return doCreate(configSchema, schemaSchema, name, active, apis, extra, state);
};
const createModes = (modes, name, active, apis, extra, state) => {
    const configSchema = modes;
    const schemaSchema = FieldSchema.optionObjOf(name, [
        FieldSchema.optionOf('config', modes)
    ]);
    return doCreate(configSchema, schemaSchema, name, active, apis, extra, state);
};
const wrapApi = (bName, apiFunction, apiName) => {
    const f = (component, ...rest) => {
        const args = [component].concat(rest);
        return component.config({
            name: Fun.constant(bName)
        }).fold(() => {
            throw new Error('We could not find any behaviour configuration for: ' + bName + '. Using API: ' + apiName);
        }, (info) => {
            const rest = Array.prototype.slice.call(args, 1);
            return apiFunction.apply(undefined, [component, info.config, info.state].concat(rest));
        });
    };
    return FunctionAnnotator.markAsBehaviourApi(f, apiName, apiFunction);
};
// I think the "revoke" idea is fragile at best.
const revokeBehaviour = (name) => ({
    key: name,
    value: undefined
});
const doCreate = (configSchema, schemaSchema, name, active, apis, extra, state) => {
    const getConfig = (info) => Obj.hasNonNullableKey(info, name) ? info[name]() : Optional.none();
    const wrappedApis = Obj.map(apis, (apiF, apiName) => wrapApi(name, apiF, apiName));
    const wrappedExtra = Obj.map(extra, (extraF, extraName) => FunctionAnnotator.markAsExtraApi(extraF, extraName));
    const me = {
        ...wrappedExtra,
        ...wrappedApis,
        revoke: Fun.curry(revokeBehaviour, name),
        config: (spec) => {
            const prepared = StructureSchema.asRawOrDie(name + '-config', configSchema, spec);
            return {
                key: name,
                value: {
                    config: prepared,
                    me,
                    configAsRaw: Thunk.cached(() => StructureSchema.asRawOrDie(name + '-config', configSchema, spec)),
                    initialConfig: spec,
                    state
                }
            };
        },
        schema: Fun.constant(schemaSchema),
        exhibit: (info, base) => {
            return Optionals.lift2(getConfig(info), Obj.get(active, 'exhibit'), (behaviourInfo, exhibitor) => {
                return exhibitor(base, behaviourInfo.config, behaviourInfo.state);
            }).getOrThunk(() => DomModification.nu({}));
        },
        name: Fun.constant(name),
        handlers: (info) => {
            return getConfig(info).map((behaviourInfo) => {
                const getEvents = Obj.get(active, 'events').getOr(() => ({}));
                return getEvents(behaviourInfo.config, behaviourInfo.state);
            }).getOr({});
        }
    };
    return me;
};
export { executeEvent, loadEvent, create, createModes };
//# sourceMappingURL=Behaviour.js.map