import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";

import {
  buildResumeContentTemplate,
  buildToolPath,
  filterToolPathChildren,
  buildInvokeToolCode,
  buildListSourcesCode,
  buildSearchToolsCode,
  extractPausedInteraction,
  extractExecutionId,
  extractExecutionResult,
  inspectToolPath,
  normalizeCliErrorText,
  parseJsonObjectInput,
} from "../apps/cli/src/tooling";

describe("CLI tooling helpers", () => {
  it.effect("parses empty input as an empty args object", () =>
    Effect.gen(function* () {
      const args = yield* parseJsonObjectInput(undefined);
      expect(args).toEqual({});
    }),
  );

  it.effect("parses JSON object input", () =>
    Effect.gen(function* () {
      const args = yield* parseJsonObjectInput('{"calendarId":"primary"}');
      expect(args).toEqual({ calendarId: "primary" });
    }),
  );

  it.effect("rejects non-object JSON input", () =>
    Effect.gen(function* () {
      const error = yield* parseJsonObjectInput("[1,2,3]").pipe(Effect.flip);
      // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: helper contract returns a native Error for CLI input parsing
      expect(error.message).toContain("must decode to a JSON object");
    }),
  );

  it("builds bracket-safe invocation code for dynamic tool paths", () => {
    const code = buildInvokeToolCode("google-drive.files.list", { pageSize: 10 });
    expect(code).toContain('const __target = tools["google-drive"]["files"]["list"]');
    expect(code).toContain("const __args = {");
  });

  it("builds tool paths from dot or segmented forms", () => {
    expect(buildToolPath(["github", "issues", "create"])).toBe("github.issues.create");
    expect(buildToolPath(["github.issues", "create"])).toBe("github.issues.create");
  });

  it("rejects invalid tool-path segments", () => {
    expect(() => buildToolPath(["github", "issues", "create now"])).toThrow();
  });

  it("builds search and sources code snippets", () => {
    const searchCode = buildSearchToolsCode({
      query: "google calendar events",
      namespace: "google",
      limit: 5,
    });
    const sourcesCode = buildListSourcesCode({ query: "google", limit: 20 });

    expect(searchCode).toBe(
      'return await tools.search({"query":"google calendar events","limit":5,"namespace":"google"});',
    );
    expect(sourcesCode).toBe(
      'return await tools.executor.sources.list({"limit":20,"query":"google"});',
    );
  });

  it("extracts completed result payload and pause execution id", () => {
    expect(extractExecutionResult({ status: "completed", result: { ok: true }, logs: [] })).toEqual(
      {
        ok: true,
      },
    );
    expect(extractExecutionResult({ status: "completed" })).toBeNull();

    expect(extractExecutionId({ executionId: "exec_123" })).toBe("exec_123");
    expect(extractExecutionId({ executionId: 123 })).toBeUndefined();
  });

  it("inspects hierarchical tool path prefixes for call help", () => {
    const view = inspectToolPath({
      toolPaths: [
        "cloudflare.dns.records.list",
        "cloudflare.dns.records.create",
        "cloudflare.dns.analytics",
        "cloudflare.zones.list",
      ],
      rawPrefixParts: ["cloudflare", "dns"],
    });

    expect(view.prefixSegments).toEqual(["cloudflare", "dns"]);
    expect(view.exactPath).toBeUndefined();
    expect(view.matchingToolCount).toBe(3);
    expect(view.children).toEqual([
      { segment: "analytics", invokable: true, hasChildren: false, toolCount: 1 },
      { segment: "records", invokable: false, hasChildren: true, toolCount: 2 },
    ]);
  });

  it("reports exact matches for leaf tool paths", () => {
    const view = inspectToolPath({
      toolPaths: ["github.issues.create", "github.issues.list"],
      rawPrefixParts: ["github", "issues", "create"],
    });

    expect(view.prefixSegments).toEqual(["github", "issues", "create"]);
    expect(view.exactPath).toBe("github.issues.create");
    expect(view.matchingToolCount).toBe(1);
    expect(view.children).toEqual([]);
  });

  it("extracts paused form interaction payload", () => {
    const interaction = extractPausedInteraction({
      status: "waiting_for_interaction",
      executionId: "exec_1",
      interaction: {
        kind: "form",
        message: "Need approval",
        requestedSchema: {
          type: "object",
          properties: {
            approved: { type: "boolean" },
          },
          required: ["approved"],
        },
      },
    });

    expect(interaction).toEqual({
      kind: "form",
      message: "Need approval",
      requestedSchema: {
        type: "object",
        properties: {
          approved: { type: "boolean" },
        },
        required: ["approved"],
      },
    });
  });

  it("builds resume content template from requested schema", () => {
    const template = buildResumeContentTemplate({
      type: "object",
      properties: {
        approved: { type: "boolean" },
        note: { type: "string" },
      },
      required: ["approved"],
    });

    expect(template).toEqual({ approved: false });
  });

  it("filters child segments with singular/plural matching", () => {
    const children = [
      { segment: "zoneRulesets", invokable: false, hasChildren: true, toolCount: 10 },
      { segment: "dnsRecordsForAZone", invokable: false, hasChildren: true, toolCount: 14 },
      { segment: "workersAi", invokable: false, hasChildren: true, toolCount: 8 },
    ] as const;

    expect(filterToolPathChildren(children, "zones").map((entry) => entry.segment)).toEqual([
      "zoneRulesets",
      "dnsRecordsForAZone",
    ]);
    expect(filterToolPathChildren(children, "worker").map((entry) => entry.segment)).toEqual([
      "workersAi",
    ]);
  });

  it("normalizes stack-heavy CLI error text", () => {
    const normalized = normalizeCliErrorText(`Error: Error: TypeError: bad
      at fn1 (/tmp/a.ts:1:1)
      at fn2 (/tmp/b.ts:2:2)
From previous event:
      at fn3 (/tmp/c.ts:3:3)`);

    expect(normalized).toBe("TypeError: bad");
  });
});
