export type MnoteRuntimeConfig = {
  /**
   * 是否启用 Convex（自部署）链路。
   * 说明：该字段由服务端在运行期注入到 window.__MNOTE_RUNTIME_CONFIG__，用于客户端按需关闭 Supabase 相关能力。
   */
  useConvex?: boolean;
  supabaseUrl?: string;
  /**
   * 服务端/本机回源用的 Supabase 地址（通常是 HTTP），用于避免 FRP/自签证书导致 Node 侧 TLS 校验失败。
   * 说明：该字段会出现在 public/mnote-env.json 中，但它主要给服务端读取使用。
   */
  supabaseInternalUrl?: string;
  supabaseAnonKey?: string;
  backendUrl?: string;
  onlyofficeBaseUrl?: string;
  onlyofficeBaseUrlWeb?: string;
  onlyofficeBaseUrlDesktop?: string;
  onlyofficeStorageHostOverride?: string;
  onlyofficeStorageHostOverrideWeb?: string;
  onlyofficeStorageHostOverrideDesktop?: string;
  cloudflareAppOrigin?: string;
  onlyofficeProxyOriginWeb?: string;
  onlyofficeCallbackOriginWeb?: string;
  onlyofficeCallbackOriginDesktop?: string;
  /**
   * 编辑器 host 选择。
   * 说明：默认主链为 leptos_tiptap_island；可通过运行时配置显式切换。
   */
  documentEditorHost?:
    | "blocknote"
    | "leptos_tiptap_island"
    | "leptos_tiptap_iframe_debug";
  /**
   * 编辑器 BlockNote 回退总开关（kill switch）。
   * 说明：开启后，未显式传入 query host 的文档页会优先回退到 blocknote。
   */
  documentEditorBlocknoteKillSwitch?: boolean;
  /**
   * 树域 renderer family 选择。
   * 说明：默认仍为 react；`rust_family` 只作为渐进切流开关，不代表已完全切主路径。
   */
  treeRendererFamily?: "react" | "rust_family";
  /**
   * 是否为桌面端（Electron）运行。
   */
  isDesktop?: boolean;
  /**
   * ONLYOFFICE 文档服务器拉取 document.url 时，如需使用 /api/onlyoffice/proxy
   * 规避 signedUrl 的 token 参数冲突，可把代理地址指向一个“文档服务器可访问”的公网 Origin。
   *
   * - 桌面端本地运行（window.location.origin）：通常可不配置
   * - 通过 Cloudflare Tunnel 使用远端 ONLYOFFICE：建议配置为 https://app.<你的域名>
   */
  onlyofficeProxyOrigin?: string;
  onlyofficeCallbackOrigin?: string;
};

export type MnotePublicRuntimeConfig = MnoteRuntimeConfig;

declare global {
  interface Window {
    __MNOTE_RUNTIME_CONFIG__?: MnoteRuntimeConfig;
  }
}

const parseRuntimeBoolean = (value: unknown): boolean | undefined => {
  if (typeof value === "boolean") {
    return value;
  }
  if (typeof value !== "string") {
    return undefined;
  }
  const normalized = value.trim().toLowerCase();
  if (!normalized) {
    return undefined;
  }
  if (["1", "true", "yes", "on"].includes(normalized)) {
    return true;
  }
  if (["0", "false", "no", "off"].includes(normalized)) {
    return false;
  }
  return undefined;
};

const parseDocumentEditorHost = (
  value: unknown,
): MnoteRuntimeConfig["documentEditorHost"] | undefined => {
  if (typeof value !== "string") {
    return undefined;
  }
  const normalized = value.trim().toLowerCase();
  if (!normalized) {
    return undefined;
  }
  if (normalized === "blocknote") {
    return "blocknote";
  }
  if (
    normalized === "leptos_tiptap_island" ||
    normalized === "leptos_tiptap_runtime" ||
    normalized === "leptos_tiptap_inline" ||
    normalized === "leptos_tiptap"
  ) {
    return "leptos_tiptap_island";
  }
  if (
    normalized === "leptos_tiptap_iframe_debug" ||
    normalized === "leptos_tiptap_debug" ||
    normalized === "iframe_debug"
  ) {
    return "leptos_tiptap_iframe_debug";
  }
  return undefined;
};

const parseTreeRendererFamily = (
  value: unknown,
): MnoteRuntimeConfig["treeRendererFamily"] | undefined => {
  if (typeof value !== "string") {
    return undefined;
  }
  const normalized = value.trim().toLowerCase();
  if (!normalized) {
    return undefined;
  }
  if (normalized === "rust_family" || normalized === "rust" || normalized === "tree_shell") {
    return "rust_family";
  }
  if (normalized === "react") {
    return "react";
  }
  return undefined;
};

function getServerNodeBuiltin<T>(moduleName: string): T | null {
  if (typeof window !== "undefined") {
    return null;
  }

  const processWithBuiltin = process as NodeJS.Process & {
    getBuiltinModule?: (id: string) => unknown;
  };
  if (typeof processWithBuiltin.getBuiltinModule === "function") {
    const builtIn = processWithBuiltin.getBuiltinModule(moduleName);
    if (builtIn) {
      return builtIn as T;
    }
  }

  try {
    // 说明：某些 Node/打包环境仍保留 CommonJS require，这里只作为兜底。
    const runtimeRequire = new Function(
      'return typeof require === "function" ? require : null',
    )() as NodeJS.Require | null;
    if (runtimeRequire) {
      return runtimeRequire(moduleName) as T;
    }
  } catch {
    return null;
  }

  return null;
}

const readFromEnv = (): MnoteRuntimeConfig => ({
  useConvex: process.env.USE_CONVEX === "1" || process.env.NEXT_PUBLIC_USE_CONVEX === "1",
  supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
  supabaseInternalUrl: process.env.SUPABASE_INTERNAL_URL,
  supabaseAnonKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
  backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL ?? process.env.BACKEND_URL,   
  ...(parseRuntimeBoolean(
    process.env.NEXT_PUBLIC_DOCUMENT_EDITOR_BLOCKNOTE_KILL_SWITCH ??
      process.env.DOCUMENT_EDITOR_BLOCKNOTE_KILL_SWITCH,
  ) !== undefined
    ? {
        documentEditorBlocknoteKillSwitch: parseRuntimeBoolean(
          process.env.NEXT_PUBLIC_DOCUMENT_EDITOR_BLOCKNOTE_KILL_SWITCH ??
            process.env.DOCUMENT_EDITOR_BLOCKNOTE_KILL_SWITCH,
        ),
      }
    : {}),
  ...(parseDocumentEditorHost(
    process.env.NEXT_PUBLIC_DOCUMENT_EDITOR_HOST ??
      process.env.DOCUMENT_EDITOR_HOST,
  ) !== undefined
    ? {
        documentEditorHost: parseDocumentEditorHost(
          process.env.NEXT_PUBLIC_DOCUMENT_EDITOR_HOST ??
            process.env.DOCUMENT_EDITOR_HOST,
        ),
      }
    : {}),
  ...(parseTreeRendererFamily(
    process.env.NEXT_PUBLIC_TREE_RENDERER_FAMILY ??
      process.env.TREE_RENDERER_FAMILY,
  ) !== undefined
    ? {
        treeRendererFamily: parseTreeRendererFamily(
          process.env.NEXT_PUBLIC_TREE_RENDERER_FAMILY ??
            process.env.TREE_RENDERER_FAMILY,
        ),
      }
    : {}),
  onlyofficeBaseUrl: process.env.NEXT_PUBLIC_ONLYOFFICE_BASE_URL,
  onlyofficeStorageHostOverride: process.env.NEXT_PUBLIC_ONLYOFFICE_STORAGE_HOST_OVERRIDE,
  onlyofficeProxyOrigin: process.env.NEXT_PUBLIC_ONLYOFFICE_PROXY_ORIGIN,
  onlyofficeCallbackOrigin: process.env.NEXT_PUBLIC_ONLYOFFICE_CALLBACK_ORIGIN,
  cloudflareAppOrigin: process.env.NEXT_PUBLIC_CLOUDFLARE_APP_ORIGIN,
});

const readFromPublicJson = (): Partial<MnoteRuntimeConfig> => {
  if (typeof window !== "undefined") return {};
  try {
    // 说明：桌面端与网页端共用 public/mnote-env.json 作为“公共环境文件”。
    // 桌面端运行时 process.cwd() 会被 Electron 切到 desktop-next 根目录。
    // 网页端运行时 process.cwd() 通常为 wolai-frontend。
    
    // 说明：这里不能直接静态引入 `node:fs` / `node:path`，
    // 否则客户端 bundle 在解析该模块时会把它们也当成浏览器依赖。
    // Node 22 优先走 `process.getBuiltinModule`，其余环境再兜底到 runtime require。
    const fs = getServerNodeBuiltin<typeof import("fs")>("node:fs");
    const path = getServerNodeBuiltin<typeof import("path")>("node:path");
    if (!fs || !path) {
      return {};
    }

    // 说明：Next standalone 产物的 server.js 会执行 `process.chdir(__dirname)`，
    // 导致 process.cwd() 变成 `.next/standalone`，此时 public/mnote-env.json 位于上层目录。
    // 这里向上查找多级目录，确保能读取到真正的 public/mnote-env.json。
    const candidates: string[] = [];
    let dir = process.cwd();
    for (let i = 0; i < 6; i += 1) {
      candidates.push(path.join(dir, "public", "mnote-env.json"));
      const next = path.dirname(dir);
      if (next === dir) break;
      dir = next;
    }
    const existing = candidates.filter((p) => fs.existsSync(p));
    if (existing.length === 0) return {};

    // 说明：standalone 产物可能包含 `.next/standalone/public/mnote-env.json`，但它通常是构建时拷贝，
    // 用户更希望修改“项目目录下”的 public/mnote-env.json 即可生效。
    // 因此这里优先选择不在 `.next` 目录下的配置文件。
    const isInNextDir = (p: string) => p.split(path.sep).includes(".next");
    const filePath = existing.find((p) => !isInNextDir(p)) ?? existing[0];

    const raw = fs.readFileSync(filePath, { encoding: "utf8" });
    const parsed = JSON.parse(raw) as unknown;
    if (!parsed || typeof parsed !== "object") return {};
    return parsed as Partial<MnoteRuntimeConfig>;
  } catch {
    return {};
  }
};

const normalizeRuntimeConfig = (cfg: MnoteRuntimeConfig): MnoteRuntimeConfig => {
  const isDesktop =
    cfg.isDesktop ??
    (typeof window === "undefined" ? process.env.MNOTE_DESKTOP === "1" : false);

  const onlyofficeBaseUrl = isDesktop
    ? cfg.onlyofficeBaseUrlDesktop ||
      cfg.onlyofficeBaseUrl ||
      cfg.onlyofficeBaseUrlWeb ||
      ""
    : cfg.onlyofficeBaseUrlWeb ||
      cfg.onlyofficeBaseUrl ||
      cfg.onlyofficeBaseUrlDesktop ||
      "";

  const onlyofficeStorageHostOverride = isDesktop
    ? (cfg.onlyofficeStorageHostOverrideDesktop ??
        cfg.onlyofficeStorageHostOverride ??
        cfg.onlyofficeStorageHostOverrideWeb ??
        "")
    : (cfg.onlyofficeStorageHostOverrideWeb ??
        cfg.onlyofficeStorageHostOverride ??
        cfg.onlyofficeStorageHostOverrideDesktop ??
        "");

  const onlyofficeProxyOrigin = isDesktop
    ? (cfg.onlyofficeCallbackOriginDesktop ?? cfg.onlyofficeProxyOrigin ?? cfg.onlyofficeProxyOriginWeb)
    : (cfg.onlyofficeProxyOriginWeb ?? cfg.onlyofficeProxyOrigin ?? cfg.onlyofficeCallbackOriginDesktop);

  const onlyofficeCallbackOrigin = isDesktop
    ? (cfg.onlyofficeCallbackOriginDesktop ?? cfg.onlyofficeCallbackOrigin ?? cfg.onlyofficeCallbackOriginWeb)
    : (cfg.onlyofficeCallbackOriginWeb ?? cfg.onlyofficeCallbackOrigin ?? cfg.onlyofficeCallbackOriginDesktop);

  const documentEditorHost =
    parseDocumentEditorHost(cfg.documentEditorHost) ?? "leptos_tiptap_island";
  const documentEditorBlocknoteKillSwitch =
    parseRuntimeBoolean(cfg.documentEditorBlocknoteKillSwitch) ?? false;
  const treeRendererFamily =
    parseTreeRendererFamily(cfg.treeRendererFamily) ?? "react";

  return {
    ...cfg,
    isDesktop,
    documentEditorHost,
    documentEditorBlocknoteKillSwitch,
    treeRendererFamily,
    onlyofficeBaseUrl,
    onlyofficeStorageHostOverride,
    onlyofficeProxyOrigin,
    onlyofficeCallbackOrigin,
  };
};

export function getMnoteRuntimeConfig(): MnoteRuntimeConfig {
  if (typeof window !== "undefined") {
    // 说明：浏览器侧禁止再兜底读取 NEXT_PUBLIC_MNOTE_WEB_BASE_URL / MNOTE_WEB_BASE_URL，
    // 否则会把 internal-only 的 mnote-web 边界重新泄漏回客户端。
    // 客户端只信任服务端注入的 public runtime 配置。
    return normalizeRuntimeConfig(window.__MNOTE_RUNTIME_CONFIG__ ?? {});
  }
  const isDesktop = process.env.MNOTE_DESKTOP === "1";
  const publicRuntime = readFromPublicJson();
  const envRuntime = readFromEnv();
  // 说明：桌面端需要优先使用 public/mnote-env.json 来覆盖 build 时注入的 NEXT_PUBLIC_*。
  // Web 端开发时则应优先使用环境变量（例如本机 http://127.0.0.1:18000），避免被
  // public/mnote-env.json 中的远程/自签地址覆盖导致浏览器登录请求失败。
  const merged: MnoteRuntimeConfig = isDesktop
    ? {
        ...envRuntime,
        ...publicRuntime,
        isDesktop,
      }
    : {
        ...publicRuntime,
        ...envRuntime,
      };
  return normalizeRuntimeConfig(merged);
}

export function getMnotePublicRuntimeConfig(): MnotePublicRuntimeConfig {
  return getMnoteRuntimeConfig();
}
