import { posix as posixPath, win32 as winPath } from "node:path";

import { describe, expect, it } from "vitest";

import {
  pathFlavorForPlatform,
  posixPathFlavor,
  win32PathFlavor,
} from "#src/path/path-flavor";

describe("win32PathFlavor", () => {
  it("exposes the win32 path implementation", () => {
    expect(win32PathFlavor.impl).toBe(winPath);
  });

  it("carries the win32 case/separator match options", () => {
    expect(win32PathFlavor.matchOptions).toEqual({
      caseInsensitive: true,
      windowsSeparators: true,
    });
  });

  it("folds to lower case", () => {
    expect(win32PathFlavor.fold("C:\\Foo\\Bar")).toBe("c:\\foo\\bar");
  });

  it("resolves, normalizes, and folds a comparable value against a base", () => {
    expect(win32PathFlavor.comparable("Foo/Bar", "C:\\base")).toBe(
      "c:\\base\\foo\\bar",
    );
  });

  it("decides containment with win32 (case-folding) geometry", () => {
    expect(win32PathFlavor.isWithin("C:\\base\\sub", "C:\\base")).toBe(true);
    expect(win32PathFlavor.isWithin("C:\\base", "C:\\base")).toBe(true);
    expect(win32PathFlavor.isWithin("C:\\other", "C:\\base")).toBe(false);
  });

  it("folds case for a case-different descendant", () => {
    expect(
      win32PathFlavor.isWithin(
        "c:\\users\\foo\\dir\\sub\\x.md",
        "C:\\Users\\Foo\\dir",
      ),
    ).toBe(true);
  });

  it("folds case when path equals directory in a different case", () => {
    expect(
      win32PathFlavor.isWithin(
        "c:\\users\\foo\\dir\\sub",
        "C:\\USERS\\foo\\DIR",
      ),
    ).toBe(true);
  });

  it("rejects a win32 sibling directory", () => {
    expect(
      win32PathFlavor.isWithin("C:\\Users\\Foo\\other", "C:\\Users\\Foo\\dir"),
    ).toBe(false);
  });

  it("recognizes either separator as a path separator", () => {
    expect(win32PathFlavor.hasPathSeparator("dir/file")).toBe(true);
    expect(win32PathFlavor.hasPathSeparator("dir\\file")).toBe(true);
    expect(win32PathFlavor.hasPathSeparator("plain")).toBe(false);
  });

  it("classifies bash tokens with MSYS semantics", () => {
    expect(win32PathFlavor.bashTokenShape("/dev/null")).toEqual({
      kind: "device",
    });
    expect(win32PathFlavor.bashTokenShape("/c/Users/x")).toEqual({
      kind: "drive-mount",
      windowsPath: "C:\\Users\\x",
    });
    expect(win32PathFlavor.bashTokenShape("/tmp/x")).toEqual({
      kind: "posix-absolute",
    });
    expect(win32PathFlavor.bashTokenShape("relative/x")).toEqual({
      kind: "plain",
    });
  });
});

describe("posixPathFlavor", () => {
  it("exposes the posix path implementation", () => {
    expect(posixPathFlavor.impl).toBe(posixPath);
  });

  it("carries no win32 match options", () => {
    expect(posixPathFlavor.matchOptions).toBeUndefined();
  });

  it("does not fold case", () => {
    expect(posixPathFlavor.fold("/Foo/Bar")).toBe("/Foo/Bar");
  });

  it("resolves and normalizes a comparable value without folding", () => {
    expect(posixPathFlavor.comparable("Foo/Bar", "/base")).toBe(
      "/base/Foo/Bar",
    );
  });

  it("decides containment with posix geometry", () => {
    expect(posixPathFlavor.isWithin("/base/sub", "/base")).toBe(true);
    expect(posixPathFlavor.isWithin("/base", "/base")).toBe(true);
    expect(posixPathFlavor.isWithin("/a/b/c/d/e", "/a/b")).toBe(true);
    expect(posixPathFlavor.isWithin("/other", "/base")).toBe(false);
  });

  it("rejects a sibling directory sharing a name prefix", () => {
    expect(posixPathFlavor.isWithin("/a/bc", "/a/b")).toBe(false);
  });

  it("stays case-sensitive", () => {
    expect(posixPathFlavor.isWithin("/a/B/c", "/a/b")).toBe(false);
  });

  it("returns false for empty operands", () => {
    expect(posixPathFlavor.isWithin("", "/a/b")).toBe(false);
    expect(posixPathFlavor.isWithin("/a/b", "")).toBe(false);
  });

  it("recognizes only the forward slash as a path separator", () => {
    expect(posixPathFlavor.hasPathSeparator("dir/file")).toBe(true);
    expect(posixPathFlavor.hasPathSeparator("dir\\file")).toBe(false);
    expect(posixPathFlavor.hasPathSeparator("plain")).toBe(false);
  });

  it("treats every bash token as an ordinary path", () => {
    expect(posixPathFlavor.bashTokenShape("/dev/null")).toEqual({
      kind: "plain",
    });
    expect(posixPathFlavor.bashTokenShape("/c/Users/x")).toEqual({
      kind: "plain",
    });
    expect(posixPathFlavor.bashTokenShape("/tmp/x")).toEqual({ kind: "plain" });
  });
});

describe("pathFlavorForPlatform", () => {
  it("selects the win32 flavor for win32", () => {
    expect(pathFlavorForPlatform("win32")).toBe(win32PathFlavor);
  });

  it("selects the posix flavor for every other platform", () => {
    expect(pathFlavorForPlatform("linux")).toBe(posixPathFlavor);
    expect(pathFlavorForPlatform("darwin")).toBe(posixPathFlavor);
  });
});
