<template>
    <section class="fmt-panel" :class="{ 'fmt-panel--collapsed': collapsed }">
        <header class="fmt-panel__header">
            <div class="fmt-panel__header-row">
                <h2 v-show="!collapsed">{{ __("Fields") }}</h2>
                <button
                    type="button"
                    class="fmt-panel__collapse-toggle"
                    :aria-expanded="!collapsed"
                    :aria-label="collapsed ? __('Expand fields panel', 'formatrica') : __('Collapse fields panel', 'formatrica')"
                    :title="collapsed ? __('Expand fields panel', 'formatrica') : __('Collapse fields panel', 'formatrica')"
                    @click="emit('update:collapsed', !collapsed)"
                >
                    <img
                        class="fmt-panel__collapse-icon"
                        :src="collapsed ? ICON_CHEVRON_RIGHT : ICON_CHEVRON_LEFT"
                        alt=""
                        aria-hidden="true"
                        draggable="false"
                    />
                </button>
            </div>
            <p v-show="!collapsed">{{ __("Drag a field into your form or click to add it.") }}</p>
            <div v-show="!collapsed" class="fmt-panel__search">
                <input
                    type="search"
                    class="fmt-panel__search-input"
                    v-model="searchQuery"
                    :placeholder="__('Search fields…', 'formatrica')"
                    :aria-label="__('Search fields', 'formatrica')"
                />
            </div>
        </header>
        <ul ref="paletteRef" class="fmt-panel__list">
            <!-- v-show (not a filtered v-for): SortableJS keeps references to
                 these nodes across drags; removing them from the DOM on every
                 keystroke desyncs it from Vue and freezes the search. -->
            <li
                v-show="matchCount === 0"
                class="fmt-panel__empty"
            >
                {{ __("No fields match your search.", "formatrica") }}
            </li>
            <li
                v-for="field in library"
                v-show="matchesSearch(field)"
                :key="field.type"
                class="fmt-panel__item"
                :class="{ 'fmt-panel__item--locked': isLocked(field) }"
                :data-field-type="field.type"
                :title="field.label"
                @click="handleClick(field)"
            >
                <img
                    class="fmt-panel__item-icon"
                    :src="getIcon(field.type)"
                    alt=""
                    aria-hidden="true"
                    draggable="false"
                />
                <div class="fmt-panel__item-body">
                    <div class="fmt-panel__item-title">
                        {{ field.label }}
                        <span v-if="isLocked(field)" class="fmt-panel__item-pro">{{ __("Pro") }}</span>
                    </div>
                    <div class="fmt-panel__item-desc">{{ field.description }}</div>
                </div>
            </li>
        </ul>
    </section>
</template>

<script setup>
import { computed, onMounted, onBeforeUnmount, ref } from "vue";
import Sortable from "sortablejs";
import { fieldLibrary } from "@/admin/constants/fields";
import { __ } from "@/utils/i18n";
import { useCapabilities } from "@/admin/composables/useCapabilities";
import { iconUrlForType } from "@/admin/fields/icons";
// Vite define — a literal after build, so the minifier deletes dead branches.
// eslint-disable-next-line no-undef
const PRO_UI = __FMT_PRO_UI__;
import ICON_CHEVRON_LEFT from "../../../assets/icons/chevron-left.svg";
import ICON_CHEVRON_RIGHT from "../../../assets/icons/chevron-right.svg";

// collapsed = icon-rail mode. State lives in App.vue (the parent owns the layout
// grid, so the rail width is its decision); we just render the boolean and emit
// update:collapsed from the toggle.
defineProps({
    collapsed: { type: Boolean, default: false },
});

const emit = defineEmits(["add-field", "update:collapsed"]);
// Free bundle: Pro field types are absent from the palette — the Free
// entrypoint never registers them. The filter is belt-and-suspenders.
const library = PRO_UI ? fieldLibrary() : fieldLibrary().filter((f) => !f.capability);
const paletteRef = ref(null);
let sortableInstance = null;

const { has: hasCapability } = useCapabilities();
const isLocked = (field) => !!field.capability && !hasCapability(field.capability);

// Free-text filter: substring match across label + description + type id
// (case-insensitive). Locked Pro fields stay in results (visible-but-locked).
// Filtering toggles visibility (v-show) rather than the rendered list —
// see the template note about SortableJS.
const searchQuery = ref("");
const matchesSearch = (field) => {
    const q = searchQuery.value.trim().toLowerCase();
    if (q === "") {
        return true;
    }
    const haystack = `${field.label} ${field.description || ""} ${field.type}`.toLowerCase();
    return haystack.includes(q);
};
const matchCount = computed(() => library.filter(matchesSearch).length);

// Guards a click firing right after a drag ends (Sortable's clone drop also
// dispatches a click on the source tile). Set on drag-start, cleared shortly
// after drag-end.
let wasDragged = false;

const handleClick = (field) => {
    if (wasDragged || isLocked(field)) {
        return;
    }
    emit("add-field", field.type);
};

const getIcon = (type) => iconUrlForType(type);

onMounted(() => {
    if (!paletteRef.value) {
        return;
    }

    sortableInstance = Sortable.create(paletteRef.value, {
        group: {
            name: "fmt-fields",
            pull: "clone",
            put: false,
        },
        sort: false,
        animation: 160,
        easing: "cubic-bezier(0.2, 0, 0, 1)",
        draggable: ".fmt-panel__item",
        filter: ".fmt-panel__item--locked",
        ghostClass: "fmt-panel__item--ghost",
        dragClass: "fmt-panel__item--drag",
        // Mobile: wait 200ms before a press becomes a drag so a finger swipe
        // scrolls the palette instead of grabbing a tile. Desktop stays instant.
        delay: 200,
        delayOnTouchOnly: true,
        touchStartThreshold: 5,
        setData(dataTransfer, dragEl) {
            dataTransfer.setData("text/plain", dragEl.dataset.fieldType || "");
        },
        onStart() {
            wasDragged = true;
        },
        onEnd(evt) {
            // After a clone-pull drop, Sortable leaves ITS clone in this list
            // and lets Vue's original node be carried off (the canvas detaches
            // it). Vue still thinks its node lives here, so the next re-render
            // patches against a detached node and throws ("parentNode is
            // null"), freezing the palette search. Swap the original back in
            // so the DOM matches Vue's virtual DOM exactly.
            if (evt.clone && evt.clone.parentNode && evt.item !== evt.clone) {
                evt.clone.parentNode.replaceChild(evt.item, evt.clone);
            }
            setTimeout(() => {
                wasDragged = false;
            }, 50);
        },
    });
});

onBeforeUnmount(() => {
    if (sortableInstance) {
        sortableInstance.destroy();
        sortableInstance = null;
    }
});
</script>

<style scoped>
.fmt-panel {
    background: #fff;
    border: 1px solid #dcdcde;
    border-radius: 10px;
    padding: 16px;
    display: flex;
    flex-direction: column;
    height: 100%;
}

.fmt-panel__header-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.fmt-panel__header h2 {
    font-size: 1.25rem;
    margin: 0;
}

.fmt-panel__header p {
    color: #6c757d;
    margin: 4px 0 12px;
    font-size: 1rem;
}

.fmt-panel__collapse-toggle {
    appearance: none;
    background: transparent;
    border: 1px solid #dcdcde;
    border-radius: 6px;
    color: #646970;
    cursor: pointer;
    width: 28px;
    height: 28px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    transition: background 0.15s ease;
}

.fmt-panel__collapse-toggle:hover {
    background: #f0f0f1;
}

.fmt-panel__collapse-icon {
    width: 16px;
    height: 16px;
    display: block;
    pointer-events: none;
}

.fmt-panel__search {
    margin: 0 0 12px;
}

.fmt-panel__search-input {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid #dcdcde;
    border-radius: 6px;
    background: #fff;
    font-size: 0.95rem;
}

.fmt-panel__empty {
    list-style: none;
    text-align: center;
    color: #6c757d;
    font-size: 0.9rem;
    padding: 16px 8px;
}

/* Collapsed = icon rail: hide header text, search, and tile bodies; only the
   icons remain for drag/click. The rail width is set by App.vue's layout grid. */
.fmt-panel--collapsed {
    padding: 12px 4px;
}

.fmt-panel--collapsed .fmt-panel__header-row {
    justify-content: center;
    margin-bottom: 8px;
}

.fmt-panel--collapsed .fmt-panel__item-body {
    display: none;
}

.fmt-panel--collapsed .fmt-panel__item {
    grid-template-columns: 1fr;
    padding: 8px 4px;
    justify-items: center;
}

.fmt-panel__item--drag {
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
    opacity: 0.95;
}

.fmt-panel__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 10px;
    /* Critical: allow the list to take remaining vertical space and scroll */
    flex: 1 1 auto;
    min-height: 0; /* allow shrinking inside a constrained parent */
    overflow-y: auto;
    /* Pack rows at the top so a search-narrowed list doesn't balloon each tile
       (grid's default align-content:stretch distributes spare height). */
    align-content: start;
}

.fmt-panel__item {
    border: 1px solid #dcdcde;
    border-radius: 8px;
    padding: 12px;
    cursor: grab;
    background: #fafafa;
    transition:
        border-color 0.2s ease,
        box-shadow 0.2s ease;
    display: grid;
    grid-template-columns: 24px 1fr;
    align-items: start;
    gap: 10px;
}

.fmt-panel__item:hover {
    border-color: #2271b1;
    box-shadow: 0 0 0 1px rgba(34, 113, 177, 0.2);
}

.fmt-panel__item-title {
    font-weight: 600;
    font-size: 1rem;
}

.fmt-panel__item-desc {
    color: #6c757d;
    font-size: 0.95rem;
}

.fmt-panel__item--ghost {
    opacity: 0.4;
}

.fmt-panel__item-icon {
    width: 20px;
    height: 20px;
    margin-top: 2px;
    opacity: 0.9;
}

.fmt-panel__item-body {
    display: grid;
    gap: 4px;
}
.fmt-panel__item--locked {
    opacity: 0.6;
    cursor: not-allowed;
}
.fmt-panel__item-pro {
    display: inline-block;
    margin-left: 6px;
    font-size: 0.65rem;
    font-weight: 700;
    text-transform: uppercase;
    color: #8b5cf6;
    border: 1px solid currentColor;
    border-radius: 4px;
    padding: 0 4px;
    vertical-align: middle;
}
</style>
