import ComponentFactory, {ComponentConfig} from "../ComponentFactory";
import Input from "./Input";
import DataFactory, {DataTypeConfig} from "../../Data/DataFactory";
import DataEndNode from "../../Data/DataEndNode";
import Component from "../Component";
import {addBoolInputToFactory} from "./Implementations/BoolInput";
import {addStringInputToFactory} from "./Implementations/StringInput";
import {addNumberInputToFactory} from "./Implementations/NumberInput";
import FormBuilder from "../../FormBuilder";
import {addLayeredCheckboxesToFactory} from "./Implementations/LayeredCheckboxes";
import {addMultiSelectToFactory} from "./Implementations/MultiSelect";
import {addRadioToFactory} from "./Implementations/Radio";
import {addSelectionToFactory} from "./Implementations/Selection";
import {addTextEditorToFactory} from "./Implementations/TextEditor";

export default class InputFactory {
    protected registeredConstructors: {[type:string]: (config:InputConfig, params: Partial<Input<any>>) => Input<any>|null|undefined} = {};

    constructor(
        protected dataFactory: DataFactory,
        protected FormBuilder: FormBuilder,
    ) {}

    public createInput(config: InputConfig, params: Partial<Component>): Input<any>|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(`Input ${config.impl.type} is not registered`);
            return null;
        }

        const inputParam: Partial<Input<any>> = params as Partial<Input<any>>;

        inputParam.index = config.index;
        inputParam.name = config.name;
        inputParam.description = config.description;
        inputParam.autocomplete = config.autocomplete;
        
        const newInput = this.registeredConstructors[config.impl.type](config, inputParam);
        if (newInput == null) {
            console.warn(`Input ${config.impl.type} could not be created`);
            return null;
        }

        const dataType = this.dataFactory.createDataNode(config.dataType, newInput);
        if (!dataType) throw new Error("Data type is not defined");
        const dataNode = new DataEndNode(dataType);
        dataNode.index = config.index;

        dataNode.defaultValue = config.defaultValue;

        newInput.setDataNode(dataNode);

        this.FormBuilder.onCommit.add(() => newInput.saveValue());

        return newInput;
    }

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

    public setDefaults(): this{
        addBoolInputToFactory(this);
        addStringInputToFactory(this);
        addNumberInputToFactory(this);
        addLayeredCheckboxesToFactory(this);
        addMultiSelectToFactory(this);
        addRadioToFactory(this);
        addSelectionToFactory(this);
        addTextEditorToFactory(this);

        return this;
    }

    public registerOnComponentFactory(factory:ComponentFactory): void{
        factory.registerConstructor("input", (config, params) => {
            if (componentConfigIsInput(config)){
                return this.createInput(config, params);
            }
        });
    }
}

export function componentConfigIsInput(config: ComponentConfig): config is InputConfig{
    return config.type === "input";
}

export interface InputConfig extends ComponentConfig{
    impl: InputImplConfig;
    defaultValue: any;
    name: string;
    description: string;
    dataType: DataTypeConfig;
    index: string;
    autocomplete: string;
}

export interface InputImplConfig{
    type: string;
}