import { beforeEach, describe, expect, it } from "vitest";
import { useAppStore } from "./useAppStore";
import {
  getRevealAncestorDirs,
  snapshotRemovedFilePath,
} from "./slices/fileTreeSlice";

const WS = "workspace-a";

function reset() {
  useAppStore.setState({
    fileTabsByWorkspace: {},
    activeFileTabByWorkspace: {},
    fileRevealTargetByWorkspace: {},
    fileBuffers: {},
    allFilesExpandedDirsByWorkspace: {},
    allFilesSelectedPathByWorkspace: {},
    tabOrderByWorkspace: {},
    filePathUndoStackByWorkspace: {},
    revealActiveFileInTree: true,
  });
}

function openLoadedFile(path: string, content = "saved") {
  useAppStore.getState().openFileTab(WS, path);
  useAppStore.getState().setFileBufferLoaded(WS, path, {
    baseline: content,
    isBinary: false,
    isSymlink: false,
    sizeBytes: content.length,
    truncated: false,
    imageBytesB64: null,
  });
}

describe("file path store updates", () => {
  beforeEach(reset);

  it("computes ancestor directories for active-file tree reveal", () => {
    expect(getRevealAncestorDirs("src/foo/bar.rs")).toEqual([
      "src/",
      "src/foo/",
    ]);
    expect(getRevealAncestorDirs("README.md")).toEqual([]);
    expect(getRevealAncestorDirs("~/.claude/plans/plan.md")).toEqual([]);
    expect(getRevealAncestorDirs("/tmp/memory.md")).toEqual([]);
  });

  it("reveals ancestor directories when opening a nested file tab", () => {
    useAppStore.getState().openFileTab(WS, "src/foo/bar.rs");

    expect(useAppStore.getState().allFilesExpandedDirsByWorkspace[WS]).toEqual({
      "src/": true,
      "src/foo/": true,
    });
  });

  it("reveals ancestor directories when selecting an already-open file tab", () => {
    useAppStore.getState().openFileTab(WS, "src/one.ts");
    useAppStore.getState().openFileTab(WS, "tests/fixtures/two.ts");
    useAppStore.setState({
      allFilesExpandedDirsByWorkspace: { [WS]: {} },
      activeFileTabByWorkspace: { [WS]: "src/one.ts" },
    });

    useAppStore.getState().selectFileTab(WS, "tests/fixtures/two.ts");

    expect(useAppStore.getState().allFilesExpandedDirsByWorkspace[WS]).toEqual({
      "tests/": true,
      "tests/fixtures/": true,
    });
  });

  it("does not reveal tree directories when the editor setting is disabled", () => {
    useAppStore.setState({ revealActiveFileInTree: false });

    useAppStore.getState().openFileTab(WS, "src/foo/bar.rs");

    expect(useAppStore.getState().allFilesExpandedDirsByWorkspace[WS]).toBeUndefined();
  });

  it("does not reveal tree directories for non-worktree file paths", () => {
    useAppStore.getState().openFileTab(WS, "~/.claude/plans/plan.md");
    useAppStore.getState().openFileTab(WS, "/tmp/claudette-memory.md");

    expect(useAppStore.getState().allFilesExpandedDirsByWorkspace[WS]).toBeUndefined();
  });

  it("renames an open file tab and preserves its buffer", () => {
    openLoadedFile("src/app.ts");
    useAppStore.getState().setFileBufferContent(WS, "src/app.ts", "dirty");
    useAppStore.setState({
      tabOrderByWorkspace: { [WS]: [{ kind: "file", path: "src/app.ts" }] },
    });

    useAppStore
      .getState()
      .renameFilePathInWorkspace(WS, "src/app.ts", "src/main.ts", false);

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual(["src/main.ts"]);
    expect(state.activeFileTabByWorkspace[WS]).toBe("src/main.ts");
    expect(state.fileBuffers[`${WS}:src/app.ts`]).toBeUndefined();
    expect(state.fileBuffers[`${WS}:src/main.ts`].buffer).toBe("dirty");
    expect(state.tabOrderByWorkspace[WS]).toEqual([
      { kind: "file", path: "src/main.ts" },
    ]);
  });

  it("stores a one-shot reveal target when opening a file tab with a line range", () => {
    useAppStore.getState().openFileTab(WS, "README.md", {
      startLine: 4,
      endLine: 8,
      startColumn: 2,
      endColumn: 5,
    });

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual(["README.md"]);
    expect(state.activeFileTabByWorkspace[WS]).toBe("README.md");
    expect(state.fileRevealTargetByWorkspace[WS]).toEqual({
      path: "README.md",
      startLine: 4,
      endLine: 8,
      startColumn: 2,
      endColumn: 5,
      nonce: 1,
    });
  });

  it("clears a reveal target only when the consumed nonce still matches", () => {
    useAppStore.getState().openFileTab(WS, "README.md", {
      startLine: 4,
      endLine: 4,
    });
    useAppStore.getState().clearFileRevealTarget(WS, 0);
    expect(useAppStore.getState().fileRevealTargetByWorkspace[WS]).toEqual({
      path: "README.md",
      startLine: 4,
      endLine: 4,
      nonce: 1,
    });

    useAppStore.getState().clearFileRevealTarget(WS, 1);
    expect(useAppStore.getState().fileRevealTargetByWorkspace[WS]).toBeNull();
  });

  it("renames child file tabs when a folder is renamed", () => {
    openLoadedFile("src/components/Button.tsx");
    openLoadedFile("src/components/Card.tsx");
    openLoadedFile("src/App.tsx");
    useAppStore.setState({
      allFilesExpandedDirsByWorkspace: { [WS]: { "src/components/": true } },
      allFilesSelectedPathByWorkspace: { [WS]: "src/components/" },
    });

    useAppStore
      .getState()
      .renameFilePathInWorkspace(WS, "src/components", "src/widgets", true);

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual([
      "src/widgets/Button.tsx",
      "src/widgets/Card.tsx",
      "src/App.tsx",
    ]);
    expect(state.fileBuffers[`${WS}:src/widgets/Button.tsx`]).toBeDefined();
    expect(state.fileBuffers[`${WS}:src/components/Button.tsx`]).toBeUndefined();
    expect(state.allFilesExpandedDirsByWorkspace[WS]).toEqual({
      "src/widgets/": true,
    });
    expect(state.allFilesSelectedPathByWorkspace[WS]).toBe("src/widgets/");
  });

  it("removes an open file tab and picks an adjacent active tab", () => {
    openLoadedFile("a.ts");
    openLoadedFile("b.ts");
    openLoadedFile("c.ts");
    useAppStore.getState().selectFileTab(WS, "b.ts");

    useAppStore.getState().removeFilePathFromWorkspace(WS, "b.ts", false);

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual(["a.ts", "c.ts"]);
    expect(state.activeFileTabByWorkspace[WS]).toBe("a.ts");
    expect(state.fileBuffers[`${WS}:b.ts`]).toBeUndefined();
  });

  it("closes an active file tab and selects the tab to its left", () => {
    openLoadedFile("a.ts");
    openLoadedFile("b.ts");
    openLoadedFile("c.ts");
    useAppStore.getState().selectFileTab(WS, "c.ts");

    useAppStore.getState().closeFileTab(WS, "c.ts");

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual(["a.ts", "b.ts"]);
    expect(state.activeFileTabByWorkspace[WS]).toBe("b.ts");
    expect(state.fileBuffers[`${WS}:c.ts`]).toBeUndefined();
  });

  it("clears closed file metadata so the same file can reopen cleanly", () => {
    useAppStore.setState({
      tabOrderByWorkspace: {
        [WS]: [
          { kind: "session", sessionId: "session-1" },
          { kind: "file", path: "README.md" },
        ],
      },
    });
    useAppStore.getState().openFileTab(WS, "README.md", {
      startLine: 8,
      endLine: 8,
    });
    useAppStore.getState().setFileBufferLoaded(WS, "README.md", {
      baseline: "hello",
      isBinary: false,
      isSymlink: false,
      sizeBytes: 5,
      truncated: false,
      imageBytesB64: null,
    });

    useAppStore.getState().closeFileTab(WS, "README.md");

    expect(useAppStore.getState().fileTabsByWorkspace[WS]).toEqual([]);
    expect(useAppStore.getState().activeFileTabByWorkspace[WS]).toBeNull();
    expect(useAppStore.getState().fileBuffers[`${WS}:README.md`]).toBeUndefined();
    expect(useAppStore.getState().fileRevealTargetByWorkspace[WS]).toBeNull();
    expect(useAppStore.getState().tabOrderByWorkspace[WS]).toEqual([
      { kind: "session", sessionId: "session-1" },
    ]);

    useAppStore.getState().openFileTab(WS, "README.md", {
      startLine: 8,
      endLine: 8,
    });

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual(["README.md"]);
    expect(state.activeFileTabByWorkspace[WS]).toBe("README.md");
    expect(state.fileBuffers[`${WS}:README.md`]).toMatchObject({
      loaded: false,
      buffer: "",
      baseline: "",
    });
    expect(state.fileRevealTargetByWorkspace[WS]).toMatchObject({
      path: "README.md",
      startLine: 8,
      endLine: 8,
    });
  });

  it("removes all child file tabs when a folder is deleted", () => {
    openLoadedFile("src/components/Button.tsx");
    openLoadedFile("src/components/Card.tsx");
    openLoadedFile("src/App.tsx");
    useAppStore.setState({
      tabOrderByWorkspace: {
        [WS]: [
          { kind: "file", path: "src/components/Button.tsx" },
          { kind: "file", path: "src/App.tsx" },
        ],
      },
    });

    useAppStore.getState().removeFilePathFromWorkspace(WS, "src/components", true);

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual(["src/App.tsx"]);
    expect(state.fileBuffers[`${WS}:src/components/Button.tsx`]).toBeUndefined();
    expect(state.fileBuffers[`${WS}:src/App.tsx`]).toBeDefined();
    expect(state.tabOrderByWorkspace[WS]).toEqual([
      { kind: "file", path: "src/App.tsx" },
    ]);
  });

  it("restores removed file tabs and buffers from a delete snapshot", () => {
    openLoadedFile("src/components/Button.tsx", "button");
    openLoadedFile("src/components/Card.tsx", "card");
    openLoadedFile("src/App.tsx", "app");
    useAppStore.getState().setFileBufferContent(
      WS,
      "src/components/Button.tsx",
      "dirty button",
    );
    useAppStore.setState({
      allFilesExpandedDirsByWorkspace: { [WS]: { "src/components/": true } },
      allFilesSelectedPathByWorkspace: { [WS]: "src/components/Button.tsx" },
      tabOrderByWorkspace: {
        [WS]: [
          { kind: "file", path: "src/components/Button.tsx" },
          { kind: "file", path: "src/App.tsx" },
        ],
      },
    });
    const snapshot = snapshotRemovedFilePath(
      useAppStore.getState(),
      WS,
      "src/components",
      true,
    );

    useAppStore.getState().removeFilePathFromWorkspace(WS, "src/components", true);
    useAppStore.getState().restoreRemovedFilePathInWorkspace(WS, snapshot);

    const state = useAppStore.getState();
    expect(state.fileTabsByWorkspace[WS]).toEqual([
      "src/App.tsx",
      "src/components/Button.tsx",
      "src/components/Card.tsx",
    ]);
    expect(state.activeFileTabByWorkspace[WS]).toBe("src/App.tsx");
    expect(state.fileBuffers[`${WS}:src/components/Button.tsx`].buffer).toBe(
      "dirty button",
    );
    expect(state.allFilesSelectedPathByWorkspace[WS]).toBe(
      "src/components/Button.tsx",
    );
    expect(state.allFilesExpandedDirsByWorkspace[WS]).toEqual({
      "src/components/": true,
    });
    expect(state.tabOrderByWorkspace[WS]).toEqual([
      { kind: "file", path: "src/App.tsx" },
      { kind: "file", path: "src/components/Button.tsx" },
    ]);
  });

  it("pushes and pops file operation undo entries", () => {
    const first = {
      kind: "rename",
      oldPath: "a.ts",
      newPath: "b.ts",
      isDirectory: false,
    } as const;

    useAppStore.getState().pushFilePathUndoOperation(WS, first);

    expect(useAppStore.getState().filePathUndoStackByWorkspace[WS]).toHaveLength(1);
    useAppStore.getState().popFilePathUndoOperation(WS);
    expect(useAppStore.getState().filePathUndoStackByWorkspace[WS]).toEqual([]);
  });

  it("only pops a matched undo entry when one is provided", () => {
    const first = {
      kind: "rename",
      oldPath: "a.ts",
      newPath: "b.ts",
      isDirectory: false,
    } as const;
    const second = {
      kind: "rename",
      oldPath: "c.ts",
      newPath: "d.ts",
      isDirectory: false,
    } as const;

    useAppStore.getState().pushFilePathUndoOperation(WS, first);
    useAppStore.getState().pushFilePathUndoOperation(WS, second);
    useAppStore.getState().popFilePathUndoOperation(WS, first);
    expect(useAppStore.getState().filePathUndoStackByWorkspace[WS]).toEqual([
      first,
      second,
    ]);

    useAppStore.getState().popFilePathUndoOperation(WS, second);
    expect(useAppStore.getState().filePathUndoStackByWorkspace[WS]).toEqual([
      first,
    ]);
  });
});

describe("requestCloseActiveFileTab", () => {
  beforeEach(() => {
    useAppStore.setState({ requestCloseFileTabNonceByWorkspace: {} });
  });

  it("bumps a per-workspace nonce so FileViewer can detect the request", () => {
    expect(
      useAppStore.getState().requestCloseFileTabNonceByWorkspace[WS],
    ).toBeUndefined();

    useAppStore.getState().requestCloseActiveFileTab(WS);
    expect(
      useAppStore.getState().requestCloseFileTabNonceByWorkspace[WS],
    ).toBe(1);

    useAppStore.getState().requestCloseActiveFileTab(WS);
    expect(
      useAppStore.getState().requestCloseFileTabNonceByWorkspace[WS],
    ).toBe(2);
  });

  it("scopes the nonce per workspace", () => {
    useAppStore.getState().requestCloseActiveFileTab("workspace-a");
    useAppStore.getState().requestCloseActiveFileTab("workspace-a");
    useAppStore.getState().requestCloseActiveFileTab("workspace-b");

    const state = useAppStore.getState();
    expect(state.requestCloseFileTabNonceByWorkspace["workspace-a"]).toBe(2);
    expect(state.requestCloseFileTabNonceByWorkspace["workspace-b"]).toBe(1);
  });
});

describe("requestNewFileAtRoot", () => {
  beforeEach(() => {
    useAppStore.setState({ requestNewFileNonceByWorkspace: {} });
  });

  it("bumps a per-workspace nonce so FilesPanel can detect the request", () => {
    expect(
      useAppStore.getState().requestNewFileNonceByWorkspace[WS],
    ).toBeUndefined();

    useAppStore.getState().requestNewFileAtRoot(WS);
    const first = useAppStore.getState().requestNewFileNonceByWorkspace[WS];
    expect(first).toBe(1);

    useAppStore.getState().requestNewFileAtRoot(WS);
    const second = useAppStore.getState().requestNewFileNonceByWorkspace[WS];
    expect(second).toBe(2);
  });

  it("scopes the nonce per workspace so unrelated workspaces are unaffected", () => {
    useAppStore.getState().requestNewFileAtRoot("workspace-a");
    useAppStore.getState().requestNewFileAtRoot("workspace-a");
    useAppStore.getState().requestNewFileAtRoot("workspace-b");

    const state = useAppStore.getState();
    expect(state.requestNewFileNonceByWorkspace["workspace-a"]).toBe(2);
    expect(state.requestNewFileNonceByWorkspace["workspace-b"]).toBe(1);
  });
});
