import Input from "../Input";
import InputFactory, {InputConfig, InputImplConfig} from "../InputFactory";
import EventHandler from "../../../Utility/EventHandler";
import {AddCustomOptionEvent} from "./MultiSelect";

export default class Selection extends Input<any>{
    options: {label: string, value: any}[] = [];
    public customInput: Input<any>|null = null;
    public onAddCustomOption: EventHandler<AddCustomOptionEvent> = new EventHandler();

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

    protected getOptionElement(config: {label: string, value: any}): HTMLElement{
        const dataNode = this.getDataNode();
        const optionElement = document.createElement("option");
        optionElement.value = String(config.value);
        optionElement.selected = dataNode.valueOrDefault === config.value;
        optionElement.innerText = config.label;

        dataNode.onChange.add(event => {
            optionElement.selected = event.value === config.value;
        });

        dataNode.onReset.add(() => {
            this.value = undefined;
            optionElement.selected = dataNode.valueOrDefault === config.value;
        });

        return optionElement;
    }

    public placeHtml(container: HTMLElement): void {
        const questionContainer = this.getQuestionContainer();
        questionContainer.classList.add("pad-select-input");

        if (this.customInput) {
            const addCustomButton = document.createElement("button");
            addCustomButton.innerText = "+";
            addCustomButton.type = "button";
            addCustomButton.classList.add("pad-add-custom-option");
            addCustomButton.addEventListener("click", () => {
                const dataNode = this.customInput?.getDataNode();
                if (!dataNode) return;
                if (dataNode.value == null || dataNode.value === "") return;
                if (!dataNode.isValid) return;
                if (this.options.some(option => option.value === dataNode.value)) {
                    this.getDataNode().value = dataNode.value;
                    this.value = dataNode.value;
                    return;
                }

                const newOption: {label: string, value: any} = {
                    label: String(dataNode.value),
                    value: dataNode.value
                };

                this.options.push(newOption);
                dataNode.value = undefined;

                this.onAddCustomOption.emit(new AddCustomOptionEvent(newOption));
                this.getDataNode().value = newOption.value;
                this.value = newOption.value;
            });

            const customInputContainer = document.createElement("div");
            customInputContainer.classList.add("pad-custom-input-container");
            customInputContainer.append(addCustomButton);
            this.customInput.placeHtml(customInputContainer);
            questionContainer.append(customInputContainer);
        }

        container.append(questionContainer);
        this.placedHtml.push(questionContainer);
    }

    protected getInputElement(): HTMLElement {
        const selectElement = document.createElement("select");
        selectElement.name = this.getFullPathName();
        selectElement.setAttribute("autocomplete", this.autocomplete);

        selectElement.addEventListener("change", () => {
            const selectedOption = this.options.find(
                option => String(option.value) === selectElement.value
            );

            if (!selectedOption) {
                this.value = null;
                this.getDataNode().value = null;
                return;
            }

            this.value = selectedOption.value;
            this.getDataNode().value = selectedOption.value;
        })

        this.getDataNode().onChange.add(event => {
            selectElement.value = String(event.value);
        });

        this.getDataNode().onReset.add(() => {
            this.value = undefined;
            selectElement.value = String(this.getDataNode().valueOrDefault ?? "");
        });

        selectElement.value = String(this.getDataNode().valueOrDefault ?? "");

        this.onAddCustomOption.add((event) => {
            selectElement.append(this.getOptionElement(event.newOption));
        });

        this.options.forEach(option => {
            selectElement.append(this.getOptionElement(option));
        });

        return selectElement;
    }

}

export function addSelectionToFactory(factory: InputFactory): void {
    factory.registerConstructor("selection", (config, params) => {
        const impl = config.impl;
        if (!isSelectionConfig(impl)) return null;

        const selectionParam: Partial<Selection> = params as Partial<Selection>;
        selectionParam.options = impl.options;

        const selection = new Selection(selectionParam);
        if (!impl.allowCustom) return selection;

        const customInputParam: Partial<Input<any>> = {};
        customInputParam.formBuilder = selection.formBuilder;

        const customInput = factory.createInput(impl.customInput, customInputParam);
        if (!customInput) return selection;

        selection.customInput = customInput;

        return selection;
    });
}

function isSelectionConfig(config: InputImplConfig): config is SelectionConfig{
    return config.type === "selection";
}

interface SelectionConfig extends InputImplConfig{
    type: "selection";
    options: {label: string, value: any}[];
    allowCustom: boolean;
    customInput: InputConfig;
}