﻿<template>
  <div class="app-container">
    <!-- 搜索区域 -->
    <div class="search-container">
      <el-form ref="queryFormRef" :model="queryParams" :inline="true">
        <el-form-item prop="keywords" label="关键字">
          <el-input
            v-model="queryParams.keywords"
            placeholder="表名"
            clearable
            @keyup.enter="handleQuery"
          />
        </el-form-item>

        <el-form-item class="search-buttons">
          <el-button type="primary" @click="handleQuery">
            <template #icon>
              <Search />
            </template>
            搜索
          </el-button>
          <el-button @click="handleResetQuery">
            <template #icon>
              <Refresh />
            </template>
            重置
          </el-button>
        </el-form-item>
      </el-form>
    </div>

    <el-card shadow="hover" class="table-card">
      <el-table
        ref="dataTableRef"
        v-loading="loading"
        :data="pageData"
        highlight-current-row
        border
        class="data-table__content"
      >
        <el-table-column type="selection" width="55" align="center" />
        <el-table-column label="表名" prop="tableName" min-width="100" />
        <el-table-column label="描述" prop="tableComment" width="150" />

        <el-table-column label="存储引擎" align="center" prop="engine" />

        <el-table-column label="排序规则" align="center" prop="tableCollation" />
        <el-table-column label="创建时间" align="center" prop="createTime" />

        <el-table-column fixed="right" label="操作" width="200">
          <template #default="scope">
            <el-button
              type="primary"
              size="small"
              link
              @click="handleOpenDialog(scope.row.tableName)"
            >
              <template #icon>
                <MagicStick />
              </template>
              生成代码
            </el-button>

            <el-button
              v-if="scope.row.isConfigured === 1"
              type="danger"
              size="small"
              link
              @click="handleResetConfig(scope.row.tableName)"
            >
              <template #icon>
                <RefreshLeft />
              </template>
              重置配置
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <pagination
        v-if="total > 0"
        v-model:total="total"
        v-model:page="queryParams.pageNum"
        v-model:limit="queryParams.pageSize"
        @pagination="handleQuery"
      />
    </el-card>

    <el-drawer
      v-model="dialog.visible"
      :title="dialog.title"
      size="80%"
      @close="dialog.visible = false"
    >
      <el-steps :active="active" align-center finish-status="success" simple>
        <el-step title="基础配置" />
        <el-step title="字段配置" />
        <el-step title="预览生成" />
      </el-steps>

      <div class="mt-5">
        <el-form
          v-show="active == 0"
          :model="genConfigFormData"
          :label-width="100"
          :rules="genConfigFormRules"
        >
          <el-row>
            <el-col :span="12">
              <el-form-item label="表名" prop="tableName">
                <el-input v-model="genConfigFormData.tableName" readonly />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="业务名" prop="businessName">
                <el-input v-model="genConfigFormData.businessName" placeholder="用户" />
              </el-form-item>
            </el-col>
          </el-row>

          <el-row>
            <el-col :span="12">
              <el-form-item label="主包名" prop="packageName">
                <el-input v-model="genConfigFormData.packageName" placeholder="com.youlai.boot" />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="模块名" prop="moduleName">
                <el-input v-model="genConfigFormData.moduleName" placeholder="system" />
              </el-form-item>
            </el-col>
          </el-row>

          <el-row>
            <el-col :span="12">
              <el-form-item label="实体名" prop="entityName">
                <el-input v-model="genConfigFormData.entityName" placeholder="User" />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="作者">
                <el-input v-model="genConfigFormData.author" placeholder="youlai" />
              </el-form-item>
            </el-col>
          </el-row>

          <el-row>
            <el-col :span="12">
              <el-form-item label="移除表前缀">
                <el-input v-model="genConfigFormData.removeTablePrefix" placeholder="如: sys_" />
              </el-form-item>
            </el-col>
            <el-col :span="12">
              <el-form-item label="页面类型">
                <el-radio-group v-model="genConfigFormData.pageType">
                  <el-radio-button value="classic">普通</el-radio-button>
                  <el-radio-button value="curd">封装(CURD)</el-radio-button>
                </el-radio-group>
              </el-form-item>
            </el-col>
          </el-row>

          <el-row>
            <el-col :span="12">
              <el-form-item>
                <template #label>
                  <div class="flex-y-between">
                    <span>上级菜单</span>
                    <el-tooltip effect="dark">
                      <template #content>
                        选择上级菜单，生成代码后会自动创建对应菜单。
                        <br />
                        注意1：生成菜单后需分配权限给角色，否则菜单将无法显示。
                        <br />
                        注意2：演示环境默认不生成菜单，如需生成，请在本地部署数据库。
                      </template>
                      <el-icon class="cursor-pointer">
                        <QuestionFilled />
                      </el-icon>
                    </el-tooltip>
                  </div>
                </template>

                <el-tree-select
                  v-model="genConfigFormData.parentMenuId"
                  placeholder="选择上级菜单"
                  :data="menuOptions"
                  check-strictly
                  :render-after-expand="false"
                  filterable
                  clearable
                />
              </el-form-item>
            </el-col>
          </el-row>
        </el-form>

        <div v-show="active == 1" class="elTableCustom">
          <div class="mb-2 flex-y-center gap-2">
            <el-tag size="small" type="info">批量设置</el-tag>
            <el-space size="small">
              <el-dropdown>
                <el-button size="small" type="primary" plain>查询</el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item @click="bulkSet('isShowInQuery', 1)">全选</el-dropdown-item>
                    <el-dropdown-item @click="bulkSet('isShowInQuery', 0)">全不选</el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-dropdown>
                <el-button size="small" type="success" plain>列表</el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item @click="bulkSet('isShowInList', 1)">全选</el-dropdown-item>
                    <el-dropdown-item @click="bulkSet('isShowInList', 0)">全不选</el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
              <el-dropdown>
                <el-button size="small" type="warning" plain>表单</el-button>
                <template #dropdown>
                  <el-dropdown-menu>
                    <el-dropdown-item @click="bulkSet('isShowInForm', 1)">全选</el-dropdown-item>
                    <el-dropdown-item @click="bulkSet('isShowInForm', 0)">全不选</el-dropdown-item>
                  </el-dropdown-menu>
                </template>
              </el-dropdown>
            </el-space>
          </div>
          <el-table
            v-loading="loading"
            row-key="id"
            :element-loading-text="loadingText"
            highlight--currentrow
            :data="genConfigFormData.fieldConfigs"
          >
            <el-table-column width="55" align="center">
              <el-icon class="cursor-move sortable-handle">
                <Rank />
              </el-icon>
            </el-table-column>

            <el-table-column label="列名" width="110">
              <template #default="scope">
                {{ scope.row.columnName }}
              </template>
            </el-table-column>

            <el-table-column label="列类型" width="80">
              <template #default="scope">
                {{ scope.row.columnType }}
              </template>
            </el-table-column>
            <el-table-column label="字段名" width="120">
              <template #default="scope">
                <el-input v-model="scope.row.fieldName" />
              </template>
            </el-table-column>
            <el-table-column label="字段类型" width="80">
              <template #default="scope">
                {{ scope.row.fieldType }}
              </template>
            </el-table-column>

            <el-table-column label="字段注释" min-width="100">
              <template #default="scope">
                <el-input v-model="scope.row.fieldComment" />
              </template>
            </el-table-column>

            <el-table-column label="最大长度" width="80">
              <template #default="scope">
                <el-input v-model="scope.row.maxLength" />
              </template>
            </el-table-column>

            <el-table-column width="70" label="查询">
              <template #default="scope">
                <el-checkbox v-model="scope.row.isShowInQuery" :true-value="1" :false-value="0" />
              </template>
            </el-table-column>

            <el-table-column width="70" label="列表">
              <template #default="scope">
                <el-checkbox v-model="scope.row.isShowInList" :true-value="1" :false-value="0" />
              </template>
            </el-table-column>

            <el-table-column width="70" label="表单">
              <template #default="scope">
                <el-checkbox v-model="scope.row.isShowInForm" :true-value="1" :false-value="0" />
              </template>
            </el-table-column>

            <el-table-column label="必填" width="70">
              <template #default="scope">
                <el-checkbox
                  v-if="scope.row.isShowInForm == 1"
                  v-model="scope.row.isRequired"
                  :true-value="1"
                  :false-value="0"
                />
                <span v-else>-</span>
              </template>
            </el-table-column>

            <el-table-column label="查询方式" min-width="120">
              <template #default="scope">
                <el-select
                  v-if="scope.row.isShowInQuery === 1"
                  v-model="scope.row.queryType"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="(item, key) in queryTypeOptions"
                    :key="key"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
                <span v-else>-</span>
              </template>
            </el-table-column>

            <el-table-column label="表单类型" min-width="120">
              <template #default="scope">
                <el-select
                  v-if="scope.row.isShowInQuery === 1 || scope.row.isShowInForm === 1"
                  v-model="scope.row.formType"
                  placeholder="请选择"
                >
                  <el-option
                    v-for="(item, key) in formTypeOptions"
                    :key="key"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
                <span v-else>-</span>
              </template>
            </el-table-column>

            <el-table-column label="字典类型" min-width="100">
              <template #default="scope">
                <el-select
                  v-if="scope.row.formType === FormTypeEnum.SELECT.value"
                  v-model="scope.row.dictType"
                  placeholder="请选择"
                  clearable
                >
                  <el-option
                    v-for="item in dictOptions"
                    :key="item.value"
                    :label="item.label"
                    :value="item.value"
                  />
                </el-select>
                <span v-else>-</span>
              </template>
            </el-table-column>
          </el-table>
        </div>

        <el-row v-show="active == 2">
          <el-col :span="24" class="mb-2">
            <div class="flex-y-center gap-3">
              <span class="text-sm color-#909399">预览范围</span>
              <el-radio-group v-model="previewScope" size="small">
                <el-radio-button value="all">全部</el-radio-button>
                <el-radio-button value="frontend">前端</el-radio-button>
                <el-radio-button value="backend">后端</el-radio-button>
              </el-radio-group>
              <span class="ml-3 text-sm color-#909399">类型</span>
              <el-checkbox-group v-model="previewTypes" size="small">
                <el-checkbox-button v-for="t in previewTypeOptions" :key="t" :value="t">
                  {{ t }}
                </el-checkbox-button>
              </el-checkbox-group>
            </div>
          </el-col>
          <el-col :span="6">
            <el-scrollbar max-height="72vh">
              <el-tree
                :data="filteredTreeData"
                default-expand-all
                highlight-current
                @node-click="handleFileTreeNodeClick"
              >
                <template #default="{ data }">
                  <div :class="`i-svg:${getFileTreeNodeIcon(data)}`" />
                  <span class="ml-1">{{ data.label }}</span>
                </template>
              </el-tree>
            </el-scrollbar>
          </el-col>
          <el-col :span="18">
            <el-scrollbar max-height="72vh">
              <div class="absolute z-36 right-5 top-2">
                <el-link type="primary" @click="handleCopyCode">
                  <el-icon>
                    <CopyDocument />
                  </el-icon>
                  一键复制
                </el-link>
              </div>

              <Codemirror
                ref="cmRef"
                v-model:value="code"
                :options="cmOptions"
                border
                :readonly="true"
                height="100%"
                width="100%"
              />
            </el-scrollbar>
          </el-col>
        </el-row>
      </div>

      <template #footer>
        <el-button v-if="active !== 0" type="success" @click="handlePrevClick">
          <el-icon>
            <Back />
          </el-icon>
          {{ prevBtnText }}
        </el-button>
        <el-button type="primary" @click="handleNextClick">
          {{ nextBtnText }}
          <el-icon v-if="active !== 2">
            <Right />
          </el-icon>
          <el-icon v-else>
            <Download />
          </el-icon>
        </el-button>
        <el-button
          v-if="active === 2"
          :disabled="!supportsFSAccess"
          type="primary"
          plain
          @click="openWriteDialog"
        >
          <template #icon>
            <el-icon><FolderOpened /></el-icon>
          </template>
          写入本地
        </el-button>
      </template>
    </el-drawer>
    <!-- 写入本地对话框 -->
    <el-dialog v-model="writeDialog.visible" title="写入本地" width="820px">
      <div class="space-y-3">
        <el-alert
          v-if="!supportsFSAccess"
          title="当前浏览器不支持 File System Access API，建议使用 Chrome/Edge 最新版"
          type="warning"
          show-icon
          :closable="false"
        />

        <el-form :label-width="110">
          <el-form-item label="前端根目录">
            <div class="flex-y-center gap-2">
              <el-input v-model="frontendDirPath" placeholder="请选择前端根目录" readonly />
              <el-button :disabled="!supportsFSAccess" @click="pickFrontendDir">选择</el-button>
            </div>
          </el-form-item>
          <el-form-item label="后端根目录">
            <div class="flex-y-center gap-2">
              <el-input v-model="backendDirPath" placeholder="请选择后端根目录" readonly />
              <el-button :disabled="!supportsFSAccess" @click="pickBackendDir">选择</el-button>
            </div>
          </el-form-item>
          <el-form-item label="写入范围">
            <el-radio-group v-model="writeScope">
              <el-radio-button value="all">全部</el-radio-button>
              <el-radio-button value="frontend">仅前端</el-radio-button>
              <el-radio-button value="backend">仅后端</el-radio-button>
            </el-radio-group>
          </el-form-item>
          <el-form-item label="覆盖策略">
            <el-radio-group v-model="overwriteMode">
              <el-radio-button value="overwrite">覆盖</el-radio-button>
              <el-radio-button value="skip">跳过已存在</el-radio-button>
              <el-radio-button value="ifChanged">仅在变更时覆盖</el-radio-button>
            </el-radio-group>
          </el-form-item>
        </el-form>

        <div v-if="writeProgress.total > 0" class="mt-2">
          <el-progress :text-inside="true" :stroke-width="16" :percentage="writeProgress.percent" />
          <div class="mt-1 text-sm color-#909399">
            {{ writeProgress.done }}/{{ writeProgress.total }} {{ writeProgress.current }}
          </div>
        </div>
      </div>

      <template #footer>
        <el-button @click="writeDialog.visible = false">取消</el-button>
        <el-button
          type="primary"
          :disabled="!canWriteToLocal || writeRunning"
          @click="confirmWrite"
        >
          写入
        </el-button>
      </template>
    </el-dialog>
  </div>
</template>

<script setup lang="ts">
defineOptions({
  name: "Codegen",
});

import Sortable from "sortablejs";
import "codemirror/mode/javascript/javascript.js";
import Codemirror from "codemirror-editor-vue3";
import type { CmComponentRef } from "codemirror-editor-vue3";
import type { EditorConfiguration } from "codemirror";

import { FormTypeEnum, QueryTypeEnum } from "@/enums/codegen";

import GeneratorAPI from "@/api/codegen";
import type {
  FieldConfig,
  GenConfigForm,
  TableQueryParams,
  TableItem,
  GeneratorPreviewItem,
} from "@/api/types";
import { ElLoading } from "element-plus";

import DictAPI from "@/api/system/dict";
import MenuAPI from "@/api/system/menu";

interface TreeNode {
  label: string;
  content?: string;
  children?: TreeNode[];
  scope?: "frontend" | "backend";
  language?: string;
}
const treeData = ref<TreeNode[]>([]);
const previewScope = ref<"all" | "frontend" | "backend">("all");
const previewTypeOptions = ref<string[]>([]);
const previewTypes = ref<string[]>([]);
const frontendType = "ts";

const filteredTreeData = computed<TreeNode[]>(() => {
  if (!treeData.value.length) return [];
  // 基于原树 scope/types 过滤叶子节点
  const match = (node: TreeNode): boolean => {
    if (previewScope.value !== "all") {
      if (node.scope !== previewScope.value) return false;
    }
    if (!previewTypes.value.length) return true;
    const language = node.language || node.label.split(".").pop() || "";
    return previewTypes.value.includes(language);
  };

  const cloneFilter = (node: TreeNode): TreeNode | null => {
    if (!node.children || node.children.length === 0) {
      return match(node) ? { ...node } : null;
    }
    const children = (node.children || []).map((c) => cloneFilter(c)).filter(Boolean) as TreeNode[];
    if (!children.length) return null;
    return { label: node.label, children };
  };

  return treeData.value.map((n) => cloneFilter(n)).filter(Boolean) as TreeNode[];
});

const queryFormRef = ref();
const queryParams = reactive<TableQueryParams>({
  pageNum: 1,
  pageSize: 10,
});

const loading = ref(false);
const loadingText = ref("loading...");

const pageData = ref<TableItem[]>([]);
const total = ref(0);

const formTypeOptions: Record<string, OptionItem> = FormTypeEnum;
const queryTypeOptions: Record<string, OptionItem> = QueryTypeEnum;
const dictOptions = ref<OptionItem[]>();
const menuOptions = ref<OptionItem[]>([]);
const genConfigFormData = ref<GenConfigForm>({
  fieldConfigs: [],
  pageType: "classic",
});

const genConfigFormRules = {
  tableName: [{ required: true, message: "请输入表名", trigger: "blur" }],
  businessName: [{ required: true, message: "请输入业务名", trigger: "blur" }],
  packageName: [{ required: true, message: "请输入主包名", trigger: "blur" }],
  moduleName: [{ required: true, message: "请输入模块名", trigger: "blur" }],
  entityName: [{ required: true, message: "请输入实体名", trigger: "blur" }],
};

const dialog = reactive({
  visible: false,
  title: "",
});

// 页面风格使用后端持久化字段 genConfigFormData.ui
watch(
  () => genConfigFormData.value.removeTablePrefix,
  (prefix) => {
    const table = genConfigFormData.value.tableName;
    if (!table) return;
    const p = prefix || "";
    const base = table.startsWith(p) ? table.slice(p.length) : table;
    // 将下划线分隔的表名转为帕斯卡命名
    const camel = base
      .split("_")
      .filter(Boolean)
      .map((s) => s.charAt(0).toUpperCase() + s.slice(1))
      .join("");
    genConfigFormData.value.entityName = camel;
  }
);

const { copy, copied } = useClipboard();
const code = ref<string>("");
const cmRef = ref<CmComponentRef>();
const cmOptions: EditorConfiguration = {
  mode: "text/javascript",
};

const prevBtnText = ref("");
const nextBtnText = ref("下一步，字段配置");
const active = ref(0);
const currentTableName = ref("");
const sortFlag = ref<Sortable | null>(null);

// ================= 本地写盘（可选） =================
const supportsFSAccess = typeof (window as any).showDirectoryPicker === "function";
const outputMode = ref<"zip" | "local">("zip");
const frontendDirHandle = ref<any>(null);
const backendDirHandle = ref<any>(null);
const frontendDirName = ref("");
const backendDirName = ref("");
// 预览的原始文件列表（用于写盘）
const lastPreviewFiles = ref<GeneratorPreviewItem[]>([]);
const needFrontend = computed(() =>
  lastPreviewFiles.value.some((f) => resolveRootForItem(f) === "frontend")
);
const needBackend = computed(() =>
  lastPreviewFiles.value.some((f) => resolveRootForItem(f) === "backend")
);
const canWriteToLocal = computed(() => {
  if (!lastPreviewFiles.value.length) return false;
  const frontOk = needFrontend.value ? !!frontendDirHandle.value : true;
  const backOk = needBackend.value ? !!backendDirHandle.value : true;
  return frontOk && backOk;
});

// 查询是否全选
const isCheckAllQuery = ref(false);
// 列表是否全选
const isCheckAllList = ref(false);
// 表单是否全选
const isCheckAllForm = ref(false);

watch(active, (val) => {
  if (val === 0) {
    nextBtnText.value = "下一步，字段配置";
  } else if (val === 1) {
    prevBtnText.value = "上一步，基础配置";
    nextBtnText.value = "下一步，确认生成";
    nextTick(() => {
      initSort();
    });
  } else if (val === 2) {
    prevBtnText.value = "上一步，字段配置";
    nextBtnText.value = "下载代码";
  }
});

watch(
  () => dialog.visible,
  (visible) => {
    if (!visible) {
      destroySort();
    }
  }
);

watch(copied, () => {
  if (copied.value) {
    ElMessage.success("复制成功");
  }
});

watch(
  () => genConfigFormData.value.fieldConfigs as FieldConfig[],
  (newVal: FieldConfig[]) => {
    newVal.forEach((fieldConfig) => {
      if (
        fieldConfig.fieldType &&
        fieldConfig.fieldType.includes("Date") &&
        fieldConfig.isShowInQuery === 1 &&
        fieldConfig.queryType == null
      ) {
        fieldConfig.queryType = QueryTypeEnum.BETWEEN.value as number;
      }
    });
  },
  { deep: true, immediate: true }
);

function destroySort() {
  if (!sortFlag.value) return;
  sortFlag.value.destroy();
  sortFlag.value = null;
}

function setNodeSort(oldIndex: number, newIndex: number) {
  const list = genConfigFormData.value?.fieldConfigs ?? [];
  if (!list || oldIndex === newIndex) return;
  const [item] = list.splice(oldIndex, 1);
  list.splice(newIndex, 0, item);
}

const initSort = () => {
  if (sortFlag.value) return;
  const table = document.querySelector(".elTableCustom .el-table__body-wrapper tbody");
  if (!table) return;
  sortFlag.value = Sortable.create(table as HTMLElement, {
    group: "shared",
    animation: 150,
    ghostClass: "sortable-ghost", //拖拽样式
    handle: ".sortable-handle", //拖拽区域
    easing: "cubic-bezier(1, 0, 0, 1)",

    // 结束拖动事件
    onEnd: (item: any) => {
      setNodeSort(item.oldIndex, item.newIndex);
    },
  });
};

/** 上一页 */
function handlePrevClick() {
  if (active.value === 2) {
    //这里需要重新获取一次数据，如果第一次生成代码后，再次点击上一步，数据不重新获取，再次点击下一步，会再次插入数据，导致索引重复报错
    genConfigFormData.value = {
      fieldConfigs: [],
    };
    nextTick(() => {
      loading.value = true;
      GeneratorAPI.getGenConfig(currentTableName.value)
        .then((data) => {
          genConfigFormData.value = data;
        })
        .finally(() => {
          loading.value = false;
        });
    });
  }
  if (active.value-- <= 0) active.value = 0;
}

/** 下一步 */
function handleNextClick() {
  if (active.value === 0) {
    //这里需要校验基础配置
    const { tableName, packageName, businessName, moduleName, entityName } =
      genConfigFormData.value;
    if (!tableName || !packageName || !businessName || !moduleName || !entityName) {
      ElMessage.error("表名、业务名、包名、模块名、实体名不能为空");
      return;
    }
  }
  if (active.value === 1) {
    // 保存生成配置
    const tableName = genConfigFormData.value.tableName;
    if (!tableName) {
      ElMessage.error("表名不能为空");
      return;
    }
    loading.value = true;
    loadingText.value = "代码生成中，请稍候...";
    GeneratorAPI.saveGenConfig(tableName, genConfigFormData.value)
      .then(() => {
        return handlePreview(tableName);
      })
      .then(() => {
        if (active.value++ >= 2) active.value = 2;
      })
      .finally(() => {
        loading.value = false;
        loadingText.value = "loading...";
      });
  } else {
    if (active.value++ >= 2) {
      active.value = 2;
    }
    if (active.value === 2) {
      const tableName = genConfigFormData.value.tableName;
      if (!tableName) {
        ElMessage.error("表名不能为空");
        return;
      }
      if (outputMode.value === "zip" || !supportsFSAccess) {
        GeneratorAPI.download(
          tableName,
          (genConfigFormData.value.pageType as any) || "classic",
          frontendType
        );
      }
    }
  }
}

/** 查询 */
function handleQuery() {
  loading.value = true;
  GeneratorAPI.getTablePage(queryParams)
    .then((res) => {
      pageData.value = res.data;
      total.value = res.page?.total ?? 0;
    })
    .finally(() => {
      loading.value = false;
    });
}

/** 重置查询 */
function handleResetQuery() {
  queryFormRef.value.resetFields();
  queryParams.pageNum = 1;
  handleQuery();
}

/** 打开弹窗 */
async function handleOpenDialog(tableName: string) {
  dialog.visible = true;
  active.value = 0;
  currentTableName.value = tableName;
  loading.value = true;
  try {
    const [menuList, dictList, config] = await Promise.all([
      MenuAPI.getOptions(true),
      DictAPI.getList(),
      GeneratorAPI.getGenConfig(tableName),
    ]);

    menuOptions.value = menuList;
    dictOptions.value = dictList;
    dialog.title = `${tableName} 代码生成`;
    genConfigFormData.value = config;

    checkAllSelected("isShowInQuery", isCheckAllQuery);
    checkAllSelected("isShowInList", isCheckAllList);
    checkAllSelected("isShowInForm", isCheckAllForm);

    if (genConfigFormData.value.id) {
      active.value = 2;
      await handlePreview(tableName);
    }
  } catch {
    ElMessage.error("获取生成配置失败");
    dialog.visible = false;
  } finally {
    loading.value = false;
  }
}

/** 重置配置 */
function handleResetConfig(tableName: string) {
  ElMessageBox.confirm("确定要重置配置吗？", "提示", {
    type: "warning",
  }).then(() => {
    GeneratorAPI.resetGenConfig(tableName).then(() => {
      ElMessage.success("重置成功");
      handleQuery();
    });
  });
}

type FieldConfigKey = "isShowInQuery" | "isShowInList" | "isShowInForm";

/** 全选 */
// 单列全选开关已移除，改为顶部“批量设置”入口；保留方法时会触发未使用告警，故删除注释

function bulkSet(key: FieldConfigKey, value: 0 | 1) {
  const list = genConfigFormData.value?.fieldConfigs || [];
  list.forEach((row: any) => {
    // 只改已有字段，保持响应式
    row[key] = value;
  });
}

const checkAllSelected = (key: keyof FieldConfig, isCheckAllRef: any) => {
  const fieldConfigs = genConfigFormData.value?.fieldConfigs || [];
  isCheckAllRef.value = fieldConfigs.every((row: FieldConfig) => row[key] === 1);
};

/** 获取生成预览 */
async function handlePreview(tableName: string) {
  treeData.value = [];
  try {
    const data = await GeneratorAPI.getPreviewData(
      tableName,
      (genConfigFormData.value.pageType as any) || "classic",
      frontendType
    );
    dialog.title = `代码生成 ${tableName}`;
    const previewList = data || [];
    const typeOptions = Array.from(
      new Set(
        previewList
          .map((item) => item.language || item.fileName.split(".").pop() || "")
          .filter(Boolean)
      )
    );
    previewTypeOptions.value = typeOptions;
    previewTypes.value = [...typeOptions];

    const tree = buildTree(previewList);
    lastPreviewFiles.value = previewList;
    treeData.value = tree?.children ? [...tree.children] : [];

    const firstLeafNode = findFirstLeafNode(tree);
    if (firstLeafNode) {
      code.value = firstLeafNode.content || "";
    }
  } catch {
    active.value = 0;
    throw new Error("preview_failed");
  }
}

/**
 * 递归构建树形结构
 *
 * @param data - 数据数组
 * @returns 树形结构根节点
 */
function buildTree(data: GeneratorPreviewItem[]): TreeNode {
  // 动态获取根节点
  const root: TreeNode = { label: "前后端代码", children: [] };

  data.forEach((item) => {
    const normalizedPath = item.path.replace(/\\/g, "/");
    const parts = normalizedPath.split("/").filter(Boolean);

    let currentNode = root;

    parts.forEach((part) => {
      // 查找或创建当前部分的子节点
      let node = currentNode.children?.find((child) => child.label === part);
      if (!node) {
        node = { label: part, children: [] };
        currentNode.children?.push(node);
      }
      currentNode = node;
    });

    // 添加文件节点
    currentNode.children?.push({
      label: item.fileName,
      content: item?.content,
      scope: item.scope,
      language: item.language,
    });
  });

  return root;
}

/**
 * 递归查找第一个叶子节点
 * @param node - 树形节点
 * @returns 第一个叶子节点
 */
function findFirstLeafNode(node: TreeNode): TreeNode | null {
  if (!node.children || node.children.length === 0) {
    return node;
  }
  for (const child of node.children) {
    const leafNode = findFirstLeafNode(child);
    if (leafNode) {
      return leafNode;
    }
  }
  return null;
}

/** 文件树节点 Click */
function handleFileTreeNodeClick(data: TreeNode) {
  if (!data.children || data.children.length === 0) {
    code.value = data.content || "";
  }
}

/** 获取文件树节点图标 */
function getFileTreeNodeIcon(node: TreeNode) {
  const ext = (node.language || node.label.split(".").pop() || "").toLowerCase();
  if (ext === "java") {
    return "java";
  }
  if (ext === "html") {
    return "html";
  }
  if (ext === "vue") {
    return "vue";
  }
  if (ext === "ts") {
    return "typescript";
  }
  if (ext === "xml") {
    return "xml";
  }
  if (["cs", "go", "py", "php", "js"].includes(ext)) {
    return "code";
  }
  return "file";
}

/** 一键复制 */
const handleCopyCode = () => {
  if (code.value) {
    copy(code.value);
  }
};

// =============== 目录选择与写入 ===============
const pickFrontendDir = async () => {
  try {
    // @ts-ignore
    frontendDirHandle.value = await (window as any).showDirectoryPicker();
    frontendDirName.value = frontendDirHandle.value?.name || "";
    ElMessage.success("前端目录选择成功");
  } catch {
    // 用户取消或浏览器不支持
  }
};

const pickBackendDir = async () => {
  try {
    // @ts-ignore
    backendDirHandle.value = await (window as any).showDirectoryPicker();
    backendDirName.value = backendDirHandle.value?.name || "";
    ElMessage.success("后端目录选择成功");
  } catch {
    // 用户取消或浏览器不支持
  }
};

async function ensureDir(root: any, path: string[], create = true) {
  let current = root;
  for (const segment of path) {
    // @ts-ignore
    current = await current.getDirectoryHandle(segment, { create });
  }
  return current;
}

async function writeFile(dirHandle: any, filePath: string, content: string) {
  const normalized = filePath.replace(/\\/g, "/");
  const parts = normalized.split("/").filter(Boolean);
  const fileName = parts.pop()!;
  const folderSegments = parts;
  const targetDir = await ensureDir(dirHandle, folderSegments, true);
  // @ts-ignore
  let fileHandle;
  try {
    // @ts-ignore
    fileHandle = await targetDir.getFileHandle(fileName, { create: true });
  } catch (err: any) {
    if (err?.name === "TypeMismatchError") {
      // 存在同名目录(或其它类型冲突)，为安全起见不自动删除
      throw err;
    } else {
      throw err;
    }
  }
  // @ts-ignore
  const writable = await fileHandle.createWritable();
  await writable.write(content ?? "");
  await writable.close();
}

async function pathExists(dirHandle: any, filePath: string): Promise<boolean> {
  try {
    const normalized = filePath.replace(/\\/g, "/");
    const parts = normalized.split("/").filter(Boolean);
    const fileName = parts.pop()!;
    const targetDir = await ensureDir(dirHandle, parts, false);
    // @ts-ignore
    await targetDir.getFileHandle(fileName, { create: false });
    return true;
  } catch {
    return false;
  }
}

async function isSameFile(dirHandle: any, filePath: string, content: string): Promise<boolean> {
  try {
    const normalized = filePath.replace(/\\/g, "/");
    const parts = normalized.split("/").filter(Boolean);
    const fileName = parts.pop()!;
    const targetDir = await ensureDir(dirHandle, parts, false);
    // @ts-ignore
    const fileHandle = await targetDir.getFileHandle(fileName, { create: false });
    // @ts-ignore
    const file = await fileHandle.getFile();
    const text = await file.text();
    return text === (content ?? "");
  } catch {
    return false;
  }
}

// 将预览条目映射到前/后端根目录（由后端给出 scope）
function resolveRootForItem(item: GeneratorPreviewItem) {
  if (item.scope === "backend") {
    return "backend" as const;
  }
  return "frontend" as const;
}

function stripProjectRoot(p: string) {
  const normalized = p.replace(/\\/g, "/");
  const frontApp = genConfigFormData.value.frontendAppName;
  const backApp = genConfigFormData.value.backendAppName;
  let rel = normalized;
  if (frontApp && normalized.startsWith(`${frontApp}/`)) {
    rel = normalized.slice(frontApp.length + 1);
  } else if (backApp && normalized.startsWith(`${backApp}/`)) {
    rel = normalized.slice(backApp.length + 1);
  } else {
    const idx = normalized.indexOf("/src/");
    if (idx > -1) {
      rel = normalized.slice(idx + 1); // 保留 'src/...'
    } else if (normalized.startsWith("src/")) {
      rel = normalized;
    }
  }
  return rel;
}

const writeGeneratedCode = async () => {
  if (!supportsFSAccess) {
    ElMessage.warning("当前浏览器不支持本地写入，请选择下载ZIP");
    return;
  }
  if (
    (needFrontend.value && !frontendDirHandle.value) ||
    (needBackend.value && !backendDirHandle.value)
  ) {
    ElMessage.warning("请先选择所需的前/后端目录");
    return;
  }
  if (!lastPreviewFiles.value.length) {
    ElMessage.warning("请先生成预览");
    return;
  }
  loading.value = true;
  const loadingSvc = ElLoading.service({
    lock: true,
    text: "正在写入代码...",
  });
  writeRunning.value = true;
  let frontCount = 0;
  let backCount = 0;
  const failed: string[] = [];
  const files = lastPreviewFiles.value.filter((f) => {
    const root = resolveRootForItem(f);
    return writeScope.value === "all" || root === writeScope.value;
  });
  writeProgress.total = files.length;
  writeProgress.done = 0;
  writeProgress.percent = 0;
  writeProgress.current = "";

  const concurrency = 4;
  const queue = files.slice();
  const workers: Promise<void>[] = [];

  async function worker() {
    while (queue.length) {
      const item = queue.shift()!;
      try {
        const root = resolveRootForItem(item);
        const relativePath = stripProjectRoot(`${item.path}/${item.fileName}`);
        writeProgress.current = relativePath;
        if (overwriteMode.value === "ifChanged") {
          // 简单差异：已有文件内容与待写内容相同则跳过
          // @ts-ignore
          const targetRoot = root === "frontend" ? frontendDirHandle.value : backendDirHandle.value;
          const existsSame = await isSameFile(targetRoot, relativePath, item.content || "");
          if (existsSame) {
            // 视作成功但不处理
            writeProgress.done++;
            writeProgress.percent = Math.round((writeProgress.done / writeProgress.total) * 100);
            continue;
          }
        }
        if (overwriteMode.value === "skip") {
          // @ts-ignore
          const targetRoot = root === "frontend" ? frontendDirHandle.value : backendDirHandle.value;
          const exists = await pathExists(targetRoot, relativePath);
          if (exists) {
            writeProgress.done++;
            writeProgress.percent = Math.round((writeProgress.done / writeProgress.total) * 100);
            continue;
          }
        }
        if (root === "frontend") {
          await writeFile(frontendDirHandle.value, relativePath, item.content || "");
          frontCount++;
        } else {
          await writeFile(backendDirHandle.value, relativePath, item.content || "");
          backCount++;
        }
      } catch (err) {
        console.error("写入失败:", item.path, err);
        failed.push(item.path);
      } finally {
        writeProgress.done++;
        writeProgress.percent = Math.round((writeProgress.done / writeProgress.total) * 100);
      }
    }
  }

  for (let i = 0; i < concurrency; i++) {
    workers.push(worker());
  }
  await Promise.all(workers);
  loading.value = false;
  loadingSvc.close();
  writeRunning.value = false;
  if (failed.length) {
    ElMessage.warning(
      `部分文件写入失败 ${failed.length} 个，成功 前端 ${frontCount} 个，后端 ${backCount} 个。打开控制台查看详情`
    );
  } else {
    ElMessage.success(`写入完成：前端 ${frontCount} 个文件，后端 ${backCount} 个文件`);
  }
};

const writeDialog = reactive({ visible: false });
const frontendDirPath = ref("");
const backendDirPath = ref("");
const writeScope = ref<"all" | "frontend" | "backend">("all");
const overwriteMode = ref<"overwrite" | "skip" | "ifChanged">("overwrite");
const writeProgress = reactive({ total: 0, done: 0, percent: 0, current: "" });
const writeRunning = ref(false);

// 提示文本已取消展示，保留逻辑意义不大，移除注释

function openWriteDialog() {
  writeDialog.visible = true;
}

// 同步展示路径
watch(frontendDirName, (v) => (frontendDirPath.value = v));
watch(backendDirName, (v) => (backendDirPath.value = v));

async function confirmWrite() {
  await writeGeneratedCode();
  writeDialog.visible = false;
}

/** 组件挂载后执行 */
onMounted(() => {
  handleQuery();
});

onBeforeUnmount(() => {
  cmRef.value?.destroy();
  destroySort();
});
</script>
