( function () {
	if ( ! window.R2Offload ) return;
	const { ajaxUrl, nonce } = window.R2Offload;

	async function post( action, data = {} ) {
		const body = new URLSearchParams( { action, nonce, ...data } );
		try {
			const res  = await fetch( ajaxUrl, { method: 'POST', credentials: 'same-origin', body } );
			const raw  = await res.text();
			let json;
			try {
				json = JSON.parse( raw );
			} catch ( e ) {
				return {
					success: false,
					data: {
						status: 'error',
						message: 'Invalid JSON (HTTP ' + res.status + '): ' + String( raw ).slice( 0, 240 ),
						last_error: 'HTTP ' + res.status,
					},
				};
			}
			if ( ! res.ok && ( ! json || json.success !== false ) ) {
				return {
					success: false,
					data: Object.assign( {}, ( json && json.data ) || {}, {
						status: 'error',
						message: ( json && json.data && json.data.message ) || ( 'HTTP ' + res.status ),
						last_error: ( json && json.data && json.data.last_error ) || ( 'HTTP ' + res.status ),
					} ),
				};
			}
			return json;
		} catch ( e ) {
			return {
				success: false,
				data: {
					status: 'error',
					message: String( e && e.message ? e.message : e ),
					last_error: String( e && e.message ? e.message : e ),
				},
			};
		}
	}

	function fmtBytes( n ) {
		n = Number( n ) || 0;
		const u = [ 'B', 'KB', 'MB', 'GB', 'TB' ];
		let i = 0;
		while ( n >= 1024 && i < u.length - 1 ) { n /= 1024; i++; }
		return n.toFixed( 2 ) + ' ' + u[ i ];
	}

	function setStat( key, value ) {
		document.querySelectorAll( '[data-r2-stat="' + key + '"]' ).forEach( el => { el.textContent = value; } );
	}

	function renderProgress( data ) {
		if ( data.scan ) {
			setStat( 'scan.scanned', data.scan.scanned );
			setStat( 'scan.queued', data.scan.queued );
			setStat( 'scan.paths_queued', data.scan.paths_queued || 0 );
			setStat( 'scan.skipped', data.scan.skipped );
			setStat( 'scan.bytes_total', fmtBytes( data.scan.bytes_total ) );
			setStat( 'scan.finished', data.scan.finished ? 'Yes' : 'No' );
		}
		const m = data.migrate || data.progress;
		if ( m ) {
			setStat( 'mig.pending', m.pending || 0 );
			setStat( 'mig.processing', m.processing || 0 );
			setStat( 'mig.offloaded', m.offloaded || 0 );
			setStat( 'mig.failed', m.failed || 0 );
			setStat( 'mig.bytes', fmtBytes( m.bytes && m.bytes.offloaded ) + ' / ' + fmtBytes( m.bytes && m.bytes.total ) );
			setStat( 'mig.status', m.control ? m.control.status : '' );
		}
		if ( data.failed ) renderFailed( data.failed );
	}

	function renderFailed( rows ) {
		const body = document.querySelector( '[data-r2-failed-body]' );
		if ( ! body ) return;
		if ( ! rows.length ) {
			body.innerHTML = '<tr><td colspan="6">No failed items.</td></tr>';
			return;
		}
		body.innerHTML = rows.map( r => {
			const label = r.relative_path ? escapeHtml( r.relative_path ) : r.attachment_id;
			return '<tr>' +
				'<td>' + label + '</td>' +
				'<td>—</td>' +
				'<td>' + r.attempts + '</td>' +
				'<td><code style="white-space:pre-wrap;">' + escapeHtml( r.last_error || '' ) + '</code></td>' +
				'<td>' + escapeHtml( r.updated_at || '' ) + '</td>' +
				'<td><button class="button" data-r2-action="retry-one" data-id="' + r.attachment_id + '">Retry</button></td>' +
			'</tr>';
		} ).join( '' );
	}

	function escapeHtml( s ) {
		return String( s ).replace( /[&<>"']/g, c => ( { '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' } )[ c ] );
	}

	let ticking = false;
	async function tickLoop() {
		if ( ticking ) return;
		ticking = true;
		try {
			while ( true ) {
				const r = await post( 'r2_offload_migrate_tick' );
				if ( ! r || ! r.success ) break;
				const d = r.data;
				renderProgress( { migrate: d.progress, failed: d.failed } );
				const status = d.progress && d.progress.control && d.progress.control.status;
				if ( status !== 'running' || ! d.processed_id ) break;
			}
		} finally {
			ticking = false;
		}
	}

	async function runScanLoop() {
		let done = false;
		while ( ! done ) {
			const r = await post( 'r2_offload_scan_tick', { batch: 200 } );
			if ( ! r || ! r.success ) return;
			renderProgress( { scan: r.data } );
			done = !! r.data.finished;
		}
	}

	let pruning = false;

	function setPruneButtons( running ) {
		document.querySelectorAll( '[data-r2-prune-btn]' ).forEach( el => { el.disabled = !! running; } );
		const stop = document.querySelector( '[data-r2-prune-stop]' );
		if ( stop ) stop.disabled = ! running;
	}

	function renderPruneProgress( state ) {
		if ( ! state ) return;
		const total = Number( state.total ) || 0;
		const considered = Number( state.considered ) || 0;
		const pct = total > 0 ? Math.floor( ( considered / total ) * 100 ) + '%' : '0%';
		setStat( 'prune.status', state.status || '' );
		setStat( 'prune.considered', considered );
		setStat( 'prune.total', total );
		setStat( 'prune.pct', pct );
		setStat( 'prune.pruned', Number( state.pruned ) || 0 );
		setStat( 'prune.skipped', Number( state.skipped ) || 0 );
		setStat( 'prune.bytes_freed', fmtBytes( state.bytes_freed ) );
		setStat( 'prune.last_error', state.last_error || '—' );
		setStat( 'prune.message', state.message || '' );
		const out = document.getElementById( 'r2-prune-result' );
		if ( out && state.errors ) {
			out.textContent = state.errors.length
				? JSON.stringify( state.errors, null, 2 )
				: ( state.status === 'error' ? ( state.message || state.last_error || '' ) : '' );
		}
		const running = state.status === 'running';
		setPruneButtons( running );
	}

	function showPruneFailure( data ) {
		const state = Object.assign( {
			status: 'error',
			last_error: ( data && ( data.last_error || data.message ) ) || 'Request failed',
			message: ( data && data.message ) || 'Prune request failed.',
			errors: ( data && data.errors ) || [],
		}, data || {} );
		renderPruneProgress( state );
	}

	async function runPruneLoop() {
		if ( pruning ) return;
		pruning = true;
		setPruneButtons( true );
		try {
			while ( true ) {
				const r = await post( 'r2_offload_prune_tick', { batch: 25 } );
				if ( ! r || ! r.success ) {
					showPruneFailure( r && r.data ? r.data : { message: 'Prune tick failed.' } );
					return;
				}
				renderPruneProgress( r.data );
				if ( ! r.data || r.data.status !== 'running' ) break;
			}
		} catch ( e ) {
			showPruneFailure( { message: String( e && e.message ? e.message : e ) } );
		} finally {
			pruning = false;
		}
	}

	document.addEventListener( 'click', async ( e ) => {
		const btn = e.target.closest( '[data-r2-action]' );
		if ( ! btn ) return;
		e.preventDefault();
		const act = btn.dataset.r2Action;

		if ( act === 'scan-start' ) {
			await post( 'r2_offload_scan_start', { dry: btn.dataset.dry || '0' } );
			runScanLoop();
			return;
		}
		if ( act === 'scan-reset' ) {
			await post( 'r2_offload_scan_reset' );
			const r = await post( 'r2_offload_progress' );
			if ( r && r.success ) renderProgress( r.data );
			return;
		}
		if ( act.startsWith( 'migrate-' ) ) {
			const op   = act.replace( 'migrate-', '' );
			const body = { op };
			if ( op === 'start' ) {
				body.batch_size  = document.getElementById( 'r2-batch-size' ).value;
				body.concurrency = document.getElementById( 'r2-concurrency' ).value;
			}
			const r = await post( 'r2_offload_migrate', body );
			if ( r && r.success ) renderProgress( { migrate: r.data } );
			if ( op === 'start' || op === 'resume' || op === 'retry' ) tickLoop();
			return;
		}
		if ( act === 'retry-one' ) {
			const id = btn.dataset.id;
			await post( 'r2_offload_retry_one', { attachment_id: id } );
			tickLoop();
			return;
		}
		if ( act === 'prune-start' ) {
			setPruneButtons( true );
			setStat( 'prune.status', 'running' );
			setStat( 'prune.message', 'Starting…' );
			const r = await post( 'r2_offload_prune_start', { dry: btn.dataset.dry || '0', batch: 25 } );
			if ( ! r || ! r.success ) {
				showPruneFailure( r && r.data ? r.data : { message: 'Prune start failed.' } );
				return;
			}
			renderPruneProgress( r.data );
			if ( r.data && r.data.status === 'running' ) {
				runPruneLoop();
			} else {
				setPruneButtons( false );
			}
			return;
		}
		if ( act === 'prune-stop' ) {
			const r = await post( 'r2_offload_prune_stop' );
			if ( r && r.success ) renderPruneProgress( r.data );
			else showPruneFailure( r && r.data ? r.data : { message: 'Prune stop failed.' } );
			return;
		}
		if ( act === 'restore' ) {
			const id = document.getElementById( 'r2-restore-id' ).value;
			const out = document.getElementById( 'r2-prune-result' );
			out.textContent = 'Restoring…';
			const r = await post( 'r2_offload_restore', { attachment_id: id } );
			out.textContent = JSON.stringify( r.data || r, null, 2 );
			return;
		}
		if ( act === 'debug-dump' ) {
			const out = document.getElementById( 'r2-debug-result' );
			out.textContent = 'Loading…';
			const r = await post( 'r2_offload_debug' );
			out.textContent = JSON.stringify( r.data || r, null, 2 );
			return;
		}
		if ( act === 'verify-one' ) {
			const id  = document.getElementById( 'r2-verify-id' ).value;
			const out = document.getElementById( 'r2-verify-result' );
			out.textContent = 'Verifying...';
			const r = await post( 'r2_offload_verify', { attachment_id: id } );
			out.textContent = JSON.stringify( r.data || r, null, 2 );
			return;
		}
		if ( act === 'install-deps' ) {
			const status = document.querySelector( '[data-r2-deps-status]' );
			btn.disabled = true;
			if ( status ) status.textContent = ' Installing… this may take a minute.';
			const r = await post( 'r2_offload_install_deps' );
			if ( status ) status.textContent = ' ' + ( r && r.data && r.data.message ? r.data.message : 'Done.' );
			if ( r && r.data && r.data.ok ) setTimeout( () => location.reload(), 1000 );
			btn.disabled = false;
			return;
		}
	} );

	document.querySelectorAll( 'input[name="r2-serve-source"]' ).forEach( el => {
		el.addEventListener( 'change', async ( ev ) => {
			await post( 'r2_offload_serve_source', { source: ev.target.value } );
		} );
	} );

	// --- Settings: provider-aware field toggling ---
	const settingsPanel = document.getElementById( 'r2-offload-settings' );
	if ( settingsPanel ) {
		const sel        = document.getElementById( 'provider' );
		const cdnReq     = [ 'r2', 'bunny' ];
		const regionsMap = ( window.R2Offload && window.R2Offload.regions ) || {};
		const guidesMap  = ( window.R2Offload && window.R2Offload.guides ) || {};

		function linkify( s ) {
			return escapeHtml( s ).replace(
				/(https?:\/\/[^\s]+)/g,
				'<a href="$1" target="_blank" rel="noopener">$1</a>'
			);
		}

		function applyProvider() {
			const prov = sel.value;
			settingsPanel.querySelectorAll( '[data-prov]' ).forEach( el => {
				const list = el.getAttribute( 'data-prov' ).split( /\s+/ );
				el.style.display = ( list.indexOf( 'all' ) !== -1 || list.indexOf( prov ) !== -1 ) ? '' : 'none';
			} );
			// CDN-required hint.
			const cdnNote = settingsPanel.querySelector( '.r2-prov-cdn-required' );
			if ( cdnNote ) cdnNote.style.display = cdnReq.indexOf( prov ) !== -1 ? '' : 'none';
			// Bunny secret note.
			const bNote = settingsPanel.querySelector( '.r2-prov-bunny-note' );
			if ( bNote ) bNote.style.display = prov === 'bunny' ? '' : 'none';
			// Region datalist.
			const dl = document.getElementById( 'r2-region-list' );
			if ( dl ) {
				dl.innerHTML = ( regionsMap[ prov ] || [] ).map( r => '<option value="' + escapeHtml( r ) + '"></option>' ).join( '' );
			}
			// Setup guide: swap summary + steps to match the chosen provider.
			const guide = guidesMap[ prov ];
			if ( guide ) {
				const sum = settingsPanel.querySelector( '.r2-setup-guide summary strong' );
				if ( sum ) sum.textContent = guide.summary;
				const ol = settingsPanel.querySelector( '.r2-guide-steps' );
				if ( ol ) ol.innerHTML = ( guide.steps || [] ).map( s => '<li>' + linkify( s ) + '</li>' ).join( '' );
			}
		}
		sel.addEventListener( 'change', applyProvider );
		applyProvider();

		// Keep the serve-switch and the "Rewrite media URLs" checkbox in sync so a
		// later Save Settings can't silently override an instant switch change.
		const serveRadios = settingsPanel.querySelectorAll( 'input[name="r2-serve-source"]' );
		const rewriteCb   = settingsPanel.querySelector( 'input[name="rewrite_urls"]' );
		serveRadios.forEach( r => r.addEventListener( 'change', ev => {
			if ( rewriteCb ) rewriteCb.checked = ( ev.target.value === 'remote' );
		} ) );
		if ( rewriteCb ) {
			rewriteCb.addEventListener( 'change', () => {
				const val = rewriteCb.checked ? 'remote' : 'local';
				serveRadios.forEach( r => { r.checked = ( r.value === val ); } );
			} );
		}
	}

	// --- Providers & Pricing: cost estimator ---
	function moneyRow( e, current ) {
		const m = e.monthly;
		const mark = e.provider === current ? ' ✓' : '';
		return '<tr' + ( e.provider === current ? ' style="font-weight:600"' : '' ) + '>' +
			'<td>' + escapeHtml( e.label ) + mark + '</td>' +
			'<td>$' + m.total.toFixed( 2 ) + '/mo</td>' +
			'<td>$' + m.storage.toFixed( 2 ) + '</td>' +
			'<td>$' + m.egress.toFixed( 2 ) + '</td>' +
			'<td>$' + m.reads.toFixed( 2 ) + '</td>' +
			'<td>$' + m.base.toFixed( 2 ) + '</td>' +
			'<td>$' + e.onetime.total.toFixed( 2 ) + '</td>' +
		'</tr>';
	}

	document.addEventListener( 'click', async ( e ) => {
		const btn = e.target.closest( '[data-r2-action="estimate"]' );
		if ( ! btn ) return;
		e.preventDefault();
		const gb      = document.getElementById( 'r2-est-gb' ).value;
		const objects = document.getElementById( 'r2-est-objects' ).value;
		const egress  = document.getElementById( 'r2-est-egress' ).value;
		btn.disabled = true;
		const r = await post( 'r2_offload_estimate', { gb, objects, egress } );
		btn.disabled = false;
		if ( ! r || ! r.success ) return;
		const table = document.getElementById( 'r2-est-table' );
		const body  = document.querySelector( '[data-r2-est-body]' );
		const list  = Object.values( r.data.estimates );
		body.innerHTML = list.map( e => moneyRow( e, r.data.current ) ).join( '' );
		table.style.display = '';
		const cheapest = list[ 0 ];
		const notes = document.getElementById( 'r2-est-notes' );
		notes.innerHTML = '<div class="r2-est-cheapest"><strong>Cheapest monthly: ' + escapeHtml( cheapest.label ) +
			'</strong> at $' + cheapest.monthly.total.toFixed( 2 ) + '/mo (' + cheapest.stored_gb + ' GB, ~' +
			cheapest.assumed_egress_gb + ' GB egress).</div>' +
			'<h4>Hidden costs &amp; notes — ' + escapeHtml( cheapest.label ) + '</h4><ul class="r2-bullets">' +
			cheapest.hidden_costs.map( n => '<li>' + escapeHtml( n ) + '</li>' ).join( '' ) + '</ul>';
	} );

	const panel = document.getElementById( 'r2-offload-migration' );
	if ( panel ) {
		( async () => {
			const r = await post( 'r2_offload_progress' );
			if ( r && r.success ) {
				renderProgress( r.data );
				if ( r.data.prune ) renderPruneProgress( r.data.prune );
			}
			const status = r && r.data && r.data.migrate && r.data.migrate.control && r.data.migrate.control.status;
			if ( status === 'running' ) tickLoop();
			const pstatus = r && r.data && r.data.prune && r.data.prune.status;
			if ( pstatus === 'running' ) runPruneLoop();
			else setPruneButtons( false );
		} )();
		setInterval( async () => {
			if ( ticking || pruning ) return;
			const r = await post( 'r2_offload_progress' );
			if ( r && r.success ) {
				renderProgress( r.data );
				if ( r.data.prune ) renderPruneProgress( r.data.prune );
			}
		}, 8000 );
	}
} )();
