<script setup lang="ts">
import Sortable from 'sortablejs';

interface ComponentItem {
  id: number;
  name: string;
}

const 组件列表 = ref<ComponentItem[]>([
  { id: 1, name: '组件1' },
  { id: 2, name: '组件2' },
  { id: 3, name: '组件3' },
  { id: 4, name: '组件4' },
  { id: 5, name: '组件5' },
]);

const 流程起点列表 = ref<ComponentItem[]>([]);
const 流程终点列表 = ref<ComponentItem[]>([]);

const 源列表引用 = useTemplateRef<HTMLDivElement | null>('源列表引用');
const 起点列表引用 = useTemplateRef<HTMLDivElement | null>('起点列表引用');
const 终点列表引用 = useTemplateRef<HTMLDivElement | null>('终点列表引用');

function getItemIndex(item: ComponentItem): number {
  return 组件列表.value.findIndex((comp) => comp.id === item.id);
}

function getItemById(id: number): ComponentItem | undefined {
  return 组件列表.value.find((comp) => comp.id === id);
}

// 添加元素到起点列表，维护相应的 DOM 和数据模型
function addToStartList(item: ComponentItem, domItem: HTMLElement) {
  const newStartIndex = getItemIndex(item);
  const endIndex = 流程终点列表.value.length > 0 ? getItemIndex(流程终点列表.value[0]) : -1;

  // 约束检查：如果终点已存在，则新起点必须在终点之前
  if (endIndex !== -1 && newStartIndex >= endIndex) {
    consola.warn('流程起点必须在流程终点之前');
    domItem.remove(); // 移除不符合约束的 DOM 项
    return false; // 表示添加失败
  }

  // 更新数据模型
  if (流程起点列表.value.length > 0) {
    流程起点列表.value.splice(0, 1, item); // 替换
  } else {
    流程起点列表.value.push(item); // 添加
  }

  return true; // 表示添加成功
}

// 添加元素到终点列表，维护相应的 DOM 和数据模型
function addToEndList(item: ComponentItem, domItem: HTMLElement) {
  const newEndIndex = getItemIndex(item);
  const startIndex = 流程起点列表.value.length > 0 ? getItemIndex(流程起点列表.value[0]) : -1;

  // 约束检查：如果起点已存在，则新终点必须在起点之后
  if (startIndex !== -1 && newEndIndex <= startIndex) {
    consola.warn('流程终点必须在流程起点之后');
    domItem.remove();
    return false; // 表示添加失败
  }

  // 更新数据模型
  if (流程终点列表.value.length > 0) {
    流程终点列表.value.splice(0, 1, item); // 替换
  } else {
    流程终点列表.value.push(item); // 添加
  }

  return true; // 表示添加成功
}

// 清理起点列表的 DOM，确保与数据模型一致
function cleanupStartListDOM(keepItemId?: number) {
  if (!起点列表引用.value) return;

  consola.info(`清理起点列表 DOM，保留的项 ID：${keepItemId ?? '无'}`);

  const children = [...起点列表引用.value.children];
  let found = false;

  for (const child of children) {
    if (child instanceof HTMLElement && child.classList.contains('drag-item') && child.dataset.id) {
      const childId = Number.parseInt(child.dataset.id, 10);

      // 如果指定了要保留的项
      if (keepItemId !== undefined && childId === keepItemId) {
        if (found) {
          consola.info(`移除重复的起点项，ID: ${childId}`);
          child.remove(); // 如果已经找到过一个相同ID的项，则这个是重复的，移除
        } else {
          consola.info(`保留起点项，ID: ${childId}`);
          found = true; // 标记找到，保留这个
        }
      }
      // 对于其他项
      else if (keepItemId === undefined || childId !== keepItemId) {
        if (流程起点列表.value.length === 1 && 流程起点列表.value[0].id === childId) {
          // 如果这个项与数据模型匹配，保留
          if (found) {
            consola.info(`移除重复的起点项，ID: ${childId}`);
            child.remove(); // 但如果已找到一个相同ID的，则这个是重复的
          } else {
            consola.info(`保留与数据模型匹配的起点项，ID: ${childId}`);
            found = true;
          }
        } else {
          // 不在数据模型中的项，移除
          consola.info(`移除不在数据模型中的起点项，ID: ${childId}`);
          child.remove();
        }
      }
    } else if (child.classList.contains('placeholder') && 流程起点列表.value.length > 0) {
      // 数据模型有项时，移除占位符
      consola.info('移除起点列表中的占位符，因为数据模型中已有项');
      child.remove();
    }
  }

  // 如果数据模型为空，且DOM中没有占位符，添加占位符
  if (流程起点列表.value.length === 0 && !起点列表引用.value.querySelector('.placeholder')) {
    consola.info('添加起点列表占位符，因为数据模型为空');
    const placeholder = document.createElement('div');
    placeholder.className = 'placeholder';
    placeholder.textContent = '请拖入组件构成流程起点';
    起点列表引用.value.append(placeholder);
  }
}

// 清理终点列表的 DOM，确保与数据模型一致
function cleanupEndListDOM(keepItemId?: number) {
  if (!终点列表引用.value) return;

  consola.info(`清理终点列表 DOM，保留的项 ID：${keepItemId ?? '无'}`);

  const children = [...终点列表引用.value.children];
  let found = false;

  for (const child of children) {
    if (child instanceof HTMLElement && child.classList.contains('drag-item') && child.dataset.id) {
      const childId = Number.parseInt(child.dataset.id, 10);

      // 如果指定了要保留的项
      if (keepItemId !== undefined && childId === keepItemId) {
        if (found) {
          consola.info(`移除重复的终点项，ID: ${childId}`);
          child.remove(); // 如果已经找到过一个相同ID的项，则这个是重复的，移除
        } else {
          consola.info(`保留终点项，ID: ${childId}`);
          found = true; // 标记找到，保留这个
        }
      }
      // 对于其他项
      else if (keepItemId === undefined || childId !== keepItemId) {
        if (流程终点列表.value.length === 1 && 流程终点列表.value[0].id === childId) {
          // 如果这个项与数据模型匹配，保留
          if (found) {
            consola.info(`移除重复的终点项，ID: ${childId}`);
            child.remove(); // 但如果已找到一个相同ID的，则这个是重复的
          } else {
            consola.info(`保留与数据模型匹配的终点项，ID: ${childId}`);
            found = true;
          }
        } else {
          consola.info(`移除不在数据模型中的终点项，ID: ${childId}`);
          // 不在数据模型中的项，移除
          child.remove();
        }
      }
    } else if (child.classList.contains('placeholder') && 流程终点列表.value.length > 0) {
      // 数据模型有项时，移除占位符
      consola.info('移除终点列表中的占位符，因为数据模型中已有项');
      child.remove();
    }
  }

  // 如果数据模型为空，且DOM中没有占位符，添加占位符
  if (流程终点列表.value.length === 0 && !终点列表引用.value.querySelector('.placeholder')) {
    consola.info('添加终点列表占位符，因为数据模型为空');
    const placeholder = document.createElement('div');
    placeholder.className = 'placeholder';
    placeholder.textContent = '请拖入组件构成流程终点';
    终点列表引用.value.append(placeholder);
  }
}

onMounted(() => {
  if (!源列表引用.value || !起点列表引用.value || !终点列表引用.value) {
    consola.error('未能获取到 SortableJS 容器元素');
    return;
  }

  // 初始化源列表
  Sortable.create(源列表引用.value, {
    group: {
      name: 'components',
      pull: 'clone', // 允许克隆
      put: false, // 不允许拖入
    },
    sort: false, // 源列表内不允许排序
    animation: 150,
    // 将 data-id 附加到拖拽数据中，供目标列表识别
    setData: (dataTransfer, dragEl) => {
      // 确保 dragEl 是有效的 HTMLElement 且包含 data-id
      if (dragEl instanceof HTMLElement && dragEl.dataset.id) {
        dataTransfer.setData('text/plain', dragEl.dataset.id);
      }
    },
  });

  // 初始化流程起点列表
  Sortable.create(起点列表引用.value, {
    group: {
      name: 'flowPoints', // 修改为新的组名，用于区分"流程点"和普通组件
      pull: true, // 允许拖出
      put: ['components', 'flowPoints'], // 允许接收"组件"和"流程点"
    },
    animation: 150,
    draggable: '.drag-item', // 只有带 .drag-item 的元素才能被拖动
    onAdd: (evt) => {
      const itemId = Number.parseInt(evt.item.dataset.id || '0', 10);
      const newItem = getItemById(itemId);

      if (!newItem) {
        consola.error('找不到拖拽的组件项数据 (起点)');
        evt.item.remove(); // 从 DOM 中移除无效项
        return;
      }

      // 检查来源，如果是从终点列表拖来的
      if (evt.from === 终点列表引用.value) {
        // 从终点列表移除
        流程终点列表.value = [];
      }

      // 尝试添加到起点列表
      const added = addToStartList(newItem, evt.item);

      // 在下一个 tick 清理 DOM
      nextTick(() => {
        cleanupStartListDOM(added ? itemId : undefined);

        // 如果来源是终点列表，需同时清理终点列表的 DOM
        if (evt.from === 终点列表引用.value) {
          cleanupEndListDOM();
        }
      });
    },
  });

  // 初始化流程终点列表
  Sortable.create(终点列表引用.value, {
    group: {
      name: 'flowPoints', // 与起点列表使用相同的组名
      pull: true, // 允许拖出
      put: ['components', 'flowPoints'], // 允许接收"组件"和"流程点"
    },
    draggable: '.drag-item',
    animation: 150,
    onAdd: (evt) => {
      const itemId = Number.parseInt(evt.item.dataset.id || '0', 10);
      const newItem = getItemById(itemId);

      if (!newItem) {
        consola.error('找不到拖拽的组件项数据 (终点)');
        evt.item.remove();
        return;
      }

      // 检查来源，如果是从起点列表拖来的
      if (evt.from === 起点列表引用.value) {
        // 从起点列表移除
        流程起点列表.value = [];
      }

      // 尝试添加到终点列表
      const added = addToEndList(newItem, evt.item);

      // 在下一个 tick 清理 DOM
      nextTick(() => {
        cleanupEndListDOM(added ? itemId : undefined);

        // 如果来源是起点列表，需同时清理起点列表的 DOM
        if (evt.from === 起点列表引用.value) {
          cleanupStartListDOM();
        }
      });
    },
  });
});

// 计算流程中的中间节点 (基于起点和终点在原始列表中的位置)
const intermediateNodes = computed(() => {
  if (流程起点列表.value.length === 0 || 流程终点列表.value.length === 0) {
    return [];
  }
  const startIndex = getItemIndex(流程起点列表.value[0]);
  const endIndex = getItemIndex(流程终点列表.value[0]);

  // 确保起点和终点都有效，且它们之间至少有一个组件
  if (startIndex !== -1 && endIndex !== -1 && startIndex < endIndex - 1) {
    // 返回原始列表中位于起点和终点之间的组件
    return 组件列表.value.slice(startIndex + 1, endIndex);
  }
  return [];
});
</script>

<template>
  <div class="flex gap-5 p-5 bg-[#1a1a1a] text-[#e0e0e0] font-sans min-h-100">
    <!-- 组件选择区域 -->
    <div class="flex-1 p-4 rounded">
      <h3 class="text-center mb-5 text-white font-bold">组件选择</h3>
      <div ref="源列表引用" class="min-h-150px rounded p-2.5 transition-bg duration-200">
        <div
          v-for="item in 组件列表"
          :key="item.id"
          class="py-2.5 px-4 mb-2 bg-[#333] border border-[#007bff] text-[#e0e0e0] rounded cursor-grab text-center drag-item component-item"
          :data-id="item.id"
        >
          <!-- data-id 用于 SortableJS 识别拖拽项 -->
          {{ item.name }}
        </div>
      </div>
    </div>

    <!-- 构建流程区域 -->
    <div class="flex-1 p-4 rounded">
      <h3 class="text-center mb-5 text-white font-bold">构建流程</h3>
      <div class="flex items-start mb-2.5">
        <div class="w-20 text-right mr-2.5 pt-2.5 text-[#ccc]">流程起点：</div>
        <div
          ref="起点列表引用"
          class="flex-1 min-h-150px rounded p-2.5 border-2 border-dashed border-[#555] bg-[#2a2a2a] mt-1.25 mb-5 target-list start-list"
        >
          <div
            v-if="流程起点列表.length === 0"
            class="flex justify-center items-center h-full min-h-130px text-[#888] text-center placeholder"
          >
            请拖入组件构成流程起点
          </div>
          <div
            v-for="item in 流程起点列表"
            :key="item.id"
            class="py-2.5 px-4 mb-2 bg-[#444] border border-[#17a2b8] text-[#e0e0e0] rounded cursor-default text-center drag-item dropped-item"
            :data-id="item.id"
          >
            {{ item.name }}
          </div>
        </div>
      </div>

      <div class="flex items-start mb-2.5">
        <div class="w-20 text-right mr-2.5 pt-2.5 text-[#ccc]">流程终点：</div>
        <div
          ref="终点列表引用"
          class="flex-1 min-h-150px rounded p-2.5 border-2 border-dashed border-[#555] bg-[#2a2a2a] mt-1.25 target-list end-list"
        >
          <div
            v-if="流程终点列表.length === 0"
            class="flex justify-center items-center h-full min-h-130px text-[#888] text-center placeholder"
          >
            请拖入组件构成流程终点
          </div>
          <div
            v-for="item in 流程终点列表"
            :key="item.id"
            class="py-2.5 px-4 mb-2 bg-[#444] border border-[#17a2b8] text-[#e0e0e0] rounded cursor-default text-center drag-item dropped-item"
            :data-id="item.id"
          >
            {{ item.name }}
          </div>
        </div>
      </div>
    </div>

    <!-- 预览区域 -->
    <div class="flex-1 p-4 rounded">
      <h3 class="text-center mb-5 text-white font-bold">预览</h3>
      <div
        class="mt-2.5 p-2.5 bg-[#2a2a2a] border border-[#444] rounded min-h-100px flex flex-col items-stretch justify-start gap-2"
      >
        <template v-if="流程起点列表.length > 0 || 流程终点列表.length > 0">
          <div
            v-if="流程起点列表.length > 0"
            :key="`start-${流程起点列表[0].id}`"
            class="py-2.5 px-4 bg-[#333] border border-[#007bff] text-[#e0e0e0] rounded cursor-default flex-shrink-0 text-center drag-item component-item preview-item"
          >
            {{ 流程起点列表[0].name }}
          </div>
          <div
            v-for="item in intermediateNodes"
            :key="`middle-${item.id}`"
            class="py-2.5 px-4 bg-[#333] border border-[#6c757d] text-[#e0e0e0] rounded cursor-default flex-shrink-0 text-center drag-item component-item preview-item preview-intermediate"
          >
            {{ item.name }}
          </div>
          <div
            v-if="流程终点列表.length > 0"
            :key="`end-${流程终点列表[0].id}`"
            class="py-2.5 px-4 bg-[#333] border border-[#007bff] text-[#e0e0e0] rounded cursor-default flex-shrink-0 text-center drag-item component-item preview-item"
          >
            {{ 流程终点列表[0].name }}
          </div>
        </template>
        <div
          v-else
          class="flex justify-center items-center h-20 text-[#888] text-center w-full preview-placeholder placeholder"
        >
          请拖入起点和终点组件以生成预览
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
/* SortableJS 拖拽过程中的辅助样式 */
.sortable-ghost {
  opacity: 0.5;
  background: #4a90e2;
}

/* .sortable-chosen {} */ /* 被选中的元素 */
/* .sortable-drag {} */ /* 拖拽中的元素 */
</style>
