import assert from "node:assert/strict";
import { test } from "node:test";
import { LlmWikiApiClient, normalizeBaseUrl } from "../src/api-client.js";
test("normalizeBaseUrl trims trailing slashes and falls back to localhost", () => {
    assert.equal(normalizeBaseUrl("http://127.0.0.1:19828///"), "http://127.0.0.1:19828");
    assert.equal(normalizeBaseUrl(""), "http://127.0.0.1:19828");
});
test("projects sends bearer token and parses current project", async () => {
    const calls = [];
    const fetchImpl = async (url, init) => {
        calls.push({ url: String(url), init });
        return new Response(JSON.stringify({
            ok: true,
            projects: [{ id: "p1", name: "Demo", path: "/tmp/demo", current: true }],
            currentProject: { id: "p1", name: "Demo", path: "/tmp/demo", current: true },
        }), { status: 200 });
    };
    const client = new LlmWikiApiClient({
        baseUrl: "http://localhost:19828/",
        token: "secret",
        fetchImpl,
    });
    const result = await client.projects();
    assert.equal(calls[0]?.url, "http://localhost:19828/api/v1/projects");
    assert.equal((calls[0]?.init?.headers).Authorization, "Bearer secret");
    assert.equal(result.currentProject?.id, "p1");
    assert.equal(result.projects[0]?.current, true);
});
test("health does not send authorization", async () => {
    const calls = [];
    const fetchImpl = async (_url, init) => {
        calls.push(init);
        return new Response(JSON.stringify({ ok: true, status: "running" }), { status: 200 });
    };
    const client = new LlmWikiApiClient({ token: "secret", fetchImpl });
    await client.health();
    assert.equal(calls[0]?.headers?.Authorization, undefined);
});
test("search posts JSON body to current project", async () => {
    let body = "";
    const fetchImpl = async (_url, init) => {
        body = String(init?.body ?? "");
        return new Response(JSON.stringify({
            ok: true,
            mode: "hybrid",
            tokenHits: 2,
            vectorHits: 1,
            results: [{ path: "wiki/a.md", title: "A", snippet: "hit", score: 0.5, vectorScore: 0.9 }],
        }), { status: 200 });
    };
    const client = new LlmWikiApiClient({ fetchImpl });
    const results = await client.search("current", "query", { topK: 3, includeContent: true });
    assert.deepEqual(JSON.parse(body), { query: "query", topK: 3, includeContent: true });
    assert.equal(results.mode, "hybrid");
    assert.equal(results.tokenHits, 2);
    assert.equal(results.vectorHits, 1);
    assert.equal(results.results[0]?.vectorScore, 0.9);
});
test("chat posts agent request and parses references", async () => {
    let url = "";
    let body = "";
    const fetchImpl = async (requestUrl, init) => {
        url = String(requestUrl);
        body = String(init?.body ?? "");
        return new Response(JSON.stringify({
            ok: true,
            projectId: "p1",
            sessionId: "s1",
            mode: "standard",
            message: { role: "assistant", content: "answer" },
            references: [{ title: "A", path: "wiki/a.md", kind: "wiki", snippet: "hit", score: 0.5 }],
            toolEvents: [{ tool: "wiki.search", status: "completed", detail: "1 result" }],
            events: [{ type: "toolEnd", tool: "wiki.search" }],
            usage: { promptChars: 100, completionChars: 6, referenceCount: 1, toolEventCount: 1 },
        }), { status: 200 });
    };
    const client = new LlmWikiApiClient({ baseUrl: "http://localhost:19828", fetchImpl });
    const response = await client.chat("current", "question", {
        sessionId: "s1",
        mode: "standard",
        topK: 4,
        includeContent: true,
        wiki: true,
        web: false,
        anytxt: true,
        skills: ["reviewer"],
    });
    assert.equal(url, "http://localhost:19828/api/v1/projects/current/chat");
    assert.deepEqual(JSON.parse(body), {
        message: "question",
        sessionId: "s1",
        mode: "standard",
        topK: 4,
        includeContent: true,
        tools: { wiki: true, web: false, anytxt: true },
        skills: ["reviewer"],
    });
    assert.equal(response.sessionId, "s1");
    assert.equal(response.message.content, "answer");
    assert.equal(response.references[0]?.path, "wiki/a.md");
    assert.equal(response.toolEvents[0]?.tool, "wiki.search");
    assert.equal(response.events[0]?.type, "toolEnd");
    assert.equal(response.usage?.promptChars, 100);
});
test("cancelChat posts to the chat cancellation endpoint", async () => {
    let url = "";
    let method = "";
    const fetchImpl = async (requestUrl, init) => {
        url = String(requestUrl);
        method = String(init?.method ?? "");
        return new Response(JSON.stringify({
            ok: true,
            sessionId: "s1",
            cancelled: true,
        }), { status: 200 });
    };
    const client = new LlmWikiApiClient({ baseUrl: "http://localhost:19828", fetchImpl });
    const response = await client.cancelChat("current", "s1");
    assert.equal(url, "http://localhost:19828/api/v1/projects/current/chat/s1/cancel");
    assert.equal(method, "POST");
    assert.deepEqual(response, { sessionId: "s1", cancelled: true });
});
test("graph parses nodeType from API graph nodes", async () => {
    const fetchImpl = async () => (new Response(JSON.stringify({
        ok: true,
        nodes: [{ id: "n1", label: "Node", nodeType: "concept", path: "wiki/concepts/n1.md", linkCount: 4 }],
        edges: [{ source: "n1", target: "n2", weight: 0.75 }],
    }), { status: 200 }));
    const client = new LlmWikiApiClient({ fetchImpl });
    const graph = await client.graph("current");
    assert.equal(graph.nodes[0]?.type, "concept");
    assert.equal(graph.nodes[0]?.linkCount, 4);
    assert.equal(graph.edges[0]?.weight, 0.75);
});
test("files exposes truncated flag", async () => {
    const fetchImpl = async () => (new Response(JSON.stringify({
        ok: true,
        files: [{ name: "index.md", path: "wiki/index.md", isDir: false }],
        truncated: true,
    }), { status: 200 }));
    const client = new LlmWikiApiClient({ fetchImpl });
    const files = await client.files("current");
    assert.equal(files.truncated, true);
    assert.equal(files.files[0]?.path, "wiki/index.md");
});
test("reviews requests unresolved review items with filters", async () => {
    const calls = [];
    const fetchImpl = async (url) => {
        calls.push(String(url));
        return new Response(JSON.stringify({
            ok: true,
            projectId: "p1",
            status: "unresolved",
            count: 1,
            reviews: [{
                    id: "r1",
                    type: "missing-page",
                    title: "Missing page: Attention",
                    description: "Add the Attention page",
                    options: [],
                    resolved: false,
                    createdAt: 1,
                }],
        }), { status: 200 });
    };
    const client = new LlmWikiApiClient({ baseUrl: "http://localhost:19828", fetchImpl });
    const result = await client.reviews("current", {
        status: "unresolved",
        type: "missing-page",
        limit: 5,
    });
    assert.equal(calls[0], "http://localhost:19828/api/v1/projects/current/reviews?status=unresolved&type=missing-page&limit=5");
    assert.equal(result.status, "unresolved");
    assert.equal(result.count, 1);
    assert.equal(result.reviews[0]?.id, "r1");
    assert.equal(result.reviews[0]?.resolved, false);
});
test("network failures include desktop app hint", async () => {
    const fetchImpl = async () => {
        throw new Error("ECONNREFUSED");
    };
    const client = new LlmWikiApiClient({ fetchImpl });
    await assert.rejects(() => client.projects(), /Is the desktop app running\? ECONNREFUSED/);
});
test("non-JSON responses include status and body preview", async () => {
    const fetchImpl = async () => (new Response("not json", { status: 502, statusText: "Bad Gateway" }));
    const client = new LlmWikiApiClient({ fetchImpl });
    await assert.rejects(() => client.projects(), /non-JSON response \(502\): not json/);
});
test("API errors include status and server message", async () => {
    const fetchImpl = async () => (new Response(JSON.stringify({ ok: false, error: "Unauthorized" }), { status: 401 }));
    const client = new LlmWikiApiClient({ fetchImpl });
    await assert.rejects(() => client.projects(), /LLM Wiki API 401: Unauthorized/);
});
