/**
 * Every opaque layer the native-video CSS claims to clear must actually exist.
 *
 * TRACES: UR-003, UR-004 | DR-185 | UT-186
 *
 * The compositing rules in app.css clear the page's painted backgrounds so the
 * ExoPlayer SurfaceView behind the WebView can be seen. One of the three
 * selectors, `[data-app-shell]`, was written against an attribute that **no
 * component ever set** — in any commit — so the app shell went on painting
 * `--color-background` across the whole viewport, underneath a player that had
 * correctly made itself transparent. The WebView therefore composited opaque
 * and the surface could never show through.
 *
 * That failure is invisible three ways over: the CSS is valid, the selector is
 * plausible, and the symptom (black screen, audio fine) is identical to a
 * genuine compositing failure — which is how it survived DR-150 through DR-172.
 * A rule that matches nothing is the specific defect worth a tripwire, so this
 * asserts the relationship rather than the rule: every attribute the block
 * targets is set somewhere in the app.
 */

import { describe, it, expect } from "vitest";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

const here = path.dirname(fileURLToPath(import.meta.url));
const srcRoot = path.resolve(here, "../..");

function read(file: string): string {
  return fs.readFileSync(file, "utf-8");
}

/** Every .svelte file under src/. */
function svelteFiles(dir: string, found: string[] = []): string[] {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    const full = path.join(dir, entry.name);
    if (entry.isDirectory()) svelteFiles(full, found);
    else if (entry.name.endsWith(".svelte")) found.push(full);
  }
  return found;
}

/**
 * The selector list of the `[data-native-video="active"]` rule in app.css.
 * Returned verbatim, one selector per entry.
 */
function compositingSelectors(css: string): string[] {
  const marker = 'html[data-native-video="active"]';
  const start = css.indexOf(marker);
  expect(start, "app.css no longer contains the native-video rule").toBeGreaterThan(-1);
  const open = css.indexOf("{", start);
  return css
    .slice(start, open)
    .split(",")
    .map((s) => s.trim())
    .filter(Boolean);
}

describe("native-video compositing layers (DR-185)", () => {
  const css = read(path.join(srcRoot, "app.css"));
  const selectors = compositingSelectors(css);
  const markup = svelteFiles(srcRoot).map(read).join("\n");

  it("clears the app shell, which paints over the whole viewport", () => {
    // The shell is the layer directly between the player and the WebView; if it
    // stays painted, nothing below it can be seen however transparent the
    // player and the WebView widget are.
    expect(selectors.some((s) => s.includes("[data-app-shell]"))).toBe(true);
    expect(markup).toContain("data-app-shell");
  });

  it("targets no attribute that nothing in the app sets", () => {
    const attributes = selectors
      .flatMap((selector) => [...selector.matchAll(/\[([a-zA-Z-]+)(?:[=\]])/g)])
      .map((match) => match[1])
      // data-native-video is set imperatively on <html> by nativeVideo.ts, not
      // in markup, so it is verified against that module instead.
      .filter((attr) => attr !== "data-native-video");

    const unset = [...new Set(attributes)].filter((attr) => !markup.includes(attr));
    expect(unset, `app.css targets attributes no component sets: ${unset.join(", ")}`)
      .toEqual([]);
  });

  it("still sets data-native-video on <html> from the store", () => {
    const store = read(path.join(srcRoot, "lib/stores/nativeVideo.ts"));
    expect(store).toContain("data-native-video");
    expect(store).toContain("documentElement");
  });
});
