import { FieldSchema, StructureSchema } from '@ephox/boulder';
import { Arr, Cell, Fun, Obj, Optional, Result } from '@ephox/katamari';
import { SugarElement, Traverse } from '@ephox/sugar';
import * as DefaultEvents from '../../events/DefaultEvents';
import * as Tagger from '../../registry/Tagger';
import * as CustomSpec from '../../spec/CustomSpec';
import { NoContextApi } from '../system/NoContextApi';
import * as GuiTypes from '../ui/GuiTypes';
import * as Component from './Component';
const buildSubcomponents = (spec, obsoleted) => {
    const components = Obj.get(spec, 'components').getOr([]);
    return obsoleted.fold(() => Arr.map(components, build), (obs) => Arr.map(components, (c, i) => {
        return buildOrPatch(c, Traverse.child(obs, i));
    }));
};
const buildFromSpec = (userSpec, obsoleted) => {
    const { events: specEvents, ...spec } = CustomSpec.make(userSpec);
    // Build the subcomponents. A spec hierarchy is built from the bottom up.
    // obsoleted is used to define which element we are attempting to replace
    // so that it might be used to patch the DOM instead of recreate it.
    const components = buildSubcomponents(spec, obsoleted);
    const completeSpec = {
        ...spec,
        events: { ...DefaultEvents, ...specEvents },
        components
    };
    return Result.value(
    // Note, this isn't a spec any more, because it has built children
    Component.build(completeSpec, obsoleted));
};
const text = (textContent) => {
    const element = SugarElement.fromText(textContent);
    return external({
        element
    });
};
const external = (spec) => {
    const extSpec = StructureSchema.asRawOrDie('external.component', StructureSchema.objOfOnly([
        FieldSchema.required('element'),
        FieldSchema.option('uid')
    ]), spec);
    const systemApi = Cell(NoContextApi());
    const connect = (newApi) => {
        systemApi.set(newApi);
    };
    const disconnect = () => {
        systemApi.set(NoContextApi(() => me));
    };
    const uid = extSpec.uid.getOrThunk(() => Tagger.generate('external'));
    Tagger.writeOnly(extSpec.element, uid);
    const me = {
        uid,
        getSystem: systemApi.get,
        config: Optional.none,
        hasConfigured: Fun.never,
        connect,
        disconnect,
        getApis: () => ({}),
        element: extSpec.element,
        spec,
        readState: Fun.constant('No state'),
        syncComponents: Fun.noop,
        components: Fun.constant([]),
        events: {}
    };
    return GuiTypes.premade(me);
};
// We experimented with just having a counter for efficiency, but that fails for situations
// where an external JS file is using alloy, and is contained within another
// alloy root container. The ids can conflict, because the counters do not
// know about each other (being parts of separate scripts).
//
// There are other solutions than this ... not sure if they are going to have better performance, though
const uids = Tagger.generate;
const isSketchSpec = (spec) => Obj.has(spec, 'uid');
// INVESTIGATE: A better way to provide 'meta-specs'
const buildOrPatch = (spec, obsoleted) => GuiTypes.getPremade(spec).getOrThunk(() => {
    // EFFICIENCY: Consider not merging here, and passing uid through separately
    const userSpecWithUid = isSketchSpec(spec) ? spec : {
        uid: uids(''),
        ...spec
    };
    return buildFromSpec(userSpecWithUid, obsoleted).getOrDie();
});
const build = (spec) => buildOrPatch(spec, Optional.none());
const premade = GuiTypes.premade;
export { build, buildOrPatch, premade, external, text };
//# sourceMappingURL=GuiFactory.js.map