import { describe, expect, test } from "bun:test";
import { readdir, readFile } from "node:fs/promises";
import { join } from "node:path";
import { parse } from "../../compiler/src/index.ts";

const componentsDir = join(import.meta.dir, "..", "components");

const replacementComponents = [
  "Container",
  "Grid",
  "Columns",
  "Card",
  "Link",
  "Image",
  "SearchBox",
  "Map",
  "List",
  "Marquee",
  "Tabs",
  "Timeline",
  "Footer",
  "Typography",
  "Divider",
] as const;

const newComponents = [
  "Section",
  "SectionHeader",
  "MarketingSectionHeader",
  "PageHeader",
  "TextLink",
  "FeatureGrid",
  "FeatureCard",
  "FeatureIconCard",
  "Hero",
  "SplitHero",
  "HeroActions",
  "AnnouncementBar",
  "MetricCard",
  "MetricGrid",
  "StatsBar",
  "CTASection",
  "BackToTop",
  "PublicPageShell",
] as const;

const allComponents = [...replacementComponents, ...newComponents];

function openingTags(source: string): string[] {
  const tags: string[] = [];
  let index = 0;

  while (index < source.length) {
    const start = source.indexOf("<", index);
    if (start === -1) break;

    let cursor = start + 1;
    let quote: "'" | '"' | null = null;

    while (cursor < source.length) {
      const char = source[cursor];
      if (quote) {
        if (char === quote) quote = null;
      } else if (char === "'" || char === '"') {
        quote = char;
      } else if (char === ">") {
        tags.push(source.slice(start, cursor + 1));
        cursor += 1;
        break;
      }
      cursor += 1;
    }

    index = cursor;
  }

  return tags;
}

describe("@wrnexus/ui redesign component pack", () => {
  test("contains every replacement and new component", async () => {
    const files = new Set(await readdir(componentsDir));

    for (const name of allComponents) {
      expect(files.has(`${name}.wrn`)).toBe(true);
    }
  });

  for (const name of allComponents) {
    test(`${name} has a valid component shell`, async () => {
      const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");

      expect(source).toContain(`component ${name} {`);
      expect(source).toContain("props {");
      expect(source).toContain("view {");
      expect(source).toContain(`data-ui-component="${name}"`);
      expect(source).not.toContain("React");
      expect(source).not.toContain("useState");
      expect(source).not.toContain("<script");
    });
  }

  test("opening tags do not contain duplicate class attributes", async () => {
    for (const name of allComponents) {
      const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");

      for (const tag of openingTags(source)) {
        const plainClassAttributes = tag.match(/(?<![:\\w-])class\\s*=/g) ?? [];
        expect(plainClassAttributes.length).toBeLessThanOrEqual(1);
      }
    }
  });

  test("dynamic attribute expressions stay single quoted", async () => {
    for (const name of allComponents) {
      const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
      expect(source).not.toMatch(/=\\"\{[^\n]*\}\\"/);
    }
  });

  test("existing public defaults remain release compatible", async () => {
    const expectations: Record<string, string[]> = {
      Container: [
        'size = "default"',
        'color = "primary"',
        "columns = 2",
        'gap = "md"',
        'maxWidth = "xl"',
      ],
      Grid: [
        'size = "default"',
        'color = "primary"',
        "columns = 2",
        'gap = "md"',
        'maxWidth = "xl"',
      ],
      Columns: [
        'size = "default"',
        'color = "primary"',
        "columns = 2",
        'gap = "md"',
        'maxWidth = "xl"',
      ],
      Link: ['label = "Link"', 'href = "#"', "external = false"],
      Image: ['src = ""', 'alt = ""', 'loading = "lazy"', "rounded = false"],
      SearchBox: [
        'label = "Search Box"',
        'type = "search"',
        "disabled = false",
        "required = false",
      ],
      Map: ['title = "Map"', "items = []", 'variant = "default"'],
      List: ['title = "List"', "items = []", 'variant = "default"'],
      Marquee: ['title = "Marquee"', "items = []", 'variant = "default"'],
      Tabs: ['label = "Tabs"', "items = []", 'active = ""', 'orientation = "horizontal"'],
      Timeline: ['title = "Timeline"', "items = []", 'variant = "default"'],
      Footer: ['label = "Footer navigation"', "items = []", "columns = 3", 'maxWidth = "compact"'],
      Typography: [
        'size = "default"',
        'color = "primary"',
        "columns = 2",
        'gap = "md"',
        'maxWidth = "xl"',
      ],
      Divider: ['label = ""', 'orientation = "horizontal"'],
    };

    for (const [name, tokens] of Object.entries(expectations)) {
      const source = await readFile(join(componentsDir, `${name}.wrn`), "utf8");
      const ast = parse(source);
      const defaults = new Map(ast.props.map((prop) => [prop.name, prop.default]));
      for (const token of tokens) {
        const match = /^([A-Za-z_$][\w$]*)\s*=\s*([\s\S]+)$/.exec(token);
        expect(match).not.toBeNull();
        expect(defaults.get(match![1]!)).toBe(match![2]!);
      }
    }
  });
});
