/**
 * HighlightedText Component Tests
 *
 * Tests the text-splitting logic used by the HighlightedText component,
 * which highlights search query matches within a string.
 *
 * What this component does:
 * - Splits text into an array of {text, isMatch} parts
 * - Matches are case-insensitive
 * - Special regex characters in the query are escaped
 * - Non-matching text is preserved verbatim around matches
 *
 * Manual testing checklist:
 * - [ ] Matching text is highlighted (yellow background) in the terminal
 * - [ ] Highlighting is case-insensitive
 * - [ ] Multiple matches on the same line all get highlighted
 * - [ ] Non-matching text renders normally alongside matches
 */

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

interface TextPart {
  text: string;
  isMatch: boolean;
}

function getHighlightedParts(text: string, query: string): TextPart[] {
  if (!query) {
    return [{ text, isMatch: false }];
  }

  const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  const regex = new RegExp(`(${escapedQuery})`, "gi");
  const parts: TextPart[] = [];
  let lastIndex = 0;
  let match: RegExpExecArray | null;

  while ((match = regex.exec(text)) !== null) {
    if (match.index > lastIndex) {
      parts.push({
        text: text.slice(lastIndex, match.index),
        isMatch: false,
      });
    }

    parts.push({
      text: match[1],
      isMatch: true,
    });

    lastIndex = regex.lastIndex;
  }

  if (lastIndex < text.length) {
    parts.push({
      text: text.slice(lastIndex),
      isMatch: false,
    });
  }

  return parts;
}

// ---

describe("getHighlightedParts", () => {
  describe("empty query", () => {
    it("returns the whole text as a single non-match when query is empty string", () => {
      const result = getHighlightedParts("hello world", "");
      expect(result).toEqual([{ text: "hello world", isMatch: false }]);
    });

    it("returns an empty non-match part when both text and query are empty", () => {
      const result = getHighlightedParts("", "");
      expect(result).toEqual([{ text: "", isMatch: false }]);
    });
  });

  describe("no match", () => {
    it("returns the whole text as a single non-match when query is not found", () => {
      const result = getHighlightedParts("hello world", "xyz");
      expect(result).toEqual([{ text: "hello world", isMatch: false }]);
    });
  });

  describe("single match", () => {
    it("returns three parts for a match in the middle", () => {
      const result = getHighlightedParts("hello world foo", "world");
      expect(result).toEqual([
        { text: "hello ", isMatch: false },
        { text: "world", isMatch: true },
        { text: " foo", isMatch: false },
      ]);
    });

    it("returns two parts for a match at the start", () => {
      const result = getHighlightedParts("hello world", "hello");
      expect(result).toEqual([
        { text: "hello", isMatch: true },
        { text: " world", isMatch: false },
      ]);
    });

    it("returns two parts for a match at the end", () => {
      const result = getHighlightedParts("hello world", "world");
      expect(result).toEqual([
        { text: "hello ", isMatch: false },
        { text: "world", isMatch: true },
      ]);
    });

    it("returns a single match part when the entire text matches", () => {
      const result = getHighlightedParts("hello", "hello");
      expect(result).toEqual([{ text: "hello", isMatch: true }]);
    });
  });

  describe("multiple matches", () => {
    it("returns interleaved match and non-match parts for multiple occurrences", () => {
      const result = getHighlightedParts("foo bar foo", "foo");
      expect(result).toEqual([
        { text: "foo", isMatch: true },
        { text: " bar ", isMatch: false },
        { text: "foo", isMatch: true },
      ]);
    });

    it("handles adjacent matches correctly", () => {
      const result = getHighlightedParts("aaa", "a");
      expect(result).toEqual([
        { text: "a", isMatch: true },
        { text: "a", isMatch: true },
        { text: "a", isMatch: true },
      ]);
    });
  });

  describe("case-insensitive matching", () => {
    it("matches uppercase query against lowercase text", () => {
      const result = getHighlightedParts("hello world", "WORLD");
      expect(result).toEqual([
        { text: "hello ", isMatch: false },
        { text: "world", isMatch: true },
      ]);
    });

    it("matches lowercase query against uppercase text", () => {
      const result = getHighlightedParts("HELLO WORLD", "hello");
      expect(result).toEqual([
        { text: "HELLO", isMatch: true },
        { text: " WORLD", isMatch: false },
      ]);
    });

    it("preserves the original casing of the matched text", () => {
      const result = getHighlightedParts("Hello World", "hello");
      const matchPart = result.find((p) => p.isMatch);
      expect(matchPart?.text).toBe("Hello");
    });
  });

  describe("special regex character escaping", () => {
    it("treats a dot in the query as a literal dot, not a wildcard", () => {
      const result = getHighlightedParts("v1.2.3 v123", "1.2");
      const matchParts = result.filter((p) => p.isMatch);
      expect(matchParts).toHaveLength(1);
      expect(matchParts[0].text).toBe("1.2");
    });

    it("handles a query with parentheses", () => {
      const result = getHighlightedParts("fn(args)", "(args)");
      expect(result).toEqual([
        { text: "fn", isMatch: false },
        { text: "(args)", isMatch: true },
      ]);
    });

    it("handles a query with a plus sign", () => {
      const result = getHighlightedParts("a+b=c", "+");
      expect(result).toEqual([
        { text: "a", isMatch: false },
        { text: "+", isMatch: true },
        { text: "b=c", isMatch: false },
      ]);
    });

    it("handles a query with a question mark", () => {
      const result = getHighlightedParts("is it true?", "?");
      expect(result).toEqual([
        { text: "is it true", isMatch: false },
        { text: "?", isMatch: true },
      ]);
    });
  });
});
