import { describe, it, expect, vi } from "vitest";
import {
  slashCommands,
  parseSlashCommand,
  getMatchingCommands,
  isSlashCommand,
  type SlashCommand,
} from "./slashCommands";

// Mock all external dependencies
vi.mock("svelte/store", () => ({
  get: vi.fn(),
}));

vi.mock("@tauri-apps/api/core", () => ({
  invoke: vi.fn(),
}));

vi.mock("$lib/stores/claude", () => ({
  claudeStore: {
    addLine: vi.fn(),
    clearTerminal: vi.fn(),
    activeConversationId: { subscribe: vi.fn() },
    currentWorkingDirectory: { subscribe: vi.fn() },
    setWorkingDirectory: vi.fn(),
    getConversationHistory: vi.fn(),
  },
}));

vi.mock("$lib/stores/character", () => ({
  characterState: {
    setState: vi.fn(),
    setTemporaryState: vi.fn(),
  },
}));

vi.mock("$lib/tauri", () => ({
  setSkipNextGreeting: vi.fn(),
}));

vi.mock("$lib/stores/search", () => ({
  searchState: {
    setQuery: vi.fn(),
    clear: vi.fn(),
  },
}));

describe("slashCommands", () => {
  describe("slashCommands array", () => {
    it("contains expected commands", () => {
      const commandNames = slashCommands.map((cmd) => cmd.name);
      expect(commandNames).toContain("cd");
      expect(commandNames).toContain("clear");
      expect(commandNames).toContain("new");
      expect(commandNames).toContain("help");
      expect(commandNames).toContain("search");
      expect(commandNames).toContain("summarise");
      expect(commandNames).toContain("skill");
    });

    it("has 7 commands total", () => {
      expect(slashCommands.length).toBe(7);
    });

    it("each command has required properties", () => {
      slashCommands.forEach((cmd) => {
        expect(cmd.name).toBeDefined();
        expect(typeof cmd.name).toBe("string");
        expect(cmd.name.length).toBeGreaterThan(0);

        expect(cmd.description).toBeDefined();
        expect(typeof cmd.description).toBe("string");
        expect(cmd.description.length).toBeGreaterThan(0);

        expect(cmd.usage).toBeDefined();
        expect(typeof cmd.usage).toBe("string");
        expect(cmd.usage.startsWith("/")).toBe(true);

        expect(cmd.execute).toBeDefined();
        expect(typeof cmd.execute).toBe("function");
      });
    });

    it("cd command has correct metadata", () => {
      const cdCmd = slashCommands.find((cmd) => cmd.name === "cd");
      expect(cdCmd).toBeDefined();
      expect(cdCmd!.description).toBe("Change the working directory");
      expect(cdCmd!.usage).toBe("/cd <path>");
    });

    it("clear command has correct metadata", () => {
      const clearCmd = slashCommands.find((cmd) => cmd.name === "clear");
      expect(clearCmd).toBeDefined();
      expect(clearCmd!.description).toBe("Clear the terminal display (keeps conversation context)");
      expect(clearCmd!.usage).toBe("/clear");
    });

    it("new command has correct metadata", () => {
      const newCmd = slashCommands.find((cmd) => cmd.name === "new");
      expect(newCmd).toBeDefined();
      expect(newCmd!.description).toBe("Start a fresh conversation (resets context)");
      expect(newCmd!.usage).toBe("/new");
    });

    it("help command has correct metadata", () => {
      const helpCmd = slashCommands.find((cmd) => cmd.name === "help");
      expect(helpCmd).toBeDefined();
      expect(helpCmd!.description).toBe("Show available slash commands");
      expect(helpCmd!.usage).toBe("/help");
    });

    it("search command has correct metadata", () => {
      const searchCmd = slashCommands.find((cmd) => cmd.name === "search");
      expect(searchCmd).toBeDefined();
      expect(searchCmd!.description).toBe("Search within the conversation (use /search to clear)");
      expect(searchCmd!.usage).toBe("/search [query]");
    });

    it("summarise command has correct metadata", () => {
      const summariseCmd = slashCommands.find((cmd) => cmd.name === "summarise");
      expect(summariseCmd).toBeDefined();
      expect(summariseCmd!.description).toBe("Get a summary of the entire conversation");
      expect(summariseCmd!.usage).toBe("/summarise");
    });

    it("skill command has correct metadata", () => {
      const skillCmd = slashCommands.find((cmd) => cmd.name === "skill");
      expect(skillCmd).toBeDefined();
      expect(skillCmd!.description).toBe("Invoke a Claude Code skill from ~/.claude/skills/");
      expect(skillCmd!.usage).toBe("/skill [name] [data]");
    });
  });

  describe("parseSlashCommand", () => {
    it("returns null for non-slash input", () => {
      const result = parseSlashCommand("hello world");
      expect(result.command).toBeNull();
      expect(result.args).toBe("");
    });

    it("returns null for empty string", () => {
      const result = parseSlashCommand("");
      expect(result.command).toBeNull();
      expect(result.args).toBe("");
    });

    it("returns null for whitespace only", () => {
      const result = parseSlashCommand("   ");
      expect(result.command).toBeNull();
      expect(result.args).toBe("");
    });

    it("parses /cd command without args", () => {
      const result = parseSlashCommand("/cd");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("cd");
      expect(result.args).toBe("");
    });

    it("parses /cd command with path argument", () => {
      const result = parseSlashCommand("/cd /home/naomi/code");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("cd");
      expect(result.args).toBe("/home/naomi/code");
    });

    it("parses /clear command", () => {
      const result = parseSlashCommand("/clear");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("clear");
      expect(result.args).toBe("");
    });

    it("parses /new command", () => {
      const result = parseSlashCommand("/new");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("new");
      expect(result.args).toBe("");
    });

    it("parses /help command", () => {
      const result = parseSlashCommand("/help");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("help");
      expect(result.args).toBe("");
    });

    it("parses /search command with query", () => {
      const result = parseSlashCommand("/search hello world");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("search");
      expect(result.args).toBe("hello world");
    });

    it("parses /search command without query", () => {
      const result = parseSlashCommand("/search");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("search");
      expect(result.args).toBe("");
    });

    it("parses /summarise command", () => {
      const result = parseSlashCommand("/summarise");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("summarise");
      expect(result.args).toBe("");
    });

    it("parses /skill command with name and data", () => {
      const result = parseSlashCommand("/skill onboard-mentee john@example.com");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("skill");
      expect(result.args).toBe("onboard-mentee john@example.com");
    });

    it("parses /skill command with name only", () => {
      const result = parseSlashCommand("/skill onboard-mentee");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("skill");
      expect(result.args).toBe("onboard-mentee");
    });

    it("parses /skill command without arguments", () => {
      const result = parseSlashCommand("/skill");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("skill");
      expect(result.args).toBe("");
    });

    it("returns null for unknown command", () => {
      const result = parseSlashCommand("/unknown");
      expect(result.command).toBeNull();
      expect(result.args).toBe("");
    });

    it("is case insensitive for command names", () => {
      const result1 = parseSlashCommand("/CD /path");
      expect(result1.command).not.toBeNull();
      expect(result1.command!.name).toBe("cd");

      const result2 = parseSlashCommand("/CLEAR");
      expect(result2.command).not.toBeNull();
      expect(result2.command!.name).toBe("clear");

      const result3 = parseSlashCommand("/Help");
      expect(result3.command).not.toBeNull();
      expect(result3.command!.name).toBe("help");
    });

    it("handles leading whitespace", () => {
      const result = parseSlashCommand("  /cd /path");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("cd");
      expect(result.args).toBe("/path");
    });

    it("handles trailing whitespace", () => {
      const result = parseSlashCommand("/cd /path  ");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("cd");
      expect(result.args).toBe("/path");
    });

    it("handles multiple spaces between args", () => {
      const result = parseSlashCommand("/search   hello   world");
      expect(result.command).not.toBeNull();
      expect(result.command!.name).toBe("search");
      expect(result.args).toBe("hello world");
    });
  });

  describe("getMatchingCommands", () => {
    it("returns empty array for non-slash input", () => {
      const result = getMatchingCommands("hello");
      expect(result).toEqual([]);
    });

    it("returns empty array for empty string", () => {
      const result = getMatchingCommands("");
      expect(result).toEqual([]);
    });

    it("returns all commands for just slash", () => {
      const result = getMatchingCommands("/");
      expect(result.length).toBe(slashCommands.length);
    });

    it("returns matching commands for partial input", () => {
      const result = getMatchingCommands("/c");
      const names = result.map((cmd) => cmd.name);
      expect(names).toContain("cd");
      expect(names).toContain("clear");
      expect(names).not.toContain("help");
    });

    it("returns single command for exact match", () => {
      const result = getMatchingCommands("/cd");
      expect(result.length).toBe(1);
      expect(result[0].name).toBe("cd");
    });

    it("returns single command for partial unique match", () => {
      const result = getMatchingCommands("/cl");
      expect(result.length).toBe(1);
      expect(result[0].name).toBe("clear");
    });

    it("returns matching commands for /s prefix", () => {
      const result = getMatchingCommands("/s");
      const names = result.map((cmd) => cmd.name);
      expect(names).toContain("search");
      expect(names).toContain("summarise");
      expect(names).toContain("skill");
    });

    it("is case insensitive", () => {
      const result1 = getMatchingCommands("/C");
      const result2 = getMatchingCommands("/c");
      expect(result1.length).toBe(result2.length);
    });

    it("returns empty array for no matches", () => {
      const result = getMatchingCommands("/xyz");
      expect(result).toEqual([]);
    });

    it("handles whitespace correctly", () => {
      const result = getMatchingCommands("  /c");
      const names = result.map((cmd) => cmd.name);
      expect(names).toContain("cd");
      expect(names).toContain("clear");
    });

    it("returns command for full command name", () => {
      const result = getMatchingCommands("/help");
      expect(result.length).toBe(1);
      expect(result[0].name).toBe("help");
    });

    it("returns command for /new", () => {
      const result = getMatchingCommands("/n");
      expect(result.length).toBe(1);
      expect(result[0].name).toBe("new");
    });
  });

  describe("isSlashCommand", () => {
    it("returns true for input starting with slash", () => {
      expect(isSlashCommand("/cd")).toBe(true);
      expect(isSlashCommand("/")).toBe(true);
      expect(isSlashCommand("/help")).toBe(true);
      expect(isSlashCommand("/unknown")).toBe(true);
    });

    it("returns false for non-slash input", () => {
      expect(isSlashCommand("hello")).toBe(false);
      expect(isSlashCommand("")).toBe(false);
      expect(isSlashCommand("cd")).toBe(false);
    });

    it("handles whitespace correctly", () => {
      expect(isSlashCommand("  /cd")).toBe(true);
      expect(isSlashCommand("  hello")).toBe(false);
    });

    it("returns false for slash in middle of string", () => {
      expect(isSlashCommand("hello/world")).toBe(false);
    });
  });

  describe("SlashCommand interface", () => {
    it("can create a valid slash command object", () => {
      const testCommand: SlashCommand = {
        name: "test",
        description: "A test command",
        usage: "/test [arg]",
        execute: vi.fn(),
      };

      expect(testCommand.name).toBe("test");
      expect(testCommand.description).toBe("A test command");
      expect(testCommand.usage).toBe("/test [arg]");
      expect(typeof testCommand.execute).toBe("function");
    });

    it("execute can be async function", () => {
      const asyncCommand: SlashCommand = {
        name: "async",
        description: "An async command",
        usage: "/async",
        execute: async () => {
          await Promise.resolve();
        },
      };

      expect(asyncCommand.execute("")).toBeInstanceOf(Promise);
    });

    it("execute can be sync function", () => {
      const syncCommand: SlashCommand = {
        name: "sync",
        description: "A sync command",
        usage: "/sync",
        execute: () => {
          // Synchronous execution
        },
      };

      const result = syncCommand.execute("");
      // Sync function returns undefined, not a Promise
      expect(result).toBeUndefined();
    });
  });
});
