import { api } from "../../../services/api";
import { renderPagination } from "../../../components/pagination";
import { getCurrentUser } from "../../../services/authState";
import type { DictionaryResponse, ModelItem, ModelListResponse } from "../../../types";
import { confirmDialog, formDialog, notify, notifyError } from "../../../ui/dialogs";
import { downloadFileFromUrl } from "../../../utils/fileDownload";
import { escapeHtml, modelNameFromFile } from "../../../utils/format";
import { resolveStorageUrl } from "../../../config/runtime";
import { appState } from "../appState";

type UploadFormState = {
  file: File | null;
  files: File[];
};

type UploadModelPayload = {
  file: File;
  name: string;
  fileName?: string;
  folderId?: number;
  brandName?: string;
  typeName?: string;
  operationTree?: string;
  properties?: Record<string, string>;
};

const defaultOperationTree = JSON.stringify({ OperationTree: "[]" });
const requestSceneModelMessageType = "DMT_MODEL_LIBRARY_REQUEST_SCENE_MODEL";
const sceneModelMessageType = "DMT_MODEL_LIBRARY_SCENE_MODEL";
const sceneModelErrorMessageType = "DMT_MODEL_LIBRARY_SCENE_MODEL_ERROR";

type SceneModelMessagePayload = {
  modelName?: string;
  fileName?: string;
  modelBuffer?: ArrayBuffer;
  operationTreeJson?: string;
};

type SceneModelMessage = {
  type?: string;
  requestId?: string;
  payload?: SceneModelMessagePayload;
  message?: string;
};

let dictionariesLoaded = false;
let dictionariesLoading: Promise<void> | null = null;
let sceneModelBridgeBound = false;
let pendingSceneModelRequestId = "";
let pendingSceneModelRequestTimer: number | null = null;
let visibleModels = new Map<number, ModelItem>();

export function bindModelActions() {
  const isAdmin = getCurrentUser()?.role === "admin";
  bindSceneModelBridge();
  ensureImportSceneModelButton();
  if (isAdmin) {
    document.querySelector("#manageUsersBtn")?.addEventListener("click", async () => {
      try {
        const { openUserManager } = await import("./users");
        await openUserManager();
      } catch (error) {
        notifyError(error);
      }
    });

    document.querySelector("#manageDictionariesBtn")?.addEventListener("click", async () => {
      try {
        const { openDictionaryManager } = await import("./dictionaries");
        await openDictionaryManager(async () => {
          invalidateDictionaries();
          await loadModels();
        });
      } catch (error) {
        notifyError(error);
      }
    });

  }

  document.querySelector("#addModelBtn")?.addEventListener("click", async () => {
    try {
      await openUploadModelDialog();
    } catch (error) {
      notifyError(error);
    }
  });

  document.querySelector("#addProcessModelBtn")?.addEventListener("click", async () => {
    try {
      await openProcessModelUploadDialog();
    } catch (error) {
      notifyError(error);
    }
  });

  document.querySelector<HTMLSelectElement>("#brandFilter")!.addEventListener("change", async (event) => {
    appState.filters.brandId = (event.currentTarget as HTMLSelectElement).value;
    appState.page = 1;
    await loadModels();
  });
  document.querySelector<HTMLSelectElement>("#typeFilter")!.addEventListener("change", async (event) => {
    appState.filters.typeId = (event.currentTarget as HTMLSelectElement).value;
    appState.page = 1;
    await loadModels();
  });
  document.querySelector<HTMLInputElement>("#keywordFilter")!.addEventListener("keydown", async (event) => {
    if (event.key !== "Enter") return;
    appState.filters.keyword = (event.currentTarget as HTMLInputElement).value.trim();
    appState.page = 1;
    await loadModels();
  });
  document.querySelector<HTMLInputElement>("#keywordFilter")!.addEventListener("change", async (event) => {
    appState.filters.keyword = (event.currentTarget as HTMLInputElement).value.trim();
    appState.page = 1;
    await loadModels();
  });
  document.querySelector("#resetFilterBtn")!.addEventListener("click", async () => {
    appState.filters.brandId = "";
    appState.filters.typeId = "";
    appState.filters.keyword = "";
    document.querySelector<HTMLSelectElement>("#brandFilter")!.value = "";
    document.querySelector<HTMLSelectElement>("#typeFilter")!.value = "";
    document.querySelector<HTMLInputElement>("#keywordFilter")!.value = "";
    appState.page = 1;
    await loadModels();
  });
}

export async function loadModels(options: { preserveCards?: boolean } = {}) {
  const grid = document.querySelector<HTMLDivElement>("#modelGrid");
  if (!grid || !appState.selectedFolderId) return;
  await loadDictionaries();
  syncSelectedFolderActions();
  document.querySelector("#folderCrumb")!.textContent = appState.selectedFolderName || "模型库";
  const params = new URLSearchParams({
    folderId: String(appState.selectedFolderId),
    page: String(appState.page),
    pageSize: String(appState.pageSize)
  });
  if (appState.filters.brandId) params.set("brandId", appState.filters.brandId);
  if (appState.filters.typeId) params.set("typeId", appState.filters.typeId);
  if (appState.filters.keyword) params.set("keyword", appState.filters.keyword);
  const result = await api<ModelListResponse>(`/api/models?${params.toString()}`);
  if (result.items.length === 0 && appState.page > 1) {
    appState.page -= 1;
    return loadModels();
  }
  renderPagination({
    container: document.querySelector<HTMLElement>("#modelPagination")!,
    total: result.total,
    page: appState.page,
    pageSize: appState.pageSize,
    onChange: async (page, pageSize) => {
      appState.page = page;
      appState.pageSize = pageSize;
      await loadModels();
    }
  });
  visibleModels = new Map(result.items.map((item) => [item.id, item]));
  renderModelGrid(grid, result.items, options.preserveCards);
  bindModelGridActions(grid);
}

function bindModelGridActions(grid: HTMLDivElement) {
  if (grid.dataset.actionsBound === "true") return;
  grid.dataset.actionsBound = "true";
  grid.addEventListener("click", async (event) => {
    const target = event.target instanceof Element ? event.target : null;
    const button = target?.closest<HTMLButtonElement>("button[data-action][data-id]");
    if (!button || !grid.contains(button)) return;
    const id = Number(button.dataset.id);
    const model = visibleModels.get(id);
    try {
      switch (button.dataset.action) {
      case "preview": {
        if (!model) return;
        const { openModelPreview } = await import("./preview");
        openModelPreview(model, loadModels);
        break;
      }
      case "edit":
        await editModel(id);
        break;
      case "import": {
        if (!model) return;
        const { createImportScenePayload, emitImportScenePayload } = await import("./preview");
        const payload = createImportScenePayload({
          model,
          basePointEnabled: false,
          basePoint: { x: 0, y: 0, z: 0, rx: 0, ry: 0, rz: 0 },
          selectedOperationIndex: null
        });
        emitImportScenePayload(payload);
        notify("导入场景数据已输出");
        break;
      }
      case "download":
        if (!model) return;
        await downloadFileFromUrl(model.file_url, model.original_filename || model.name);
        break;
      case "delete":
        await deleteModel(id);
        break;
      }
    } catch (error) {
      notifyError(error);
    }
  });
}

function renderModelGrid(grid: HTMLDivElement, items: ModelItem[], preserveCards = false) {
  if (!preserveCards) {
    grid.innerHTML = items.map(renderModelCard).join("") || `<div class="empty-state">当前目录暂无模型</div>`;
    return;
  }
  if (items.length === 0) {
    grid.innerHTML = `<div class="empty-state">当前目录暂无模型</div>`;
    return;
  }
  const existingCards = new Map(
    Array.from(grid.querySelectorAll<HTMLElement>(".model-card[data-model-id]"))
      .map((card) => [Number(card.dataset.modelId), card])
  );
  const fragment = document.createDocumentFragment();
  for (const item of items) {
    const existingCard = existingCards.get(item.id);
    if (existingCard) {
      fragment.append(existingCard);
      continue;
    }
    const template = document.createElement("template");
    template.innerHTML = renderModelCard(item).trim();
    fragment.append(template.content);
  }
  grid.replaceChildren(fragment);
}

async function reloadFoldersAndModels(preserveModelCards = false) {
  const { loadFolders } = await import("./folders");
  await loadFolders({ reloadModels: false });
  await loadModels({ preserveCards: preserveModelCards });
}

async function loadDictionaries(force = false) {
  if (!force && dictionariesLoaded) {
    syncDictionaryFilters();
    return;
  }
  if (!force && dictionariesLoading) {
    await dictionariesLoading;
    syncDictionaryFilters();
    return;
  }

  dictionariesLoading = api<DictionaryResponse>("/api/dictionaries")
    .then((result) => {
      appState.brands = result.brands;
      appState.types = result.types;
      dictionariesLoaded = true;
    })
    .finally(() => {
      dictionariesLoading = null;
    });
  await dictionariesLoading;
  syncDictionaryFilters();
}

function invalidateDictionaries() {
  dictionariesLoaded = false;
}

function syncDictionaryFilters() {
  syncDictionarySelect("#brandFilter", appState.brands, "全部品牌", appState.filters.brandId);
  syncDictionarySelect("#typeFilter", appState.types, "全部类型", appState.filters.typeId);
}

function syncDictionarySelect(selector: string, items: { id: number; name: string }[], emptyText: string, value: string) {
  const select = document.querySelector<HTMLSelectElement>(selector);
  if (!select) return;
  const current = select.value || value;
  select.innerHTML = `<option value="">${emptyText}</option>` + items
    .map((item) => `<option value="${item.id}">${escapeHtml(item.name)}</option>`)
    .join("");
  select.value = current;
}

function renderModelCard(item: ModelItem) {
  const canWrite = item.permissions.write;
  const prop = item.properties ?? {};
  const thumb = item.thumbnail_url
    ? `<img src="${resolveStorageUrl(item.thumbnail_url)}" alt="" />`
    : `<div class="thumb-placeholder">暂无预览图</div>`;
  return `
    <article class="model-card" data-model-id="${item.id}">
      <div class="model-info">
        <strong>${escapeHtml(item.name)}</strong>
      </div>
      <div class="thumb">
        ${thumb}
        <div class="thumb-actions">
          <button data-action="preview" data-id="${item.id}">预览</button>
          <button data-action="download" data-id="${item.id}">下载</button>
          ${canWrite ? `<button data-action="edit" data-id="${item.id}">编辑</button>` : ""}
          <button data-action="import" data-id="${item.id}">导入</button>
          ${canWrite ? `<button data-action="delete" data-id="${item.id}">删除</button>` : ""}
        </div>
      </div>
      <dl>
        <div><dt>品牌</dt><dd>${escapeHtml(item.brand_name ?? "")}</dd></div>
        <div><dt>类型</dt><dd>${escapeHtml(item.type_name ?? "")}</dd></div>
        <div><dt>型号</dt><dd>${escapeHtml(prop.model ?? "")}</dd></div>
      </dl>
    </article>
  `;
}

function syncSelectedFolderActions() {
  const currentFolder = appState.folders.find((folder) => folder.id === appState.selectedFolderId);
  const canWrite = Boolean(currentFolder?.permissions.write);
  const addModelBtn = document.querySelector<HTMLButtonElement>("#addModelBtn");
  const addProcessModelBtn = document.querySelector<HTMLButtonElement>("#addProcessModelBtn");
  const importSceneModelBtn = document.querySelector<HTMLButtonElement>("#importSceneModelBtn");
  if (addModelBtn) addModelBtn.hidden = !canWrite;
  if (addProcessModelBtn) addProcessModelBtn.hidden = !canWrite;
  if (importSceneModelBtn) importSceneModelBtn.hidden = !canWrite;
}

function ensureImportSceneModelButton() {
  if (document.querySelector("#importSceneModelBtn")) return;
  const addModelBtn = document.querySelector<HTMLButtonElement>("#addModelBtn");
  if (!addModelBtn) return;
  const button = document.createElement("button");
  button.id = "importSceneModelBtn";
  button.type = "button";
  button.textContent = "导入场景模型";
  button.addEventListener("click", requestSceneModelImport);
  addModelBtn.insertAdjacentElement("afterend", button);
}

function bindSceneModelBridge() {
  if (sceneModelBridgeBound) return;
  sceneModelBridgeBound = true;
  window.addEventListener("message", (event) => {
    const data = event.data as SceneModelMessage;
    if (data?.type !== sceneModelMessageType && data?.type !== sceneModelErrorMessageType) return;
    if (!isAllowedParentOrigin(event.origin)) return;
    void handleSceneModelMessage(data);
  });
}

function requestSceneModelImport() {
  if (!appState.selectedFolderId) {
    notify("请先选择目录");
    return;
  }
  if (!window.parent || window.parent === window) {
    notify("请在 DMT 主程序中使用导入场景模型");
    return;
  }
  const requestId = createSceneModelRequestId();
  pendingSceneModelRequestId = requestId;
  clearSceneModelRequestTimer();
  pendingSceneModelRequestTimer = window.setTimeout(() => {
    if (pendingSceneModelRequestId !== requestId) return;
    pendingSceneModelRequestId = "";
    pendingSceneModelRequestTimer = null;
    notifyError("主程序未响应导入场景模型请求，请确认模型库是在主程序面板中打开");
  }, 15000);
  window.parent.postMessage({
    type: requestSceneModelMessageType,
    requestId
  }, resolveParentPostMessageOrigin());
  // notify("已请求主程序导出当前选中模型");
}

async function handleSceneModelMessage(data: SceneModelMessage) {
  console.log("Received scene model message:", data);
  if (pendingSceneModelRequestId && data.requestId && data.requestId !== pendingSceneModelRequestId) return;
  pendingSceneModelRequestId = "";
  clearSceneModelRequestTimer();
  if (data.type === sceneModelErrorMessageType) {
    notifyError(data.message || "主程序导出模型失败");
    return;
  }

  const payload = data.payload;
  if (!payload?.modelBuffer) {
    notifyError("主程序返回的模型数据为空");
    return;
  }

  const fileName = ensureGlbFileName(payload.fileName || payload.modelName || "scene-model.glb");
  const file = new File([payload.modelBuffer], fileName, { type: "model/gltf-binary" });
  await openProcessModelUploadDialog({
    file,
    modelName: payload.modelName || modelNameFromFile(fileName),
    operationTreeJson: payload.operationTreeJson || defaultOperationTree
  });
}

function clearSceneModelRequestTimer() {
  if (pendingSceneModelRequestTimer === null) return;
  window.clearTimeout(pendingSceneModelRequestTimer);
  pendingSceneModelRequestTimer = null;
}

function createSceneModelRequestId() {
  return `scene-model-${Date.now()}-${Math.random().toString(36).slice(2)}`;
}

function isAllowedParentOrigin(origin: string) {
  const expectedOrigin = resolveParentOrigin();
  return expectedOrigin === "*" || origin === expectedOrigin || isLocalDmtParentOrigin(origin);
}

function resolveParentPostMessageOrigin() {
  const origin = resolveParentOrigin();
  return origin === window.location.origin ? "*" : origin;
}

function resolveParentOrigin() {
  try {
    const meta = import.meta as ImportMeta & { env?: Record<string, string | undefined> };
    const configuredOrigin = meta.env?.VITE_DMT_PARENT_ORIGIN;
    if (configuredOrigin) return configuredOrigin;
    if (document.referrer) return new URL(document.referrer).origin;
  } catch {
    return "*";
  }
  return "*";
}

function isLocalDmtParentOrigin(origin: string) {
  try {
    const url = new URL(origin);
    const isLocalHost = url.hostname === "localhost" || url.hostname === "127.0.0.1";
    return isLocalHost && url.port === "3000";
  } catch {
    return false;
  }
}

function ensureGlbFileName(fileName: string) {
  return fileName.toLowerCase().endsWith(".glb") ? fileName : `${fileName}.glb`;
}

export async function uploadModelToBackend(payload: UploadModelPayload) {
  const fileName = (payload.fileName || payload.file.name || payload.name).trim();
  const displayName = (payload.name || modelNameFromFile(fileName)).trim();
  if (!displayName) {
    throw new Error("模型名称不能为空");
  }
  if (!fileName.toLowerCase().endsWith(".glb")) {
    throw new Error("当前阶段只允许上传 .glb 模型");
  }
  const folderId = payload.folderId ?? appState.selectedFolderId;
  if (!folderId) {
    throw new Error("请先选择目录");
  }

  const form = new FormData();
  form.set("folderId", String(folderId));
  form.set("name", displayName);
  form.set("brandName", payload.brandName ?? "");
  form.set("typeName", payload.typeName ?? "");
  form.set("operationTree", normalizeOperationTreeInput(payload.operationTree));
  form.set("file", payload.file, fileName);
  for (const [key, value] of Object.entries(payload.properties ?? {})) {
    form.set(`prop.${key}`, value);
  }

  await api("/api/models/upload", {
    method: "POST",
    body: form
  });
}

async function openUploadModelDialog() {
  if (!appState.selectedFolderId) {
    notify("请先选择目录");
    return;
  }

  const state: UploadFormState = { file: null, files: [] };
  await loadDictionaries();
  const result = await formDialog<boolean>({
    title: "增加模型",
    width: 560,
    height: 430,
    blockPage: false,
    body: `
      <form id="modelUploadPopupForm" class="popup-form upload-popup-form">
        <label>
          <span>模型文件</span>
          <div id="modelDropZone" class="drop-zone">
            <input id="modelUploadFile" type="file" accept=".glb" multiple />
            <strong>选择模型</strong>
            <em>或拖拽一个或多个 .glb 模型到这里</em>
            <small id="selectedFileName">未选择文件</small>
          </div>
        </label>
        <label>
          <span>模型名称</span>
          <input id="modelUploadName" name="name" placeholder="选择文件后自动填入" />
        </label>
        <div class="popup-form-grid">
          <label><span>品牌</span><input name="brandName" list="brandOptions" /></label>
          <label><span>类型</span><input name="typeName" list="typeOptions" /></label>
          <label><span>型号</span><input name="model" /></label>
        </div>
        <div id="modelUploadStatus" class="upload-status" aria-live="polite" hidden></div>
        ${dictionaryDatalistHtml()}
      </form>
    `,
    onOpen: () => bindUploadDialogEvents(state),
    onSubmit: async () => {
      if (state.files.length === 0) {
        throw new Error("请选择 .glb 模型文件");
      }
      const name = document.querySelector<HTMLInputElement>("#modelUploadName")?.value.trim();
      if (state.files.length === 1 && !name) {
        throw new Error("模型名称不能为空");
      }
      const form = new FormData(document.querySelector<HTMLFormElement>("#modelUploadPopupForm")!);
      const brandName = String(form.get("brandName") ?? "");
      const typeName = String(form.get("typeName") ?? "");
      const properties = {
        model: String(form.get("model") ?? "")
      };
      let completedCount = 0;
      const totalCount = state.files.length;
      setUploadDialogBusy(state, true);
      showUploadProgressOverlay(totalCount);
      try {
        for (const [index, file] of state.files.entries()) {
          const uploadingMessage = `正在上传 ${index + 1}/${totalCount}：${file.name}`;
          updateUploadStatus(uploadingMessage);
          updateUploadProgressOverlay({
            total: totalCount,
            completed: completedCount,
            message: uploadingMessage,
            fileName: file.name
          });
          await waitForUploadStatusPaint();
          await uploadModelToBackend({
            file,
            name: state.files.length === 1 ? name ?? "" : modelNameFromFile(file.name),
            fileName: file.name,
            brandName,
            typeName,
            operationTree: defaultOperationTree,
            properties
          });
          completedCount += 1;
          const completedMessage = `已完成 ${completedCount}/${totalCount}`;
          updateUploadStatus(completedMessage);
          updateUploadProgressOverlay({
            total: totalCount,
            completed: completedCount,
            message: completedMessage,
            fileName: file.name
          });
        }
      } catch (error) {
        updateUploadStatus(`上传失败，已完成 ${completedCount}/${totalCount}，请处理后重试。`);
        throw error;
      } finally {
        hideUploadProgressOverlay();
        if (completedCount < state.files.length) {
          setUploadDialogBusy(state, false);
        }
      }
      return true;
    }
  });

  if (result) {
    notify("模型上传完成");
    invalidateDictionaries();
    await reloadFoldersAndModels();
  }
}

export async function openProcessModelUploadDialog(input?: {
  file?: File | null;
  modelName?: string;
  operationTreeJson?: string;
}) {
  if (!appState.selectedFolderId) {
    notify("请先选择目录");
    return;
  }

  const state: UploadFormState = { file: input?.file ?? null, files: input?.file ? [input.file] : [] };
  await loadDictionaries();
  const initialName = input?.modelName || (input?.file ? modelNameFromFile(input.file.name) : "");
  const result = await formDialog<boolean>({
    title: `上传工艺模型-【${appState.selectedFolderName || "当前目录"}】`,
    width: 400,
    height: 300,
    blockPage: false,
    body: `
      <form id="processModelUploadPopupForm" class="popup-form upload-popup-form">
        <div class="popup-form-grid">
          <label><span>模型名称</span><input id="processModelUploadName" name="name" value="${escapeHtml(initialName)}" autocomplete="off" /></label>
          <label><span>品牌</span><input name="brandName" list="brandOptions" autocomplete="off" /></label>
          <label><span>类型</span><input name="typeName" list="typeOptions" autocomplete="off" /></label>
          <label><span>型号</span><input name="model" autocomplete="off" /></label>
        </div>
        ${dictionaryDatalistHtml()}
      </form>
    `,
    onOpen: () => bindProcessUploadDialogEvents(state),
    onSubmit: async () => {
      if (!state.file) {
        throw new Error("请选择 .glb 模型文件");
      }
      const form = new FormData(document.querySelector<HTMLFormElement>("#processModelUploadPopupForm")!);
      const name = String(form.get("name") ?? "").trim();
      await uploadModelToBackend({
        file: state.file,
        name,
        fileName: state.file.name,
        brandName: String(form.get("brandName") ?? ""),
        typeName: String(form.get("typeName") ?? ""),
        operationTree: String(form.get("operationTree") ?? ""),
        properties: {
          model: String(form.get("model") ?? "")
        }
      });
      return true;
    }
  });

  if (result) {
    invalidateDictionaries();
    await reloadFoldersAndModels();
  }
}

function bindUploadDialogEvents(state: UploadFormState) {
  const dropZone = document.querySelector<HTMLDivElement>("#modelDropZone")!;
  const fileInput = document.querySelector<HTMLInputElement>("#modelUploadFile")!;
  const nameInput = document.querySelector<HTMLInputElement>("#modelUploadName")!;
  const selectedFileName = document.querySelector<HTMLElement>("#selectedFileName")!;

  const renderSelectedFiles = () => {
    if (state.files.length === 0) {
      selectedFileName.textContent = "未选择文件";
      nameInput.disabled = false;
      nameInput.value = "";
      nameInput.placeholder = "选择文件后自动填入";
      return;
    }

    if (state.files.length === 1) {
      const [file] = state.files;
      selectedFileName.textContent = file.name;
      nameInput.disabled = false;
      nameInput.value = modelNameFromFile(file.name);
      nameInput.placeholder = "选择文件后自动填入";
      return;
    }

    selectedFileName.innerHTML = `
      <span class="selected-file-summary">已选择 ${state.files.length} 个模型，名称按文件名自动生成</span>
      <span class="selected-file-list">${state.files.map((file) => escapeHtml(file.name)).join("<br>")}</span>
    `;
    nameInput.disabled = true;
    nameInput.value = `${state.files.length} 个模型按文件名自动命名`;
    nameInput.placeholder = "";
  };

  const selectFiles = (files: FileList | File[]) => {
    const selectedFiles = Array.from(files);
    if (selectedFiles.length === 0) return;
    const invalidFiles = selectedFiles.filter((file) => !file.name.toLowerCase().endsWith(".glb"));
    if (invalidFiles.length > 0) {
      notify("当前阶段只允许上传 .glb 模型");
      return;
    }
    state.files = selectedFiles;
    state.file = selectedFiles[0] ?? null;
    renderSelectedFiles();
  };

  fileInput.addEventListener("change", () => {
    if (fileInput.files) selectFiles(fileInput.files);
  });

  dropZone.addEventListener("click", (event) => {
    if (event.target !== fileInput) fileInput.click();
  });

  dropZone.addEventListener("dragover", (event) => {
    event.preventDefault();
    dropZone.classList.add("is-dragover");
  });
  dropZone.addEventListener("dragleave", () => {
    dropZone.classList.remove("is-dragover");
  });
  dropZone.addEventListener("drop", (event) => {
    event.preventDefault();
    dropZone.classList.remove("is-dragover");
    const files = event.dataTransfer?.files;
    if (files) selectFiles(files);
  });
}

function updateUploadStatus(message: string) {
  const status = document.querySelector<HTMLElement>("#modelUploadStatus");
  if (!status) return;
  status.hidden = false;
  status.textContent = message;
}

function waitForUploadStatusPaint() {
  return new Promise<void>((resolve) => {
    window.requestAnimationFrame(() => window.setTimeout(resolve, 0));
  });
}

function showUploadProgressOverlay(total: number) {
  let overlay = document.querySelector<HTMLElement>("#modelUploadProgressOverlay");
  if (!overlay) {
    overlay = document.createElement("div");
    overlay.id = "modelUploadProgressOverlay";
    overlay.className = "upload-progress-overlay";
    overlay.setAttribute("role", "status");
    overlay.setAttribute("aria-live", "polite");
    overlay.innerHTML = `
      <div class="upload-progress-panel">
        <div class="upload-progress-title">模型上传中</div>
        <div id="modelUploadProgressMessage" class="upload-progress-message">正在准备上传...</div>
        <div class="upload-progress-track">
          <div id="modelUploadProgressBar" class="upload-progress-bar"></div>
        </div>
        <div id="modelUploadProgressMeta" class="upload-progress-meta">0/${total}</div>
        <div id="modelUploadProgressFile" class="upload-progress-file"></div>
      </div>
    `;
    document.body.appendChild(overlay);
  }
  overlay.hidden = false;
  updateUploadProgressOverlay({
    total,
    completed: 0,
    message: "正在准备上传...",
    fileName: ""
  });
}

function updateUploadProgressOverlay(options: {
  total: number;
  completed: number;
  message: string;
  fileName: string;
}) {
  const message = document.querySelector<HTMLElement>("#modelUploadProgressMessage");
  const meta = document.querySelector<HTMLElement>("#modelUploadProgressMeta");
  const file = document.querySelector<HTMLElement>("#modelUploadProgressFile");
  const bar = document.querySelector<HTMLElement>("#modelUploadProgressBar");
  const percent = options.total > 0 ? Math.round((options.completed / options.total) * 100) : 0;
  if (message) message.textContent = options.message;
  if (meta) meta.textContent = `${options.completed}/${options.total}`;
  if (file) file.textContent = options.fileName;
  if (bar) bar.style.width = `${percent}%`;
}

function hideUploadProgressOverlay() {
  const overlay = document.querySelector<HTMLElement>("#modelUploadProgressOverlay");
  if (overlay) overlay.hidden = true;
}

function setUploadDialogBusy(state: UploadFormState, busy: boolean) {
  const form = document.querySelector<HTMLFormElement>("#modelUploadPopupForm");
  if (!form) return;
  form.classList.toggle("is-uploading", busy);
  form.querySelectorAll<HTMLInputElement>("input").forEach((input) => {
    input.disabled = busy || (input.id === "modelUploadName" && state.files.length > 1);
  });
}

function bindProcessUploadDialogEvents(state: UploadFormState) {
  // const dropZone = document.querySelector<HTMLDivElement>("#processModelDropZone")!;
  // const fileInput = document.querySelector<HTMLInputElement>("#processModelUploadFile")!;
  // const nameInput = document.querySelector<HTMLInputElement>("#processModelUploadName")!;
  // const selectedFileName = document.querySelector<HTMLElement>("#processSelectedFileName")!;

  // const selectFile = (file: File) => {
  //   if (!file.name.toLowerCase().endsWith(".glb")) {
  //     notify("当前阶段只允许上传 .glb 模型");
  //     return;
  //   }
  //   state.file = file;
  //   selectedFileName.textContent = file.name;
  //   if (!nameInput.value.trim()) {
  //     nameInput.value = file.name;
  //   }
  // };

  // if (state.file) {
  //   selectedFileName.textContent = state.file.name;
  //   if (!nameInput.value.trim()) {
  //     nameInput.value = state.file.name;
  //   }
  // }

  // fileInput.addEventListener("change", () => {
  //   const file = fileInput.files?.[0];
  //   if (file) selectFile(file);
  // });

  // dropZone.addEventListener("click", (event) => {
  //   if (event.target !== fileInput) fileInput.click();
  // });

  // dropZone.addEventListener("dragover", (event) => {
  //   event.preventDefault();
  //   dropZone.classList.add("is-dragover");
  // });
  // dropZone.addEventListener("dragleave", () => {
  //   dropZone.classList.remove("is-dragover");
  // });
  // dropZone.addEventListener("drop", (event) => {
  //   event.preventDefault();
  //   dropZone.classList.remove("is-dragover");
  //   const file = event.dataTransfer?.files?.[0];
  //   if (file) selectFile(file);
  // });
}

function normalizeOperationTreeInput(value?: string) {
  const clean = value?.trim();
  if (!clean) return defaultOperationTree;
  let parsed: unknown;
  try {
    parsed = JSON.parse(clean);
  } catch {
    throw new Error("工艺数据必须是合法 JSON 字符串");
  }

  if (Array.isArray(parsed)) {
    return JSON.stringify({ OperationTree: JSON.stringify(parsed) });
  }

  if (parsed && typeof parsed === "object" && "OperationTree" in parsed) {
    const normalized = { ...(parsed as Record<string, unknown>) };
    const operationTree = normalized.OperationTree;
    if (typeof operationTree === "string") {
      JSON.parse(operationTree);
      normalized.OperationTree = operationTree;
      normalizeOptionalJsonListField(normalized, "ModelProcess");
      normalizeOptionalJsonListField(normalized, "ProcesslLabelList");
      return JSON.stringify(normalized);
    }
    if (Array.isArray(operationTree)) {
      normalized.OperationTree = JSON.stringify(operationTree);
      normalizeOptionalJsonListField(normalized, "ModelProcess");
      normalizeOptionalJsonListField(normalized, "ProcesslLabelList");
      return JSON.stringify(normalized);
    }
  }

  throw new Error('工艺数据格式必须是 {"OperationTree":"[...]"} 或 OperationTree[] 数组');
}

function normalizeOptionalJsonListField(target: Record<string, unknown>, field: string) {
  const value = target[field];
  if (value === undefined || value === null || value === "") return;
  if (typeof value === "string") {
    JSON.parse(value);
    return;
  }
  if (Array.isArray(value)) {
    target[field] = JSON.stringify(value);
  }
}

async function editModel(id: number) {
  const card = document.querySelector<HTMLButtonElement>(`button[data-id="${id}"]`)?.closest(".model-card");
  const oldName = card?.querySelector("strong")?.textContent ?? "";
  await loadDictionaries();
  const result = await formDialog<{ name: string; brandName: string; typeName: string; model: string }>({
    title: "编辑模型",
    width: 520,
    height: 390,
    blockPage: false,
    body: `
      <form id="modelEditPopupForm" class="popup-form">
        <label><span>模型名称</span><input name="name" value="${escapeHtml(oldName)}" /></label>
        <div class="popup-form-grid">
          <label><span>品牌</span><input name="brandName" list="brandOptions" value="${escapeHtml(card?.querySelector("dl div:nth-child(1) dd")?.textContent ?? "")}" /></label>
          <label><span>类型</span><input name="typeName" list="typeOptions" value="${escapeHtml(card?.querySelector("dl div:nth-child(2) dd")?.textContent ?? "")}" /></label>
          <label><span>型号</span><input name="model" value="${escapeHtml(card?.querySelector("dl div:nth-child(3) dd")?.textContent ?? "")}" /></label>
        </div>
        ${dictionaryDatalistHtml()}
      </form>
    `,
    onSubmit: () => {
      const form = new FormData(document.querySelector<HTMLFormElement>("#modelEditPopupForm")!);
      const name = String(form.get("name") ?? "").trim();
      if (!name) {
        throw new Error("模型名称不能为空");
      }
      return {
        name,
        brandName: String(form.get("brandName") ?? ""),
        typeName: String(form.get("typeName") ?? ""),
        model: String(form.get("model") ?? "")
      };
    }
  });
  if (!result) return;
  await api(`/api/models/${id}`, {
    method: "PUT",
    body: JSON.stringify({
      name: result.name,
      brandName: result.brandName,
      typeName: result.typeName,
      properties: {
        model: result.model
      }
    })
  });
  invalidateDictionaries();
  await loadModels();
}

function dictionaryDatalistHtml() {
  return `
    <datalist id="brandOptions">
      ${appState.brands.map((item) => `<option value="${escapeHtml(item.name)}"></option>`).join("")}
    </datalist>
    <datalist id="typeOptions">
      ${appState.types.map((item) => `<option value="${escapeHtml(item.name)}"></option>`).join("")}
    </datalist>
  `;
}

async function deleteModel(id: number) {
  const confirmed = await confirmDialog("确认删除该模型？");
  if (!confirmed) return;
  await api(`/api/models/${id}`, { method: "DELETE" });
  await reloadFoldersAndModels(true);
}
