<script setup lang="ts">
import type { BlockInstance, BlockSchema, FixedField } from './types';

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

import type Sortable from 'sortablejs';
import type { SortableOptions } from 'sortablejs';

import { Button, message } from 'ant-design-vue';

import BlockLibrary from './BlockLibrary.vue';
import Canvas from './Canvas.vue';
import JsonPreview from './JsonPreview.vue';
import PropertyPanel from './PropertyPanel.vue';

/**
 * 通用拖拽式可视化编辑器
 *
 * 用途：把「左侧组件库 → 中间画布拖拽组装 → 右侧属性面板 → 自动生成 JSON」
 *      做成 schema 驱动的通用组件，未来 OA / 工单 / 通知等任何场景需要可视化拼装 JSON 都能复用。
 *
 * 核心机制：
 * - 左侧库 + 画布各分组共用同一个 SortableJS group（pull:clone 模式：左侧库克隆到画布）
 * - 拖入 / 排序 / 删除时同步内部 instances 状态
 * - 通过 computed 把「fixedFields 值 + 各 targetArrayPath 数组」合并成最终 JSON，emit 给父级
 *
 * SortableJS 用法参照项目内 views/system/role/components/quick-nav-transfer.vue
 */

const props = withDefaults(
  defineProps<{
    /** 双向绑定的 JSON 数据 */
    modelValue: Record<string, any>;
    /** 块定义 schema（决定左侧能拖入哪些块、各自有哪些字段） */
    schema: BlockSchema[];
    /** 是否显示 JSON 预览面板 */
    showJsonPreview?: boolean;
    /** 顶层固定字段（不可拖拽，固定显示在属性面板顶部） */
    fixedFields?: FixedField[];
  }>(),
  {
    showJsonPreview: true,
    fixedFields: () => [] as FixedField[],
  },
);

const emit = defineEmits<{
  'update:modelValue': [value: Record<string, any>];
}>();

// ====== 内部状态 ======

/** 所有块实例（按拖入顺序） */
const instances = ref<BlockInstance[]>([]);
/** 当前选中的块实例 ID */
const selectedInstanceId = ref<string>('');
/** 顶层固定字段值 */
const fixedValues = ref<Record<string, any>>({});
/** 顶层固定字段 + 块数组组装后的最终 JSON（双向同步给父级） */
const assembledJson = computed<Record<string, any>>(() => {
  const result: Record<string, any> = { ...fixedValues.value };
  // 按 targetArrayPath 收集块实例数据
  for (const schema of props.schema) {
    const path = schema.targetArrayPath;
    const items = instances.value
      .filter((inst) => inst.type === schema.type)
      .map((inst) => {
        // 深拷贝避免引用问题
        const data = JSON.parse(JSON.stringify(inst.data));
        // 移除内部使用的 id 字段（如果有）
        delete data._blockType;
        return data;
      });
    if (items.length > 0) {
      // 设置嵌套路径
      setByPath(result, path, items);
    }
  }
  return result;
});

/** 当前选中的块实例对象 */
const selectedInstance = computed<BlockInstance | undefined>(() => {
  return instances.value.find((i) => i.id === selectedInstanceId.value);
});

/** 当前选中的块 schema */
const selectedBlockSchema = computed<BlockSchema | undefined>(() => {
  if (!selectedInstance.value) return undefined;
  return props.schema.find((s) => s.type === selectedInstance.value!.type);
});

// ====== SortableJS 实例管理 ======

const editorRootRef = ref<HTMLElement>();
const libraryRef = ref<HTMLElement>();
/** 画布中各分组的 dropzone DOM 引用（key = targetArrayPath） */
const dropzoneRefs = ref<Record<string, HTMLElement>>({});
const librarySortable = ref<Sortable | null>(null);
const dropzoneSortables = ref<Record<string, Sortable>>({});

/**
 * 动态导入 SortableJS（参照 quick-nav-transfer.vue 模式）
 */
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;
}

/**
 * 初始化 SortableJS 实例
 * 1. 左侧组件库：pull:clone 模式（拖出后元素保留）
 * 2. 画布各分组：pull:clone/put，跨组可移动
 */
async function initSortables() {
  destroySortables();
  await nextTick();

  if (libraryRef.value) {
    librarySortable.value = await createSortable(libraryRef.value, {
      group: {
        name: 'template-editor',
        pull: 'clone',
        put: false, // 左侧库不接受拖入
      },
      sort: false, // 左侧库不排序
      onEnd: handleLibraryDragEnd,
    });
  }

  // 通过 querySelector 直接查询画布中的所有 dropzone DOM（避免父子组件 DOM 引用传递）
  const editorRoot = editorRootRef.value;
  const dropzoneEls = editorRoot?.querySelectorAll('.group-dropzone[data-target-array-path]') ?? [];
  dropzoneEls.forEach((el) => {
    const path = el.getAttribute('data-target-array-path');
    if (!path) return;
    dropzoneRefs.value[path] = el as HTMLElement;
    void createSortable(el as HTMLElement, {
      group: {
        name: 'template-editor',
        pull: true,
        put: true,
      },
      onAdd: (evt) => handleDropzoneAdd(evt, path),
      onRemove: (evt) => handleDropzoneRemove(evt, path),
      onUpdate: (evt) => handleDropzoneUpdate(evt, path),
    }).then((s) => {
      dropzoneSortables.value[path] = s;
    });
  });
}

function destroySortables() {
  librarySortable.value?.destroy();
  librarySortable.value = null;
  for (const sortable of Object.values(dropzoneSortables.value)) {
    sortable.destroy();
  }
  dropzoneRefs.value = {};
  dropzoneSortables.value = {};
}

/**
 * 设置 dropzone DOM 引用（Canvas 子组件通过回调注入）
 */
function setDropzoneRef(path: string, el: HTMLElement | null) {
  if (el) {
    dropzoneRefs.value[path] = el;
  } else {
    delete dropzoneRefs.value[path];
  }
}

// ====== 拖拽事件处理（在 SortableJS 修改 DOM 后，同步到 Vue 状态） ======

/**
 * 左侧库 → 画布的拖拽结束回调
 * SortableJS 会把左侧库的元素 clone 一份移到画布 DOM，我们要：
 * 1. 移除 DOM 中被克隆过去的元素（由 Vue 重新渲染）
 * 2. 在 instances 中追加新块
 */
function handleLibraryDragEnd(evt: any) {
  // 如果只是库内拖动（没拖到画布），不做处理
  if (!evt.to || !evt.to.classList.contains('group-dropzone')) {
    return;
  }
  const blockType = evt.item?.dataset?.blockType;
  if (!blockType) return;

  // 检查 maxCount 限制
  const schema = props.schema.find((s) => s.type === blockType);
  if (!schema) return;

  const targetPath = schema.targetArrayPath;
  const currentCount = instances.value.filter(
    (i) => props.schema.find((s) => s.type === i.type)?.targetArrayPath === targetPath,
  ).length;
  if (schema.maxCount && currentCount >= schema.maxCount) {
    // 移除 SortableJS 已经克隆到画布 DOM 的元素
    evt.item?.remove();
    message.warning(`「${schema.label}」最多 ${schema.maxCount} 项`);
    return;
  }

  // 移除 SortableJS 已经克隆到画布 DOM 的元素（Vue 会重新渲染）
  evt.item?.remove();

  // 根据 SortableJS 在画布 DOM 中的位置，计算插入位置
  // 简化处理：直接追加到末尾（实现严格的位置同步太复杂，先保留追加行为）
  const newInstance: BlockInstance = {
    id: generateId(),
    type: blockType,
    data: schema.defaultValue ? JSON.parse(JSON.stringify(schema.defaultValue)) : {},
  };
  instances.value = [...instances.value, newInstance];
  selectedInstanceId.value = newInstance.id;
}

/**
 * 画布分组间或分组内的添加事件（块从其他分组拖入）
 */
function handleDropzoneAdd(_evt: any, _path: string) {
  // SortableJS 自动处理 DOM 顺序，但因为 Vue 控制渲染，需要同步状态
  syncInstancesFromDom();
}

function handleDropzoneRemove(_evt: any, _path: string) {
  syncInstancesFromDom();
}

function handleDropzoneUpdate(_evt: any, _path: string) {
  syncInstancesFromDom();
}

/**
 * 从画布 DOM 同步块实例状态
 * SortableJS 修改了 DOM 后，从 DOM 重新读取 instanceId 顺序
 */
function syncInstancesFromDom() {
  const newInstances: BlockInstance[] = [];
  for (const path of Object.keys(dropzoneRefs.value)) {
    const el = dropzoneRefs.value[path];
    if (!el) continue;
    const items = el.querySelectorAll('[data-instance-id]');
    items.forEach((item) => {
      const id = item.getAttribute('data-instance-id');
      if (!id) return;
      const existing = instances.value.find((i) => i.id === id);
      if (existing) {
        newInstances.push(existing);
      }
    });
  }
  instances.value = newInstances;
}

/**
 * 删除块实例
 */
function removeInstance(id: string) {
  instances.value = instances.value.filter((i) => i.id !== id);
  if (selectedInstanceId.value === id) {
    selectedInstanceId.value = '';
  }
}

/**
 * 选中块实例
 */
function selectInstance(id: string) {
  selectedInstanceId.value = id;
}

/**
 * 更新顶层固定字段值（PropertyPanel 触发）
 */
function updateFixedValues(values: Record<string, any>) {
  fixedValues.value = values;
}

/**
 * 更新块实例数据（PropertyPanel 触发）
 */
function updateInstance(updated: BlockInstance) {
  instances.value = instances.value.map((i) =>
    i.id === updated.id ? updated : i,
  );
}

// ====== 工具函数 ======

function generateId(): string {
  return `inst_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}

/**
 * 按 a.b.c 路径设置对象嵌套属性（不可变更新）
 */
function setByPath(obj: Record<string, any>, path: string, value: any) {
  const keys = path.split('.');
  let cursor: any = obj;
  for (let i = 0; i < keys.length - 1; i++) {
    const k = keys[i]!;
    if (!cursor[k]) cursor[k] = {};
    cursor = cursor[k];
  }
  cursor[keys[keys.length - 1]!] = value;
}

/**
 * 按 a.b.c 路径读取对象嵌套属性
 */
function getByPath(obj: Record<string, any> | undefined, path: string): any {
  if (!obj) return undefined;
  const keys = path.split('.');
  let cursor: any = obj;
  for (const k of keys) {
    if (cursor == null) return undefined;
    cursor = cursor[k];
  }
  return cursor;
}

// ====== modelValue 双向同步 ======

/**
 * 监听 modelValue 变化，反向初始化内部状态（用于编辑场景回显）
 */
watch(
  () => props.modelValue,
  (val) => {
    if (!val || Object.keys(val).length === 0) {
      // 重置
      instances.value = [];
      fixedValues.value = {};
      selectedInstanceId.value = '';
      return;
    }
    // 简化处理：只在 modelValue 引用变化时才反解析（避免无限循环）
    // 从 modelValue 还原 fixedValues 和 instances
    const newFixed: Record<string, any> = {};
    for (const field of props.fixedFields || []) {
      const v = getByPath(val, field.name);
      if (v !== undefined) {
        setByPath(newFixed, field.name, v);
      }
    }
    fixedValues.value = newFixed;

    // 从各 targetArrayPath 还原 instances
    const newInstances: BlockInstance[] = [];
    for (const schema of props.schema) {
      const arr = getByPath(val, schema.targetArrayPath);
      if (Array.isArray(arr)) {
        for (const item of arr) {
          newInstances.push({
            id: generateId(),
            type: schema.type,
            data: JSON.parse(JSON.stringify(item)),
          });
        }
      }
    }
    instances.value = newInstances;
  },
  { immediate: false, deep: false },
);

/**
 * 监听 assembledJson 变化，emit 给父级
 */
watch(
  assembledJson,
  (val) => {
    // 浅比较避免不必要的 emit
    const current = JSON.stringify(props.modelValue || {});
    const next = JSON.stringify(val);
    if (current !== next) {
      emit('update:modelValue', val);
    }
  },
  { deep: true },
);

// 监听 schema 变化时重置 SortableJS（画布 DOM 可能重建）
watch(
  () => props.schema,
  () => {
    void nextTick(() => initSortables());
  },
);

onMounted(async () => {
  // 初始化 fixedValues（从 props.fixedFields 的 defaultValue）
  for (const field of props.fixedFields || []) {
    if (field.defaultValue !== undefined) {
      setByPath(fixedValues.value, field.name, field.defaultValue);
    }
  }
  // 从 modelValue 回显数据
  if (props.modelValue && Object.keys(props.modelValue).length > 0) {
    for (const field of props.fixedFields || []) {
      const v = getByPath(props.modelValue, field.name);
      if (v !== undefined) {
        setByPath(fixedValues.value, field.name, v);
      }
    }
    for (const schema of props.schema) {
      const arr = getByPath(props.modelValue, schema.targetArrayPath);
      if (Array.isArray(arr)) {
        for (const item of arr) {
          instances.value.push({
            id: generateId(),
            type: schema.type,
            data: JSON.parse(JSON.stringify(item)),
          });
        }
      }
    }
  }
  await nextTick();
  await initSortables();
});

onUnmounted(() => {
  destroySortables();
});

// 暴露给父组件
defineExpose({
  initSortables,
});
</script>

<template>
  <div ref="editorRootRef" class="template-editor">
    <div class="editor-toolbar">
      <span class="title">模板编辑器</span>
      <Button
        size="small"
        type="link"
        @click="initSortables"
      >
        重置拖拽
      </Button>
    </div>
    <div class="editor-main">
      <!-- 左侧：组件库（用 ref 暴露给 SortableJS 初始化） -->
      <div ref="libraryRef">
        <BlockLibrary :blocks="schema" />
      </div>

      <!-- 中间：画布 -->
      <Canvas
        :block-schemas="schema"
        :instances="instances"
        :selected-instance-id="selectedInstanceId"
        @select="selectInstance"
        @remove="removeInstance"
      />

      <!-- 右侧：属性面板 -->
      <PropertyPanel
        :fixed-fields="fixedFields"
        :fixed-values="fixedValues"
        :selected-block-schema="selectedBlockSchema"
        :selected-instance="selectedInstance"
        @update:fixed-values="updateFixedValues"
        @update:instance="updateInstance"
      />

      <!-- 最右侧：JSON 预览（可选） -->
      <JsonPreview
        v-if="showJsonPreview"
        :value="assembledJson"
      />
    </div>
  </div>
</template>

<style scoped lang="scss">
.template-editor {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 480px;
  border: 1px solid var(--ant-color-border-secondary, #e5e6eb);
  border-radius: 6px;
  overflow: hidden;
  background: var(--ant-color-bg-container, #fff);
}

.editor-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  border-bottom: 1px solid var(--ant-color-border-secondary, #e5e6eb);
  background: var(--ant-color-fill-quaternary, #fafbfc);
  font-size: 13px;

  .title {
    font-weight: 600;
    color: var(--ant-color-text, #1d2129);
  }
}

.editor-main {
  display: flex;
  flex: 1;
  min-height: 0;
}
</style>
