const PRINT_SHEET_CLASS = 'cliq-sl-print-sheet';
const PRINTING_BODY_CLASS = 'cliq-sl-is-printing';
const PRINT_RESULTS_PROPERTIES = [
	'--cliq-results-cols',
	'--cliq-results-gap',
	'--cliq-results-card-w',
];

const removePrintOnlyControls = ( sheet ) => {
	sheet.querySelectorAll( [
		'.cliq-sl-count__print',
		'.cliq-sl-results-tools',
		'.cliq-sl-filter-drawer',
		'.cliq-sl-loadmore',
		'.cliq-sl-loadmore-sentinel',
		'.cliq-sl-results-progress',
		'.cliq-sl-directions-host',
	].join( ',' ) ).forEach( ( node ) => node.remove() );
};

const preparePrintImages = ( source, copy ) => {
	const sourceImages = Array.from( source.querySelectorAll( 'img' ) );
	const copiedImages = Array.from( copy.querySelectorAll( 'img' ) );
	copiedImages.forEach( ( image, index ) => {
		const original = sourceImages[ index ];
		// A freshly cloned lazy/responsive image has not selected or decoded a
		// resource yet. Pin it to the exact source already rendered on screen.
		const renderedSource = original?.currentSrc || original?.src || original?.getAttribute?.( 'src' );
		image.removeAttribute( 'loading' );
		image.loading = 'eager';
		if ( renderedSource ) {
			image.removeAttribute( 'srcset' );
			image.removeAttribute( 'sizes' );
			image.src = renderedSource;
		}
	} );
};

const preserveResultsLayout = ( source, copy, doc ) => {
	const sourceList = source.querySelector?.( '.cliq-widget-results' );
	const copiedList = copy.querySelector?.( '.cliq-widget-results' );
	const getComputedStyle = doc.defaultView?.getComputedStyle;
	if ( ! sourceList || ! copiedList?.style || ! getComputedStyle ) return;

	const computed = getComputedStyle( sourceList );
	PRINT_RESULTS_PROPERTIES.forEach( ( property ) => {
		const value = computed.getPropertyValue( property ).trim();
		if ( value ) copiedList.style.setProperty( property, value );
	} );
};

export const waitForPrintImages = ( sheet, win = window ) => Promise.all(
	Array.from( sheet.querySelectorAll( 'img' ) ).map( ( image ) => {
		if ( image.complete ) return Promise.resolve();
		return new Promise( ( resolve ) => {
			let timer;
			const done = () => {
				win.clearTimeout?.( timer );
				resolve();
			};
			image.addEventListener( 'load', done, { once: true } );
			image.addEventListener( 'error', done, { once: true } );
			timer = win.setTimeout?.( done, 4000 );
			image.decode?.().then( done ).catch( () => {} );
		} );
	} )
);

/**
 * A printed list must be complete: an accordion's collapsed detail rows are
 * meaningful content (hours, phone, description), and a reader cannot expand
 * paper. Open every accordion item and every collapsible-row reveal region in
 * the COPY, so the sheet shows all stores fully expanded while the live locator
 * keeps whatever the visitor had open.
 *
 * `.cliq-widget-reveal` collapses via `grid-template-rows: 0fr` and opens with
 * `.is-open` (design.scss); accordion items use `.cliq-sl-acc.is-open`.
 */
const expandPrintReveals = ( copy ) => {
	copy.querySelectorAll?.( '.cliq-widget-reveal, .cliq-sl-acc' ).forEach( ( node ) => {
		node.classList?.add?.( 'is-open' );
		// Toggles inside the clone are inert, but their state should not read
		// "collapsed" to assistive tech or leave a rotated chevron on paper.
		if ( node.hasAttribute?.( 'aria-expanded' ) ) {
			node.setAttribute( 'aria-expanded', 'true' );
		}
	} );
	copy.querySelectorAll?.( '[aria-expanded="false"]' ).forEach( ( node ) =>
		node.setAttribute( 'aria-expanded', 'true' )
	);
	copy.querySelectorAll?.( '[hidden]' ).forEach( ( node ) => node.removeAttribute( 'hidden' ) );
};

/**
 * Build a body-level copy of the Results widget for printing. Keeping the
 * locator's scope classes on the sheet preserves generated Store Card CSS,
 * while placing it directly under body lets print CSS exclude the theme,
 * page content, filters, and map without changing the live locator.
 */
export const createStoreListPrintSheet = ( source, doc = document ) => {
	const locator = source?.closest?.( '.cliq-sl' );
	if ( ! locator ) return null;

	// An embedded Print button belongs to its own Results widget. A standalone
	// Result Count prints the first Store List in the same locator.
	const results = source.closest( '.cliq-sl-w--results' ) || locator.querySelector( '.cliq-sl-w--results' );
	if ( ! results ) return null;

	doc.querySelectorAll( `.${ PRINT_SHEET_CLASS }` ).forEach( ( oldSheet ) => oldSheet.remove() );

	const sheet = doc.createElement( 'div' );
	const scopeClasses = Array.from( locator.classList ).filter( ( name ) => ! [ 'is-map-only', 'has-map-transition' ].includes( name ) );
	sheet.className = [ ...scopeClasses, PRINT_SHEET_CLASS ].join( ' ' );
	sheet.setAttribute( 'aria-hidden', 'true' );

	const copy = results.cloneNode( true );
	copy.classList.add( 'cliq-sl-print-content' );
	copy.removeAttribute( 'aria-busy' );
	expandPrintReveals( copy );
	removePrintOnlyControls( copy );
	preparePrintImages( results, copy );
	preserveResultsLayout( results, copy, doc );
	copy.querySelectorAll( '[tabindex]' ).forEach( ( node ) => node.removeAttribute( 'tabindex' ) );
	sheet.appendChild( copy );

	return sheet;
};

export const printStoreList = async ( source, doc = document, win = window ) => {
	const sheet = createStoreListPrintSheet( source, doc );
	if ( ! sheet ) return false;

	doc.body.appendChild( sheet );
	doc.body.classList.add( PRINTING_BODY_CLASS );
	// Do not open the browser dialog until the body-level copies have loaded.
	// Otherwise cloned lazy images commonly print as empty boxes.
	await waitForPrintImages( sheet, win );

	let fallback;
	const cleanup = () => {
		win.clearTimeout?.( fallback );
		doc.body.classList.remove( PRINTING_BODY_CLASS );
		sheet.remove();
	};

	win.addEventListener?.( 'afterprint', cleanup, { once: true } );
	// afterprint is not reliable in every embedded browser. This is only a
	// safety cleanup; window.print() is blocking in the mainstream browsers.
	fallback = win.setTimeout?.( cleanup, 60000 );

	try {
		win.print();
	} catch ( error ) {
		cleanup();
		throw error;
	}

	return true;
};

export { PRINT_SHEET_CLASS, PRINTING_BODY_CLASS };
