// ST-BME vector sync orchestration controller.
//
// Extracted from index.js syncVectorState so it can be unit-tested by direct
// import instead of slicing index.js into a vm sandbox. All runtime
// dependencies (current graph, status setters, embedding config, vector index
// engine, host context) are injected explicitly; this module owns no
// module-level mutable state.

/**
 * Runs a vector index sync for the current graph and reports terminal status.
 *
 * @param {object} runtime injected dependencies
 * @param {() => void} runtime.ensureCurrentGraphRuntimeState
 * @param {() => object} runtime.getCurrentGraph
 * @param {(text: string, meta: string, level: string, opts?: object) => void} runtime.setLastVectorStatus
 * @param {() => object} runtime.getEmbeddingConfig
 * @param {(config: object) => {valid: boolean, error?: string}} runtime.validateVectorConfig
 * @param {(graph: object) => object} runtime.getVectorIndexStats
 * @param {(graph: object, config: object, opts: object) => Promise<object>} runtime.syncGraphVectorIndex
 * @param {(context: object, graph: object) => string} runtime.resolveOperationalChatId
 * @param {() => object} runtime.getContext
 * @param {(message: string) => void} runtime.markVectorStateDirty
 * @param {(error: unknown) => boolean} runtime.isAbortError
 * @param {(() => object)} [runtime.getRequestHeaders]
 * @param {Console} [runtime.console]
 * @param {object} [options]
 * @param {boolean} [options.force]
 * @param {boolean} [options.purge]
 * @param {{start: number, end: number}|null} [options.range]
 * @param {AbortSignal} [options.signal]
 */
export async function syncVectorStateController(runtime, options = {}) {
  const {
    ensureCurrentGraphRuntimeState,
    getCurrentGraph,
    getEmbeddingConfig,
    validateVectorConfig,
    getVectorIndexStats,
    syncGraphVectorIndex,
    resolveOperationalChatId,
    getContext,
    markVectorStateDirty,
    isAbortError,
    getRequestHeaders,
    console: logger = console,
  } = runtime;

  // Status setters are invoked via `runtime` (method-call style) so the runtime
  // object stays the single owner of status state, matching the extraction and
  // rebuild controllers.
  const setLastVectorStatus = (...args) => runtime.setLastVectorStatus(...args);

  const { force = false, purge = false, range = null, signal = undefined } =
    options || {};

  ensureCurrentGraphRuntimeState();
  const currentGraph = getCurrentGraph();

  const scopeLabel =
    range && Number.isFinite(range.start) && Number.isFinite(range.end)
      ? `范围 ${Math.min(range.start, range.end)}-${Math.max(range.start, range.end)}`
      : "当前聊天";
  setLastVectorStatus(
    "向量处理中",
    `${scopeLabel} · ${force ? "强制同步" : "增量同步"}`,
    "running",
    { syncRuntime: true },
  );

  const config = getEmbeddingConfig();
  const validation = validateVectorConfig(config);

  if (!validation.valid) {
    currentGraph.vectorIndexState.lastWarning = validation.error;
    currentGraph.vectorIndexState.dirty = true;
    setLastVectorStatus("向量不可用", validation.error, "warning", {
      syncRuntime: true,
    });
    return {
      insertedHashes: [],
      stats: getVectorIndexStats(currentGraph),
      error: validation.error,
    };
  }

  try {
    const result = await syncGraphVectorIndex(currentGraph, config, {
      chatId: resolveOperationalChatId(getContext(), currentGraph),
      force,
      purge,
      range,
      signal,
      headerProvider:
        typeof getRequestHeaders === "function"
          ? () => getRequestHeaders()
          : null,
    });
    if (result?.error) {
      setLastVectorStatus("向量待修复", result.error, "warning", {
        syncRuntime: true,
      });
      return result;
    }
    setLastVectorStatus(
      "向量完成",
      `${scopeLabel} · indexed ${result.stats?.indexed ?? 0} · pending ${result.stats?.pending ?? 0}`,
      "success",
      { syncRuntime: true },
    );
    return result;
  } catch (error) {
    if (isAbortError(error)) {
      setLastVectorStatus("向量已终止", scopeLabel, "warning", {
        syncRuntime: true,
      });
      return {
        insertedHashes: [],
        stats: getVectorIndexStats(currentGraph),
        error: error?.message || "向量任务已终止",
        aborted: true,
      };
    }
    const message = error?.message || String(error) || "向量同步失败";
    markVectorStateDirty(message);
    logger.error("[ST-BME] 向量同步失败:", error);
    setLastVectorStatus("向量失败", message, "error", {
      syncRuntime: true,
      toastKind: "error",
    });
    return {
      insertedHashes: [],
      stats: getVectorIndexStats(currentGraph),
      error: message,
    };
  }
}
