import type { Page } from "playwright";
import type { RenderedProbe } from "./types.js";

export const INVALID_RENDER_TOKENS = [
  "undefined",
  "NaN",
  "[object Object]",
  "Invalid Date",
] as const;

export async function captureRenderedProbe(page: Page): Promise<RenderedProbe> {
  return page.evaluate(() => {
    function isElementVisible(element: Element): boolean {
      const style = window.getComputedStyle(element);
      if (style.display === "none" || style.visibility === "hidden") {
        return false;
      }
      const rect = element.getBoundingClientRect();
      return rect.width > 0 && rect.height > 0;
    }

    function buildLocator(element: Element): string {
      if (element.id.length > 0) {
        return `#${element.id}`;
      }
      const testId = element.getAttribute("data-testid");
      if (testId !== null && testId.length > 0) {
        return `[data-testid="${testId}"]`;
      }
      return element.tagName.toLowerCase();
    }

    function normalizeVisibleText(text: string): string {
      return text.replace(/\s+/g, " ").trim();
    }

    function isCodeExampleElement(element: Element): boolean {
      if (element.closest("[data-invariantum-code-example], [data-code-example]") !== null) {
        return true;
      }
      if (element.closest("pre, code, samp, kbd") !== null) {
        return true;
      }
      return element.getAttribute("data-invariantum-code-example") !== null;
    }

    function collectVisibleTexts(root: ParentNode): RenderedProbe["visibleTexts"] {
      const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
      const observations: RenderedProbe["visibleTexts"] = [];
      const seen = new Set<string>();

      while (walker.nextNode()) {
        const node = walker.currentNode;
        const parent = node.parentElement;
        if (parent === null || !isElementVisible(parent) || isCodeExampleElement(parent)) {
          continue;
        }

        const text = normalizeVisibleText(node.textContent ?? "");
        if (text.length === 0) {
          continue;
        }

        const locator = buildLocator(parent);
        const key = `${locator}::${text}`;
        if (seen.has(key)) {
          continue;
        }
        seen.add(key);

        observations.push({
          locator,
          text,
          normalizedText: text,
        });
      }

      return observations;
    }

    function collectCodeExampleLocators(root: ParentNode): string[] {
      const locators = new Set<string>();
      for (const element of Array.from(root.querySelectorAll(
        "[data-invariantum-code-example], [data-code-example], pre, code",
      ))) {
        locators.add(buildLocator(element));
      }
      return [...locators];
    }

    function collectLandmarks(root: ParentNode): RenderedProbe["landmarks"] {
      const selectors = [
        "main",
        "nav",
        "header",
        "footer",
        "aside",
        "[role='main']",
        "[role='navigation']",
        "h1",
        "h2",
      ];
      const landmarks: RenderedProbe["landmarks"] = [];

      for (const selector of selectors) {
        for (const element of Array.from(root.querySelectorAll(selector))) {
          const textContent = element.textContent;
          const trimmedText = textContent.trim();
          const accessibleName =
            element.getAttribute("aria-label") ??
            (trimmedText.length > 0
              ? trimmedText
              : (element.getAttribute("title") ?? ""));
          const hasAccessibleContent = normalizeVisibleText(textContent).length > 0;
          landmarks.push({
            role: element.getAttribute("role") ?? element.tagName.toLowerCase(),
            locator: buildLocator(element),
            accessibleName,
            hasAccessibleContent,
          });
        }
      }

      return landmarks;
    }

    function collectLoadingIndicators(
      root: ParentNode,
    ): RenderedProbe["loadingIndicators"] {
      const selectors = [
        "[aria-busy='true']",
        "[data-loading='true']",
        ".loading",
        "[role='progressbar']",
      ];
      const indicators: RenderedProbe["loadingIndicators"] = [];

      for (const selector of selectors) {
        for (const element of Array.from(root.querySelectorAll(selector))) {
          indicators.push({
            locator: buildLocator(element),
            visible: isElementVisible(element),
            ariaBusy: element.getAttribute("aria-busy") === "true",
          });
        }
      }

      return indicators;
    }

    function collectHydrationDiagnostics(
      root: ParentNode,
    ): RenderedProbe["hydrationDiagnostics"] {
      const diagnostics: RenderedProbe["hydrationDiagnostics"] = [];

      for (const element of Array.from(root.querySelectorAll(
        "[data-hydration-error], [data-reactroot-mismatch], [data-invariantum-hydration-error]",
      ))) {
        diagnostics.push({
          kind: "dom",
          message:
            element.getAttribute("data-hydration-error") ??
            element.getAttribute("data-reactroot-mismatch") ??
            element.getAttribute("data-invariantum-hydration-error") ??
            "hydration marker",
          locator: buildLocator(element),
        });
      }

      const globalWindow = window as typeof window & {
        __HYDRATION_MISMATCH__?: string;
        __REACT_HYDRATION_ERROR__?: string;
      };
      if (globalWindow.__HYDRATION_MISMATCH__ !== undefined) {
        diagnostics.push({
          kind: "identity-mismatch",
          message: globalWindow.__HYDRATION_MISMATCH__,
        });
      }
      if (globalWindow.__REACT_HYDRATION_ERROR__ !== undefined) {
        diagnostics.push({
          kind: "console",
          message: globalWindow.__REACT_HYDRATION_ERROR__,
        });
      }

      return diagnostics;
    }

    const root = document.documentElement;
    const html = document.documentElement;

    return {
      htmlLang: html.lang,
      htmlDir: html.dir || "",
      textDirection: getComputedStyle(html).direction,
      documentScroll: {
        scrollWidth: html.scrollWidth,
        clientWidth: html.clientWidth,
      },
      visibleTexts: collectVisibleTexts(root),
      landmarks: collectLandmarks(root),
      loadingIndicators: collectLoadingIndicators(root),
      hydrationDiagnostics: collectHydrationDiagnostics(root),
      codeExampleLocators: collectCodeExampleLocators(root),
    };
  });
}
