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

export default class LayeredCheckboxes extends Input<any[]>{
    public options: CheckBoxOptionConfig[] = [];
    public optionsObjects: CheckBoxOption[] = [];

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

    public setDataNode(dataNode: DataEndNode<any[]>): this {
        super.setDataNode(dataNode);

        this.options.forEach(
            option => this.optionsObjects.push(
                this.getCheckBoxOption(option)
            )
        );

        return this;
    }

    protected getCheckBoxOption(config: CheckBoxOptionConfig): CheckBoxOption{
        const optionObj = new CheckBoxOption();
        const dataNode = this.getDataNode();

        optionObj.label = config.label;
        if (Array.isArray(config.value)) {
            config.value.forEach( (option) => optionObj.addChildren(this.getCheckBoxOption(option)));
            optionObj.checkChildren();
        } else {
            dataNode.onChange.add(event => {
                optionObj.checked = event.value?.includes(config.value) ?? false;
            });

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

            optionObj.onChange.add((value: boolean) => {
                let valueArray = (dataNode.valueOrDefault ?? []).concat();
                if (value) {
                    valueArray.push(config.value);
                    dataNode.value = valueArray;
                } else {
                    dataNode.value = valueArray.filter(v => v !== config.value);
                }
                this.value = dataNode.value;
            });

            optionObj.checked = dataNode.valueOrDefault?.includes(config.value) ?? false;
            optionObj.value = config.value;
        }

        return optionObj;
    }

    public placeHtml(container: HTMLElement): void {
        const questionContainer = this.getQuestionContainer();
        questionContainer.classList.add("pad-layered-checkbox-input");
        container.append(questionContainer);
        this.placedHtml.push(questionContainer);
    }

    protected getInputElement(): HTMLElement {
        const optionsContainer = document.createElement("div");
        optionsContainer.classList.add("pad-layered-checkbox-options");

        this.optionsObjects.forEach(option => {
            optionsContainer.append(this.getChildBox(option));
        })
        
        return optionsContainer;
    }

    protected getChildBox(box:CheckBoxOption): HTMLElement {
        const container = document.createElement("div");
        container.classList.add("pad-layered-checkbox-container");

        const checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        checkbox.name = this.getFullPathName() + "[]";

        const label = document.createElement("label");
        label.innerText = box.label;
        label.classList.add("pad-layered-checkbox-label");
        label.prepend(checkbox);
        container.append(label);

        box.onChange.add(() => {
            checkbox.checked = box.checked;
            checkbox.indeterminate = box.indeterminate;
        })

        checkbox.addEventListener("change", () => {
            box.checked = checkbox.checked;
        })

        if (box.getChildren().length > 0){
            const childContainer = document.createElement("div");
            childContainer.classList.add("pad-layered-checkbox-children");
            container.append(childContainer);

            box.getChildren().forEach(child => {
                childContainer.append(this.getChildBox(child));
            })
        } else {
            checkbox.value = String(box.value);
        }

        return container;
    }
}

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

        const layeredCheckboxesParam: Partial<LayeredCheckboxes> = params as Partial<LayeredCheckboxes>;

        layeredCheckboxesParam.options = impl.options;

        return new LayeredCheckboxes(layeredCheckboxesParam);
    });
}

function isLayeredCheckboxesConfig(config: InputImplConfig): config is LayeredCheckboxesConfig{
    return config.type === "layeredCheckboxes";
}

class CheckBoxOption {
    public label: string;
    public value: any|undefined = undefined;
    protected _checked: boolean = false;
    protected _indeterminate: boolean = false;
    protected children: CheckBoxOption[] = [];

    public onChange: EventHandler<boolean> = new EventHandler();

    public addChildren(...children: CheckBoxOption[]){
        children.forEach(child => {
            this.children.push(child);
            child.onChange.add(() => this.checkChildren());
        })
    }

    public checkChildren(){
        this.checked = this.children.every(child => child.checked);
        this.indeterminate =
            this.children.some(child => child.checked || child.indeterminate) &&
            !this.checked;
    }

    public getChildren(): CheckBoxOption[]{
        return this.children;
    }

    public set indeterminate(value: boolean){
        if (this._indeterminate === value) return;
        this._indeterminate = value;
        this.onChange.emit(this._checked);
    }

    public get indeterminate(): boolean{
        return this._indeterminate;
    }

    public get checked(): boolean{
        return this._checked;
    }

    public set checked(value: boolean){
        if (this._checked === value) return;
        this._checked = value;
        this.children.forEach(child => child.checked = value);
        this.onChange.emit(this._checked);
    }
}

interface LayeredCheckboxesConfig extends InputImplConfig{
    type: "layeredCheckboxes";
    options: CheckBoxOptionConfig[]
}

interface CheckBoxOptionConfig {
    label: string;
    value: any|CheckBoxOptionConfig[];
}