import Structure from "../Structure";
import DataNode from "../../../Data/DataNode";
import StructureFactory, {StructureImplConfig} from "../StructureFactory";
import Input from "../../Input/Input";
import {ComponentConfig} from "../../ComponentFactory";
import FormBuilder from "../../../FormBuilder";

class RemotelyLoadedForm extends Structure{
    public url: string = "";
    public headers: object = {};
    public dependencies: DataNode<any>[] = [];

    protected isLoading: boolean = true;
    protected currentRequest: Promise<any>|null = null;

    protected currentMessage: string|null = null;
    protected currentForm: FormBuilder|null = null;

    constructor(params: Partial<RemotelyLoadedForm> = {}) {
        super(params);
        Object.assign(this, params);
    }

    public placeHtml(container: HTMLElement): void {
        const structureDiv = document.createElement("div");
        this.addStandardContainerProperties(structureDiv);
        structureDiv.classList.add("pad-remotely-loaded-form");

        container.append(structureDiv);
        this.placedHtml.push(structureDiv);

        if (this.isLoading) structureDiv.innerHTML = `<div class="pad-spinner"></div>`;
        if (this.currentMessage) structureDiv.innerHTML = `<div class="pad-error">${this.currentMessage}</div>`;
        if (this.currentForm) {
            structureDiv.innerHTML = "";
            this.currentForm.placeHtml(structureDiv);
        }
    }

    public addDependency(dependency: DataNode<any>): void {
        dependency.onChange.add(() => this.updateForm());
        this.dependencies.push(dependency);
    }

    public updateForm(): void {
        if (!this.isLoading) this.placeSpinner();
        this.isLoading = true;
        let data: any = {};

        this.dependencies.forEach(d => {
            const index = d.index;

            if (index == null) {
                Object.assign(data, d.value);
                return;
            }
            data[index] = d.value;
        });

        const request = this.currentRequest = fetch(this.url, {
            method: 'POST',
            headers: {
                ...this.headers,
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(data)
        });

        request.then(
            response => {
                if (this.currentRequest !== request) return;
                this.currentRequest = null;

                response.json().then(
                    data => {
                        this.placeForm(data.options);
                    }
                ).catch(error => {
                    console.error(error);
                    this.placeMessage("Er is iets misgegaan, probeer het later opnieuw.");
                });
            }
        ).catch(
            error => {
                this.currentRequest = null;
                this.placeMessage(error.message);
                console.error(error);
            }
        );
    }

    protected placeForm(config: ComponentConfig){
        const currentData = this.formBuilder.getData(false) ?? {};
        const newForm = new FormBuilder(config);
        newForm.form.parent = this;

        if (this.currentForm) {
            this.currentForm.clean();
            this.dataNode.removeChild(this.currentForm.form.getDataNode());
        }
        this.dataNode.addChildren(newForm.form.getDataNode());
        this.formBuilder.loadData(currentData);

        this.currentForm = newForm;
        this.currentMessage = null;
        this.isLoading = false;

        this.placedHtml.forEach(container => {
            container.innerHTML = "";
            newForm.placeHtml(container);
        });
    }

    protected placeMessage(message: string){
        this.placedHtml.forEach(container => container.innerHTML = `<div class="pad-error">${message}</div>`);
    }

    protected placeSpinner(): void{
        this.placedHtml.forEach(container => {
            const spinner = document.createElement('div');
            spinner.classList.add('pad-spinner');
            container.prepend(spinner);
        });
    }
}

export function addRemotelyLoadedFormToFactory(factory: StructureFactory): void {
    factory.registerConstructor("remotelyLoadedForm", (config, params) => {
        const impl = config.impl;
        if (!isRemotelyLoadedFormConfig(impl)) return null;

        const remoteParam: Partial<RemotelyLoadedForm> = params as Partial<RemotelyLoadedForm>;

        remoteParam.url = impl.url;
        remoteParam.headers = impl.headers;
        const instance = new RemotelyLoadedForm(remoteParam);

        remoteParam.formBuilder?.onComponentsCreated.add(() => {
            impl.dependencies.forEach(d => {
                let dependency = remoteParam.formBuilder?.registry[d];
                if (dependency instanceof Input || dependency instanceof Structure)
                    instance.addDependency(dependency.getDataNode());
            });
            instance.updateForm();
        });

        return instance;
    });
}

function isRemotelyLoadedFormConfig(config:StructureImplConfig): config is RemotelyLoadedFormConfig{
    return config.type === "remotelyLoadedForm";
}

interface RemotelyLoadedFormConfig extends StructureImplConfig{
    type: "remotelyLoadedForm";
    url: string;
    headers: object;
    dependencies: string[];
}