import ComponentFactory, {ComponentConfig} from "../ComponentFactory";
import Structure from "./Structure";
import InnerNode from "../../Data/InnerNode";
import Input from "../Input/Input";
import DataNode from "../../Data/DataNode";
import FormFactory from "./Form/FormFactory";
import Component from "../Component";
import {addBasicToFactory} from "./Implementations/Basic";
import {addTabbedToFactory} from "./Implementations/Tabbed";
import {addQuestionClusterToFactory} from "./Implementations/QuestionCluster";
import {addCollapsibleToFactory} from "./Implementations/Collapsible";
import {addFullScreenPopupToFactory} from "./Implementations/FullScreenPopup";
import {addRepeatingToFactory} from "./Implementations/Repeating";
import {addRemotelyLoadedFormToFactory} from "./Implementations/RemotelyLoadedForm";

export default class StructureFactory {
    protected registeredConstructors: {[type:string]: (config:StructureConfig, params: Partial<Structure>) => Structure|null|undefined} = {};

    constructor(
        protected componentFactory: ComponentFactory
    ) {}

    public createStructure(config: StructureConfig, params: Partial<Component>): Structure|null {
        if (typeof config.impl.type !== "string" ) throw new Error("Type is not a string");
        if (!(typeof this.registeredConstructors[config.impl.type] === "function")) {
            console.warn(`Structure ${config.impl.type} is not registered`);
            return null;
        }

        const structureParam: Partial<Structure> = params as Partial<Structure>;
        structureParam.index = config.index;
        structureParam.classes = config.classes;
        structureParam.displayDefaultNotice = config.displayDefaultNotice;

        const structure = this.registeredConstructors[config.impl.type](config, structureParam);
        if (structure == null) {
            console.warn(`Structure ${config.impl.type} could not be created`);
            return null;
        }
        const dataNode = new InnerNode();
        dataNode.index = config.index;

        structure.setDataNode(dataNode);

        const dataChildren: DataNode<any>[] = [];
        config.contents.forEach(c => {
            const component = this.componentFactory.createComponent(c, structure);
            if (component === null) return;
            structure.addChildren(component);

            if (component instanceof Input)
                dataChildren.push(component.getDataNode());

            if ( component instanceof Structure )
                dataChildren.push(component.getDataNode());
        });

        dataNode.addChildren(...dataChildren);

        return structure;
    }

    public registerConstructor(type: string, constructor: (config:StructureConfig, params:Partial<Structure>) => Structure|null|undefined): this {
        this.registeredConstructors[type] = constructor;
        return this;
    }

    public setDefaults(): this{
        new FormFactory().addToStructureFactory(this);
        addBasicToFactory(this);
        addTabbedToFactory(this);
        addQuestionClusterToFactory(this);
        addCollapsibleToFactory(this);
        addFullScreenPopupToFactory(this);
        addRepeatingToFactory(this);
        addRemotelyLoadedFormToFactory(this);

        return this;
    }

    public registerOnComponentFactory(factory:ComponentFactory): void{
        factory.registerConstructor("structure", (config, params) => {
            if (componentConfigIsStructure(config)){
                return this.createStructure(config, params);
            }
        });
    }
}

export function componentConfigIsStructure(config: ComponentConfig): config is StructureConfig{
    return config.type === "structure";
}

export interface StructureConfig extends ComponentConfig{
    index: string|null;
    impl: StructureImplConfig;
    classes: string[];
    contents: ComponentConfig[];
    displayDefaultNotice: boolean;
}

export interface StructureImplConfig{
    type: string;
}