'use strict';

var drizzleOrm = require('drizzle-orm');
var pgCore = require('drizzle-orm/pg-core');
var AdmZip = require('adm-zip');
var crypto = require('crypto');
var promises = require('fs/promises');
var path = require('path');

function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }

var AdmZip__default = /*#__PURE__*/_interopDefault(AdmZip);
var path__default = /*#__PURE__*/_interopDefault(path);

// src/mmd/server/drizzle-schema.ts
var mmdPlaylists = pgCore.pgTable(
  "mmd_playlists",
  {
    /** 主键ID */
    id: pgCore.uuid("id").primaryKey().defaultRandom(),
    /** 播放列表名称 */
    name: pgCore.varchar("name", { length: 255 }).notNull(),
    /** 播放列表描述 */
    description: pgCore.text("description"),
    /** 是否启用列表循环 */
    loop: pgCore.boolean("loop").notNull().default(false),
    /** 预加载策略: none, next, all */
    preloadStrategy: pgCore.varchar("preload_strategy", { length: 20 }).notNull().default("none"),
    /** 是否自动播放 */
    autoPlay: pgCore.boolean("auto_play").notNull().default(false),
    /** 播放列表缩略图文件ID (关联 file_metadata.id) */
    thumbnailFileId: pgCore.uuid("thumbnail_file_id"),
    /** 播放列表状态: draft, published, archived */
    status: pgCore.varchar("status", { length: 20 }).notNull().default("draft"),
    /** 显示顺序 */
    sortOrder: pgCore.integer("sort_order").notNull().default(0),
    /** 额外配置（JSON格式，存储舞台配置等） */
    config: pgCore.json("config"),
    /** 创建者ID */
    createdBy: pgCore.varchar("created_by", { length: 255 }).notNull(),
    /** 创建时间 */
    createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
    /** 更新时间 */
    updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull(),
    /** 删除时间（软删除） */
    deletedAt: pgCore.timestamp("deleted_at")
  },
  (table) => ({
    /** 按状态查询的索引 */
    statusIndex: pgCore.index("mmd_playlists_status_idx").on(table.status),
    /** 按创建者查询的索引 */
    createdByIndex: pgCore.index("mmd_playlists_created_by_idx").on(table.createdBy),
    /** 按删除状态查询的索引 */
    deletedAtIndex: pgCore.index("mmd_playlists_deleted_at_idx").on(table.deletedAt),
    /** 按排序查询的索引 */
    sortOrderIndex: pgCore.index("mmd_playlists_sort_order_idx").on(table.sortOrder)
  })
);
var mmdPlaylistNodes = pgCore.pgTable(
  "mmd_playlist_nodes",
  {
    /** 主键ID */
    id: pgCore.uuid("id").primaryKey().defaultRandom(),
    /** 所属播放列表ID */
    playlistId: pgCore.uuid("playlist_id").references(() => mmdPlaylists.id, { onDelete: "cascade" }).notNull(),
    /** 节点名称 */
    name: pgCore.varchar("name", { length: 255 }).notNull(),
    /** 节点描述 */
    description: pgCore.text("description"),
    /** 是否启用节点循环 */
    loop: pgCore.boolean("loop").notNull().default(false),
    /** 预计时长（秒） */
    duration: pgCore.integer("duration"),
    /** 节点缩略图文件ID */
    thumbnailFileId: pgCore.uuid("thumbnail_file_id"),
    /** 显示顺序 */
    sortOrder: pgCore.integer("sort_order").notNull().default(0),
    /** 模型文件ID (关联 file_metadata.id) */
    modelFileId: pgCore.uuid("model_file_id").notNull(),
    /** 动作文件ID */
    motionFileId: pgCore.uuid("motion_file_id"),
    /** 相机动画文件ID */
    cameraFileId: pgCore.uuid("camera_file_id"),
    /** 音频文件ID */
    audioFileId: pgCore.uuid("audio_file_id"),
    /** 舞台模型文件ID */
    stageModelFileId: pgCore.uuid("stage_model_file_id"),
    /** 附加动作文件ID列表（JSON数组） */
    additionalMotionFileIds: pgCore.json("additional_motion_file_ids").$type(),
    /** 额外配置（JSON格式） */
    config: pgCore.json("config"),
    /** 创建时间 */
    createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
    /** 更新时间 */
    updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
  },
  (table) => ({
    /** 按播放列表查询的索引 */
    playlistIndex: pgCore.index("mmd_playlist_nodes_playlist_idx").on(table.playlistId),
    /** 按排序查询的索引 */
    sortOrderIndex: pgCore.index("mmd_playlist_nodes_sort_order_idx").on(table.sortOrder),
    /** 按模型文件查询的索引 */
    modelFileIndex: pgCore.index("mmd_playlist_nodes_model_file_idx").on(table.modelFileId),
    /** 组合索引：播放列表+排序 */
    playlistSortIndex: pgCore.index("mmd_playlist_nodes_playlist_sort_idx").on(
      table.playlistId,
      table.sortOrder
    )
  })
);
var mmdResourceOptions = pgCore.pgTable(
  "mmd_resource_options",
  {
    /** 主键ID */
    id: pgCore.uuid("id").primaryKey().defaultRandom(),
    /** 资源名称 */
    name: pgCore.varchar("name", { length: 255 }).notNull(),
    /** 资源描述 */
    description: pgCore.text("description"),
    /** 资源类型: model, motion, camera, audio, stage */
    resourceType: pgCore.varchar("resource_type", { length: 20 }).notNull(),
    /** 文件ID (关联 file_metadata.id) */
    fileId: pgCore.uuid("file_id").notNull(),
    /** 缩略图文件ID */
    thumbnailFileId: pgCore.uuid("thumbnail_file_id"),
    /** 资源标签（JSON数组，用于分类和筛选） */
    tags: pgCore.json("tags").$type(),
    /** 显示顺序 */
    sortOrder: pgCore.integer("sort_order").notNull().default(0),
    /** 是否启用 */
    isActive: pgCore.boolean("is_active").notNull().default(true),
    /** 创建者ID */
    createdBy: pgCore.varchar("created_by", { length: 255 }).notNull(),
    /** 创建时间 */
    createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
    /** 更新时间 */
    updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
  },
  (table) => ({
    /** 按资源类型查询的索引 */
    resourceTypeIndex: pgCore.index("mmd_resource_options_resource_type_idx").on(table.resourceType),
    /** 按文件ID查询的索引 */
    fileIdIndex: pgCore.index("mmd_resource_options_file_id_idx").on(table.fileId),
    /** 按活跃状态查询的索引 */
    isActiveIndex: pgCore.index("mmd_resource_options_is_active_idx").on(table.isActive),
    /** 按创建者查询的索引 */
    createdByIndex: pgCore.index("mmd_resource_options_created_by_idx").on(table.createdBy),
    /** 组合索引：资源类型+活跃状态+排序 */
    typeActiveSortIndex: pgCore.index("mmd_resource_options_type_active_sort_idx").on(
      table.resourceType,
      table.isActive,
      table.sortOrder
    )
  })
);
var mmdPresetItems = pgCore.pgTable(
  "mmd_preset_items",
  {
    /** 主键ID */
    id: pgCore.uuid("id").primaryKey().defaultRandom(),
    /** 预设名称 */
    name: pgCore.varchar("name", { length: 255 }).notNull(),
    /** 预设描述 */
    description: pgCore.text("description"),
    /** 缩略图文件ID */
    thumbnailFileId: pgCore.uuid("thumbnail_file_id"),
    /** 模型文件ID */
    modelFileId: pgCore.uuid("model_file_id").notNull(),
    /** 动作文件ID */
    motionFileId: pgCore.uuid("motion_file_id"),
    /** 相机动画文件ID */
    cameraFileId: pgCore.uuid("camera_file_id"),
    /** 音频文件ID */
    audioFileId: pgCore.uuid("audio_file_id"),
    /** 舞台模型文件ID */
    stageModelFileId: pgCore.uuid("stage_model_file_id"),
    /** 附加动作文件ID列表（JSON数组） */
    additionalMotionFileIds: pgCore.json("additional_motion_file_ids").$type(),
    /** 显示顺序 */
    sortOrder: pgCore.integer("sort_order").notNull().default(0),
    /** 是否启用 */
    isActive: pgCore.boolean("is_active").notNull().default(true),
    /** 预设标签（JSON数组） */
    tags: pgCore.json("tags").$type(),
    /** 创建者ID */
    createdBy: pgCore.varchar("created_by", { length: 255 }).notNull(),
    /** 创建时间 */
    createdAt: pgCore.timestamp("created_at").defaultNow().notNull(),
    /** 更新时间 */
    updatedAt: pgCore.timestamp("updated_at").defaultNow().notNull()
  },
  (table) => ({
    /** 按活跃状态查询的索引 */
    isActiveIndex: pgCore.index("mmd_preset_items_is_active_idx").on(table.isActive),
    /** 按排序查询的索引 */
    sortOrderIndex: pgCore.index("mmd_preset_items_sort_order_idx").on(table.sortOrder),
    /** 按创建者查询的索引 */
    createdByIndex: pgCore.index("mmd_preset_items_created_by_idx").on(table.createdBy),
    /** 按模型文件查询的索引 */
    modelFileIndex: pgCore.index("mmd_preset_items_model_file_idx").on(table.modelFileId)
  })
);
var mmdPlaylistsRelations = drizzleOrm.relations(mmdPlaylists, ({ many }) => ({
  nodes: many(mmdPlaylistNodes)
}));
var mmdPlaylistNodesRelations = drizzleOrm.relations(mmdPlaylistNodes, ({ one }) => ({
  playlist: one(mmdPlaylists, {
    fields: [mmdPlaylistNodes.playlistId],
    references: [mmdPlaylists.id]
  })
}));
var MMD_MODEL_ARCHIVE_MIME_TYPES = [
  "application/zip",
  "application/x-zip-compressed",
  "multipart/x-zip"
];
async function processMmdModelArchive(buffer, options) {
  const folderName = options.folderName ?? crypto.randomUUID();
  const targetDir = path__default.default.join(options.storageRoot, folderName);
  await promises.mkdir(targetDir, { recursive: true });
  const zip = new AdmZip__default.default(buffer);
  const entries = zip.getEntries();
  if (!entries.length) {
    await promises.rm(targetDir, { recursive: true, force: true });
    throw new Error("\u538B\u7F29\u5305\u4E3A\u7A7A\uFF0C\u8BF7\u68C0\u67E5\u4E0A\u4F20\u6587\u4EF6\u5185\u5BB9");
  }
  let modelRelativePath = null;
  let filesExtracted = 0;
  for (const entry of entries) {
    if (!entry.entryName || entry.entryName.startsWith("__MACOSX") || entry.entryName.endsWith(".DS_Store")) {
      continue;
    }
    const safeRelativePath = sanitizeEntryPath(entry.entryName);
    if (!safeRelativePath) {
      continue;
    }
    const destinationPath = path__default.default.join(targetDir, safeRelativePath);
    if (entry.isDirectory) {
      await promises.mkdir(destinationPath, { recursive: true });
      continue;
    }
    await promises.mkdir(path__default.default.dirname(destinationPath), { recursive: true });
    await promises.writeFile(destinationPath, entry.getData());
    filesExtracted += 1;
    const entryExt = path__default.default.extname(safeRelativePath).toLowerCase();
    if (!modelRelativePath && (entryExt === ".pmx" || entryExt === ".pmd")) {
      modelRelativePath = safeRelativePath.split(path__default.default.sep).join("/");
    }
  }
  if (!modelRelativePath) {
    await promises.rm(targetDir, { recursive: true, force: true });
    throw new Error("\u538B\u7F29\u5305\u4E2D\u672A\u627E\u5230 PMX/PMD \u6A21\u578B\u6587\u4EF6\uFF0C\u8BF7\u786E\u8BA4\u76EE\u5F55\u7ED3\u6784\u662F\u5426\u6B63\u786E");
  }
  const format = path__default.default.extname(modelRelativePath).slice(1).toLowerCase();
  const publicRoot = options.publicRoot ?? `/uploads/mmd/models`;
  const modelUrl = joinPublicPath(publicRoot, folderName, modelRelativePath);
  return {
    directory: targetDir,
    relativeDirectory: folderName,
    modelRelativePath,
    modelUrl,
    format,
    filesExtracted
  };
}
function sanitizeEntryPath(entryName) {
  const normalized = path__default.default.normalize(entryName).replace(/^(\.\.(\/|\\|$))+/, "");
  if (!normalized || normalized === "." || normalized.startsWith("..") || path__default.default.isAbsolute(normalized)) {
    return null;
  }
  return normalized;
}
function joinPublicPath(...segments) {
  return segments.map((segment) => segment.replace(/\/+/g, "/").replace(/^\//, "").replace(/\/$/, "")).filter(Boolean).join("/").replace(/\/{2,}/g, "/").replace(/^/, "/");
}

// src/mmd/server/playlistBuilder.ts
var defaultNormalizer = (value) => {
  if (!value) return "";
  if (value.startsWith("http://") || value.startsWith("https://")) {
    return value;
  }
  return value.startsWith("/") ? value : "/" + value;
};
function buildMmdPlaylistFromSources(options) {
  if (!options.models.length) {
    throw new Error("\u6784\u5EFA MMD \u64AD\u653E\u5217\u8868\u5931\u8D25\uFF1Amodels \u4E3A\u7A7A");
  }
  const limit = Math.max(1, Math.min(options.limit ?? options.models.length, options.models.length));
  const normalizeUrl = options.normalizeUrl ?? defaultNormalizer;
  const motions = options.motions ?? [];
  const hasMotions = motions.length > 0;
  const duration = options.nodeDuration ?? 30;
  const nodes = options.models.slice(0, limit).map((model, index2) => {
    const motion = hasMotions ? motions[index2 % motions.length] : void 0;
    return {
      id: String(model.id ?? index2),
      name: model.name,
      loop: options.loop ?? true,
      duration,
      thumbnail: model.thumbnailPath ? normalizeUrl(model.thumbnailPath) : void 0,
      resources: {
        modelPath: normalizeUrl(model.filePath),
        motionPath: motion ? normalizeUrl(motion.filePath) : void 0,
        cameraPath: void 0,
        audioPath: void 0,
        stageModelPath: void 0,
        additionalMotions: void 0
      }
    };
  });
  return {
    id: options.playlistId,
    name: options.playlistName ?? "MMD \u64AD\u653E\u5217\u8868 - " + options.playlistId,
    nodes,
    loop: options.loop ?? true,
    preload: options.preload ?? "next",
    autoPlay: options.autoPlay ?? true
  };
}

// src/mmd/server/mmdUpload.ts
var MMD_SUPPORTED_TYPES = {
  model: [...MMD_MODEL_ARCHIVE_MIME_TYPES],
  animation: ["application/octet-stream", "animation/vmd"],
  audio: ["audio/wav", "audio/mp3", "audio/mpeg", "audio/ogg"]
};
var MMD_FILE_EXTENSIONS = {
  model: [".zip"],
  animation: [".vmd"],
  audio: [".wav", ".mp3", ".ogg"]
};
async function uploadMmdResource(fileService, options) {
  const { file, resourceType, name, description, userId } = options;
  const ext = "." + file.name.split(".").pop()?.toLowerCase();
  const allowedExtensions = MMD_FILE_EXTENSIONS[resourceType];
  if (!allowedExtensions.includes(ext)) {
    throw new Error(
      "\u4E0D\u652F\u6301\u7684\u6587\u4EF6\u6269\u5C55\u540D: " + ext + "\u3002\u652F\u6301\u7684\u683C\u5F0F: " + allowedExtensions.join(", ")
    );
  }
  const moduleId = "mmd-" + resourceType + "s";
  const metadata = await fileService.uploadFile({
    file,
    moduleId,
    businessId: "default",
    permission: "public",
    metadata: {
      uploadedBy: userId,
      uploadedAt: (/* @__PURE__ */ new Date()).toISOString(),
      originalFileName: file.name,
      resourceType,
      name,
      description: description || ""
    },
    needsProcessing: false
  });
  const format = ext.slice(1).toUpperCase();
  const fileUrl = metadata.cdnUrl || metadata.storagePath;
  return {
    id: metadata.id,
    name,
    url: fileUrl,
    filePath: metadata.storagePath,
    fileSize: metadata.size,
    type: resourceType,
    format,
    uploadTime: metadata.uploadTime,
    metadata
  };
}
async function uploadMmdModel(fileService, options) {
  const { file, storageRoot, publicRoot } = options;
  const baseResult = await uploadMmdResource(fileService, options);
  try {
    const fileBuffer = Buffer.from(await file.arrayBuffer());
    const archiveResult = await processMmdModelArchive(fileBuffer, {
      storageRoot,
      publicRoot,
      folderName: baseResult.id
    });
    return {
      ...baseResult,
      url: archiveResult.modelUrl,
      filePath: archiveResult.modelUrl,
      format: archiveResult.format.toUpperCase(),
      extractedPath: archiveResult.modelUrl
    };
  } catch (extractError) {
    console.error("\u6A21\u578B\u538B\u7F29\u5305\u5904\u7406\u5931\u8D25:", extractError);
    throw new Error(
      extractError instanceof Error ? extractError.message : "\u6A21\u578B\u538B\u7F29\u5305\u5904\u7406\u5931\u8D25"
    );
  }
}
async function batchUploadMmdResources(fileService, uploads) {
  const results = [];
  for (const uploadOptions of uploads) {
    try {
      const result = await uploadMmdResource(fileService, uploadOptions);
      results.push(result);
    } catch (error) {
      console.error("\u4E0A\u4F20\u5931\u8D25: " + uploadOptions.file.name, error);
    }
  }
  return results;
}

exports.MMD_FILE_EXTENSIONS = MMD_FILE_EXTENSIONS;
exports.MMD_MODEL_ARCHIVE_MIME_TYPES = MMD_MODEL_ARCHIVE_MIME_TYPES;
exports.MMD_SUPPORTED_TYPES = MMD_SUPPORTED_TYPES;
exports.batchUploadMmdResources = batchUploadMmdResources;
exports.buildMmdPlaylistFromSources = buildMmdPlaylistFromSources;
exports.mmdPlaylistNodes = mmdPlaylistNodes;
exports.mmdPlaylistNodesRelations = mmdPlaylistNodesRelations;
exports.mmdPlaylists = mmdPlaylists;
exports.mmdPlaylistsRelations = mmdPlaylistsRelations;
exports.mmdPresetItems = mmdPresetItems;
exports.mmdResourceOptions = mmdResourceOptions;
exports.processMmdModelArchive = processMmdModelArchive;
exports.uploadMmdModel = uploadMmdModel;
exports.uploadMmdResource = uploadMmdResource;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map