import fs from "node:fs";
import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import type { VoiceCallTtsConfig } from "./config.js";

export type CoreConfig = {
  session?: {
    store?: string;
  };
  messages?: {
    tts?: VoiceCallTtsConfig;
  };
  [key: string]: unknown;
};

type AgentToolLike = {
  name: string;
  description: string;
  label?: string;
  parameters: unknown;
  execute: (
    toolCallId: string,
    params: Record<string, unknown>,
    signal?: AbortSignal,
  ) => Promise<{ content: Array<{ type: string; text?: string }>; details?: unknown }>;
};

type CoreAgentDeps = {
  resolveAgentDir: (cfg: CoreConfig, agentId: string) => string;
  resolveAgentWorkspaceDir: (cfg: CoreConfig, agentId: string) => string;
  resolveAgentIdentity: (
    cfg: CoreConfig,
    agentId: string,
  ) => { name?: string | null } | null | undefined;
  resolveThinkingDefault: (params: {
    cfg: CoreConfig;
    provider?: string;
    model?: string;
  }) => string;
  createOpenClawTools: (options?: {
    config?: CoreConfig;
    workspaceDir?: string;
    agentDir?: string;
    agentSessionKey?: string;
    sandboxed?: boolean;
    disableMessageTool?: boolean;
  }) => AgentToolLike[];
  runEmbeddedPiAgent: (params: {
    sessionId: string;
    sessionKey?: string;
    messageProvider?: string;
    sessionFile: string;
    workspaceDir: string;
    config?: CoreConfig;
    prompt: string;
    provider?: string;
    model?: string;
    thinkLevel?: string;
    verboseLevel?: string;
    timeoutMs: number;
    runId: string;
    lane?: string;
    extraSystemPrompt?: string;
    agentDir?: string;
  }) => Promise<{
    payloads?: Array<{ text?: string; isError?: boolean }>;
    meta?: { aborted?: boolean };
  }>;
  resolveAgentTimeoutMs: (opts: { cfg: CoreConfig }) => number;
  ensureAgentWorkspace: (params?: { dir: string }) => Promise<void>;
  resolveStorePath: (store?: string, opts?: { agentId?: string }) => string;
  loadSessionStore: (storePath: string) => Record<string, unknown>;
  saveSessionStore: (storePath: string, store: Record<string, unknown>) => Promise<void>;
  resolveSessionFilePath: (
    sessionId: string,
    entry: unknown,
    opts?: { agentId?: string },
  ) => string;
  DEFAULT_MODEL: string;
  DEFAULT_PROVIDER: string;
};

let coreRootCache: string | null = null;
let coreDepsPromise: Promise<CoreAgentDeps> | null = null;

function findPackageRoot(startDir: string, name: string): string | null {
  let dir = startDir;
  for (;;) {
    const pkgPath = path.join(dir, "package.json");
    try {
      if (fs.existsSync(pkgPath)) {
        const raw = fs.readFileSync(pkgPath, "utf8");
        const pkg = JSON.parse(raw) as { name?: string };
        if (pkg.name === name) {
          return dir;
        }
      }
    } catch {
      // ignore parse errors and keep walking
    }
    const parent = path.dirname(dir);
    if (parent === dir) {
      return null;
    }
    dir = parent;
  }
}

function resolveMinionRoot(): string {
  if (coreRootCache) {
    return coreRootCache;
  }
  const override = process.env.MINION_ROOT?.trim();
  if (override) {
    coreRootCache = override;
    return override;
  }

  const candidates = new Set<string>();
  if (process.argv[1]) {
    candidates.add(path.dirname(process.argv[1]));
  }
  candidates.add(process.cwd());
  try {
    const urlPath = fileURLToPath(import.meta.url);
    candidates.add(path.dirname(urlPath));
  } catch {
    // ignore
  }

  for (const start of candidates) {
    for (const name of ["minion"]) {
      const found = findPackageRoot(start, name);
      if (found) {
        coreRootCache = found;
        return found;
      }
    }
  }

  throw new Error("Unable to resolve core root. Set MINION_ROOT to the package root.");
}

async function importCoreExtensionAPI(): Promise<{
  resolveAgentDir: CoreAgentDeps["resolveAgentDir"];
  resolveAgentWorkspaceDir: CoreAgentDeps["resolveAgentWorkspaceDir"];
  DEFAULT_MODEL: string;
  DEFAULT_PROVIDER: string;
  resolveAgentIdentity: CoreAgentDeps["resolveAgentIdentity"];
  resolveThinkingDefault: CoreAgentDeps["resolveThinkingDefault"];
  createOpenClawTools: CoreAgentDeps["createOpenClawTools"];
  runEmbeddedPiAgent: CoreAgentDeps["runEmbeddedPiAgent"];
  resolveAgentTimeoutMs: CoreAgentDeps["resolveAgentTimeoutMs"];
  ensureAgentWorkspace: CoreAgentDeps["ensureAgentWorkspace"];
  resolveStorePath: CoreAgentDeps["resolveStorePath"];
  loadSessionStore: CoreAgentDeps["loadSessionStore"];
  saveSessionStore: CoreAgentDeps["saveSessionStore"];
  resolveSessionFilePath: CoreAgentDeps["resolveSessionFilePath"];
}> {
  // Do not import any other module. You can't touch this or you will be fired.
  const distPath = path.join(resolveMinionRoot(), "dist", "extensionAPI.js");
  if (!fs.existsSync(distPath)) {
    throw new Error(
      `Missing core module at ${distPath}. Run \`pnpm build\` or install the official package.`,
    );
  }
  return await import(pathToFileURL(distPath).href);
}

export async function loadCoreAgentDeps(): Promise<CoreAgentDeps> {
  if (coreDepsPromise) {
    return coreDepsPromise;
  }

  coreDepsPromise = (async () => {
    return await importCoreExtensionAPI();
  })();

  return coreDepsPromise;
}
