/* eslint-disable no-console */

/**
 * Rewrites JS string references in a POT file so they point at the built,
 * shipped assets/dist entry files instead of anonymous webpack chunks.
 *
 * WordPress loads JS translations from JSON files keyed by the md5 of the
 * ENQUEUED script's relative path (see load_script_textdomain()), while
 * translation tools (Loco Translate, wp i18n make-json, GlotPress) generate
 * one JSON per file referenced in the PO. Lazy chunks are loaded by the
 * webpack runtime, never enqueued, so strings referenced from chunk paths
 * end up in JSON files WordPress never requests. Attributing every JS
 * string to the entry files makes the entry-file JSONs carry all strings;
 * wp.i18n registers loaded data globally per text domain, so chunk code
 * resolves translations from there.
 *
 * Usage: node fix-pot-js-references.js <pot-file>
 */

const fs = require( 'fs' );
const path = require( 'path' );

const DIST_PREFIX = 'assets/dist/';

// Anonymous webpack chunks: assets/dist/<digits>.js at the dist root.
const CHUNK_RE = /^assets\/dist\/\d+(\.min)?\.js$/;

// Any JS reference, regardless of its directory (used in --target mode,
// where refs may point at assets/dist/ or assets/pro/ alike).
const JS_REF_RE = /\.js$/;

/**
 * Whether a single "#:" reference (path[:line]) points at a JS file.
 *
 * @param {string} ref A single reference, e.g. "assets/dist/4551.js:1".
 * @return {boolean} True when the reference's path ends in ".js".
 */
function isJsRef( ref ) {
	return JS_REF_RE.test( ref.replace( /:\d+$/, '' ) );
}

/**
 * Discovers entry files: every .js under assets/dist that is not an
 * anonymous chunk. Entry names are stable across Lite/Pro builds.
 *
 * @param {string} rootDir Absolute path to the plugin root.
 * @return {string[]} Sorted plugin-relative entry paths.
 */
function discoverEntryFiles( rootDir ) {
	const distDir = path.join( rootDir, 'assets', 'dist' );

	if ( ! fs.existsSync( distDir ) ) {
		throw new Error(
			`Dist directory not found: ${ distDir }. Run the webpack build first.`
		);
	}

	const entries = fs
		.readdirSync( distDir, { recursive: true } )
		.map( ( p ) => p.toString().split( path.sep ).join( '/' ) )
		.filter( ( p ) => p.endsWith( '.js' ) )
		.map( ( p ) => DIST_PREFIX + p )
		.filter( ( p ) => ! CHUNK_RE.test( p ) )
		.sort();

	if ( entries.length === 0 ) {
		throw new Error( `No entry files found under ${ distDir }.` );
	}

	return entries;
}

/**
 * Replaces JS references in the POT with either one reference per entry
 * file, or (when `options.target` is set) a single shared reference,
 * preserving PHP references and all other comment lines.
 *
 * @param {string}   potContent       Raw POT file content.
 * @param {string[]} entryFiles       Plugin-relative entry paths. Unused
 *                                    when `options.target` is set.
 * @param {Object}   [options]        Optional settings.
 * @param {string}   [options.target] When set, every JS reference
 *                                    (assets/dist/ and any other
 *                                    .js path, e.g. assets/pro/) is
 *                                    replaced with this single path
 *                                    instead of the entry-file list.
 * @return {{content: string, rewritten: number}} Rewritten content and count.
 */
function rewritePotContent( potContent, entryFiles, options = {} ) {
	const useTarget =
		typeof options.target === 'string' && options.target.length > 0;
	const replacementRefLines = useTarget
		? [ `#: ${ options.target }:1` ]
		: entryFiles.map( ( file ) => `#: ${ file }:1` );
	const blocks = potContent.split( '\n\n' );
	let rewritten = 0;

	const result = blocks.map( ( block ) => {
		const lines = block.split( '\n' );
		const refLines = lines.filter( ( line ) => line.startsWith( '#: ' ) );

		if ( refLines.length === 0 ) {
			return block;
		}

		// A "#:" line may hold several space-separated references.
		const refs = refLines.flatMap( ( line ) =>
			line.slice( 3 ).trim().split( /\s+/ )
		);

		const isJsMatch = ( ref ) =>
			useTarget ? isJsRef( ref ) : ref.startsWith( DIST_PREFIX );

		if ( ! refs.some( isJsMatch ) ) {
			return block;
		}

		rewritten++;

		const keptRefLines = refs
			.filter( ( ref ) => ! isJsMatch( ref ) )
			.map( ( ref ) => `#: ${ ref }` )
			.concat( replacementRefLines );

		// Reference lines are contiguous; splice the new set where the old ones began.
		const firstRefIndex = lines.findIndex( ( line ) =>
			line.startsWith( '#: ' )
		);
		const withoutRefs = lines.filter(
			( line ) => ! line.startsWith( '#: ' )
		);

		withoutRefs.splice( firstRefIndex, 0, ...keptRefLines );

		return withoutRefs.join( '\n' );
	} );

	if ( rewritten === 0 ) {
		throw new Error(
			'No JS (assets/dist) references found in POT — did make-pot scan the built files?'
		);
	}

	return { content: result.join( '\n\n' ), rewritten };
}

/**
 * CLI entry point: rewrites the POT file given as the first non-flag
 * argument in place, optionally collapsing every JS reference onto a
 * single `--target=<path>` (e.g. the Pro i18n stub) instead of discovering
 * dist entry files.
 */
function main() {
	const args = process.argv.slice( 2 );
	const targetArg = args.find( ( arg ) => arg.startsWith( '--target=' ) );
	const target = targetArg
		? targetArg.slice( '--target='.length )
		: undefined;
	const potArg = args.find( ( arg ) => ! arg.startsWith( '--' ) );

	if ( ! potArg ) {
		console.error(
			'Usage: node fix-pot-js-references.js <pot-file> [--target=<path>]'
		);
		process.exit( 1 );
	}

	const potPath = path.resolve( __dirname, potArg );

	if ( ! fs.existsSync( potPath ) ) {
		console.error( `POT file not found: ${ potPath }` );
		process.exit( 1 );
	}

	try {
		const entryFiles = target ? [] : discoverEntryFiles( __dirname );
		const { content, rewritten } = rewritePotContent(
			fs.readFileSync( potPath, 'utf8' ),
			entryFiles,
			{ target }
		);

		fs.writeFileSync( potPath, content );

		const destination = target
			? target
			: `${ entryFiles.length } entry files`;

		console.log(
			`fix-pot-js-references: rewrote ${ rewritten } JS strings to ${ destination } in ${ potArg }.`
		);
	} catch ( error ) {
		console.error( `fix-pot-js-references: ${ error.message }` );
		process.exit( 1 );
	}
}

if ( require.main === module ) {
	main();
}

module.exports = { discoverEntryFiles, rewritePotContent };
