import { describe, expect, it } from "vitest";
import {
  extractMarkdownTables,
  extractCodeBlocks,
  extractLinks,
  stripMarkdown,
  processLineMessage,
  convertTableToFlexBubble,
  convertCodeBlockToFlexBubble,
  hasMarkdownToConvert,
} from "./markdown-to-line.js";

describe("extractMarkdownTables", () => {
  it("extracts a simple 2-column table", () => {
    const text = `Here is a table:

| Name | Value |
|------|-------|
| foo  | 123   |
| bar  | 456   |

And some more text.`;

    const { tables, textWithoutTables } = extractMarkdownTables(text);

    expect(tables).toHaveLength(1);
    expect(tables[0].headers).toEqual(["Name", "Value"]);
    expect(tables[0].rows).toEqual([
      ["foo", "123"],
      ["bar", "456"],
    ]);
    expect(textWithoutTables).toContain("Here is a table:");
    expect(textWithoutTables).toContain("And some more text.");
    expect(textWithoutTables).not.toContain("|");
  });

  it("extracts multiple tables", () => {
    const text = `Table 1:

| A | B |
|---|---|
| 1 | 2 |

Table 2:

| X | Y |
|---|---|
| 3 | 4 |`;

    const { tables } = extractMarkdownTables(text);

    expect(tables).toHaveLength(2);
    expect(tables[0].headers).toEqual(["A", "B"]);
    expect(tables[1].headers).toEqual(["X", "Y"]);
  });

  it("handles tables with alignment markers", () => {
    const text = `| Left | Center | Right |
|:-----|:------:|------:|
| a    | b      | c     |`;

    const { tables } = extractMarkdownTables(text);

    expect(tables).toHaveLength(1);
    expect(tables[0].headers).toEqual(["Left", "Center", "Right"]);
    expect(tables[0].rows).toEqual([["a", "b", "c"]]);
  });

  it("returns empty when no tables present", () => {
    const text = "Just some plain text without tables.";

    const { tables, textWithoutTables } = extractMarkdownTables(text);

    expect(tables).toHaveLength(0);
    expect(textWithoutTables).toBe(text);
  });
});

describe("extractCodeBlocks", () => {
  it("extracts code blocks across language/no-language/multiple variants", () => {
    const withLanguage = `Here is some code:

\`\`\`javascript
const x = 1;
console.log(x);
\`\`\`

And more text.`;
    const withLanguageResult = extractCodeBlocks(withLanguage);
    expect(withLanguageResult.codeBlocks).toHaveLength(1);
    expect(withLanguageResult.codeBlocks[0].language).toBe("javascript");
    expect(withLanguageResult.codeBlocks[0].code).toBe("const x = 1;\nconsole.log(x);");
    expect(withLanguageResult.textWithoutCode).toContain("Here is some code:");
    expect(withLanguageResult.textWithoutCode).toContain("And more text.");
    expect(withLanguageResult.textWithoutCode).not.toContain("```");

    const withoutLanguage = `\`\`\`
plain code
\`\`\``;
    const withoutLanguageResult = extractCodeBlocks(withoutLanguage);
    expect(withoutLanguageResult.codeBlocks).toHaveLength(1);
    expect(withoutLanguageResult.codeBlocks[0].language).toBeUndefined();
    expect(withoutLanguageResult.codeBlocks[0].code).toBe("plain code");

    const multiple = `\`\`\`python
print("hello")
\`\`\`

Some text

\`\`\`bash
echo "world"
\`\`\``;
    const multipleResult = extractCodeBlocks(multiple);
    expect(multipleResult.codeBlocks).toHaveLength(2);
    expect(multipleResult.codeBlocks[0].language).toBe("python");
    expect(multipleResult.codeBlocks[1].language).toBe("bash");
  });
});

describe("extractLinks", () => {
  it("extracts markdown links", () => {
    const text = "Check out [Google](https://google.com) and [GitHub](https://github.com).";

    const { links, textWithLinks } = extractLinks(text);

    expect(links).toHaveLength(2);
    expect(links[0]).toEqual({ text: "Google", url: "https://google.com" });
    expect(links[1]).toEqual({ text: "GitHub", url: "https://github.com" });
    expect(textWithLinks).toBe("Check out Google and GitHub.");
  });
});

describe("stripMarkdown", () => {
  it("strips inline markdown marker variants", () => {
    const cases = [
      ["strips bold **", "This is **bold** text", "This is bold text"],
      ["strips bold __", "This is __bold__ text", "This is bold text"],
      ["strips italic *", "This is *italic* text", "This is italic text"],
      ["strips italic _", "This is _italic_ text", "This is italic text"],
      ["strips strikethrough", "This is ~~deleted~~ text", "This is deleted text"],
      ["removes hr ---", "Above\n---\nBelow", "Above\n\nBelow"],
      ["removes hr ***", "Above\n***\nBelow", "Above\n\nBelow"],
      ["strips inline code markers", "Use `const` keyword", "Use const keyword"],
    ] as const;
    for (const [name, input, expected] of cases) {
      expect(stripMarkdown(input), name).toBe(expected);
    }
  });

  it("handles complex markdown", () => {
    const input = `# Title

This is **bold** and *italic* text.

> A quote

Some ~~deleted~~ content.`;

    const result = stripMarkdown(input);

    expect(result).toContain("Title");
    expect(result).toContain("This is bold and italic text.");
    expect(result).toContain("A quote");
    expect(result).toContain("Some deleted content.");
    expect(result).not.toContain("#");
    expect(result).not.toContain("**");
    expect(result).not.toContain("~~");
    expect(result).not.toContain(">");
  });
});

describe("convertTableToFlexBubble", () => {
  it("replaces empty cells with placeholders", () => {
    const table = {
      headers: ["A", "B"],
      rows: [["", ""]],
    };

    const bubble = convertTableToFlexBubble(table);
    const body = bubble.body as {
      contents: Array<{ contents?: Array<{ contents?: Array<{ text: string }> }> }>;
    };
    const rowsBox = body.contents[2] as { contents: Array<{ contents: Array<{ text: string }> }> };

    expect(rowsBox.contents[0].contents[0].text).toBe("-");
    expect(rowsBox.contents[0].contents[1].text).toBe("-");
  });

  it("strips bold markers and applies weight for fully bold cells", () => {
    const table = {
      headers: ["**Name**", "Status"],
      rows: [["**Alpha**", "OK"]],
    };

    const bubble = convertTableToFlexBubble(table);
    const body = bubble.body as {
      contents: Array<{ contents?: Array<{ text: string; weight?: string }> }>;
    };
    const headerRow = body.contents[0] as { contents: Array<{ text: string; weight?: string }> };
    const dataRow = body.contents[2] as { contents: Array<{ text: string; weight?: string }> };

    expect(headerRow.contents[0].text).toBe("Name");
    expect(headerRow.contents[0].weight).toBe("bold");
    expect(dataRow.contents[0].text).toBe("Alpha");
    expect(dataRow.contents[0].weight).toBe("bold");
  });
});

describe("convertCodeBlockToFlexBubble", () => {
  it("creates a code card with language label", () => {
    const block = { language: "typescript", code: "const x = 1;" };

    const bubble = convertCodeBlockToFlexBubble(block);

    const body = bubble.body as { contents: Array<{ text: string }> };
    expect(body.contents[0].text).toBe("Code (typescript)");
  });

  it("creates a code card without language", () => {
    const block = { code: "plain code" };

    const bubble = convertCodeBlockToFlexBubble(block);

    const body = bubble.body as { contents: Array<{ text: string }> };
    expect(body.contents[0].text).toBe("Code");
  });

  it("truncates very long code", () => {
    const longCode = "x".repeat(3000);
    const block = { code: longCode };

    const bubble = convertCodeBlockToFlexBubble(block);

    const body = bubble.body as { contents: Array<{ contents: Array<{ text: string }> }> };
    const codeText = body.contents[1].contents[0].text;
    expect(codeText.length).toBeLessThan(longCode.length);
    expect(codeText).toContain("...");
  });
});

describe("processLineMessage", () => {
  it("processes text with code blocks", () => {
    const text = `Check this code:

\`\`\`js
console.log("hi");
\`\`\`

That's it.`;

    const result = processLineMessage(text);

    expect(result.flexMessages).toHaveLength(1);
    expect(result.text).toContain("Check this code:");
    expect(result.text).toContain("That's it.");
    expect(result.text).not.toContain("```");
  });

  it("handles mixed content", () => {
    const text = `# Summary

Here's **important** info:

| Item | Count |
|------|-------|
| A    | 5     |

\`\`\`python
print("done")
\`\`\`

> Note: Check the link [here](https://example.com).`;

    const result = processLineMessage(text);

    // Should have 2 flex messages (table + code)
    expect(result.flexMessages).toHaveLength(2);

    // Text should be cleaned
    expect(result.text).toContain("Summary");
    expect(result.text).toContain("important");
    expect(result.text).toContain("Note: Check the link here.");
    expect(result.text).not.toContain("#");
    expect(result.text).not.toContain("**");
    expect(result.text).not.toContain("|");
    expect(result.text).not.toContain("```");
    expect(result.text).not.toContain("[here]");
  });

  it("handles plain text unchanged", () => {
    const text = "Just plain text with no markdown.";

    const result = processLineMessage(text);

    expect(result.text).toBe(text);
    expect(result.flexMessages).toHaveLength(0);
  });
});

describe("hasMarkdownToConvert", () => {
  it("detects supported markdown patterns", () => {
    const cases = [
      `| A | B |
|---|---|
| 1 | 2 |`,
      "```js\ncode\n```",
      "**bold**",
      "~~deleted~~",
      "# Title",
      "> quote",
    ];

    for (const text of cases) {
      expect(hasMarkdownToConvert(text)).toBe(true);
    }
  });

  it("returns false for plain text", () => {
    expect(hasMarkdownToConvert("Just plain text.")).toBe(false);
  });
});
