/**
 * Promises that are properties of the SOURCE and cannot be observed at runtime.
 *
 * Each is codebase-wide: that EVERY image carries the never-capture class, that
 * NO module imports the recorder, that the un-delayable loader stays small. No
 * single moment in a running page can check any of those, so they are checked
 * against the files — a lint rule that happens to live in the test suite.
 *
 * The bundle one matters more here than anywhere else in this product: the
 * plugin's whole promise is that it is a thin connector that leaves the store
 * fast, and it ships through wordpress.org to every installed site.
 *
 * Anything a running page CAN show is asserted there instead — the recorder's
 * config lives in replay.test.ts, against the object handed to init().
 */
import { describe, it, expect } from 'vitest';
import { readFileSync, readdirSync, statSync } from 'node:fs';
import { join, resolve } from 'node:path';

const SRC = resolve( __dirname );
const WIDGET_ROOT = resolve( __dirname, '..' );
const PLUGIN_ROOT = resolve( __dirname, '../..' );

/**
 * Source with its comments removed.
 *
 * Every assertion below is about CODE. Without this the checks match their own
 * documentation, which proves nothing.
 */
function code( source: string ): string {
	return source.replace( /\/\*[\s\S]*?\*\//g, '' ).replace( /(^|[^:])\/\/.*$/gm, '$1' );
}

/** Every .ts/.tsx source file under src, tests excluded. */
function sourceFiles(): string[] {
	const found: string[] = [];
	const walk = ( dir: string ) => {
		for ( const entry of readdirSync( dir ) ) {
			const full = join( dir, entry );
			if ( statSync( full ).isDirectory() ) {
				walk( full );
				continue;
			}
			if ( ! /\.tsx?$/.test( entry ) || entry.includes( '.test.' ) ) continue;
			found.push( full );
		}
	};
	walk( SRC );
	return found;
}

describe( 'replay masking', () => {
	it( 'marks every image the widget renders as never-capture', () => {
		const offenders: string[] = [];
		for ( const file of sourceFiles() ) {
			if ( ! file.endsWith( '.tsx' ) ) continue;
			const source = code( readFileSync( file, 'utf8' ) );
			for ( const img of source.match( /<img[\s\S]*?\/>/g ) ?? [] ) {
				if ( ! img.includes( 'ph-no-capture' ) ) {
					offenders.push( `${ file.replace( WIDGET_ROOT, '' ) }: ${ img.slice( 0, 60 ).replace( /\s+/g, ' ' ) }…` );
				}
			}
		}
		// Every image here shows the shopper's own photo or the try-on made from
		// it. A new one without the class is a body in a replay.
		expect( offenders, 'add ph-no-capture to these images' ).toEqual( [] );
	} );

} );

// The recorder's own configuration — maskAllInputs, blockSelector, api_host — is
// asserted in replay.test.ts against the object actually handed to init(), not
// against this file's source. Grepping source text for `maskAllInputs: true`
// proved only that the source is the source: it would fail on a rename that
// changed nothing and stay silent on a config built and never passed. Both were
// mutation-checked before those assertions were removed here — flipping
// maskAllInputs and hardcoding a posthog.com URL each fail replay.test.ts.

describe( 'the storefront bundle budget', () => {
	it( 'never imports a recorder SDK into the bundle', () => {
		// This widget builds as a single IIFE — no code splitting — so an import
		// here would put the whole recorder on every product page of every
		// installed store, whether or not a shopper opens the modal.
		const offenders = sourceFiles().filter( ( file ) => {
			const source = code( readFileSync( file, 'utf8' ) );
			return /from\s+['"]posthog-js['"]/.test( source ) || /require\(['"]posthog-js['"]\)/.test( source );
		} );
		expect( offenders, 'the recorder is loaded at runtime, never bundled' ).toEqual( [] );
	} );

	it( 'declares no analytics dependency', () => {
		const pkg = JSON.parse( readFileSync( join( WIDGET_ROOT, 'package.json' ), 'utf8' ) );
		const deps = Object.keys( pkg.dependencies ?? {} );
		expect( deps ).not.toContain( 'posthog-js' );
		// React and react-dom are the only runtime dependencies, which is what
		// keeps this budget answerable at a glance.
		expect( deps.sort() ).toEqual( [ 'react', 'react-dom' ] );
	} );


	it( 'keeps the un-delayable loader small — it runs on every product page', () => {
		const bootstrap = readFileSync( join( PLUGIN_ROOT, 'assets/bootstrap.js' ), 'utf8' );
		// This file is excluded from every optimizer and loads unconditionally.
		// The ceiling is generous but finite: it exists so "just add it to the
		// bootstrap" is a decision someone has to make on purpose.
		expect( bootstrap.length ).toBeLessThan( 40_000 );
	} );
} );

describe( 'the single product-analytics path', () => {
	it( 'never posts a product-analytics event except through the bootstrap', () => {
		// events.ts keeps a direct-POST fallback for ATTRIBUTION, which the
		// merchant's own opt-in legitimately covers. Product analytics must not
		// have one: only the bootstrap can read the shopper's CMP, so a fallback
		// would be a way to emit without ever consulting it.
		const analytics = code( readFileSync( join( SRC, 'analytics.ts' ), 'utf8' ) );
		expect( analytics ).not.toContain( 'fetch(' );
		expect( analytics ).toContain( 'aisthetixTrackProductEvent' );
	} );

	it( 'asks the bootstrap whether replay may run, rather than deciding itself', () => {
		const analytics = code( readFileSync( join( SRC, 'analytics.ts' ), 'utf8' ) );
		expect( analytics ).toContain( 'aisthetixProductAnalyticsAllowed' );
		// No CMP reading in the widget: there is one implementation of that
		// decision and it is not here.
		expect( analytics ).not.toContain( 'Cookiebot' );
		expect( analytics ).not.toContain( 'cmplz' );
	} );
} );
