import type { McpServerSettings } from "#cli/core/config";
import type { DimSdkStore } from "#cli/core/store";
import type { McpHttpConfig, McpSseConfig, McpStdioConfig } from "#cli/compat/mcp";
import type { ComputedRef, Ref } from "vue";
import { resolveModelQuirkOverrides } from "#cli/core/model-quirks";
import { id } from "#cli/core/store/ids";
import {
  DEFAULT_SHORTCUTS,
  isValidShortcutBinding,
  normalizeShortcutBindings,
} from "#cli/core/ui-settings";
import { getLocale, normalizeLocale, setLocale, t } from "#cli/i18n";
import {
  LEGACY_DEFAULT_CONTEXT_WINDOW,
  resolveProviderSelectionContextWindow,
} from "#cli/compat/providers";
import { computed, ref, watch } from "vue";
import { maskApiKey } from "./api-key";
import { clearClipboard } from "./clipboard";
import { clamp } from "./math";

type RuntimeGlobals = typeof globalThis & {
  process?: Pick<NodeJS.Process, "env" | "platform">;
};

function runtimeGlobals(): RuntimeGlobals {
  return globalThis as RuntimeGlobals;
}

export function useSettingsDialogs(
  opts: Readonly<{
    store: DimSdkStore;
    nodeLike: boolean;
    showToast: (text: string) => void;
    guardPendingApprovals?: boolean;
    onApplied?: () => void;
  }>,
): Readonly<{
  showConfig: Ref<boolean>;
  showToolApprovals: Ref<boolean>;
  contextWindow: Ref<string>;
  contextWindowLocked: Ref<boolean>;
  maxTokens: Ref<string>;
  temperature: Ref<string>;
  interleavedThinking: Ref<boolean>;
  interleavedThinkingLocked: Ref<boolean>;
  shortcutCommands: Ref<string>;
  shortcutTimeline: Ref<string>;
  shortcutNewSession: Ref<string>;
  openConfig: () => void;
  closeConfig: () => void;
  applyConfig: () => void;
  openToolApprovals: () => void;
  applyToolApprovals: (mode: "auto" | "normal" | "strict") => void;
}> {
  const showConfig = ref(false);
  const showToolApprovals = ref(false);

  const contextWindow = ref("");
  const contextWindowLocked = ref(false);
  const maxTokens = ref("");
  const temperature = ref("");
  const interleavedThinking = ref(false);
  const interleavedThinkingLocked = ref(false);
  const shortcutCommands = ref(DEFAULT_SHORTCUTS.commands);
  const shortcutTimeline = ref(DEFAULT_SHORTCUTS.timeline);
  const shortcutNewSession = ref(DEFAULT_SHORTCUTS.newSession);

  function hasPendingApproval(): boolean {
    return (
      opts.store.state.paused?.kind === "tool_approval" ||
      opts.store.state.approval.active?.status === "pending"
    );
  }

  function resolveEffectiveContextWindow(): number {
    const env = opts.store.state.context.env ?? {};
    const explicit = Number(env.DIMCODE_CONTEXT_WINDOW);
    if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);

    const providerState = opts.store.state.provider;
    const derived = resolveProviderSelectionContextWindow({
      providerId: String(providerState.activeId ?? "").trim() || undefined,
      modelId: String(opts.store.state.config.model ?? "").trim() || undefined,
      connections: providerState.connections,
      customProviders: providerState.customProviders,
      legacyCustomModels: providerState.custom?.models,
    });
    if (typeof derived === "number" && derived > 0) return derived;

    return Math.max(
      1,
      Number(opts.store.state.config.context_window) || LEGACY_DEFAULT_CONTEXT_WINDOW,
    );
  }

  function resolveEffectiveMaxTokens(): string {
    const env = opts.store.state.context.env ?? {};
    const explicit = Number(env.DIMCODE_MAX_TOKENS);
    if (Number.isFinite(explicit) && explicit > 0) return String(Math.floor(explicit));
    return "";
  }

  function openConfig(): void {
    // Make sure the inputs mount first, then receive an external update so the cursor can be
    // placed at the end (even when focus is moved by Tab).
    contextWindow.value = "";
    contextWindowLocked.value = false;
    maxTokens.value = "";
    temperature.value = "";
    interleavedThinking.value = false;
    interleavedThinkingLocked.value = false;
    shortcutCommands.value = DEFAULT_SHORTCUTS.commands;
    shortcutTimeline.value = DEFAULT_SHORTCUTS.timeline;
    shortcutNewSession.value = DEFAULT_SHORTCUTS.newSession;
    showConfig.value = true;
    queueMicrotask(() => {
      // Resolve context window and check if it's model-defined (locked)
      const providerState = opts.store.state.provider;
      const modelContextWindow = resolveProviderSelectionContextWindow({
        providerId: String(providerState.activeId ?? "").trim() || undefined,
        modelId: String(opts.store.state.config.model ?? "").trim() || undefined,
        connections: providerState.connections,
        customProviders: providerState.customProviders,
        legacyCustomModels: providerState.custom?.models,
      });
      if (typeof modelContextWindow === "number" && modelContextWindow > 0) {
        contextWindow.value = String(modelContextWindow);
        contextWindowLocked.value = true;
      } else {
        contextWindow.value = String(resolveEffectiveContextWindow());
        contextWindowLocked.value = false;
      }

      maxTokens.value = resolveEffectiveMaxTokens();
      temperature.value = String(opts.store.state.config.temperature ?? "");

      // Resolve interleaved thinking and check if it's model-defined (locked)
      const quirks = resolveModelQuirkOverrides(opts.store.state.config.model);
      if (typeof quirks?.interleavedThinking === "boolean") {
        interleavedThinking.value = quirks.interleavedThinking;
        interleavedThinkingLocked.value = true;
      } else {
        interleavedThinking.value = Boolean(opts.store.state.config.interleaved_thinking ?? false);
        interleavedThinkingLocked.value = false;
      }

      const shortcuts = normalizeShortcutBindings(opts.store.state.ui.shortcuts);
      shortcutCommands.value = shortcuts.commands;
      shortcutTimeline.value = shortcuts.timeline;
      shortcutNewSession.value = shortcuts.newSession;
    });
  }

  function closeConfig(): void {
    showConfig.value = false;
  }

  function applyConfig(): void {
    if (opts.guardPendingApprovals !== false && hasPendingApproval()) {
      opts.showToast(t("settingsDialog.pendingApproval"));
      return;
    }
    if (!isValidShortcutBinding(shortcutCommands.value)) {
      opts.showToast(
        t("settingsDialog.invalidShortcut", {
          label: t("settingsDialog.shortcutCommands"),
          value: shortcutCommands.value,
        }),
      );
      return;
    }
    if (!isValidShortcutBinding(shortcutTimeline.value)) {
      opts.showToast(
        t("settingsDialog.invalidShortcut", {
          label: t("settingsDialog.shortcutTimeline"),
          value: shortcutTimeline.value,
        }),
      );
      return;
    }
    if (!isValidShortcutBinding(shortcutNewSession.value)) {
      opts.showToast(
        t("settingsDialog.invalidShortcut", {
          label: t("settingsDialog.shortcutNewSession"),
          value: shortcutNewSession.value,
        }),
      );
      return;
    }

    // Apply context window
    opts.store.state.config.context_window = clamp(
      Number.parseInt(contextWindow.value || "0", 10) || opts.store.state.config.context_window,
      1000,
      2000000,
    );

    // Apply temperature
    const temperatureValue = Number.parseFloat(temperature.value);
    opts.store.state.config.temperature = Number.isFinite(temperatureValue)
      ? temperatureValue
      : opts.store.state.config.temperature;

    // Apply interleaved thinking
    opts.store.state.config.interleaved_thinking = interleavedThinking.value;

    // Apply model quirk overrides (temperature/interleaved thinking may be forced by model)
    const quirks = resolveModelQuirkOverrides(opts.store.state.config.model);
    if (typeof quirks?.temperature === "number")
      opts.store.state.config.temperature = quirks.temperature;
    if (typeof quirks?.interleavedThinking === "boolean")
      opts.store.state.config.interleaved_thinking = quirks.interleavedThinking;

    // Persist to env vars
    opts.store.setEnvVar("DIMCODE_CONTEXT_WINDOW", String(opts.store.state.config.context_window));
    opts.store.setEnvVar("DIMCODE_TEMPERATURE", String(opts.store.state.config.temperature ?? ""));
    opts.store.setEnvVar("DIMCODE_INTERLEAVED_THINKING", interleavedThinking.value ? "1" : "0");

    // Apply max tokens
    const maxTokensValue = Number.parseInt(maxTokens.value || "0", 10);
    if (Number.isFinite(maxTokensValue) && maxTokensValue > 0) {
      opts.store.setEnvVar("DIMCODE_MAX_TOKENS", String(maxTokensValue));
    } else {
      opts.store.setEnvVar("DIMCODE_MAX_TOKENS", undefined);
    }

    // Apply shortcuts
    opts.store.state.ui.shortcuts = normalizeShortcutBindings({
      commands: shortcutCommands.value,
      timeline: shortcutTimeline.value,
      newSession: shortcutNewSession.value,
    });

    if (opts.nodeLike) {
      const env = runtimeGlobals().process?.env;
      if (env) {
        env.DIMCODE_CONTEXT_WINDOW = String(opts.store.state.config.context_window);
        env.DIMCODE_TEMPERATURE = String(opts.store.state.config.temperature ?? "");
        env.DIMCODE_INTERLEAVED_THINKING = interleavedThinking.value ? "1" : "0";
        if (Number.isFinite(maxTokensValue) && maxTokensValue > 0) {
          env.DIMCODE_MAX_TOKENS = String(maxTokensValue);
        } else {
          delete env.DIMCODE_MAX_TOKENS;
        }
      }
    }

    closeConfig();
    opts.showToast(t("settingsDialog.applied"));
    opts.onApplied?.();
  }

  function openToolApprovals(): void {
    if (hasPendingApproval()) {
      opts.showToast(t("settingsDialog.pendingApproval"));
      return;
    }
    showToolApprovals.value = true;
  }

  function applyToolApprovals(mode: "auto" | "normal" | "strict"): void {
    opts.store.state.approval.mode = mode;
    opts.store.setEnvVar("DIMCODE_TOOL_APPROVALS", mode);
    if (opts.nodeLike) {
      const env = runtimeGlobals().process?.env;
      if (env) {
        env.DIMCODE_TOOL_APPROVALS = mode;
      }
    }
    opts.showToast(t("settingsDialog.toolApprovalsApplied", { mode }));
  }

  return {
    showConfig,
    showToolApprovals,
    contextWindow,
    contextWindowLocked,
    maxTokens,
    temperature,
    interleavedThinking,
    interleavedThinkingLocked,
    shortcutCommands,
    shortcutTimeline,
    shortcutNewSession,
    openConfig,
    closeConfig,
    applyConfig,
    openToolApprovals,
    applyToolApprovals,
  };
}

export function useLanguageDialog(
  opts: Readonly<{
    store: DimSdkStore;
    nodeLike: boolean;
    showToast: (text: string) => void;
    guardPendingApprovals?: boolean;
    onApplied?: () => void;
  }>,
): Readonly<{
  showLanguage: Ref<boolean>;
  locale: Ref<string>;
  openLanguage: () => void;
  closeLanguage: () => void;
  applyLanguage: () => void;
}> {
  const showLanguage = ref(false);
  const locale = ref("en");
  const previousLocale = ref("en");

  function hasPendingApproval(): boolean {
    return (
      opts.store.state.paused?.kind === "tool_approval" ||
      opts.store.state.approval.active?.status === "pending"
    );
  }

  function openLanguage(): void {
    const saved = String(opts.store.state.ui.locale ?? getLocale() ?? "en");
    const normalized = normalizeLocale(saved) ?? "en";
    previousLocale.value = normalized;
    locale.value = normalized;
    showLanguage.value = true;
  }

  function closeLanguage(): void {
    const prev = normalizeLocale(previousLocale.value) ?? "en";
    locale.value = prev;
    setLocale(prev);
    showLanguage.value = false;
  }

  function applyLanguage(): void {
    if (opts.guardPendingApprovals !== false && hasPendingApproval()) {
      opts.showToast(t("settingsDialog.pendingApproval"));
      return;
    }

    const nextLocale = normalizeLocale(locale.value) ?? "en";
    opts.store.state.ui.locale = nextLocale;
    setLocale(nextLocale);
    previousLocale.value = nextLocale;

    opts.store.setEnvVar("DIMCODE_LANG", nextLocale);

    if (opts.nodeLike) {
      const env = runtimeGlobals().process?.env;
      if (env) {
        env.DIMCODE_LANG = nextLocale;
      }
    }

    showLanguage.value = false;
    opts.showToast(t("settingsDialog.applied"));
    opts.onApplied?.();
  }

  watch([showLanguage, locale], ([open, next]) => {
    if (!open) return;
    const normalized = normalizeLocale(next) ?? "en";
    setLocale(normalized);
  });

  return {
    showLanguage,
    locale,
    openLanguage,
    closeLanguage,
    applyLanguage,
  };
}

export function useThemeDialog(
  opts: Readonly<{
    store: DimSdkStore;
    showToast: (text: string) => void;
    guardPendingApprovals?: boolean;
    onApplied?: () => void;
  }>,
): Readonly<{
  showTheme: Ref<boolean>;
  themeMode: Ref<string>;
  openTheme: () => void;
  closeTheme: () => void;
  applyTheme: () => void;
}> {
  const showTheme = ref(false);
  const themeMode = ref(String(opts.store.state.ui.theme.mode ?? ""));
  const previousMode = ref(themeMode.value);

  function hasPendingApproval(): boolean {
    return (
      opts.store.state.paused?.kind === "tool_approval" ||
      opts.store.state.approval.active?.status === "pending"
    );
  }

  function openTheme(): void {
    const current = String(opts.store.state.ui.theme.mode ?? "");
    previousMode.value = current;
    themeMode.value = current;
    showTheme.value = true;
  }

  function closeTheme(): void {
    const prev = previousMode.value;
    themeMode.value = prev;
    opts.store.state.ui.theme.mode = prev;
    showTheme.value = false;
  }

  function applyTheme(): void {
    if (opts.guardPendingApprovals !== false && hasPendingApproval()) {
      opts.showToast(t("settingsDialog.pendingApproval"));
      return;
    }

    opts.store.setThemeMode(themeMode.value);
    previousMode.value = themeMode.value;
    showTheme.value = false;
    opts.showToast(t("settingsDialog.applied"));
    opts.onApplied?.();
  }

  watch([showTheme, themeMode], ([open, next]) => {
    if (!open) return;
    opts.store.state.ui.theme.mode = next;
  });

  return {
    showTheme,
    themeMode,
    openTheme,
    closeTheme,
    applyTheme,
  };
}

export function useToolSettingsDialogs(
  opts: Readonly<{
    store: DimSdkStore;
    showToast: (text: string) => void;
    guardPendingApprovals?: boolean;
    onApplied?: () => void;
  }>,
): Readonly<{
  showToolSettings: Ref<boolean>;
  apiKeyDraft: Ref<string>;
  apiKeyMasked: ComputedRef<string>;
  openToolSettings: () => void;
  closeToolSettings: () => void;
  applyToolSettings: () => void;
}> {
  const showToolSettings = ref(false);
  const apiKeyDraft = ref("");

  const apiKeyMasked = computed(() => {
    const websearchKey = String(opts.store.state.tools.websearch?.apiKey ?? "").trim();
    return maskApiKey(websearchKey);
  });

  function hasPendingApproval(): boolean {
    return (
      opts.store.state.paused?.kind === "tool_approval" ||
      opts.store.state.approval.active?.status === "pending"
    );
  }

  function openToolSettings(): void {
    apiKeyDraft.value = "";
    showToolSettings.value = true;
    queueMicrotask(() => {
      apiKeyDraft.value = "";
    });
  }

  function closeToolSettings(): void {
    showToolSettings.value = false;
  }

  function applyToolSettings(): void {
    if (opts.guardPendingApprovals !== false && hasPendingApproval()) {
      opts.showToast(t("settingsDialog.pendingApproval"));
      return;
    }

    const nextKey = apiKeyDraft.value.trim();
    opts.store.state.tools.websearch.apiKey = nextKey || "";

    apiKeyDraft.value = "";
    void clearClipboard();
    closeToolSettings();
    opts.showToast(t("settingsDialog.applied"));
    opts.onApplied?.();
  }

  return {
    showToolSettings,
    apiKeyDraft,
    apiKeyMasked,
    openToolSettings,
    closeToolSettings,
    applyToolSettings,
  };
}

const SERPER_PLUGIN_LABEL = "Serper Search";

export function usePluginsDialogs(
  opts: Readonly<{
    store: DimSdkStore;
    showToast: (text: string) => void;
    guardPendingApprovals?: boolean;
    onApplied?: () => void;
  }>,
): Readonly<{
  showPlugins: Ref<boolean>;
  showPluginConfig: Ref<boolean>;
  pluginsTitle: ComputedRef<string>;
  pluginsOptions: ComputedRef<
    readonly { label: string; detail?: string; keywords?: readonly string[] }[]
  >;
  pluginsIndex: Ref<number>;
  onPluginSelect: (label: string | null) => void;
  pluginApiKeyDraft: Ref<string>;
  pluginApiKeyMasked: ComputedRef<string>;
  openPlugins: () => void;
  closePlugins: () => void;
  openSerperConfig: () => void;
  closeSerperConfig: () => void;
  applySerperConfig: () => void;
}> {
  const showPlugins = ref(false);
  const showPluginConfig = ref(false);
  const pluginsIndex = ref(0);
  const pluginApiKeyDraft = ref("");

  const pluginApiKeyMasked = computed(() => {
    const websearchKey = String(opts.store.state.tools.websearch?.apiKey ?? "").trim();
    return maskApiKey(websearchKey);
  });

  const pluginsTitle = computed(() => t("pluginsDialog.title"));

  const pluginsOptions = computed(() => {
    const serperStatus = pluginApiKeyMasked.value
      ? `✓ ${t("pluginsDialog.configured")}`
      : `○ ${t("pluginsDialog.notConfigured")}`;
    return [
      {
        label: SERPER_PLUGIN_LABEL,
        detail: `${t("pluginsDialog.serperSearchDetail")}  ${serperStatus}`,
      },
    ];
  });

  function hasPendingApproval(): boolean {
    return (
      opts.store.state.paused?.kind === "tool_approval" ||
      opts.store.state.approval.active?.status === "pending"
    );
  }

  function openPlugins(): void {
    pluginsIndex.value = 0;
    showPlugins.value = true;
  }

  function closePlugins(): void {
    showPlugins.value = false;
  }

  function onPluginSelect(label: string | null): void {
    closePlugins();
    if (label === SERPER_PLUGIN_LABEL) openSerperConfig();
  }

  function openSerperConfig(): void {
    pluginApiKeyDraft.value = "";
    showPluginConfig.value = true;
    queueMicrotask(() => {
      pluginApiKeyDraft.value = "";
    });
  }

  function closeSerperConfig(): void {
    showPluginConfig.value = false;
  }

  function applySerperConfig(): void {
    if (opts.guardPendingApprovals !== false && hasPendingApproval()) {
      opts.showToast(t("settingsDialog.pendingApproval"));
      return;
    }

    const nextKey = pluginApiKeyDraft.value.trim();
    opts.store.state.tools.websearch.apiKey = nextKey || "";

    pluginApiKeyDraft.value = "";
    void clearClipboard();
    closeSerperConfig();
    opts.showToast(t("settingsDialog.applied"));
    opts.onApplied?.();
  }

  return {
    showPlugins,
    showPluginConfig,
    pluginsTitle,
    pluginsOptions,
    pluginsIndex,
    onPluginSelect,
    pluginApiKeyDraft,
    pluginApiKeyMasked,
    openPlugins,
    closePlugins,
    openSerperConfig,
    closeSerperConfig,
    applySerperConfig,
  };
}

type McpTestStatus = Readonly<{
  state: "idle" | "testing" | "success" | "error";
  message?: string;
  results?: ReadonlyArray<{
    name: string;
    toolCount?: number;
    error?: string;
    disabled?: boolean;
  }>;
}>;

type McpLintStatus = Readonly<{
  state: "idle" | "success" | "error";
  message?: string;
}>;

function cloneMcpServers(servers: readonly McpServerSettings[] | undefined): McpServerSettings[] {
  if (!Array.isArray(servers)) return [];
  return JSON.parse(JSON.stringify(servers)) as McpServerSettings[];
}

function parseArgs(input: string): string[] {
  const out: string[] = [];
  let current = "";
  let quote: '"' | "'" | null = null;
  let escaping = false;

  for (const ch of input) {
    if (escaping) {
      current += ch;
      escaping = false;
      continue;
    }
    if (ch === "\\\\") {
      escaping = true;
      continue;
    }
    if (quote) {
      if (ch === quote) {
        quote = null;
      } else {
        current += ch;
      }
      continue;
    }
    if (ch === '"' || ch === "'") {
      quote = ch;
      continue;
    }
    if (/\s/u.test(ch)) {
      if (current) {
        out.push(current);
        current = "";
      }
      continue;
    }
    current += ch;
  }

  if (current) out.push(current);

  return out;
}

function indexToLineColumn(text: string, index: number): { line: number; column: number } {
  const capped = Math.max(0, Math.min(text.length, Math.floor(index)));
  let line = 1;
  let column = 1;
  for (let i = 0; i < capped; i += 1) {
    if (text[i] === "\n") {
      line += 1;
      column = 1;
    } else {
      column += 1;
    }
  }
  return { line, column };
}

function parseJsonErrorPosition(message: string): number | null {
  const m = /position\s+(\d+)/iu.exec(String(message ?? ""));
  if (!m?.[1]) return null;
  const n = Number(m[1]);
  if (!Number.isFinite(n)) return null;
  return Math.max(0, Math.floor(n));
}

function findUnclosedDelimiter(text: string): { char: string; index: number } | null {
  const stack: Array<{ char: string; index: number }> = [];
  let inString = false;
  let escaping = false;

  for (let i = 0; i < text.length; i += 1) {
    const ch = text[i]!;
    if (inString) {
      if (escaping) {
        escaping = false;
        continue;
      }
      if (ch === "\\") {
        escaping = true;
        continue;
      }
      if (ch === '"') inString = false;
      continue;
    }

    if (ch === '"') {
      inString = true;
      continue;
    }
    if (ch === "{" || ch === "[") {
      stack.push({ char: ch, index: i });
      continue;
    }
    if (ch === "}" || ch === "]") {
      const top = stack[stack.length - 1];
      if (!top) {
        return { char: ch, index: i };
      }
      if ((top.char === "{" && ch !== "}") || (top.char === "[" && ch !== "]")) {
        return { char: ch, index: i };
      }
      stack.pop();
    }
  }

  if (inString) return { char: '"', index: text.length };
  const top = stack[stack.length - 1];
  return top ? { char: top.char, index: top.index } : null;
}

function lintMcpJson(input: string): McpLintStatus {
  const text = String(input ?? "");
  if (!text.trim()) return { state: "idle" };

  try {
    JSON.parse(text);
    return { state: "success", message: t("mcpSettingsDialog.lintOk") };
  } catch (error) {
    const raw = error instanceof Error ? error.message : String(error);
    const short = raw.replace(/\s*at position\s+\d.*$/iu, "").trim() || raw;
    const pos = parseJsonErrorPosition(raw);
    if (pos != null) {
      const { line, column } = indexToLineColumn(text, pos);
      return {
        state: "error",
        message: t("mcpSettingsDialog.lintAt", { error: short, line, column }),
      };
    }
    const unclosed = findUnclosedDelimiter(text);
    if (unclosed) {
      const { line, column } = indexToLineColumn(text, unclosed.index);
      return {
        state: "error",
        message: t("mcpSettingsDialog.lintUnclosed", {
          char: unclosed.char,
          line,
          column,
        }),
      };
    }
    return {
      state: "error",
      message: t("mcpSettingsDialog.lintInvalid", { error: short }),
    };
  }
}

function normalizeStr(value: unknown): string | undefined {
  if (typeof value !== "string") return undefined;
  const trimmed = value.trim();
  return trimmed || undefined;
}

function normalizeNum(value: unknown): number | undefined {
  if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
  return value;
}

function asRecord(value: unknown): Record<string, unknown> | null {
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
  return value as Record<string, unknown>;
}

function extractStringRecord(value: unknown): Record<string, string> | undefined {
  const record = asRecord(value);
  if (!record) return undefined;
  const out: Record<string, string> = {};
  for (const [key, entry] of Object.entries(record)) {
    if (typeof entry === "string") out[key] = entry;
  }
  return Object.keys(out).length ? out : undefined;
}

function resolveTransport(raw: Record<string, unknown>): "http" | "sse" | "stdio" | null {
  const transport = normalizeStr(raw.transport ?? raw.type)?.toLowerCase();
  if (transport === "http" || transport === "sse" || transport === "stdio") return transport;
  if (normalizeStr(raw.url)) return "http";
  if (normalizeStr(raw.command) || Array.isArray(raw.args)) return "stdio";
  return null;
}

function formatMcpServersJson(servers: readonly McpServerSettings[]): string {
  const mcpServers: Record<string, unknown> = {};
  const usedNames = new Set<string>();

  const ensureUniqueKey = (input: string): string => {
    const base = input || "mcp-server";
    if (!usedNames.has(base)) {
      usedNames.add(base);
      return base;
    }
    let i = 2;
    while (usedNames.has(`${base}-${i}`)) i += 1;
    const next = `${base}-${i}`;
    usedNames.add(next);
    return next;
  };

  for (const server of servers) {
    const key = ensureUniqueKey(server.name);
    const entry: Record<string, unknown> = {};

    if (server.transport === "http" || server.transport === "sse") {
      if (server.transport === "sse") entry.type = "sse";
      entry.url = server.url;
      const headers: Record<string, string> = { ...server.headers };
      const token = typeof server.auth?.token === "string" ? server.auth.token.trim() : "";
      if (token && !headers.Authorization) headers.Authorization = `Bearer ${token}`;
      if (Object.keys(headers).length) entry.headers = headers;
      if (server.requestTimeoutMs != null) entry.requestTimeoutMs = server.requestTimeoutMs;
    } else {
      entry.type = "stdio";
      entry.command = server.command;
      if (server.args.length > 0) entry.args = [...server.args];
      else entry.args = [];
      if (server.env && Object.keys(server.env).length) entry.env = server.env;
      if (server.cwd) entry.cwd = server.cwd;
      if (server.autoRestart === false) entry.autoRestart = false;
      if (server.requestTimeoutMs != null) entry.requestTimeoutMs = server.requestTimeoutMs;
      if (server.restartDelayMs != null) entry.restartDelayMs = server.restartDelayMs;
      if (server.maxRestarts != null) entry.maxRestarts = server.maxRestarts;
      if (server.restartWindowMs != null) entry.restartWindowMs = server.restartWindowMs;
    }

    if (server.enabled === false) entry.enabled = false;

    if (server.name && server.name !== key) entry.name = server.name;

    mcpServers[key] = entry;
  }

  return JSON.stringify({ mcpServers }, null, 2);
}

function getMcpServersExampleJson(): string {
  return JSON.stringify(
    {
      mcpServers: {
        google_calendar: {
          transport: "stdio",
          command: "python3",
          args: ["/absolute/path/to/calendar-mcp/run_server.py"],
          enabled: true,
        },
      },
    },
    null,
    2,
  );
}

function parseMcpServersJson(
  input: string,
  existing: readonly McpServerSettings[],
): McpServerSettings[] {
  const trimmed = input.trim();
  if (!trimmed) return [];

  let parsed: unknown;
  try {
    parsed = JSON.parse(trimmed);
  } catch (error) {
    const msg = error instanceof Error ? error.message : String(error);
    throw new Error(t("mcpSettingsDialog.invalidJson", { error: msg }));
  }

  const root = asRecord(parsed);
  const rawServers = root?.mcpServers ?? root?.mcp_servers ?? parsed;

  const maybeMap = asRecord(rawServers);
  const serverMap = (() => {
    if (!maybeMap) return null;
    const keys = Object.keys(maybeMap);
    if (keys.length !== 1) return maybeMap;
    const onlyKey = String(keys[0] ?? "")
      .trim()
      .toLowerCase();
    if (!["tools", "servers", "connectors", "mcpservers"].includes(onlyKey)) return maybeMap;
    const nested = asRecord(maybeMap[keys[0]!]);
    return nested ?? maybeMap;
  })();
  const serverList = Array.isArray(rawServers) ? rawServers : null;
  if (!serverMap && !serverList) {
    throw new Error(t("mcpSettingsDialog.invalidShape"));
  }

  const existingByName = new Map(existing.map((server) => [server.name, server.id]));
  const usedNames = new Set<string>();
  const nextServers: McpServerSettings[] = [];

  const ensureUniqueName = (name: string): string => {
    const base = name || "mcp-server";
    if (!usedNames.has(base)) {
      usedNames.add(base);
      return base;
    }
    let i = 2;
    while (usedNames.has(`${base} (${i})`)) i += 1;
    const next = `${base} (${i})`;
    usedNames.add(next);
    return next;
  };

  const pushServer = (nameKey: string, raw: unknown) => {
    const rawServer = asRecord(raw);
    if (!rawServer) {
      throw new Error(t("mcpSettingsDialog.invalidServer", { name: nameKey }));
    }
    const rawName = normalizeStr(rawServer.name) ?? nameKey;
    const name = ensureUniqueName(rawName || nameKey);
    const transport = resolveTransport(rawServer);
    if (!transport) {
      throw new Error(t("mcpSettingsDialog.invalidServer", { name }));
    }
    const enabled = rawServer.enabled !== false;
    const idFromExisting = existingByName.get(name);
    const idValue = normalizeStr(rawServer.id) ?? idFromExisting ?? id("mcp");

    if (transport === "http" || transport === "sse") {
      const url = normalizeStr(rawServer.url);
      if (!url) throw new Error(t("mcpSettingsDialog.invalidServer", { name }));
      const headers = extractStringRecord(rawServer.headers);
      let authToken = normalizeStr(rawServer.authToken ?? rawServer.bearerToken);
      const authRecord = asRecord(rawServer.auth);
      if (!authToken && authRecord && normalizeStr(authRecord.type)?.toLowerCase() === "bearer") {
        authToken = normalizeStr(authRecord.token);
      }
      const auth =
        authToken && !(headers && typeof headers.Authorization === "string")
          ? { type: "bearer" as const, token: authToken }
          : undefined;
      const requestTimeoutMs = normalizeNum(rawServer.requestTimeoutMs);

      nextServers.push({
        id: idValue,
        name,
        transport,
        url,
        auth,
        headers,
        requestTimeoutMs,
        enabled,
      });
      return;
    }

    const command = normalizeStr(rawServer.command);
    if (!command) throw new Error(t("mcpSettingsDialog.invalidServer", { name }));
    let args: string[] = [];
    if (Array.isArray(rawServer.args)) {
      args = rawServer.args.map((arg) => String(arg));
    } else if (typeof rawServer.args === "string") {
      args = parseArgs(rawServer.args);
    }
    const env = extractStringRecord(rawServer.env);
    const cwd = normalizeStr(rawServer.cwd);
    const autoRestart = rawServer.autoRestart !== false;
    const requestTimeoutMs = normalizeNum(rawServer.requestTimeoutMs);
    const restartDelayMs = normalizeNum(rawServer.restartDelayMs);
    const maxRestarts = normalizeNum(rawServer.maxRestarts);
    const restartWindowMs = normalizeNum(rawServer.restartWindowMs);

    nextServers.push({
      id: idValue,
      name,
      transport: "stdio",
      command,
      args,
      env,
      cwd,
      autoRestart,
      requestTimeoutMs,
      restartDelayMs,
      maxRestarts,
      restartWindowMs,
      enabled,
    });
  };

  if (serverList) {
    serverList.forEach((raw, index) => {
      const name = normalizeStr(asRecord(raw)?.name) ?? `mcp-${index + 1}`;
      pushServer(name, raw);
    });
  } else {
    for (const [name, raw] of Object.entries(serverMap ?? {})) {
      pushServer(String(name).trim(), raw);
    }
  }

  return nextServers;
}

function toMcpClientConfig(
  server: McpServerSettings,
): McpHttpConfig | McpSseConfig | McpStdioConfig {
  if (server.transport === "http" || server.transport === "sse") {
    return {
      id: server.id,
      name: server.name,
      transport: server.transport,
      url: server.url,
      auth: server.auth?.token ? { type: "bearer", token: server.auth.token } : undefined,
      headers: server.headers,
      requestTimeoutMs: server.requestTimeoutMs,
      enabled: server.enabled,
    };
  }
  return {
    id: server.id,
    name: server.name,
    transport: "stdio",
    command: server.command,
    args: [...server.args],
    env: server.env,
    cwd: server.cwd,
    autoRestart: server.autoRestart,
    requestTimeoutMs: server.requestTimeoutMs,
    restartDelayMs: server.restartDelayMs,
    maxRestarts: server.maxRestarts,
    restartWindowMs: server.restartWindowMs,
    enabled: server.enabled,
  };
}

export function useMcpSettingsDialogs(
  opts: Readonly<{
    store: DimSdkStore;
    showToast: (text: string) => void;
    guardPendingApprovals?: boolean;
    onApplied?: () => void;
  }>,
): Readonly<{
  showMcpSettings: Ref<boolean>;
  mcpJsonFilePath: Ref<string>;
  mcpServerCount: Ref<number>;
  testStatus: Ref<McpTestStatus>;
  openMcpSettings: () => void;
  closeMcpSettings: () => void;
  openMcpFile: () => void;
  reloadMcpConfig: () => void;
  testConnection: () => void;
}> {
  const showMcpSettings = ref(false);
  const mcpJsonFilePath = ref("");
  const mcpServerCount = ref(0);
  const testStatus = ref<McpTestStatus>({ state: "idle" });

  function hasPendingApproval(): boolean {
    return (
      opts.store.state.paused?.kind === "tool_approval" ||
      opts.store.state.approval.active?.status === "pending"
    );
  }

  function openMcpSettings(): void {
    if (opts.guardPendingApprovals !== false && hasPendingApproval()) {
      opts.showToast(t("settingsDialog.pendingApproval"));
      return;
    }
    const servers = opts.store.state.mcp.servers;
    mcpServerCount.value = servers.length;
    testStatus.value = { state: "idle" };

    // Resolve the mcp.json file path
    const env = opts.store.state.context.env ?? {};
    import("#cli/compat/config")
      .then(({ loadMcpJson }) => {
        loadMcpJson(env)
          .then(({ filePath }) => {
            mcpJsonFilePath.value = filePath;
          })
          .catch(() => {});
      })
      .catch(() => {});

    showMcpSettings.value = true;
  }

  function closeMcpSettings(): void {
    showMcpSettings.value = false;
  }

  function openMcpFile(): void {
    const filePath = mcpJsonFilePath.value;
    if (!filePath) return;

    // Ensure the file exists with a default template, then open it
    import("#cli/compat/config")
      .then(({ loadMcpJson, saveMcpJson }) => {
        const env = opts.store.state.context.env ?? {};
        loadMcpJson(env)
          .then(async ({ filePath: fp, servers }) => {
            if (!servers.length) {
              // Write default template if file is empty/missing
              await saveMcpJson(fp, []);
            }
            // Open in the user's default editor
            const { exec } = await import("node:child_process");
            const platform = runtimeGlobals().process?.platform ?? "linux";
            const cmd =
              platform === "darwin"
                ? `open "${fp}"`
                : platform === "win32"
                  ? `start "" "${fp}"`
                  : `xdg-open "${fp}"`;
            exec(cmd);
          })
          .catch(() => {});
      })
      .catch(() => {});
  }

  async function reloadMcpConfig(): Promise<void> {
    try {
      const { loadMcpJson } = await import("#cli/compat/config");
      const env = opts.store.state.context.env ?? {};
      const { servers } = await loadMcpJson(env);

      const target = opts.store.state.mcp.servers;
      target.splice(0, target.length, ...servers);
      mcpServerCount.value = servers.length;

      opts.showToast(t("mcpSettingsDialog.reloaded"));
      opts.onApplied?.();
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error);
      opts.showToast(t("mcpSettingsDialog.reloadFailed", { error: msg }));
    }
  }

  async function testConnection(): Promise<void> {
    const servers = cloneMcpServers(opts.store.state.mcp.servers);

    if (!servers.length) {
      testStatus.value = {
        state: "success",
        message: t("mcpSettingsDialog.noServers"),
      };
      return;
    }

    testStatus.value = {
      state: "testing",
      message: t("mcpSettingsDialog.testing"),
    };
    const results: Array<{
      name: string;
      toolCount?: number;
      error?: string;
      disabled?: boolean;
    }> = [];
    let hasError = false;

    try {
      const { createMcpClient } = await import("#cli/compat/mcp");
      for (const server of servers) {
        if (server.enabled === false) {
          results.push({ name: server.name, disabled: true });
          continue;
        }
        const client = createMcpClient(toMcpClientConfig(server));
        try {
          await client.initialize();
          const tools = await client.listTools().catch(() => []);
          results.push({ name: server.name, toolCount: tools.length });
        } catch (error) {
          const msg = error instanceof Error ? error.message : String(error);
          results.push({ name: server.name, error: msg });
          hasError = true;
        } finally {
          await client.disconnect().catch(() => {});
        }
      }
    } catch (error) {
      const msg = error instanceof Error ? error.message : String(error);
      testStatus.value = {
        state: "error",
        message: t("mcpSettingsDialog.testFailed", { error: msg }),
      };
      return;
    }

    testStatus.value = {
      state: hasError ? "error" : "success",
      message: hasError ? t("mcpSettingsDialog.testPartial") : t("mcpSettingsDialog.testComplete"),
      results,
    };
  }

  return {
    showMcpSettings,
    mcpJsonFilePath,
    mcpServerCount,
    testStatus,
    openMcpSettings,
    closeMcpSettings,
    openMcpFile,
    reloadMcpConfig,
    testConnection,
  };
}

export const __test__ = {
  parseMcpServersJson,
  formatMcpServersJson,
  getMcpServersExampleJson,
  lintMcpJson,
  findUnclosedDelimiter,
};
