import * as NodeOS from "node:os";

import * as NodeServices from "@effect/platform-node/NodeServices";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import { describe, expect, it } from "vitest";
import type * as EffectAcpSchema from "effect-acp/schema";
import type { CursorSettings, ServerProviderModel } from "@belweave/contracts";
import { createModelCapabilities } from "@belweave/shared/model";

import {
  buildCursorProviderSnapshot,
  buildCursorCapabilitiesFromConfigOptions,
  buildCursorDiscoveredModelsFromConfigOptions,
  checkCursorProviderStatus,
  detectNonCursorBinaryMessage,
  discoverCursorModelCapabilitiesViaAcp,
  discoverCursorModelsViaAcp,
  getCursorFallbackModels,
  getCursorParameterizedModelPickerUnsupportedMessage,
  parseCursorAboutOutput,
  parseCursorCliConfigChannel,
  parseCursorVersionDate,
  resolveCursorAcpBaseModelId,
  resolveCursorAcpConfigUpdates,
} from "./CursorProvider.ts";

const runNode = <A, E>(
  effect: Effect.Effect<A, E, FileSystem.FileSystem | Path.Path>,
): Promise<A> => Effect.runPromise(effect.pipe(Effect.provide(NodeServices.layer)));

const resolveMockAgentPath = Effect.fn("resolveMockAgentPath")(function* () {
  const path = yield* Path.Path;
  return yield* path.fromFileUrl(new URL("../../../scripts/acp-mock-agent.ts", import.meta.url));
});

function selectDescriptor(
  id: string,
  label: string,
  options: ReadonlyArray<{ id: string; label: string; isDefault?: boolean }>,
) {
  return {
    id,
    label,
    type: "select" as const,
    options: [...options],
    ...(options.find((option) => option.isDefault)?.id
      ? { currentValue: options.find((option) => option.isDefault)?.id }
      : {}),
  };
}

function booleanDescriptor(id: string, label: string, currentValue?: boolean) {
  return {
    id,
    label,
    type: "boolean" as const,
    ...(typeof currentValue === "boolean" ? { currentValue } : {}),
  };
}

const makeMockAgentWrapper = Effect.fn("makeMockAgentWrapper")(function* (
  extraEnv?: Record<string, string>,
) {
  const fileSystem = yield* FileSystem.FileSystem;
  const path = yield* Path.Path;
  const mockAgentPath = yield* resolveMockAgentPath();
  const dir = yield* fileSystem.makeTempDirectory({
    directory: NodeOS.tmpdir(),
    prefix: "cursor-provider-mock-",
  });
  const wrapperPath = path.join(dir, "fake-agent.sh");
  // @effect-diagnostics-next-line preferSchemaOverJson:off
  const bunCommand = JSON.stringify("bun");
  // @effect-diagnostics-next-line preferSchemaOverJson:off
  const mockAgentPathJson = JSON.stringify(mockAgentPath);
  const envExports = Object.entries(extraEnv ?? {})
    .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
    .join("\n");
  const script = `#!/bin/sh
${envExports}
exec ${bunCommand} ${mockAgentPathJson} "$@"
`;
  yield* fileSystem.writeFileString(wrapperPath, script);
  yield* fileSystem.chmod(wrapperPath, 0o755);
  return wrapperPath;
});

const makeMockAgentWithAboutWrapper = Effect.fn("makeMockAgentWithAboutWrapper")(function* () {
  const fileSystem = yield* FileSystem.FileSystem;
  const path = yield* Path.Path;
  const mockAgentPath = yield* resolveMockAgentPath();
  const dir = yield* fileSystem.makeTempDirectory({
    directory: NodeOS.tmpdir(),
    prefix: "cursor-provider-about-mock-",
  });
  const wrapperPath = path.join(dir, "fake-agent.sh");
  // @effect-diagnostics-next-line preferSchemaOverJson:off
  const bunCommand = JSON.stringify("bun");
  // @effect-diagnostics-next-line preferSchemaOverJson:off
  const mockAgentPathJson = JSON.stringify(mockAgentPath);
  const script = `#!/bin/sh
if [ "$1" = "about" ]; then
  printf 'CLI Version         2026.04.09-f2b0fcd\\n'
  printf 'User Email          cursor@example.com\\n'
  exit 0
fi
exec ${bunCommand} ${mockAgentPathJson} "$@"
`;
  yield* fileSystem.writeFileString(wrapperPath, script);
  yield* fileSystem.chmod(wrapperPath, 0o755);
  return wrapperPath;
});

const waitForFileContent = Effect.fn("waitForFileContent")(function* (
  filePath: string,
  attempts = 40,
) {
  const fileSystem = yield* FileSystem.FileSystem;
  for (let attempt = 0; attempt < attempts; attempt += 1) {
    const content = yield* fileSystem
      .readFileString(filePath)
      .pipe(Effect.catch(() => Effect.void));
    if (content !== undefined) {
      if (content.trim().length > 0) {
        return content;
      }
    }
    yield* Effect.sleep("50 millis");
  }
  return yield* Effect.die(`Timed out waiting for file content at ${filePath}`);
});

const makeProviderStatusEnvFixture = Effect.fn("makeProviderStatusEnvFixture")(function* () {
  const fileSystem = yield* FileSystem.FileSystem;
  const path = yield* Path.Path;
  const tempDir = yield* fileSystem.makeTempDirectory({
    directory: NodeOS.tmpdir(),
    prefix: "cursor-provider-status-env-",
  });
  return {
    requestLogPath: path.join(tempDir, "requests.ndjson"),
    wrapperPath: yield* makeMockAgentWithAboutWrapper(),
  };
});

const makeExitLogFixture = Effect.fn("makeExitLogFixture")(function* (prefix: string) {
  const fileSystem = yield* FileSystem.FileSystem;
  const path = yield* Path.Path;
  const tempDir = yield* fileSystem.makeTempDirectory({
    directory: NodeOS.tmpdir(),
    prefix,
  });
  const exitLogPath = path.join(tempDir, "exit.log");
  return {
    exitLogPath,
    wrapperPath: yield* makeMockAgentWrapper({
      BELWEAVE_ACP_EXIT_LOG_PATH: exitLogPath,
    }),
  };
});

const parameterizedGpt54ConfigOptions = [
  {
    type: "select",
    currentValue: "gpt-5.4-medium-fast",
    options: [{ name: "GPT-5.4", value: "gpt-5.4-medium-fast" }],
    category: "model",
    id: "model",
    name: "Model",
  },
  {
    type: "select",
    currentValue: "medium",
    options: [
      { name: "None", value: "none" },
      { name: "Low", value: "low" },
      { name: "Medium", value: "medium" },
      { name: "High", value: "high" },
      { name: "Extra High", value: "extra-high" },
    ],
    category: "thought_level",
    id: "reasoning",
    name: "Reasoning",
  },
  {
    type: "select",
    currentValue: "272k",
    options: [
      { name: "272K", value: "272k" },
      { name: "1M", value: "1m" },
    ],
    category: "model_config",
    id: "context",
    name: "Context",
  },
  {
    type: "select",
    currentValue: "false",
    options: [
      { name: "Off", value: "false" },
      { name: "Fast", value: "true" },
    ],
    category: "model_config",
    id: "fast",
    name: "Fast",
  },
] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>;

const parameterizedClaudeConfigOptions = [
  {
    type: "select",
    currentValue: "claude-4.6-opus-high-thinking",
    options: [{ name: "Opus 4.6", value: "claude-4.6-opus-high-thinking" }],
    category: "model",
    id: "model",
    name: "Model",
  },
  {
    type: "select",
    currentValue: "high",
    options: [
      { name: "Low", value: "low" },
      { name: "Medium", value: "medium" },
      { name: "High", value: "high" },
    ],
    category: "thought_level",
    id: "reasoning",
    name: "Reasoning",
  },
  {
    type: "boolean",
    currentValue: true,
    category: "model_config",
    id: "thinking",
    name: "Thinking",
  },
] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>;

const parameterizedClaudeModelOptionConfigOptions = [
  {
    type: "select",
    currentValue: "claude-opus-4-6",
    options: [{ name: "Opus 4.6", value: "claude-opus-4-6" }],
    category: "model",
    id: "model",
    name: "Model",
  },
  {
    type: "select",
    currentValue: "high",
    options: [
      { name: "Low", value: "low" },
      { name: "Medium", value: "medium" },
      { name: "High", value: "high" },
    ],
    category: "thought_level",
    id: "reasoning",
    name: "Reasoning",
  },
  {
    type: "select",
    currentValue: "max",
    options: [
      { name: "Low", value: "low" },
      { name: "Medium", value: "medium" },
      { name: "High", value: "high" },
      { name: "Max", value: "max" },
    ],
    category: "model_option",
    id: "effort",
    name: "Effort",
  },
  {
    type: "select",
    currentValue: "true",
    options: [
      { name: "Off", value: "false" },
      { name: "Fast", value: "true" },
    ],
    category: "model_config",
    id: "fast",
    name: "Fast",
  },
  {
    type: "select",
    currentValue: "true",
    options: [
      { name: "Off", value: "false" },
      { name: ":icon-brain:", value: "true" },
    ],
    category: "model_config",
    id: "thinking",
    name: "Thinking",
  },
] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>;

const sessionNewCursorConfigOptions = [
  {
    type: "select",
    currentValue: "agent",
    options: [
      { name: "Agent", value: "agent", description: "Full agent capabilities with tool access" },
    ],
    category: "mode",
    id: "mode",
    name: "Mode",
    description: "Controls how the agent executes tasks",
  },
  {
    type: "select",
    currentValue: "composer-2",
    options: [
      { name: "Auto", value: "default" },
      { name: "Composer 2", value: "composer-2" },
      { name: "GPT-5.4", value: "gpt-5.4" },
      { name: "Sonnet 4.6", value: "claude-sonnet-4-6" },
      { name: "Opus 4.6", value: "claude-opus-4-6" },
      { name: "Codex 5.3 Spark", value: "gpt-5.3-codex-spark" },
    ],
    category: "model",
    id: "model",
    name: "Model",
    description: "Controls which model is used for responses",
  },
  {
    type: "select",
    currentValue: "true",
    options: [
      { name: "Off", value: "false" },
      { name: "Fast", value: "true" },
    ],
    category: "model_config",
    id: "fast",
    name: "Fast",
    description: "Faster speeds.",
  },
] satisfies ReadonlyArray<EffectAcpSchema.SessionConfigOption>;

const baseCursorSettings: CursorSettings = {
  enabled: true,
  binaryPath: "agent",
  apiEndpoint: "",
  customModels: [],
};

const emptyCapabilities = createModelCapabilities({ optionDescriptors: [] });

describe("getCursorFallbackModels", () => {
  it("does not publish any built-in cursor models before ACP discovery", () => {
    expect(
      getCursorFallbackModels({
        customModels: ["internal/cursor-model"],
      }).map((model) => model.slug),
    ).toEqual(["internal/cursor-model"]);
  });
});

describe("buildCursorProviderSnapshot", () => {
  it("downgrades ready status to warning when ACP model discovery times out", () => {
    expect(
      buildCursorProviderSnapshot({
        checkedAt: "2026-01-01T00:00:00.000Z",
        cursorSettings: baseCursorSettings,
        parsed: {
          version: "2026.04.09-f2b0fcd",
          status: "ready",
          auth: { status: "authenticated", type: "Team", label: "Cursor Team Subscription" },
        },
        discoveryWarning: "Cursor ACP model discovery timed out after 15000ms.",
      }),
    ).toMatchObject({
      status: "warning",
      message: "Cursor ACP model discovery timed out after 15000ms.",
      models: [],
    });
  });

  it("preserves provider error state while appending discovery warnings", () => {
    expect(
      buildCursorProviderSnapshot({
        checkedAt: "2026-01-01T00:00:00.000Z",
        cursorSettings: {
          ...baseCursorSettings,
          customModels: ["claude-sonnet-4-6"],
        },
        parsed: {
          version: "2026.04.09-f2b0fcd",
          status: "error",
          auth: { status: "unauthenticated" },
          message: "Cursor Agent is not authenticated. Run `cursor-agent login` and try again.",
        },
        discoveryWarning: "Cursor ACP model discovery failed. Check server logs for details.",
      }),
    ).toMatchObject({
      status: "error",
      message:
        "Cursor Agent is not authenticated. Run `cursor-agent login` and try again. Cursor ACP model discovery failed. Check server logs for details.",
      models: [
        {
          slug: "claude-sonnet-4-6",
          isCustom: true,
        },
      ],
    });
  });
});

describe("buildCursorCapabilitiesFromConfigOptions", () => {
  it("derives model capabilities from parameterized Cursor ACP config options", () => {
    expect(buildCursorCapabilitiesFromConfigOptions(parameterizedGpt54ConfigOptions)).toEqual(
      createModelCapabilities({
        optionDescriptors: [
          selectDescriptor("reasoning", "Reasoning", [
            { id: "low", label: "Low" },
            { id: "medium", label: "Medium", isDefault: true },
            { id: "high", label: "High" },
            { id: "xhigh", label: "Extra High" },
          ]),
          selectDescriptor("contextWindow", "Context", [
            { id: "272k", label: "272K", isDefault: true },
            { id: "1m", label: "1M" },
          ]),
          booleanDescriptor("fastMode", "Fast", false),
        ],
      }),
    );
  });

  it("detects boolean thinking toggles from model_config options", () => {
    expect(buildCursorCapabilitiesFromConfigOptions(parameterizedClaudeConfigOptions)).toEqual(
      createModelCapabilities({
        optionDescriptors: [
          selectDescriptor("reasoning", "Reasoning", [
            { id: "low", label: "Low" },
            { id: "medium", label: "Medium" },
            { id: "high", label: "High", isDefault: true },
          ]),
          booleanDescriptor("thinking", "Thinking", true),
        ],
      }),
    );
  });

  it("prefers the newer model_option effort control over legacy thought_level", () => {
    expect(
      buildCursorCapabilitiesFromConfigOptions(parameterizedClaudeModelOptionConfigOptions),
    ).toEqual(
      createModelCapabilities({
        optionDescriptors: [
          selectDescriptor("reasoning", "Effort", [
            { id: "low", label: "Low" },
            { id: "medium", label: "Medium" },
            { id: "high", label: "High" },
            { id: "max", label: "Max", isDefault: true },
          ]),
          booleanDescriptor("fastMode", "Fast", true),
          booleanDescriptor("thinking", "Thinking", true),
        ],
      }),
    );
  });
});

describe("buildCursorDiscoveredModelsFromConfigOptions", () => {
  it("publishes ACP model choices immediately from session/new config options", () => {
    expect(buildCursorDiscoveredModelsFromConfigOptions(sessionNewCursorConfigOptions)).toEqual([
      {
        slug: "default",
        name: "Auto",
        isCustom: false,
        capabilities: emptyCapabilities,
      },
      {
        slug: "composer-2",
        name: "Composer 2",
        isCustom: false,
        capabilities: createModelCapabilities({
          optionDescriptors: [booleanDescriptor("fastMode", "Fast", true)],
        }),
      },
      {
        slug: "gpt-5.4",
        name: "GPT-5.4",
        isCustom: false,
        capabilities: emptyCapabilities,
      },
      {
        slug: "claude-sonnet-4-6",
        name: "Sonnet 4.6",
        isCustom: false,
        capabilities: emptyCapabilities,
      },
      {
        slug: "claude-opus-4-6",
        name: "Opus 4.6",
        isCustom: false,
        capabilities: emptyCapabilities,
      },
      {
        slug: "gpt-5.3-codex-spark",
        name: "Codex 5.3 Spark",
        isCustom: false,
        capabilities: emptyCapabilities,
      },
    ]);
  });
});

describe("checkCursorProviderStatus", () => {
  it("passes the injected environment to ACP model discovery", async () => {
    const { requestLogPath, wrapperPath } = await runNode(makeProviderStatusEnvFixture());

    const provider = await Effect.runPromise(
      checkCursorProviderStatus(
        {
          enabled: true,
          binaryPath: wrapperPath,
          apiEndpoint: "",
          customModels: [],
        },
        {
          ...process.env,
          BELWEAVE_ACP_REQUEST_LOG_PATH: requestLogPath,
        },
      ).pipe(Effect.provide(NodeServices.layer)),
    );

    expect(provider.models.map((model) => model.slug)).toEqual([
      "default",
      "composer-2",
      "gpt-5.4",
      "claude-opus-4-6",
    ]);
    await expect(runNode(waitForFileContent(requestLogPath))).resolves.toContain("initialize");
  });
});

describe("discoverCursorModelsViaAcp", () => {
  it("keeps the ACP probe runtime alive long enough to discover models", async () => {
    const wrapperPath = await runNode(makeMockAgentWrapper());

    const models = await Effect.runPromise(
      discoverCursorModelsViaAcp({
        enabled: true,
        binaryPath: wrapperPath,
        apiEndpoint: "",
        customModels: [],
      }).pipe(Effect.provide(NodeServices.layer), Effect.scoped),
    );

    expect(models.map((model) => model.slug)).toEqual([
      "default",
      "composer-2",
      "gpt-5.4",
      "claude-opus-4-6",
    ]);
  });

  it("closes the ACP probe runtime after discovery completes", async () => {
    const { exitLogPath, wrapperPath } = await runNode(
      makeExitLogFixture("cursor-provider-exit-log-"),
    );

    await Effect.runPromise(
      discoverCursorModelsViaAcp({
        enabled: true,
        binaryPath: wrapperPath,
        apiEndpoint: "",
        customModels: [],
      }).pipe(Effect.provide(NodeServices.layer)),
    );

    const exitLog = await runNode(waitForFileContent(exitLogPath));
    expect(exitLog).toContain("SIGTERM");
  });
});

describe("discoverCursorModelCapabilitiesViaAcp", () => {
  it("closes all ACP probe runtimes after capability enrichment completes", async () => {
    const { exitLogPath, wrapperPath } = await runNode(
      makeExitLogFixture("cursor-capabilities-exit-log-"),
    );
    const existingModels: ReadonlyArray<ServerProviderModel> = [
      { slug: "default", name: "Auto", isCustom: false, capabilities: emptyCapabilities },
      { slug: "composer-2", name: "Composer 2", isCustom: false, capabilities: emptyCapabilities },
      { slug: "gpt-5.4", name: "GPT-5.4", isCustom: false, capabilities: emptyCapabilities },
      {
        slug: "claude-opus-4-6",
        name: "Opus 4.6",
        isCustom: false,
        capabilities: emptyCapabilities,
      },
    ];

    const models = await Effect.runPromise(
      discoverCursorModelCapabilitiesViaAcp(
        {
          enabled: true,
          binaryPath: wrapperPath,
          apiEndpoint: "",
          customModels: [],
        },
        existingModels,
      ).pipe(Effect.provide(NodeServices.layer)),
    );

    expect(models.map((model) => model.slug)).toEqual([
      "default",
      "composer-2",
      "gpt-5.4",
      "claude-opus-4-6",
    ]);

    const exitLog = await runNode(waitForFileContent(exitLogPath));
    expect(exitLog.match(/SIGTERM/g)?.length ?? 0).toBe(4);
  });
});

describe("parseCursorAboutOutput", () => {
  it("parses json about output and forwards subscription metadata", () => {
    expect(
      parseCursorAboutOutput({
        code: 0,
        stdout: JSON.stringify({
          cliVersion: "2026.04.09-f2b0fcd",
          subscriptionTier: "Team",
          userEmail: "jmarminge@gmail.com",
        }),
        stderr: "",
      }),
    ).toEqual({
      version: "2026.04.09-f2b0fcd",
      status: "ready",
      auth: {
        status: "authenticated",
        email: "jmarminge@gmail.com",
        type: "Team",
        label: "Cursor Team Subscription",
      },
    });
  });

  it("treats json about output with a logged-out email as unauthenticated", () => {
    expect(
      parseCursorAboutOutput({
        code: 0,
        stdout: JSON.stringify({
          cliVersion: "2026.04.09-f2b0fcd",
          subscriptionTier: "Team",
          userEmail: "Not logged in",
        }),
        stderr: "",
      }),
    ).toEqual({
      version: "2026.04.09-f2b0fcd",
      status: "error",
      auth: {
        status: "unauthenticated",
      },
      message: "Cursor Agent is not authenticated. Run `cursor-agent login` and try again.",
    });
  });

  it("treats json about output with a null email as unauthenticated", () => {
    expect(
      parseCursorAboutOutput({
        code: 0,
        stdout: JSON.stringify({
          cliVersion: "2026.04.09-f2b0fcd",
          subscriptionTier: null,
          userEmail: null,
        }),
        stderr: "",
      }),
    ).toEqual({
      version: "2026.04.09-f2b0fcd",
      status: "error",
      auth: {
        status: "unauthenticated",
      },
      message: "Cursor Agent is not authenticated. Run `cursor-agent login` and try again.",
    });
  });
});

describe("detectNonCursorBinaryMessage", () => {
  it("flags Grok's `agent` binary as not Cursor", () => {
    expect(
      detectNonCursorBinaryMessage({
        code: 2,
        stdout: "",
        stderr: "error: unrecognized subcommand 'about'\n\nUsage: agent [OPTIONS] [COMMAND]\n",
      }),
    ).toBe(
      "The configured `binaryPath` does not appear to be the Cursor Agent CLI. Install Cursor (https://cursor.com/install) and set `binaryPath` to `cursor-agent`.",
    );
  });

  it("flags output that names a known unrelated CLI", () => {
    expect(
      detectNonCursorBinaryMessage({
        code: 0,
        stdout: "grok 0.1.210 (8b63e9068c)",
        stderr: "",
      }),
    ).toMatch(/does not appear to be the Cursor Agent CLI/);
  });

  it("returns undefined when stdout mentions Cursor", () => {
    expect(
      detectNonCursorBinaryMessage({
        code: 0,
        stdout: "About Cursor CLI\n\nCLI Version  2026.05.07-42ddaca",
        stderr: "",
      }),
    ).toBeUndefined();
  });

  it("trusts Cursor JSON payloads even when the JSON body lacks the substring 'cursor'", () => {
    // Regression: modern `cursor-agent about --format json` returns
    // `{"cliVersion": "...", "model": "Gemini 2.5 Flash", "userEmail": "..."}`
    // with no literal "cursor" anywhere in the response.
    expect(
      detectNonCursorBinaryMessage({
        code: 0,
        stdout: JSON.stringify({
          cliVersion: "2026.05.16-0338208",
          model: "Gemini 2.5 Flash",
          subscriptionTier: "Pro",
          osPlatform: "darwin",
          osArch: "arm64",
          userEmail: "user@example.com",
          terminalProgram: "unknown",
          shell: "zsh",
          lastRequestId: null,
        }),
        stderr: "",
      }),
    ).toBeUndefined();
  });

  it("returns undefined for an empty failure (lets the normal parser handle it)", () => {
    expect(
      detectNonCursorBinaryMessage({
        code: 1,
        stdout: "",
        stderr: "",
      }),
    ).toBeUndefined();
  });
});

describe("Cursor parameterized model picker preview gating", () => {
  it("parses Cursor CLI version dates from build versions", () => {
    expect(parseCursorVersionDate("2026.04.08-c4e73a3")).toBe(20260408);
    expect(parseCursorVersionDate("2026.04.09")).toBe(20260409);
    expect(parseCursorVersionDate("not-a-version")).toBeUndefined();
  });

  it("parses the Cursor CLI channel from cli-config.json", () => {
    expect(parseCursorCliConfigChannel('{ "channel": "lab" }')).toBe("lab");
    expect(parseCursorCliConfigChannel('{ "channel": "stable" }')).toBe("stable");
    expect(parseCursorCliConfigChannel('{ "version": 1 }')).toBeUndefined();
    expect(parseCursorCliConfigChannel("not-json")).toBeUndefined();
  });

  it("returns no warning when the preview requirements are met", () => {
    expect(
      getCursorParameterizedModelPickerUnsupportedMessage({
        version: "2026.04.08-c4e73a3",
        channel: "lab",
      }),
    ).toBeUndefined();
  });

  it("explains when the Cursor Agent version is too old", () => {
    expect(
      getCursorParameterizedModelPickerUnsupportedMessage({
        version: "2026.04.07-c4e73a3",
        channel: "lab",
      }),
    ).toContain("too old");
  });

  it("explains when the Cursor Agent channel is not lab", () => {
    expect(
      getCursorParameterizedModelPickerUnsupportedMessage({
        version: "2026.04.08-c4e73a3",
        channel: "stable",
      }),
    ).toContain("lab channel");
  });
});

describe("resolveCursorAcpBaseModelId", () => {
  it("drops bracket traits without rewriting raw ACP model ids", () => {
    expect(resolveCursorAcpBaseModelId("gpt-5.4[reasoning=medium,context=272k]")).toBe("gpt-5.4");
    expect(resolveCursorAcpBaseModelId("gpt-5.4-medium-fast")).toBe("gpt-5.4-medium-fast");
    expect(resolveCursorAcpBaseModelId("claude-4.6-opus-high-thinking")).toBe(
      "claude-4.6-opus-high-thinking",
    );
    expect(resolveCursorAcpBaseModelId("composer-2")).toBe("composer-2");
    expect(resolveCursorAcpBaseModelId("auto")).toBe("auto");
  });
});

describe("resolveCursorAcpConfigUpdates", () => {
  it("maps Cursor model options onto separate ACP config option updates", () => {
    expect(
      resolveCursorAcpConfigUpdates(parameterizedGpt54ConfigOptions, [
        { id: "reasoning", value: "xhigh" },
        { id: "fastMode", value: true },
        { id: "contextWindow", value: "1m" },
      ]),
    ).toEqual([
      { configId: "reasoning", value: "extra-high" },
      { configId: "context", value: "1m" },
      { configId: "fast", value: "true" },
    ]);
  });

  it("maps boolean thinking toggles when the model exposes them separately", () => {
    expect(
      resolveCursorAcpConfigUpdates(parameterizedClaudeConfigOptions, [
        { id: "thinking", value: false },
      ]),
    ).toEqual([{ configId: "thinking", value: false }]);
  });

  it("maps explicit fastMode: false so the adapter can clear a prior fast selection", () => {
    expect(
      resolveCursorAcpConfigUpdates(parameterizedGpt54ConfigOptions, [
        { id: "fastMode", value: false },
      ]),
    ).toEqual([{ configId: "fast", value: "false" }]);
  });

  it("writes Cursor effort changes through the newer model_option config when available", () => {
    expect(
      resolveCursorAcpConfigUpdates(parameterizedClaudeModelOptionConfigOptions, [
        { id: "reasoning", value: "max" },
        { id: "thinking", value: false },
      ]),
    ).toEqual([
      { configId: "effort", value: "max" },
      { configId: "thinking", value: "false" },
    ]);
  });
});
