import { describe, expect, it } from "vitest";
import { resolveServers } from "./config.js";

describe("config", () => {
	it("built-in typescript resolves when tsconfig.json exists", async () => {
		const { servers } = await resolveServers({
			cwd: "/project",
			dispatchLspJson: null,
			opencodeJson: null,
			exists: async (path) => path === "/project/tsconfig.json",
		});

		const ts = servers.find((s) => s.id === "typescript");
		expect(ts).toBeDefined();
		expect(ts?.command).toEqual(["typescript-language-server", "--stdio"]);
		expect(ts?.extensions).toContain(".ts");
		expect(ts?.rootMarkers).toContain("tsconfig.json");
	});

	it(".dispatch/lsp.json servers resolve", async () => {
		const config = JSON.stringify({
			servers: {
				mylsp: {
					command: ["my-lsp", "--stdio"],
					extensions: [".ml"],
					rootMarkers: ["Makefile"],
				},
			},
		});

		const { servers } = await resolveServers({
			cwd: "/project",
			dispatchLspJson: config,
			opencodeJson: null,
			exists: async () => false,
		});

		expect(servers).toHaveLength(1);
		expect(servers[0]?.id).toBe("mylsp");
		expect(servers[0]?.command).toEqual(["my-lsp", "--stdio"]);
	});

	it("opencode.json lsp is used only as fallback", async () => {
		const opencodeConfig = JSON.stringify({
			lsp: {
				fallback: {
					command: ["fallback-lsp"],
					extensions: [".fb"],
				},
			},
		});

		const { servers } = await resolveServers({
			cwd: "/project",
			dispatchLspJson: null,
			opencodeJson: opencodeConfig,
			exists: async () => false,
		});

		expect(servers).toHaveLength(1);
		expect(servers[0]?.id).toBe("fallback");
	});

	it(".dispatch/lsp.json wins over opencode.json", async () => {
		const dispatchConfig = JSON.stringify({
			servers: { primary: { command: ["primary-lsp"], extensions: [".p"] } },
		});
		const opencodeConfig = JSON.stringify({
			lsp: { fallback: { command: ["fallback-lsp"], extensions: [".f"] } },
		});

		const { servers } = await resolveServers({
			cwd: "/project",
			dispatchLspJson: dispatchConfig,
			opencodeJson: opencodeConfig,
			exists: async () => false,
		});

		expect(servers).toHaveLength(1);
		expect(servers[0]?.id).toBe("primary");
	});

	it("luau-lsp sourcemap.autogenerate yields a rojo --watch sidecar", async () => {
		const config = JSON.stringify({
			servers: {
				luau: {
					command: ["luau-lsp", "lsp"],
					extensions: [".luau"],
					initialization: {
						"luau-lsp": {
							sourcemap: {
								autogenerate: true,
								rojoProjectFile: "default.project.json",
							},
						},
					},
				},
			},
		});

		const { servers } = await resolveServers({
			cwd: "/project",
			dispatchLspJson: config,
			opencodeJson: null,
			exists: async () => false,
		});

		expect(servers).toHaveLength(1);
		expect(servers[0]?.sidecar).toBeDefined();
		expect(servers[0]?.sidecar?.command).toEqual([
			"rojo",
			"sourcemap",
			"default.project.json",
			"--watch",
			"-o",
			"sourcemap.json",
		]);
	});

	it("config: resolveServers records configSource", async () => {
		// .dispatch/lsp.json entry → ".dispatch/lsp.json"
		const dispatch = JSON.stringify({
			servers: {
				mylsp: { command: ["my-lsp"], extensions: [".ml"] },
			},
		});
		const fromDispatch = await resolveServers({
			cwd: "/project",
			dispatchLspJson: dispatch,
			opencodeJson: null,
			exists: async () => false,
		});
		expect(fromDispatch.servers[0]?.configSource).toBe(".dispatch/lsp.json");

		// fallback to opencode.json → "opencode.json"
		const opencode = JSON.stringify({
			lsp: {
				oc: { command: ["oc-lsp"], extensions: [".oc"] },
			},
		});
		const fromOpencode = await resolveServers({
			cwd: "/project",
			dispatchLspJson: null,
			opencodeJson: opencode,
			exists: async () => false,
		});
		expect(fromOpencode.servers[0]?.configSource).toBe("opencode.json");

		// built-in TS → "built-in"
		const fromBuiltin = await resolveServers({
			cwd: "/project",
			dispatchLspJson: null,
			opencodeJson: null,
			exists: async () => false,
		});
		expect(fromBuiltin.servers[0]?.configSource).toBe("built-in");
	});

	it("config: shadowed flag is true when .dispatch/lsp.json shadows opencode.json lsp", async () => {
		const dispatch = JSON.stringify({
			servers: { primary: { command: ["primary-lsp"], extensions: [".p"] } },
		});
		const opencode = JSON.stringify({
			lsp: { fallback: { command: ["fallback-lsp"], extensions: [".f"] } },
		});

		const { shadowed, servers } = await resolveServers({
			cwd: "/project",
			dispatchLspJson: dispatch,
			opencodeJson: opencode,
			exists: async () => false,
		});

		// dispatch wins, opencode entry is shadowed
		expect(shadowed).toBe(true);
		expect(servers.map((s) => s.id)).toEqual(["primary"]);
	});

	it("config: shadowed flag is false when only one config source declares lsp", async () => {
		const dispatch = JSON.stringify({
			servers: { primary: { command: ["primary-lsp"], extensions: [".p"] } },
		});

		// dispatch present, opencode has NO lsp key → not shadowed
		const onlyDispatch = await resolveServers({
			cwd: "/project",
			dispatchLspJson: dispatch,
			opencodeJson: JSON.stringify({ lsp: {} }),
			exists: async () => false,
		});
		expect(onlyDispatch.shadowed).toBe(false);

		// dispatch absent, opencode has lsp → not shadowed (opencode is used)
		const onlyOpencode = await resolveServers({
			cwd: "/project",
			dispatchLspJson: null,
			opencodeJson: JSON.stringify({ lsp: { fb: { command: ["fb"] } } }),
			exists: async () => false,
		});
		expect(onlyOpencode.shadowed).toBe(false);
	});
});
