// src/docs-asset-service.ts
import fs from "node:fs";
import path from "node:path";
var DOCS_BASE_PLACEHOLDER = "__DOCS_BASE__";
var STATIC_EXTS = /* @__PURE__ */ new Set([
  ".woff",
  ".woff2",
  ".ttf",
  ".eot",
  ".png",
  ".jpg",
  ".jpeg",
  ".gif",
  ".svg",
  ".ico",
  ".webp",
  ".mp4",
  ".webm",
  ".mp3",
  ".wav",
  ".json",
  ".map",
  ".txt",
  ".xml"
]);
var REPLACEABLE_EXTS = /* @__PURE__ */ new Set([".html", ".js", ".css"]);
function getDefaultStaticDir() {
  const candidates = [
    path.resolve(__dirname, "static"),
    path.resolve(process.cwd(), "node_modules/ai-blueking-docs/dist/static"),
    path.resolve(process.cwd(), "../node_modules/ai-blueking-docs/dist/static"),
    path.resolve(process.cwd(), "../../node_modules/ai-blueking-docs/dist/static")
  ];
  return candidates.find((candidate) => fs.existsSync(path.join(candidate, "index.html"))) ?? candidates[0];
}
function resolveDocsBasePath(siteUrl) {
  const normalized = (siteUrl || "").replace(/\/$/, "");
  return normalized ? `${normalized}/docs` : "/docs";
}
function warmCache(staticDir, cache) {
  const walk = (dir) => {
    for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
      const full = path.join(dir, entry.name);
      if (entry.isDirectory()) {
        walk(full);
      } else if (REPLACEABLE_EXTS.has(path.extname(entry.name).toLowerCase())) {
        cache.set(full, fs.readFileSync(full, "utf-8"));
      }
    }
  };
  walk(staticDir);
}
function isStaticFile(p) {
  return STATIC_EXTS.has(path.extname(p).toLowerCase());
}
function buildGlobalsScript(globals) {
  const entries = Object.entries(globals).map(([k, v]) => `window.${k}=${JSON.stringify(v)};`).join("");
  return `<script>${entries}</script>`;
}
function replaceDocsBase(rawContent, basePath) {
  const basePathNoSlash = basePath.replace(/^\//, "");
  return rawContent.replaceAll("/" + DOCS_BASE_PLACEHOLDER, "/" + basePathNoSlash).replaceAll(DOCS_BASE_PLACEHOLDER, basePath);
}
function injectGlobals(html, globals) {
  if (Object.keys(globals).length === 0) {
    return html;
  }
  return html.replace("</head>", `${buildGlobalsScript(globals)}
</head>`);
}
function resolveSafePath(staticDir, reqPath) {
  const staticRoot = path.resolve(staticDir);
  const fullPath = path.resolve(staticRoot, reqPath.startsWith("/") ? reqPath.slice(1) : reqPath);
  if (!fullPath.startsWith(staticRoot + path.sep) && fullPath !== staticRoot) {
    return null;
  }
  return fullPath;
}
var DocsAssetService = class {
  constructor(options) {
    this.staticDir = options.staticDir || getDefaultStaticDir();
    this.basePath = options.basePath.replace(/\/$/, "") || "/docs";
    this.globals = options.globals || {};
    this.contentCache = options.contentCache ?? /* @__PURE__ */ new Map();
    if (fs.existsSync(this.staticDir) && this.contentCache.size === 0) {
      warmCache(this.staticDir, this.contentCache);
    }
  }
  /**
   * 处理文档站 GET 请求（路径相对于 /docs 挂载点，如 /guide/foo.html）。
   */
  handleGet(requestPath) {
    let reqPath = requestPath || "/";
    if (!reqPath.startsWith("/")) {
      reqPath = `/${reqPath}`;
    }
    const ext = path.extname(reqPath).toLowerCase();
    if (REPLACEABLE_EXTS.has(ext)) {
      return this.serveReplaceable(reqPath, ext);
    }
    if (isStaticFile(reqPath)) {
      return this.serveBinary(reqPath);
    }
    return this.serveSpaFallback();
  }
  readCached(fullPath) {
    let raw = this.contentCache.get(fullPath);
    if (raw === void 0 && fs.existsSync(fullPath)) {
      raw = fs.readFileSync(fullPath, "utf-8");
      this.contentCache.set(fullPath, raw);
    }
    return raw;
  }
  serveReplaceable(reqPath, ext) {
    let filePath = reqPath;
    if (ext === ".html") {
      filePath = reqPath === "/" ? "/index.html" : reqPath;
    }
    const fullPath = resolveSafePath(this.staticDir, filePath);
    if (fullPath === null) {
      return { kind: "forbidden", status: 403 };
    }
    let rawContent = this.readCached(fullPath);
    if (rawContent === void 0 && ext === ".html") {
      const indexPath = path.join(this.staticDir, "index.html");
      rawContent = this.readCached(indexPath);
    }
    if (rawContent === void 0) {
      return { kind: "not_found", status: 404 };
    }
    const content = replaceDocsBase(rawContent, this.basePath);
    if (ext === ".html") {
      return {
        kind: "text",
        status: 200,
        contentType: "text/html; charset=utf-8",
        body: injectGlobals(content, this.globals),
        cacheControl: "no-cache"
      };
    }
    if (ext === ".css") {
      return {
        kind: "text",
        status: 200,
        contentType: "text/css; charset=utf-8",
        body: content,
        cacheControl: "public, max-age=31536000, immutable"
      };
    }
    return {
      kind: "text",
      status: 200,
      contentType: "application/javascript; charset=utf-8",
      body: content,
      cacheControl: "public, max-age=31536000, immutable",
      extraHeaders: { "X-Content-Type-Options": "nosniff" }
    };
  }
  serveBinary(reqPath) {
    const fullPath = resolveSafePath(this.staticDir, reqPath);
    if (fullPath === null) {
      return { kind: "forbidden", status: 403 };
    }
    if (!fs.existsSync(fullPath)) {
      return { kind: "not_found", status: 404 };
    }
    return {
      kind: "file",
      status: 200,
      filePath: fullPath,
      cacheControl: "public, max-age=31536000, immutable"
    };
  }
  serveSpaFallback() {
    const indexPath = path.join(this.staticDir, "index.html");
    let rawHtml = this.readCached(indexPath);
    if (rawHtml === void 0) {
      return { kind: "not_found", status: 404 };
    }
    let html = replaceDocsBase(rawHtml, this.basePath);
    html = injectGlobals(html, this.globals);
    return {
      kind: "text",
      status: 200,
      contentType: "text/html; charset=utf-8",
      body: html,
      cacheControl: "no-cache"
    };
  }
};
function createDocsAssetService(options) {
  return new DocsAssetService(options);
}

// src/middleware.ts
import fs2 from "node:fs";
import send from "koa-send";
function resolveBasePathFromCtx(ctx) {
  const mountPath = ctx.mountPath;
  if (mountPath) {
    return mountPath.endsWith("/") ? mountPath.slice(0, -1) : mountPath;
  }
  return "/";
}
function applyResultToCtx(ctx, result) {
  if (result.kind === "forbidden") {
    ctx.status = 403;
    ctx.body = "Forbidden";
    return true;
  }
  if (result.kind === "not_found") {
    ctx.status = 404;
    ctx.body = "Not Found";
    return true;
  }
  if (result.kind === "file") {
    return false;
  }
  ctx.status = result.status;
  ctx.set("Content-Type", result.contentType);
  ctx.set("Cache-Control", result.cacheControl);
  if (result.extraHeaders) {
    for (const [k, v] of Object.entries(result.extraHeaders)) {
      ctx.set(k, v);
    }
  }
  ctx.body = result.body;
  return true;
}
function createDocsMiddleware(options = {}) {
  const staticDir = options.staticDir || getDefaultStaticDir();
  const globals = options.globals || {};
  const contentCache = /* @__PURE__ */ new Map();
  if (fs2.existsSync(staticDir)) {
    warmCache(staticDir, contentCache);
  }
  return async (ctx) => {
    if (ctx.method !== "GET" && ctx.method !== "HEAD") {
      return;
    }
    const basePath = (options.basePath ?? resolveBasePathFromCtx(ctx)).replace(/\/$/, "") || "/";
    const service = createDocsAssetService({ staticDir, basePath, globals, contentCache });
    const result = service.handleGet(ctx.path || "/");
    if (result.kind === "file") {
      try {
        await send(ctx, ctx.path || "/", {
          root: staticDir,
          maxAge: 365 * 24 * 60 * 60 * 1e3,
          immutable: true,
          gzip: true
        });
      } catch {
      }
      return;
    }
    applyResultToCtx(ctx, result);
  };
}

// src/mock-routes.ts
import Router from "@koa/router";

// src/mock-handlers.ts
function ok(data) {
  return { code: 0, data, message: "success" };
}
function sseLine(obj) {
  return `data: ${JSON.stringify(obj)}

`;
}
function pushSse(res, obj) {
  if (res.writableEnded)
    return false;
  res.write(sseLine(obj));
  return true;
}
function delay(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}
function disableNagleIfPossible(res) {
  const socket = res.socket;
  if (socket && typeof socket.setNoDelay === "function") {
    socket.setNoDelay(true);
  }
}
var store = {
  lastUserText: /* @__PURE__ */ Object.create(null),
  messageIdSeq: 1
};
function mockSessionContent(body) {
  const sessionCode = body.session_code;
  if (!sessionCode) {
    return { status: 400, json: { code: 1, data: null, message: "session_code required" } };
  }
  const id = String(store.messageIdSeq++);
  const saved = {
    ...body,
    id,
    message_id: id,
    session_code: sessionCode,
    status: body.status || "complete"
  };
  if (body.role === "user") {
    const c = body.content;
    store.lastUserText[String(sessionCode)] = typeof c === "string" ? c : JSON.stringify(c ?? "");
  }
  return { status: 200, json: ok(saved) };
}
function mockSessionContentBatchDelete(body) {
  const ids = body.ids || [];
  return { status: 200, json: ok(ids.length) };
}
function mockSessionContentStop() {
  return { status: 200, json: ok(null) };
}
function mockSessionFeedbackReasons() {
  return { status: 200, json: ok(["\u51C6\u786E\u6709\u7528", "\u89E3\u91CA\u6E05\u6670", "\u5176\u4ED6"]) };
}
function mockSessionFeedback() {
  return { status: 200, json: ok({ saved: true }) };
}
async function handleMockChatCompletion(body, res) {
  const sessionCode = body.session_code || "atomic-stream-demo";
  const runId = Date.now();
  const threadId = `thread-${sessionCode}-${runId}`;
  const userLine = store.lastUserText[sessionCode] || "";
  const messageId = `mock-asst-${runId}`;
  const reply = [
    "\u3010Mock \u6D41\u5F0F\u3011\u8FD9\u91CC\u6A21\u62DF\u8F83\u6162\u7684 AG-UI \u6587\u672C\u901A\u9053\u3002",
    "",
    "\u63A8\u8350\u4E8B\u4EF6\u987A\u5E8F\uFF1ARUN_STARTED \u2192 TEXT_MESSAGE_START \u2192 \u591A\u6761 TEXT_MESSAGE_CHUNK \u2192 TEXT_MESSAGE_END \u2192 RUN_FINISHED\u3002",
    "",
    "DevTools \u7684 EventStream \u9762\u677F\u4F1A\u6309**\u5230\u8FBE\u987A\u5E8F**\u8FFD\u52A0\u884C\uFF1B\u82E5\u4ECD\u50CF\u300C\u4E00\u95EA\u800C\u8FC7\u300D\uFF0C\u591A\u534A\u662F\u6574\u6BB5\u54CD\u5E94\u5728\u6781\u77ED\u65F6\u95F4\u5185\u6536\u5B8C\uFF0C\u53EF\u62C9\u5927\u4E0B\u65B9 delay \u6216\u770B Timing \u91CC\u7684 TTFB / Content Download \u5206\u5E03\u3002",
    "",
    "\u63A5\u5165\u771F\u5B9E Agent \u65F6\uFF0C\u628A\u540C\u6837\u683C\u5F0F\u7684 SSE \u6302\u5230\u7F51\u5173\u5373\u53EF\uFF1Bchat-helper \u7684 FetchClient \u6309\u884C\u89E3\u6790 data: JSON\u3002",
    "",
    userLine ? `\u4F60\u8BF4\uFF1A${userLine.slice(0, 200)}${userLine.length > 200 ? "\u2026" : ""}` : "\uFF08\u5F53\u524D\u65E0\u7528\u6237\u53E5\u67C4\uFF0C\u4EC5\u6F14\u793A\u56FA\u5B9A\u6587\u6848\u3002\uFF09",
    "",
    "\u2014\u2014 \u4EE5\u4E0A\u4E3A Mock \u751F\u6210\u7684\u8F83\u957F\u6B63\u6587\uFF0C\u7528\u4E8E\u591A\u6BB5 TEXT_MESSAGE_CHUNK\u3002 \u2014\u2014"
  ].join("\n");
  const PAUSE_AFTER_RUN_STARTED = 160;
  const PAUSE_AFTER_TEXT_START = 130;
  const PAUSE_PER_CHUNK = 95;
  const PAUSE_AFTER_TEXT_END = 120;
  const PAUSE_BEFORE_FINISHED = 100;
  const chunkSize = 18;
  res.statusCode = 200;
  res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
  res.setHeader("Cache-Control", "no-cache, no-transform");
  res.setHeader("Connection", "keep-alive");
  res.setHeader("X-Accel-Buffering", "no");
  try {
    disableNagleIfPossible(res);
    pushSse(res, { type: "RUN_STARTED", runId, threadId });
    await delay(PAUSE_AFTER_RUN_STARTED);
    pushSse(res, { type: "TEXT_MESSAGE_START", messageId, role: "assistant" });
    await delay(PAUSE_AFTER_TEXT_START);
    for (let i = 0; i < reply.length; i += chunkSize) {
      const delta = reply.slice(i, i + chunkSize);
      pushSse(res, { type: "TEXT_MESSAGE_CHUNK", messageId, delta, role: "assistant" });
      await delay(PAUSE_PER_CHUNK);
    }
    pushSse(res, { type: "TEXT_MESSAGE_END", messageId });
    await delay(PAUSE_AFTER_TEXT_END);
    pushSse(res, { type: "RUN_FINISHED", runId, threadId });
    await delay(PAUSE_BEFORE_FINISHED);
  } catch (e) {
    if (!res.headersSent) {
      const msg = e instanceof Error ? e.message : String(e);
      res.statusCode = 500;
      res.end(JSON.stringify({ code: 1, data: null, message: msg }));
      return;
    }
  } finally {
    if (!res.writableEnded) {
      try {
        res.end();
      } catch {
      }
    }
  }
}
async function dispatchMockAguiRequest(method, relativePath, body, res) {
  const normalizedPath = relativePath.endsWith("/") ? relativePath : `${relativePath}/`;
  const m = method.toUpperCase();
  if (m === "POST" && normalizedPath === "/chat_completion/") {
    await handleMockChatCompletion(body, res);
    return { handled: true };
  }
  if (m === "POST" && normalizedPath === "/session_content/") {
    const result = mockSessionContent(body);
    return { handled: true, json: result };
  }
  if (m === "POST" && normalizedPath === "/session_content/batch_delete/") {
    return { handled: true, json: mockSessionContentBatchDelete(body) };
  }
  if (m === "POST" && normalizedPath === "/session_content/stop/") {
    return { handled: true, json: mockSessionContentStop() };
  }
  if (m === "GET" && normalizedPath === "/session_feedback/reasons/") {
    return { handled: true, json: mockSessionFeedbackReasons() };
  }
  if (m === "POST" && normalizedPath === "/session_feedback/") {
    return { handled: true, json: mockSessionFeedback() };
  }
  return { handled: false };
}

// src/mock-routes.ts
function createMockAguiRouter() {
  const router = new Router();
  const handle = async (ctx) => {
    const body = ctx.request.body || {};
    const relativePath = ctx.path || "/";
    const dispatched = await dispatchMockAguiRequest(ctx.method, relativePath, body, ctx.res);
    if (!dispatched.handled) {
      ctx.status = 404;
      ctx.body = { code: 1, data: null, message: "Not Found" };
      return;
    }
    if (dispatched.json) {
      ctx.status = dispatched.json.status;
      ctx.body = dispatched.json.json;
    }
  };
  router.post("/session_content/", handle);
  router.post("/session_content/batch_delete/", handle);
  router.post("/session_content/stop/", handle);
  router.get("/session_feedback/reasons/", handle);
  router.post("/session_feedback/", handle);
  router.post("/chat_completion/", handle);
  return router.routes();
}
export {
  DOCS_BASE_PLACEHOLDER,
  DocsAssetService,
  createDocsAssetService,
  createDocsMiddleware,
  createMockAguiRouter,
  dispatchMockAguiRequest,
  getDefaultStaticDir,
  handleMockChatCompletion,
  mockSessionContent,
  mockSessionContentBatchDelete,
  mockSessionContentStop,
  mockSessionFeedback,
  mockSessionFeedbackReasons,
  resolveDocsBasePath,
  warmCache,
  warmCache as warmDocsCache
};
