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

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

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

    protected getOptionElement(config: {label: string, value: any, description?: string}): HTMLElement{
        const dataNode = this.getDataNode();
        const label = document.createElement("label");
        const name = document.createElement("span");
        name.classList.add("pad-multi-select-option-name");
        label.append(name);
        if (config.description) {
            const description = document.createElement("p");
            description.classList.add("pad-multi-select-option-description");
            description.innerText = config.description;
            label.append(description);
        }
        name.innerText = config.label;
        label.classList.add("pad-multi-select-option");

        const checkbox = document.createElement("input");
        checkbox.value = String(config.value);
        checkbox.checked = dataNode.valueOrDefault?.includes(config.value) ?? false;
        checkbox.type = "checkbox";
        checkbox.name = this.getFullPathName() + "[]";
        label.prepend(checkbox);

        dataNode.onChange.add(event => {
            checkbox.checked = event.value?.includes(config.value) ?? false;
        });

        dataNode.onReset.add(() => {
            this.value = undefined;
            checkbox.checked = dataNode.valueOrDefault?.includes(config.value) ?? false;
        });

        checkbox.addEventListener("change", () => {
            let valueArray = (dataNode.valueOrDefault ?? []).concat();
            if (checkbox.checked) {
                valueArray.push(config.value);
                dataNode.value = valueArray;
            } else {
                dataNode.value = valueArray.filter(v => v !== config.value);
            }
            this.value = dataNode.value;
        });

        return label;
    }

    public placeHtml(container: HTMLElement): void {
        const questionContainer = this.getQuestionContainer();
        questionContainer.classList.add("pad-multi-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)) {
                    let valueArray = (this.getDataNode().valueOrDefault ?? []).concat();

                    valueArray.push(dataNode.value);
                    this.getDataNode().value = valueArray;
                    this.value = valueArray;
                    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));
                let valueArray = (this.getDataNode().valueOrDefault ?? []).concat();

                valueArray.push(newOption.value);
                this.getDataNode().value = valueArray;
                this.value = valueArray;
            });

            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 optionsContainer = document.createElement("div");
        optionsContainer.classList.add("pad-multi-select-options");

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

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

        this.dataNode.value?.forEach(
            (value) => {
                let isOption = false;
                for (let option of this.options){
                    if (option.value === value) {
                        isOption = true;
                        break;
                    }
                }

                if (!isOption) optionsContainer.append(this.getOptionElement({value: value, label: value}))
            }
        )

        return optionsContainer;
    }

}

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

        const multiSelectParam: Partial<MultiSelect> = params as Partial<MultiSelect>;
        multiSelectParam.options = impl.options;

        const multiSelect = new MultiSelect(multiSelectParam);
        if (!impl.allowCustom) return multiSelect;

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

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

        multiSelect.customInput = customInput;

        return multiSelect;
    });
}

function isMultiSelectConfig(config: InputImplConfig): config is MultiSelectConfig{
    return config.type === "multiSelect";
}

interface MultiSelectConfig extends InputImplConfig{
    type: "multiSelect";
    options: {label: string, value: any, description?: string}[];
    allowCustom: boolean;
    customInput: InputConfig;
}

export class AddCustomOptionEvent{
    public constructor(
        public newOption: {label: string, value: any}
    ) {}
}