<template>
    <div :class="cx('root')" :style="rootStyle" role="tree" :aria-multiselectable="selectionMode === 'multiple' || selectionMode === 'checkbox'" v-bind="ptmi('root')">
        <ul :class="cx('subtree', { root: true })" role="group" v-bind="ptm('subtree')">
            <OrganizationChartNode
                v-for="item in normalizedValue"
                :key="item.key"
                :node="item"
                :level="1"
                :templates="$slots"
                :selectable="selectable"
                :collapsible="collapsible"
                :selectionMode="selectionMode"
                :selectionKeys="d_selectionKeys"
                :collapsedKeys="d_collapsedKeys"
                :pt="pt"
                @node-click="onNodeClick"
                @node-toggle="onNodeToggle"
                :unstyled="unstyled"
            />
        </ul>
    </div>
</template>

<script>
import BaseOrganizationChart from './BaseOrganizationChart.vue';
import OrganizationChartNode from './OrganizationChartNode.vue';

export default {
    name: 'OrganizationChart',
    extends: BaseOrganizationChart,
    inheritAttrs: false,
    emits: ['node-unselect', 'node-select', 'update:selectionKeys', 'node-expand', 'node-collapse', 'update:collapsedKeys'],
    data() {
        return {
            d_collapsedKeys: this.collapsedKeys ? { ...this.collapsedKeys } : {},
            d_selectionKeys: this.selectionKeys ? { ...this.selectionKeys } : {}
        };
    },
    watch: {
        collapsedKeys(newValue) {
            this.d_collapsedKeys = newValue ? { ...newValue } : {};
        },
        selectionKeys(newValue) {
            this.d_selectionKeys = newValue ? { ...newValue } : {};
        }
    },
    methods: {
        onNodeClick(node) {
            if (!this.selectionMode) return;

            if (this.selectionMode === 'checkbox') {
                this.onCheckboxNodeClick(node);

                return;
            }

            const key = node.key;
            const isSelected = this.d_selectionKeys[key] === true;
            let next;

            if (isSelected) {
                next = this.selectionMode === 'single' ? {} : { ...this.d_selectionKeys };

                if (this.selectionMode !== 'single') delete next[key];

                this.$emit('node-unselect', node);
            } else {
                next = this.selectionMode === 'single' ? {} : { ...this.d_selectionKeys };
                next[key] = true;

                this.$emit('node-select', node);
            }

            this.d_selectionKeys = next;
            this.$emit('update:selectionKeys', next);
        },
        onCheckboxNodeClick(node) {
            const next = { ...this.d_selectionKeys };
            const selecting = next[node.key]?.checked !== true;
            const descendantKeys = [node.key, ...this.getDescendantKeys(node)];

            descendantKeys.forEach((key) => {
                if (selecting) next[key] = { checked: true, partialChecked: false };
                else delete next[key];
            });

            (this.getAncestorKeys(node.key) ?? [])
                .reverse()
                .forEach((key) => {
                    const ancestor = this.findNodeByKey(key);
                    const childKeys = ancestor?.children?.map((child) => child.key) ?? [];
                    const allChecked = childKeys.every((childKey) => next[childKey]?.checked === true);
                    const someChecked = childKeys.some((childKey) => next[childKey]?.checked === true || next[childKey]?.partialChecked === true);

                    if (allChecked) next[key] = { checked: true, partialChecked: false };
                    else if (someChecked) next[key] = { checked: false, partialChecked: true };
                    else delete next[key];
                });

            this.d_selectionKeys = next;
            this.$emit(selecting ? 'node-select' : 'node-unselect', node);
            this.$emit('update:selectionKeys', next);
        },
        onNodeToggle(node) {
            const key = node.key;
            const next = { ...this.d_collapsedKeys };

            if (next[key]) {
                delete next[key];
                this.$emit('node-expand', node);
            } else {
                next[key] = true;
                this.$emit('node-collapse', node);
            }

            this.d_collapsedKeys = next;
            this.$emit('update:collapsedKeys', next);
        },
        findNodeByKey(key, nodes = this.normalizedValue) {
            for (const node of nodes) {
                if (node.key === key) return node;

                const match = node.children ? this.findNodeByKey(key, node.children) : null;

                if (match) return match;
            }

            return null;
        },
        getDescendantKeys(node) {
            return (node.children ?? []).flatMap((child) => [child.key, ...this.getDescendantKeys(child)]);
        },
        getAncestorKeys(key, nodes = this.normalizedValue, trail = []) {
            for (const node of nodes) {
                if (node.key === key) return trail;

                const path = node.children ? this.getAncestorKeys(key, node.children, [...trail, node.key]) : null;

                if (path) return path;
            }

            return null;
        }
    },
    computed: {
        normalizedValue() {
            if (!this.value) return [];

            return Array.isArray(this.value) ? this.value : [this.value];
        },
        rootStyle() {
            let gap = this.gap ?? 40;

            if (!Array.isArray(gap)) {
                gap = [gap, gap];
            } else if (gap.length === 0) {
                gap = [40, 40];
            } else if (gap.length === 1) {
                gap = [gap[0], gap[0]];
            }

            return {
                '--px-gap-x': `${gap[0]}px`,
                '--px-gap-y': `${gap[1]}px`
            };
        }
    },
    components: {
        OrganizationChartNode
    }
};
</script>
