/**
 * Collects console errors and uncaught exceptions from a Playwright page.
 * Filters out expected noise so tests can assert "no real errors".
 */
import type { Page, ConsoleMessage } from "@playwright/test";

/** Patterns to ignore — expected noise from EncryptID, HMR, analytics, SW */
const IGNORE_PATTERNS = [
  /encryptid/i,
  /session/i,
  /service.?worker/i,
  /umami/i,
  /HMR/i,
  /hot.?update/i,
  /favicon/i,
  /\[vite\]/i,
  /net::ERR_/,                   // network errors in background fetches
  /ResizeObserver loop/i,        // benign browser warning
  /Failed to register.*SW/i,    // service worker blocked by config
  /attachShadow/i,              // benign: duplicate shadow root from HMR/re-registration
  /already hosts a shadow tree/i,
  /Failed to resolve module specifier/i,  // known importmap gaps (e.g. three-forcegraph)
  /folk-graph-viewer.*Failed to load/i,   // rNetwork 3d-force-graph load failure (known)
  /Failed to load 3d-force-graph/i,
];

function isNoise(text: string): boolean {
  return IGNORE_PATTERNS.some((re) => re.test(text));
}

export class ConsoleCollector {
  readonly errors: string[] = [];
  readonly pageErrors: string[] = [];

  constructor(page: Page) {
    page.on("console", (msg: ConsoleMessage) => {
      if (msg.type() === "error") {
        const text = msg.text();
        if (!isNoise(text)) this.errors.push(text);
      }
    });

    page.on("pageerror", (err) => {
      const text = err.message || String(err);
      if (!isNoise(text)) this.pageErrors.push(text);
    });
  }

  /** Returns all collected real errors (console.error + uncaught exceptions). */
  get allErrors(): string[] {
    return [...this.errors, ...this.pageErrors];
  }

  /** Asserts no real errors were logged. */
  assertNoErrors() {
    if (this.allErrors.length > 0) {
      throw new Error(
        `Unexpected console errors:\n${this.allErrors.map((e) => `  - ${e}`).join("\n")}`
      );
    }
  }
}
