<script lang="ts" setup>
import type { SortableOptions } from 'sortablejs';
import type Sortable from 'sortablejs';

import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';

import { Empty, Input } from 'ant-design-vue';
import { SearchOutlined } from '@ant-design/icons-vue';

import { Icon } from '#/components/icon';
import { $t } from '#/locales';

export interface QuickNavMenuItem {
  id: number;
  title: string;
  icon?: string;
  path: string;
  component?: string;
}

export interface SavedQuickNavItem {
  menu_id: number;
  title?: string;
  icon?: string;
  url?: string;
}

interface Props {
  modelValue?: number[];
  menuTree?: any[];
  roleMenuIds?: number[];
  savedItems?: SavedQuickNavItem[];
  loading?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: () => [],
  menuTree: () => [],
  roleMenuIds: () => [],
  savedItems: () => [],
  loading: false,
});

const emit = defineEmits<{
  (e: 'update:modelValue', value: number[]): void;
}>();

const searchKeyword = ref('');
const availableListRef = ref<HTMLElement | null>(null);
const selectedListRef = ref<HTMLElement | null>(null);
const availableSortable = ref<Sortable | null>(null);
const selectedSortable = ref<Sortable | null>(null);

/** 从菜单树提取角色可授权的叶子页面（支持父级授权继承） */
function flattenAuthorizedLeafNodes(
  nodes: any[],
  roleMenuIds: number[],
): QuickNavMenuItem[] {
  const roleIdSet = new Set(roleMenuIds);
  const hasRoleFilter = roleMenuIds.length > 0;
  const leafNodes: QuickNavMenuItem[] = [];

  const traverse = (node: any, parentAuthorized: boolean) => {
    const nodeAuthorized = roleIdSet.has(node.id);
    const isAuthorized = !hasRoleFilter || parentAuthorized || nodeAuthorized;
    const nextParentAuthorized = isAuthorized;

    const hasPath = node.path && String(node.path).trim() !== '';
    const hasComponent =
      node.component && String(node.component).trim() !== '';
    const noChildren = !node.children || node.children.length === 0;

    if (hasPath && hasComponent && noChildren && isAuthorized) {
      leafNodes.push({
        id: node.id,
        title: node.title,
        icon: node.icon,
        path: node.path,
        component: node.component,
      });
    }

    if (node.children?.length) {
      node.children.forEach((child: any) =>
        traverse(child, nextParentAuthorized),
      );
    }
  };

  nodes.forEach((node) => traverse(node, false));
  return leafNodes;
}

const allPoolMenus = computed(() =>
  flattenAuthorizedLeafNodes(props.menuTree, props.roleMenuIds),
);

const menuMap = computed(() => {
  const map = new Map<number, QuickNavMenuItem>();
  allPoolMenus.value.forEach((m) => map.set(m.id, m));
  return map;
});

const savedItemMap = computed(() => {
  const map = new Map<number, SavedQuickNavItem>();
  (props.savedItems || []).forEach((item) => {
    if (item.menu_id) {
      map.set(item.menu_id, item);
    }
  });
  return map;
});

const selectedMenus = computed(() =>
  (props.modelValue || [])
    .map((id) => {
      const fromPool = menuMap.value.get(id);
      if (fromPool) return fromPool;

      const saved = savedItemMap.value.get(id);
      if (saved) {
        return {
          id: saved.menu_id,
          title: saved.title || '',
          icon: saved.icon || '',
          path: saved.url || '',
        } as QuickNavMenuItem;
      }
      return null;
    })
    .filter((m): m is QuickNavMenuItem => !!m),
);

const availableMenus = computed(() => {
  const selectedSet = new Set(props.modelValue || []);
  const keyword = searchKeyword.value.trim().toLowerCase();
  return allPoolMenus.value.filter((menu) => {
    if (selectedSet.has(menu.id)) return false;
    if (!keyword) return true;
    const title = $t(menu.title).toLowerCase();
    return (
      title.includes(keyword) ||
      menu.path.toLowerCase().includes(keyword)
    );
  });
});

const hasNoRoleMenus = computed(
  () => props.roleMenuIds.length === 0 && !props.loading,
);

function getIdsFromList(el: HTMLElement | null): number[] {
  if (!el) return [];
  return [...el.querySelectorAll('[data-menu-id]')].map((node) =>
    Number((node as HTMLElement).dataset.menuId),
  );
}

function syncSelectedFromDom() {
  const ids = getIdsFromList(selectedListRef.value);
  emit('update:modelValue', ids);
}

function destroySortables() {
  availableSortable.value?.destroy();
  selectedSortable.value?.destroy();
  availableSortable.value = null;
  selectedSortable.value = null;
}

async function createSortable(
  el: HTMLElement,
  options: SortableOptions = {},
): Promise<Sortable> {
  const mod = await import(
    // @ts-expect-error sortablejs modular esm path
    'sortablejs/modular/sortable.complete.esm.js'
  );
  return mod.default.create(el, {
    animation: 200,
    ...options,
  }) as Sortable;
}

async function initSortables() {
  destroySortables();
  await nextTick();

  const groupName = 'quickNav';

  if (availableListRef.value) {
    availableSortable.value = await createSortable(availableListRef.value, {
    group: {
      name: groupName,
      pull: 'clone',
      put: true,
    },
    sort: false,
    ghostClass: 'quick-nav-item--ghost',
    chosenClass: 'quick-nav-item--chosen',
    dragClass: 'quick-nav-item--drag',
    onAdd(evt) {
      // 从右侧拖回左侧：以右侧列表为准同步
      syncSelectedFromDom();
      evt.item?.remove();
      nextTick(() => initSortables());
    },
    });
  }

  if (selectedListRef.value) {
    selectedSortable.value = await createSortable(selectedListRef.value, {
    group: {
      name: groupName,
      pull: true,
      put: true,
    },
    ghostClass: 'quick-nav-item--ghost',
    chosenClass: 'quick-nav-item--chosen',
    dragClass: 'quick-nav-item--drag',
    onAdd(evt) {
      syncSelectedFromDom();
      evt.item?.remove();
      nextTick(() => initSortables());
    },
    onRemove() {
      syncSelectedFromDom();
    },
    onUpdate() {
      syncSelectedFromDom();
    },
    onEnd() {
      syncSelectedFromDom();
      nextTick(() => initSortables());
    },
    });
  }
}

function handleAuthorize(menuId: number) {
  const current = props.modelValue || [];
  if (current.includes(menuId)) return;
  emit('update:modelValue', [...current, menuId]);
}

function handleRemove(menuId: number) {
  const next = (props.modelValue || []).filter((id) => id !== menuId);
  emit('update:modelValue', next);
}

watch(
  () => [
    props.modelValue,
    props.menuTree,
    props.roleMenuIds,
    props.savedItems,
    props.loading,
  ],
  () => {
    if (!props.loading) {
      nextTick(() => initSortables());
    }
  },
  { deep: true },
);

watch(searchKeyword, () => {
  nextTick(() => initSortables());
});

onMounted(() => {
  if (!props.loading) {
    initSortables();
  }
});

onUnmounted(() => {
  destroySortables();
});
</script>

<template>
  <div class="quick-nav-transfer">
    <div
      class="mb-4 rounded-md border-l-4 border-blue-500 bg-blue-50 px-4 py-3 dark:border-blue-400 dark:bg-blue-900/20"
    >
      <p class="m-0 text-sm text-gray-600 dark:text-gray-300">
        左侧点击或拖拽到右侧即可授权；拖回左侧或点击移除可取消；右侧可拖拽排序。
      </p>
    </div>

    <div class="quick-nav-transfer__panels">
      <!-- 左侧：可授权 -->
      <div class="quick-nav-transfer__panel">
        <div class="quick-nav-transfer__panel-header">
          <span class="font-medium">可授权菜单</span>
          <span class="text-xs text-gray-500">
            {{ availableMenus.length }} / {{ allPoolMenus.length }}
          </span>
        </div>
        <Input
          v-model:value="searchKeyword"
          allow-clear
          class="mb-3"
          placeholder="搜索菜单名称或路径..."
        >
          <template #prefix>
            <SearchOutlined class="text-gray-400" />
          </template>
        </Input>
        <div class="quick-nav-transfer__list-wrap">
          <Empty
            v-if="hasNoRoleMenus"
            class="quick-nav-transfer__empty-hint"
            description="该角色暂无已授权菜单，请先在「授权菜单」中配置"
          />
          <Empty
            v-else-if="availableMenus.length === 0 && !loading"
            class="quick-nav-transfer__empty-hint"
            description="暂无可授权菜单（已全部添加或搜索无结果）"
          />
          <ul ref="availableListRef" class="quick-nav-transfer__list">
            <li
              v-for="menu in availableMenus"
              :key="`avail-${menu.id}`"
              :data-menu-id="menu.id"
              class="quick-nav-item quick-nav-item--available"
              @click="handleAuthorize(menu.id)"
            >
              <div class="quick-nav-item__handle" @click.stop>
                <Icon icon="ant-design:menu-outlined" class="text-base" />
              </div>
              <Icon
                v-if="menu.icon"
                :icon="menu.icon"
                class="quick-nav-item__icon"
              />
              <div class="quick-nav-item__info">
                <div class="quick-nav-item__title">{{ $t(menu.title) }}</div>
                <div class="quick-nav-item__path">{{ menu.path }}</div>
              </div>
              <button
                class="quick-nav-item__add"
                type="button"
                title="添加到快捷导航"
                @click.stop="handleAuthorize(menu.id)"
              >
                <Icon icon="ant-design:plus-outlined" />
              </button>
            </li>
          </ul>
        </div>
      </div>

      <!-- 右侧：已配置 -->
      <div class="quick-nav-transfer__panel">
        <div class="quick-nav-transfer__panel-header">
          <span class="font-medium">已配置快捷导航</span>
          <span class="text-xs text-gray-500">
            {{ selectedMenus.length }} 项
          </span>
        </div>
        <div class="quick-nav-transfer__list-wrap">
          <Empty
            v-if="selectedMenus.length === 0 && !loading"
            class="quick-nav-transfer__empty-hint"
            description="从左侧点击或拖拽菜单到此处，添加后可拖拽调整顺序"
          />
          <ul ref="selectedListRef" class="quick-nav-transfer__list">
            <li
              v-for="(menu, index) in selectedMenus"
              :key="`sel-${menu.id}`"
              :data-menu-id="menu.id"
              class="quick-nav-item quick-nav-item--selected"
            >
              <div class="quick-nav-item__handle">
                <Icon icon="ant-design:menu-outlined" class="text-base" />
              </div>
              <Icon
                v-if="menu.icon"
                :icon="menu.icon"
                class="quick-nav-item__icon"
              />
              <div class="quick-nav-item__info">
                <div class="quick-nav-item__title">{{ $t(menu.title) }}</div>
                <div class="quick-nav-item__path">{{ menu.path }}</div>
              </div>
              <div class="quick-nav-item__order">{{ index + 1 }}</div>
              <button
                class="quick-nav-item__remove"
                type="button"
                title="移除"
                @click.stop="handleRemove(menu.id)"
              >
                <Icon icon="ant-design:close-outlined" />
              </button>
            </li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

<style lang="scss" scoped>
.quick-nav-transfer {
  height: 100%;
  display: flex;
  flex-direction: column;

  &__panels {
    display: flex;
    flex: 1;
    gap: 16px;
    min-height: 0;
  }

  &__panel {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
    padding: 12px;
    border: 1px solid var(--ant-color-border, #e5e7eb);
    border-radius: 8px;
    background: var(--ant-color-bg-container, #fff);
  }

  &__panel-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
  }

  &__list-wrap {
    flex: 1;
    min-height: 320px;
    max-height: calc(100vh - 280px);
    overflow-y: auto;

  }

  &__empty-hint {
    margin-bottom: 12px;
  }

  &__list {
    margin: 0;
    padding: 0;
    list-style: none;
  }
}

.quick-nav-item {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
  padding: 10px 12px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  background: #fff;
  cursor: grab;
  transition:
    box-shadow 0.2s,
    border-color 0.2s;

  &:hover {
    border-color: #1890ff;
    box-shadow: 0 2px 8px rgba(24, 144, 255, 0.12);
  }

  &--available {
    cursor: pointer;
  }

  &--selected {
    border-color: #91caff;
    background: #f0f7ff;
  }

  &--ghost {
    opacity: 0.4;
  }

  &--chosen {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
  }

  &--drag {
    opacity: 0.85;
  }

  &__handle {
    flex-shrink: 0;
    color: #999;
  }

  &__icon {
    flex-shrink: 0;
    font-size: 18px;
    color: #666;
  }

  &__info {
    flex: 1;
    min-width: 0;
  }

  &__title {
    font-size: 14px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  &__path {
    font-size: 12px;
    color: #999;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  &__order {
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: #e6f4ff;
    color: #1677ff;
    font-size: 12px;
    font-weight: 500;
  }

  &__add,
  &__remove {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: none;
    border-radius: 4px;
    background: transparent;
    cursor: pointer;
  }

  &__add {
    color: #999;

    &:hover {
      color: #1677ff;
      background: #e6f4ff;
    }
  }

  &__remove {
    color: #999;

    &:hover {
      color: #ff4d4f;
      background: #fff1f0;
    }
  }
}

.dark {
  .quick-nav-transfer__panel {
    background: #1f2937;
    border-color: #374151;
  }

  .quick-nav-item {
    background: #1f2937;
    border-color: #374151;

    &--selected {
      background: #1e3a5f;
      border-color: #2563eb;
    }

    &__title {
      color: #f3f4f6;
    }
  }
}
</style>
