import type { McpServerSettings } from "#cli/core/config";
import type { GoatChainContext as DimSdkContext } from "#cli/core/model";
import type { CliAgentBridge as DimSdkAgent } from "../../agent-bridge";
import type { DimSdkStore } from "../../core/store";
import type { DimSdkSessionsApi } from "../../keys";
import type { ToolRunner } from "../../shell";
import type { DimSdkSessionsBackend } from "../persistence";
import {
  applyStateDirEnv,
  resolveStateDir,
  toMcpServerConfigs,
} from "#cli/commands/server-helpers";
import { createEnvReader, resolveInitialRunConfig } from "#cli/core/store/env";
import { createCliAgent } from "../agent";
import { createDimcodeContext } from "../context";
import { createDimSdkPersistence } from "../persistence";
import { resolveStartupSessionId } from "./startup-session";
import { createNoopToolRunner } from "./tool-runner";

export type DimSdkShellProps = Readonly<{
  toolRunner: ToolRunner;
  context: DimSdkContext;
  agent: DimSdkAgent;
  onStoreCreated: (store: DimSdkStore) => void;
  sessions?: DimSdkSessionsApi;
  uiForcedSkills?: Set<string>;
}>;

type MutableEnvRecord = Record<string, string | undefined>;
type ToolsSettings = NonNullable<DimcodeUserToolsV1["settings"]>;
type ToolsRefState = {
  websearch?: ToolsSettings["websearch"];
  mcpServers?: McpServerSettings[];
};

/**
 * Build the props object expected by `DimSdkShell`.
 *
 * This includes:
 * - merged context (env + defaults + cached UI settings)
 * - the agent instance (or an error agent)
 * - persistence hooks for sessions + UI cache
 */
export async function createDimSdkShellProps(
  opts: Readonly<{
    cwd: string;
    env: Record<string, string | undefined>;
    sessionId?: string;
  }>,
): Promise<DimSdkShellProps> {
  const {
    context,
    cacheDir,
    cacheFile,
    cache,
    configFile,
    config,
    toolsFile,
    tools,
    mcpJsonFile,
    mcpServers,
  } = await createDimcodeContext({ cwd: opts.cwd, env: opts.env });
  const fastStartRaw = String(opts.env.DIMCODE_FAST_START ?? "")
    .trim()
    .toLowerCase();
  const fastStart =
    fastStartRaw === "1" ||
    fastStartRaw === "true" ||
    fastStartRaw === "yes" ||
    fastStartRaw === "on";
  const contextEnv = context.env as MutableEnvRecord;

  const stateDir = resolveStateDir(cacheDir, opts.env);
  applyStateDirEnv({
    env: opts.env,
    contextEnv,
    stateDir,
  });

  // Create a mutable reference to tools settings that can be updated when store changes
  const toolsRef: { current: ToolsRefState } = {
    current: { ...tools.settings },
  };

  // Keep tool settings live so the next turn sees updated search credentials/config.
  const toolsConfig = {
    websearch: {
      apiKey: () => toolsRef.current?.websearch?.apiKey ?? "",
      apiEndpoint: () => toolsRef.current?.websearch?.apiEndpoint,
      numResults: () => toolsRef.current?.websearch?.numResults,
    },
  };

  // Mutable set of skills force-activated by the user via /skills dialog.
  // The function form is re-evaluated on every middleware turn.
  const uiForcedSkills = new Set<string>();

  const agent = createCliAgent({
    context,
    processEnv: opts.env,
    stateDir,
    tools: toolsConfig,
    mcpServers: () => toMcpServerConfigs(toolsRef.current.mcpServers),
    forcedSkills: () => Array.from(uiForcedSkills),
  });
  const sessionsBackend = (agent.sessions ?? null) as DimSdkSessionsBackend | null;

  const initialSessionId = await resolveStartupSessionId({
    envSessionId: opts.sessionId,
  });
  opts.env.DIMCODE_SESSION_ID = initialSessionId ?? "";
  contextEnv.DIMCODE_SESSION_ID = initialSessionId ?? "";

  const { sessions, onStoreCreated, rehydrate } = createDimSdkPersistence({
    cacheFile,
    cache,
    configFile,
    config,
    toolsFile,
    tools,
    mcpJsonFile,
    mcpServers,
    sessionsBackend,
    agent,
    toolsRef,
    deferPersist: fastStart,
  });

  let storeRef: DimSdkStore | null = null;
  let hydrateStarted = false;
  const hydrateFromDisk = async () => {
    if (hydrateStarted) return;
    hydrateStarted = true;
    try {
      const reloadEnv = {
        ...opts.env,
        DIMCODE_FAST_START: "0",
      };
      const full = await createDimcodeContext({ cwd: opts.cwd, env: reloadEnv });
      rehydrate({ cache: full.cache, config: full.config, tools: full.tools });
      if (!storeRef) return;
      if (full.context.cwd) storeRef.setCwd(full.context.cwd);
      if (full.context.focusFiles) storeRef.setFocusFiles(full.context.focusFiles);
      const nextEnv = full.context.env ?? {};
      for (const [key, value] of Object.entries(nextEnv))
        storeRef.setEnvVar(key, value as string | undefined);
      const envReader = createEnvReader(full.context.env ?? {});
      const nextRun = resolveInitialRunConfig(envReader);
      storeRef.state.config = { ...storeRef.state.config, ...nextRun };
      const activeProviderId = String(storeRef.state.provider?.activeId ?? "").trim();
      const currentModel = String(storeRef.state.config.model ?? "").trim();
      if (
        activeProviderId &&
        currentModel &&
        storeRef.state.provider &&
        !storeRef.state.provider.activeModelByProvider[activeProviderId]
      ) {
        storeRef.state.provider.activeModelByProvider[activeProviderId] = currentModel;
      }
    } catch {
      // ignore background hydration errors
    }
  };

  const onStoreCreatedWithHydrate = (store: DimSdkStore) => {
    storeRef = store;
    onStoreCreated(store);
    if (fastStart) void hydrateFromDisk();
  };

  return {
    toolRunner: createNoopToolRunner(),
    context,
    agent,
    onStoreCreated: onStoreCreatedWithHydrate,
    sessions,
    uiForcedSkills,
  };
}
