import React from 'react';
import { __ } from '@wordpress/i18n';

/**
 * The screen that appears instead of nothing.
 *
 * React unmounts the whole tree when a render throws, so before this existed
 * any single broken component left an empty white panel where the plugin
 * should be — no message, no route, nothing to send to support. "The page
 * does not work" was all anyone could report, and it was all we could know.
 *
 * The most likely cause is not a bug in a component at all. The admin app is
 * split into per-page chunks, so a page cache that keeps serving yesterday's
 * `index.js` after an update makes it ask for chunk files that no longer
 * exist. The dynamic import rejects, Suspense never resolves, and the panel
 * stays blank forever. That case is detected and given the fix that actually
 * works — a reload that cannot be served from cache.
 */
export default class Boundary extends React.Component {
	constructor(props) {
		super(props);
		this.state = { error: null, info: null };
	}

	static getDerivedStateFromError(error) {
		return { error };
	}

	/**
	 * A caught error is about one screen, not about the session.
	 *
	 * The boundary wraps the whole application, so until this existed a single
	 * bad screen bricked the plugin until somebody pressed one of the two
	 * buttons below: the sidebar is inside the boundary and therefore gone,
	 * and the browser's back button changed the hash while the same error
	 * panel stayed on top of it. Moving to another route clears it.
	 */
	componentDidMount() {
		this.onHash = () => {
			if (this.state.error) this.setState({ error: null, info: null });
		};
		window.addEventListener('hashchange', this.onHash);
	}

	componentWillUnmount() {
		window.removeEventListener('hashchange', this.onHash);
	}

	componentDidCatch(error, info) {
		this.setState({ info });
		// Kept: the browser console is where a developer looks first, and the
		// boundary swallowing the stack would make this harder to debug, not
		// easier.
		// eslint-disable-next-line no-console
		console.error('AI Rank Suite: a screen failed to render.', error, info);
	}

	/** A stale bundle asking for chunks that were replaced by the update. */
	isChunkFailure() {
		const message = String(this.state.error?.message || '');
		return (
			/Loading chunk|dynamically imported module|Importing a module script failed|Failed to fetch dynamically/i.test(
				message
			) || /ChunkLoadError/i.test(String(this.state.error?.name || ''))
		);
	}

	hardReload() {
		// A plain reload can be answered from the same cache that caused this,
		// so the URL is changed. The hash is preserved: landing back on the
		// screen that failed is the point.
		const url = new URL(window.location.href);
		url.searchParams.set('airanksuite-reload', String(Date.now()));
		window.location.replace(url.toString());
	}

	render() {
		const { error } = this.state;
		if (!error) return this.props.children;

		const chunk = this.isChunkFailure();
		const version = (window.airanksuiteData || {}).version || 'unknown';
		const route = window.location.hash || '#/';
		const detail = [
			`AI Rank Suite ${version}`,
			`Screen: ${route}`,
			`Error: ${error.name || 'Error'}: ${error.message || String(error)}`,
			this.state.info?.componentStack ? `Component: ${String(this.state.info.componentStack).trim().split('\n')[1] || ''}` : '',
		]
			.filter(Boolean)
			.join('\n');

		return (
			<div className="ars-boundary">
				<div className="ars-boundary-mark" aria-hidden>
					!
				</div>

				<div className="grid gap-2 min-w-0">
					<h2 className="m-0 text-[17px] font-semibold">
						{chunk ? __('This screen was served from a stale cache', 'ai-rank-suite') : __('This screen could not be drawn', 'ai-rank-suite')}
					</h2>

					<p className="m-0 text-[13px]" style={{ color: 'var(--ars-ink-2)', maxWidth: '68ch' }}>
						{chunk
							? __('The plugin was updated but your browser or a caching plugin is still serving the previous version of its code, which asks for files that no longer exist. Reloading past the cache fixes it. If it comes back, clear the site cache (LiteSpeed, WP Rocket, Cloudflare) once.', 'ai-rank-suite')
							: __('Something in this screen threw an error, so it was stopped rather than left half-drawn. The rest of the plugin is unaffected — every other screen, and everything on the front end, keeps working.', 'ai-rank-suite')}
					</p>

					<div className="flex items-center gap-2 flex-wrap mt-1">
						<button type="button" className="ars-btn ars-btn-primary" onClick={() => this.hardReload()}>{__('Reload past the cache', 'ai-rank-suite')}</button>
						<button
							type="button"
							className="ars-btn ars-btn-ghost"
							onClick={() => {
								window.location.hash = '/dashboard';
								this.setState({ error: null, info: null });
							}}
						>{__('Back to the dashboard', 'ai-rank-suite')}</button>
						<button
							type="button"
							className="ars-linkbtn"
							onClick={() => {
								try {
									navigator.clipboard.writeText(detail);
								} catch (e) {
									/* A refused clipboard must not replace one error with another. */
								}
							}}
						>{__('Copy the details', 'ai-rank-suite')}</button>
					</div>

					{/* The detail, shown rather than hidden: it is what makes a
					    support message useful, and it contains nothing private. */}
					<pre className="ars-boundary-detail">{detail}</pre>
				</div>
			</div>
		);
	}
}
