import {generateNewComponentIds} from './layout/utils.js';

let instanceCount = 0;

export default {

    name: 'field-layout',

    _meta: {
        label: 'Layout',
        info: 'Component based layouts',
        icon: 'layout:assets/icons/layout.svg',
        settings: [
            {
                name: 'allowedComponents',
                type: 'select',
                label: 'Components',
                info: 'List of components to pick from',
                opts: {
                    multiple: true,
                    src: {
                        route: `/layout/components?list=1&v=${App.version}`,
                        map: {
                            value: 'name',
                            label: 'label',
                        }
                    }
                }
            },
        ],
    },

    data() {

        if (!Array.isArray(this.modelValue)) {
            this.$emit('update:modelValue', []);
        }

        return {
            uid: `field-layout-${++instanceCount}`,
            ready: false,
            showPreview: this.preview,
            actionComponent: null,
            actionComponentTimeout: null,
            isDragging: false,
            globalComponents: null,
            eventHandlers: null,
            showPreviewAfterDragging: false,
        }
    },

    mounted() {

        let changed = false;
        (this.val || []).forEach((component, idx) => {
            if (!component.id) {
                this.val.splice(idx, 1, Object.assign({ id: App.utils.nanoid() }, component));
                changed = true;
            }
        });
        if (changed) this.update();

        App.utils.getlayoutComponents().then(components => {

            this.globalComponents = components;
            this.ready = true;
        });

        if (this.level === 0) {

            const rootEl = this.$el.parentNode;

            const mouseoverHandler = function(e) {
                e.stopPropagation();
                this.classList.add('isHovering');
                const parent = this.parentNode.closest('.layout-component-container.isHovering');
                if (parent) parent.classList.remove('isHovering');
            };

            const mouseoutHandler = function(e) {
                this.classList.remove('isHovering');
            };

            KISS.events.on(rootEl, 'mouseover', '.layout-component-container', mouseoverHandler);
            KISS.events.on(rootEl, 'mouseout', '.layout-component-container', mouseoutHandler);

            // Store handlers for cleanup
            this.eventHandlers = {
                rootEl,
                mouseoverHandler,
                mouseoutHandler
            };
        }
    },

    beforeUnmount() {
        // Cleanup event listeners
        if (this.eventHandlers) {
            KISS.events.off(this.eventHandlers.rootEl, 'mouseover', '.layout-component-container', this.eventHandlers.mouseoverHandler);
            KISS.events.off(this.eventHandlers.rootEl, 'mouseout', '.layout-component-container', this.eventHandlers.mouseoutHandler);
        }

        // Clear any pending timeouts
        if (this.actionComponentTimeout) {
            clearTimeout(this.actionComponentTimeout);
        }
    },

    props: {
        modelValue: {
            type: Array,
            default: () => []
        },
        group: {
            type: String,
            default: null
        },
        level: {
            type: Number,
            default: 0
        },
        preview: {
            type: Boolean,
            default: true
        },
        allowedComponents: {
            type: Array,
            default: () => []
        },
    },

    components: {
        gridComponent: Vue.defineAsyncComponent(() =>
            App.utils.import('layout:assets/vue-components/layout/grid.js')
        ),
        rowComponent: Vue.defineAsyncComponent(() =>
            App.utils.import('layout:assets/vue-components/layout/row.js')
        ),
        componentPreview: Vue.defineAsyncComponent(() =>
            App.utils.import('layout:assets/vue-components/layout/component-preview.js')
        )
    },

    watch: {
        preview(val) {
            this.showPreview = val;
        },
        actionComponent(val) {

            if (this.actionComponentTimeout) {
                clearTimeout(this.actionComponentTimeout);
                this.actionComponentTimeout = null;
            }

            if (val) {
                this.actionComponentTimeout = setTimeout(() => {
                    const menu = document.getElementById('popout-layout-component-context-menu');
                    if (menu) {
                        menu.show();
                    }
                    this.actionComponentTimeout = null;
                }, 50);
            }
        }
    },

    computed: {
        val: {
            get() {
                return this.modelValue || [];
            },
            set(value) {
                this.$emit('update:modelValue', value || []);
            }
        }
    },

    methods: {

        addComponent(previous) {

            let components = {},
                restrict = Array.isArray(this.allowedComponents) && this.allowedComponents.length ? this.allowedComponents : Object.keys(this.globalComponents);

            Object.keys(this.globalComponents).forEach(name => {
                if (restrict.includes(name)) components[name] = this.globalComponents[name];
            });

            VueView.ui.offcanvas('layout:assets/vue-components/layout/component-picker.js', {
                components,
                layout: this.val,
                insertPosition: previous ? this.val.indexOf(previous) : -1,
                globalComponents: this.globalComponents
            }, {

                select: component => {
                    this.update();
                },

                selectFromClipboard: component => {

                    generateNewComponentIds(component);

                    if (previous) {
                        this.val.splice(this.val.indexOf(previous) + 1, 0, component);
                    } else {
                        this.val.push(component);
                    }

                    this.update();
                }

            }, {flip: true, size: 'medium'})

        },

        edit(item) {

            if (!item || typeof item !== 'object') {
                return;
            }

            if (item.shared) {
                window.open(this.$routeUrl(`/layout-components/shared/component/${item.shared}`), '_blank');
                return;
            }

            if (!item.component || !this.globalComponents) {
                return;
            }

            let meta = this.globalComponents[item.component];

            if (!meta) {
                App.ui.notify(`Component definition for "${item.component}" not found`, 'error');
                return;
            }

            if (!item.meta || Array.isArray(item.meta)) {
                item.meta = {};
            }

            VueView.ui.modal('layout:assets/vue-components/layout/component-edit.js', {component: item, mode: 'edit', globalComponents: this.globalComponents}, {

                save: component => {
                    Object.assign(item, component);
                }

            }, {size: meta.opts?.['dialog.size'] || 'large'})
        },

        remove(item) {
            const index = this.val.indexOf(item);
            if (index > -1) {
                this.val.splice(index, 1);
                this.update();
            }
        },

        duplicate(item) {

            if (!item || typeof item !== 'object') {
                return;
            }

            const index = this.val.indexOf(item);
            if (index === -1) {
                return;
            }

            try {
                let cItem = JSON.parse(JSON.stringify(item));
                generateNewComponentIds(cItem);
                this.val.splice(index + 1, 0, cItem);
                this.update();
                this.refreshComponentPreview();
            } catch (e) {
                console.error('Failed to duplicate component:', e);
            }
        },

        paste(item) {

            if (!item || typeof item !== 'object') {
                return false;
            }

            const index = this.val.indexOf(item);
            if (index === -1) {
                return false;
            }

            let history = [];

            try {
                history = JSON.parse(App.storage.get('layout.field.clipboard.history', '[]', true));
            } catch (e) {
                history = [];
            }

            if (!history.length) {
                return;
            }

            if (history.length > 1) {
                
                this.$offcanvas('layout:assets/vue-components/layout/clipboard-manager.js', {
                    
                }, {
                    paste: (component) => {

                        let cItem = JSON.parse(JSON.stringify(component));
                        generateNewComponentIds(cItem);
                        this.val.splice(index + 1, 0, cItem);
                        this.update();
                        this.refreshComponentPreview();
                    }
                }, {flip: true, size: 'medium'});

                return;
            }

            let cItem = history[0].component;

            if (!cItem || !this.globalComponents || !this.globalComponents[cItem.component]) {
                return false;
            }

            try {
                cItem = JSON.parse(JSON.stringify(cItem));
                generateNewComponentIds(cItem);
                this.val.splice(index + 1, 0, cItem);
                this.update();
                this.refreshComponentPreview();
            } catch (e) {
                console.error('Failed to paste component:', e);
                return false;
            }
        },

        clipboardComponent() {
            try {
                let history = JSON.parse(App.storage.get('layout.field.clipboard.history', '[]', true));
                return history.length ? history[0].component : null;
            } catch (e) {
                return null;
            }
        },

        copy(item) {

            if (!item || typeof item !== 'object') {
                return;
            }

            try {
                let cItem = JSON.parse(JSON.stringify(item));
                
                generateNewComponentIds(cItem);
                
                let history = [];

                try {
                    history = JSON.parse(App.storage.get('layout.field.clipboard.history', '[]', true));
                } catch(e) {}

                // Deduplicate history
                const clean = (i) => {
                    let c = JSON.parse(JSON.stringify(i));
                    const walk = (o) => {
                        if (o.id) delete o.id;
                        if (o.children && Array.isArray(o.children)) o.children.forEach(walk);
                    }
                    walk(c);
                    return JSON.stringify(c);
                };

                const currentHash = clean(cItem);
                
                // Remove existing instances of the same component
                history = history.filter(h => clean(h.component) !== currentHash);

                history.unshift({
                    component: cItem,
                    label: item.label || 'Unknown',
                    time: Date.now()
                });

                if (history.length > 10) {
                    history = history.slice(0, 10);
                }

                App.storage.set('layout.field.clipboard.history', JSON.stringify(history));
                App.storage.set('layout.field.clipboard.component', JSON.stringify(cItem));

                App.storage.set('layout.field.clipboard.history', JSON.stringify(history));
                App.storage.set('layout.field.clipboard.component', JSON.stringify(cItem));

                App.ui.notify('Component copied!');
                App.trigger('layout.field.clipboard.change', {component: cItem, items: history});
            } catch (e) {
                console.error('Failed to copy component:', e);
                App.ui.notify('Failed to copy component', 'error');
            }
        },

        change(evt) {
            this.update();
        },

        update() {
            this.$emit('update:modelValue', this.val)
        },

        hasPreview(component) {
            if (
                this.globalComponents[component.component] &&
                (
                    // template preview
                    this.globalComponents[component.component].preview ||
                    //component preview Url
                    (
                        this.globalComponents[component.component].opts &&
                        this.globalComponents[component.component].opts.previewComponent
                    )
                )
            ) {
                return true;
            }

            return false;
        },

        componentType(element) {
            const meta = this.globalComponents && element ? this.globalComponents[element.component] : null;
            const explicit = meta && meta.type ? meta.type : null;
            if (explicit) return explicit; // 'grid' | 'row' | custom
            // fallback by name for built-ins
            if (element && element.component === 'grid') return 'grid';
            if (element && element.component === 'row') return 'row';
            return meta && meta.children ? 'container' : 'standard';
        },

        getAllowedComponents(component) {

            let allowedComponents = this.globalComponents[component]?.allowedComponents;

            return Array.isArray(allowedComponents) && allowedComponents.length ? allowedComponents : this.allowedComponents;
        },

        // hack for fixing preview after duplicate/paste component
        refreshComponentPreview() {

            this.showPreview = false;

            setTimeout(() => {
                this.showPreview = true;
            }, 10);
        },

        dragStart() {

            this.isDragging = true;
            this.showPreviewAfterDragging = false;

            if (!this.showPreview) {
                return;
            }

            this.showPreviewAfterDragging = true;
            this.$emit('hide-preview')
            this.showPreview = false;

            document.documentElement.classList.add('app-sortable-active');
        },

        dragEnd(e) {

            this.isDragging = false;

            if (this.showPreviewAfterDragging) {
                this.$emit('show-preview');
                this.showPreview = true;
            }

            const componentId = e.clone && e.clone.getAttribute('data-component-id');

            if (componentId) {

                setTimeout(() => {

                    const component = document.querySelector(`[data-component-id="${CSS.escape(componentId)}"]`);

                    if (component && !KISS.utils.isInViewport(component, true)) {
                        component.scrollIntoView({behavior: 'smooth', block: 'start'});
                    }
                }, 100);
            }

            document.documentElement.classList.remove('app-sortable-active');
        },

        // Movement helpers to ensure updates are emitted
        moveUp(index) {
            this.val.splice(index - 1, 0, this.val.splice(index, 1)[0]);
            this.update();
        },
        moveDown(index) {
            this.val.splice(index, 2, this.val[index + 1], this.val[index]);
            this.update();
        },
        moveFirst(item) {
            this.val.unshift(this.val.splice(this.val.indexOf(item), 1)[0]);
            this.update();
        },
        moveLast(item) {
            this.val.push(this.val.splice(this.val.indexOf(item), 1)[0]);
            this.update();
        },

    },

    template: /*html*/`
        <div field="layout" v-if="ready">
            <div :class="{'kiss-position-relative': (Array.isArray(val) && !val.length)}" :style="{minHeight:(Array.isArray(val) && !val.length) ? '100px':''}">
                <vue-draggable
                    v-model="val"
                    :group="group || uid"
                    @change="change"
                    @start="dragStart"
                    @end="dragEnd"
                    handle=".lm-handle"
                    class="field-layout-dragarea"
                    :class="{'isDragging':isDragging}"
                    :swapThreshold="0.65"
                    :animation="100"
                >
                    <div class="layout-component-container kiss-position-relative kiss-margin-small" :data-component-id="element.id || ''" v-for="(element, index) in val">
                        <kiss-card
                            class="layout-component kiss-visible-toggle"
                            theme="bordered contrast"
                            hover="contrast shadow"
                        >
                            <div class="layout-component-topbar kiss-padding-small kiss-flex kiss-flex-middle" gap="small">
                                <a class="lm-handle kiss-margin-small-end kiss-cursor-grab" :class="{'kiss-color-muted': !element.shared}"><icon>{{ element.shared ? 'share' : 'drag_handle' }}</icon></a>
                                <div class="kiss-flex-1 kiss-size-xsmall kiss-text-bold kiss-text-truncate">
                                    <a class="kiss-link-muted kiss-flex kiss-flex-middle" :class="{'kiss-color-muted': element.hidden}" @click="edit(element)">
                                        <icon class="kiss-margin-xsmall-end" v-if="element.hidden">visibility_off</icon>
                                        <span>{{ element.label }}</span>
                                    </a>
                                </div>
                                <span class="kiss-color-muted kiss-text-caption kiss-size-xsmall kiss-invisible-hover">{{ element.component }}</span>

                                <div class="kiss-button-group kiss-invisible-hover">
                                    <a class="kiss-button kiss-button-small" :title="t('Move up')" v-if="val.indexOf(element) !== 0" @click="moveUp(index)"><icon>arrow_upward</icon></a>
                                    <a class="kiss-button kiss-button-small" :title="t('Move down')" v-if="val.indexOf(element) !== val.length - 1" @click="moveDown(index)"><icon>arrow_downward</icon></a>
                                </div>

                                <a class="kiss-invisible-hover kiss-color-muted" @click="duplicate(element)" :title="t('Duplicate')"><icon>control_point_duplicate</icon></a>
                                <a class="kiss-invisible-hover kiss-color-muted" @click="remove(element)" :title="t('Delete')"><icon>delete</icon></a>
                                <a class="kiss-invisible-hover" @click="actionComponent = element"><icon>more_horiz</icon></a>
                            </div>
                            <div class="kiss-padding-small field-layout-children" v-if="componentType(element) == 'container'">
                                <field-layout v-model="element.children" :group="group || uid" :allowedComponents="getAllowedComponents(element.component)" :level="level + 1" :preview="showPreview" @hide-preview="dragStart" @show-preview="showPreview = true"></field-layout>
                            </div>
                            <grid-component class="field-layout-component-grid kiss-margin-small kiss-padding-small" v-model="element" :group="group || uid" :allowedComponents="getAllowedComponents(element.component)" :level="level + 1" :preview="showPreview" v-if="componentType(element) == 'grid'"></grid-component>
                            <row-component class="field-layout-component-row kiss-margin-small kiss-padding-small" v-model="element" :group="group || uid" :allowedComponents="getAllowedComponents(element.component)" :level="level + 1" :preview="showPreview" v-if="componentType(element) == 'row'"></row-component>
                            <div class="kiss-bgcolor-contrast" v-if="!element.hidden && showPreview && !element.children && hasPreview(element)">
                                <component class="kiss-margin-xsmall-top kiss-size-small" :is="'component-preview'" :component="element" :globalComponents="globalComponents"></component>
                            </div>
                        </kiss-card>
                        <a class="field-layout-btn-add-after animated fadeIn faster" :title="t('Add component')" @click="addComponent(element)"></a>

                    </div>
                </vue-draggable>

                <div class="field-layout-dragarea-empty kiss-cover kiss-flex kiss-flex-center kiss-flex-middle" v-if="Array.isArray(val) && !val.length">
                    <div class="kiss-color-muted kiss-align-center">
                        <kiss-svg :src="App.base('layout:assets/icons/layout.svg')" width="50" height="50"></kiss-svg>
                        <div><icon size="larger">add_box</icon></div>
                    </div>
                    <a class="kiss-cover" @click="addComponent()"></a>
                </div>
            </div>

            <div class="kiss-button-group kiss-margin-small" v-if="!level">
                <button type="button" class="kiss-button kiss-button-small" @click="addComponent()">{{ t('Add component') }}</button>
                <button type="button" class="kiss-button kiss-button-small" @click="showPreview = !showPreview">{{ showPreview ? t('Hide preview') : t('Show preview') }}</button>
            </div>

        </div>
        <app-loader v-else size="small"></app-loader>

        <teleport to="body" v-if="actionComponent">
            <kiss-popout id="popout-layout-component-context-menu" @popoutclose="actionComponent = null">
                <kiss-content>
                    <kiss-navlist>
                        <ul>
                            <li class="kiss-nav-header">{{ t('Component actions') }}</li>
                            <li>
                                <a class="kiss-flex kiss-flex-middle" @click="edit(actionComponent)" v-if="!actionComponent.shared">
                                    <icon class="kiss-margin-small-end">create</icon>
                                    {{ t('Edit') }}
                                </a>
                            </li>
                            <li>
                                <a class="kiss-flex kiss-flex-middle" @click="duplicate(actionComponent)">
                                    <icon class="kiss-margin-small-end">control_point_duplicate</icon>
                                    {{ t('Duplicate') }}
                                </a>
                            </li>
                            <li class="kiss-nav-divider"></li>
                            <li>
                                <a class="kiss-flex kiss-flex-middle" @click="copy(actionComponent)">
                                    <icon class="kiss-margin-small-end">content_copy</icon>
                                    {{ t('Copy') }}
                                </a>
                            </li>
                            <li>
                                <a class="kiss-flex kiss-flex-middle" :class="{'kiss-color-muted kiss-disabled': !clipboardComponent()}" @click="paste(actionComponent)">
                                    <icon class="kiss-margin-small-end">content_paste</icon>
                                    {{ t('Paste') }}
                                </a>
                            </li>
                            <li class="kiss-nav-divider" v-if="val.length > 1"></li>

                            <li v-if="val.indexOf(actionComponent) !== 0">
                                <a class="kiss-flex kiss-flex-middle" @click="moveFirst(actionComponent)">
                                    <icon class="kiss-margin-small-end">arrow_upward</icon>
                                    {{ t('Move first') }}
                                </a>
                            </li>
                            <li v-if="val.indexOf(actionComponent) !== val.length - 1">
                                <a class="kiss-flex kiss-flex-middle" @click="moveLast(actionComponent)">
                                    <icon class="kiss-margin-small-end">arrow_downward</icon>
                                    {{ t('Move last') }}
                                </a>
                            </li>
                            <li class="kiss-nav-divider"></li>
                            <li>
                                <a class="kiss-color-danger kiss-flex kiss-flex-middle" @click="remove(actionComponent)">
                                    <icon class="kiss-margin-small-end">delete</icon>
                                    {{ t('Delete') }}
                                </a>
                            </li>
                        </ul>
                    </kiss-navlist>
                </kiss-content>
            </kiss-popout>
        </teleport>
    `
}
