<template>
    <div class="hy-split-view" :class="[direction, handleType, { isDragging }]">
        <div class="section" v-for="(slotName, index) in slots" :key="index" :style="styles[index]">
            <div class="content hy-remove-leading-trailing-margins">
                <slot :name="slotName" />
            </div>

            <!-- Drag handle between sections -->
            <div
                class="dragHandle"
                :class="[handleType, { disabled }]"
                v-if="index !== slots.length - 1"
                @pointerdown="startResize(index, $event)"
            >
                <div class="visual"></div>
            </div>
        </div>
    </div>
</template>

<script>
// TODO: Add automatic container sections
// TODO: does $slots update when children are added/removed?
// TODO: Improve programmatic interface: setSizes, getSizes, emit events to parent, etc.

export default {
    props: {
        minSizes: {
            type: Array,
            default: null,
        },
        maxSizes: {
            type: Array,
            default: null,
        },
        initialSizes: {
            type: Array,
            default: null,
        },
        direction: {
            type: String,
            default: "row",
            validator: (value) => {
                return ["row", "column"].includes(value);
            },
        },
        handleType: {
            type: String,
            default: "normal",
            validator: (value) => {
                return ["normal", "normal-transparent", "slim"].includes(value);
            },
        },
        disabled: {
            type: Boolean,
            default: false,
        },
        doBSL: {
            type: Boolean,
            default: true,
        },
    },
    data() {
        return {
            sections: [], // Stores flex-basis percentages for each section
            initialPos: null, // Stores initial mouse position
            isDragging: false, // To track if the drag is happening
            currentHandleIndex: null, // Stores the current handle index being dragged
        };
    },
    computed: {
        slots() {
            return Object.keys(this.$slots);
        },
        styles() {
            return this.slots.map((_, index) => this.calculateStyle(index));
        },
        dragHandleSize() {
            if (this.handleType == "normal") return 24 + 10; // 10px margin
            if (this.handleType == "normal-transparent") return 24 + 10; // 10px margin
            if (this.handleType == "slim") return 14; // has no margin
        },
        isRow() {
            return this.direction == "row";
        },
    },
    methods: {
        // Initialize section sizes
        initializeSections() {
            let initialSizes = this.initialSizes ? [...this.initialSizes] : null;

            // If no initial sizes are provided or the array length is not equal to the number of sections, complete the array with null values
            if (!initialSizes || initialSizes.length != this.slots.length) {
                initialSizes = new Array(this.slots.length);

                for (let i = 0; i < this.slots.length; i++) {
                    initialSizes[i] = this.initialSizes?.[i] ?? null;
                }
            }

            // Parse initial sizes to percentage values
            initialSizes = initialSizes.map((size) => this.parseSize(size, null));

            // Check if the total sum of initial sizes is not greater than 100%
            let totalInitialSize = initialSizes.reduce((acc, size) => acc + size, 0);
            if (totalInitialSize > 100) {
                console.error(
                    "HyperID Error at HySplitView: The sum of initial sizes cannot be greater than 100%. Defaulting to equal distribution of items.",
                );
                initialSizes = new Array(this.slots.length).fill(null);
            }

            // Check them against the min/max constraints and resolve them to null if they don't fit
            initialSizes = initialSizes.map((size, i) => {
                let doesFitMinConstraint = size >= this.parseSize(this.minSizes?.[i], 0);
                let doesFitMaxConstraint = size <= this.parseSize(this.maxSizes?.[i], 100);

                if (doesFitMaxConstraint && doesFitMinConstraint) return size;
                else return null;
            });

            // Count number of sections without a (valid) initial size
            let numberOfSectionsWithoutInitialSize = initialSizes.reduce((acc, size) => (size == null ? acc + 1 : acc), 0);

            // Calculate size of each section without an initial size => Equal distribution of the remaining space
            let sumOfValidInitialSizes = initialSizes.reduce((acc, size) => (size != null ? acc + size : acc), 0);
            let sizeOfEachSectionWithoutInitialSize = (100 - sumOfValidInitialSizes) / numberOfSectionsWithoutInitialSize;

            // Assign the flex-basis values for each section
            for (let i = 0; i < this.slots.length; i++) {
                if (initialSizes[i] != null) {
                    this.sections.push(initialSizes[i]);
                } else {
                    this.sections.push(sizeOfEachSectionWithoutInitialSize);
                }
            }
        },

        // Starts the drag interaction
        startResize(index, event) {
            if (this.disabled) return;

            if (this.doBSL) this.hy_bsl_manager.push(this.$el);

            this.isDragging = true;
            this.currentHandleIndex = index;
            this.initialPos = this.isRow ? event.clientX : event.clientY;

            // Listen for mouse move and mouse up events
            document.addEventListener("pointermove", this.handleMouseMove);
            document.addEventListener("pointerup", this.stopResize);
        },

        // Handles the mouse move event for resizing
        handleMouseMove(event) {
            if (!this.isDragging) return;

            let totalMouseMoved = this.isRow ? event.clientX : event.clientY;
            let deltaMoved = totalMouseMoved - this.initialPos;

            this.resizeSections(this.currentHandleIndex, deltaMoved);
            this.initialPos = totalMouseMoved;
        },

        // Adjusts the flex-basis of adjacent sections based on the drag movement (deltaMoved, provided in pixels)
        resizeSections(index, deltaMoved) {
            // NOTE: percentageChange is negative if dragging to the left and positive if dragging to the right

            let containerSize = this.isRow ? this.$el.offsetWidth : this.$el.offsetHeight;

            let percentageChange = (deltaMoved / containerSize) * 100;

            percentageChange = this.respectMinMaxConstraints(index, percentageChange);

            // Update calculated section sizes
            this.sections[index] += percentageChange;
            this.sections[index + 1] -= percentageChange;
        },

        // Stops the drag interaction
        stopResize() {
            if (this.doBSL) this.hy_bsl_manager.remove(this.$el);

            this.isDragging = false;

            document.removeEventListener("pointermove", this.handleMouseMove);
            document.removeEventListener("pointerup", this.stopResize);
        },

        respectMinMaxConstraints(index, percentageChange) {
            // Calculate the minimum drag handle size
            let minDragHandleSize = this.pixelToPercentage(this.dragHandleSize); // Minimum size for the drag handle

            // MIN SIZE CONSTRAINTS

            // Calculate the minimum sizes for the sections
            let minLeftContent = this.parseSize(this.minSizes?.[index], 0); // Minimum size for the left content (excluding drag handle)
            let minRightContent = this.parseSize(this.minSizes?.[index + 1], 0);

            let minSizeForLeftSection = minLeftContent + minDragHandleSize; // Minimum size for the left section
            let minSizeForRightSection = minRightContent + (this.isLastSection(index + 1) ? 0 : minDragHandleSize); // Only add minDragHandleSize if it's not the last section

            // Check whether the sections are within the min size limits => if not, adjust the percentageChange to fit the min size requirements
            if (this.sections[index] + percentageChange < minSizeForLeftSection) {
                return minSizeForLeftSection - this.sections[index]; // Calculate gap to reach min size
            }

            if (this.sections[index + 1] - percentageChange < minSizeForRightSection) {
                return this.sections[index + 1] - minSizeForRightSection; // Calculate gap to reach min size
            }

            // MAX SIZE CONSTRAINTS

            // Calculate the maximum sizes for the sections
            let maxLeftContent = this.parseSize(this.maxSizes?.[index], 100);
            let maxRightContent = this.parseSize(this.maxSizes?.[index + 1], 100);

            let maxSizeForLeftSection = maxLeftContent + minDragHandleSize;
            let maxSizeForRightSection = maxRightContent + (this.isLastSection(index + 1) ? 0 : minDragHandleSize); // Only add minDragHandleSize if it's not the last section

            // Check whether the sections are withinthe max size limits => if not, adjust the percentageChange to fit the max size requirements
            if (this.sections[index] + percentageChange > maxSizeForLeftSection) {
                return maxSizeForLeftSection - this.sections[index]; // Calculate gap to reach max size
            }

            if (this.sections[index + 1] - percentageChange > maxSizeForRightSection) {
                return this.sections[index + 1] - maxSizeForRightSection; // Calculate gap to reach max size
            }

            // NO MIN/MAX SIZE CONSTRAINTS
            return percentageChange;
        },

        // Calculate the minWidth/minHeight for each section
        calculateStyle(index) {
            let property = this.isRow ? "Width" : "Height";
            let considerableDragHandleSize = this.isLastSection(index) ? 0 : this.dragHandleSize; // No drag handle for the last section

            return {
                flexBasis: this.sections[index] + "%",
                [`min${property}`]: `${considerableDragHandleSize}px`,
            };
        },

        // Parse size values (px, %) to number percentages
        parseSize(size, defaultToIfInvalid = 0) {
            if (typeof size == "number") return size;
            if (!size || typeof size !== "string") return defaultToIfInvalid;

            // Extract value and mode
            let value = parseInt(size);
            let mode = size.replace(/[0-9]/g, "");

            // Compute the correct percentage based on the mode
            if (mode == "%") return value;
            if (mode == "px") return this.pixelToPercentage(value);
            return defaultToIfInvalid;
        },

        // Convert pixel value to a percentage
        pixelToPercentage(value) {
            let containerSize = this.isRow ? this.$el.offsetWidth : this.$el.offsetHeight;

            return (value / containerSize) * 100;
        },

        isLastSection(index) {
            return index == this.slots.length - 1;
        },
    },
    mounted() {
        this.initializeSections();
    },
    beforeUnmount() {
        if (this.doBSL) this.hy_bsl_manager.remove(this.$el);

        document.removeEventListener("pointermove", this.handleMouseMove);
        document.removeEventListener("pointerup", this.stopResize);
    },
};
</script>

<style lang="scss">
.hy-split-view {
    display: flex;
    width: 100%;
    height: 100%;

    // FLEX DIRECTION
    &.row {
        flex-direction: row;

        & > .section {
            flex-direction: row;

            & > .dragHandle {
                max-width: var(--dragHandleSize);
                cursor: col-resize;

                &.normal,
                &.normal-transparent {
                    margin: 0 5px;

                    & > .visual {
                        transform: translate(-50%, -50%);
                    }
                }

                &.slim {
                    & > .visual {
                        left: 50%;
                        transform: translateX(-50%);
                        height: 100%;
                        width: 4px;
                    }
                }
            }
        }
    }

    &.column {
        flex-direction: column;

        & > .section {
            flex-direction: column;

            & > .dragHandle {
                max-height: var(--dragHandleSize);
                cursor: row-resize;

                &.normal,
                &.normal-transparent {
                    margin: 5px 0;

                    & > .visual {
                        transform: translate(-50%, -50%) rotate(90deg);
                    }
                }

                &.slim {
                    & > .visual {
                        top: 50%;
                        height: 4px;
                        width: 100%;
                        transform: translateY(-50%);
                    }
                }
            }
        }
    }

    // DISABLE TEXT SELECTION
    &.isDragging {
        -webkit-user-select: none;
        -ms-user-select: none;
        user-select: none;
    }

    // ELEMENT STYLES
    & > .section {
        display: flex;
        position: relative;
        overflow: hidden;

        & > .content {
            flex-grow: 1;
            overflow: hidden;
        }

        & > .dragHandle {
            top: 0;
            right: 0;
            bottom: 0;
            z-index: 1;
            height: 100%;
            width: 100%;
            color: var(--hy-font-color-gray);
            min-width: var(--dragHandleSize);
            min-height: var(--dragHandleSize);
            position: relative;

            &.disabled {
                cursor: not-allowed !important;
            }

            // Apply transitions
            &,
            & > .visual {
                transition: var(--hy-element-transition);
                transition-property: background-color, color, border, filter;
            }

            // Drag handle styles
            &.normal,
            &.normal-transparent {
                border-radius: var(--hy-element-border-radius);
                border: var(--hy-element-border-secondary);

                &:not(.normal) {
                    border-color: transparent;
                }

                &:not(.normal-transparent) {
                    border-style: dashed;
                    background-color: var(--hy-variant-ghost-bg-color);
                }

                &:not(.disabled) {
                    &:hover {
                        background-color: var(--hy-variant-ghost-hover-color);
                    }

                    &:focus,
                    &:active {
                        border-style: solid;
                        border: var(--hy-element-border-accent);
                    }
                }

                &.disabled {
                    filter: opacity(50%);
                }

                & > .visual {
                    position: absolute;
                    top: 50%;
                    left: 50%;
                    border-radius: 999px;
                    background-color: var(--hy-element-border-secondary-color);
                    height: 40px;
                    width: 5px;
                }
            }

            &.slim {
                border: none;

                & > .visual {
                    position: relative;
                    border-radius: 999px;
                    background-color: var(--hy-variant-ghost-bg-color);
                }

                &:not(.disabled) {
                    &:hover,
                    &:focus,
                    &:active {
                        & > .visual {
                            background-color: var(--hy-accent-color);
                        }
                    }
                }
            }
        }
    }

    // Drag handle size for normal and slim style
    &.normal,
    &.normal-transparent {
        --dragHandleSize: 24px;
    }

    &.slim {
        --dragHandleSize: 14px;
    }
}
</style>
