/**
 * GravityView Divi Visual Builder Component
 *
 * This React component renders GravityView content in the Divi Visual Builder.
 * It receives the computed __view_content prop from the PHP module and renders it.
 *
 * @package GravityKit\GravityView\Extensions\Divi
 * @since 3.0.0
 */

import React, { Component } from 'react';
import { applyBuilderPreviewFallbacks } from '@gv-pb-shared/preview-fallbacks';

// WordPress ships `@wordpress/i18n` as `wp.i18n`; use it directly so we don't
// need an extra npm dep or webpack external just for a handful of strings.
const { __ } = ( typeof window !== 'undefined' && window.wp && window.wp.i18n ) || { __: ( s ) => s };

/**
 * Wait for the shared GravityView asset loader to be available.
 * The asset loader is defined in gv-builder-asset-loader.js.
 *
 * @return {Promise} Promise that resolves when the asset loader is ready.
 */
const waitForAssetLoader = () => {
	return new Promise( ( resolve ) => {
		if ( window.gkGravityViewLoadAssets ) {
			resolve();
			return;
		}

		// Check every 50ms for up to 5 seconds
		let attempts = 0;
		const maxAttempts = 100;

		const checkInterval = setInterval( () => {
			attempts++;
			if ( window.gkGravityViewLoadAssets || attempts >= maxAttempts ) {
				clearInterval( checkInterval );
				if ( attempts >= maxAttempts && ! window.gkGravityViewLoadAssets ) {
					console.warn( 'GravityView: asset loader (gkGravityViewLoadAssets) not available after 5s.' );
				}
				resolve();
			}
		}, 50 );
	} );
};

/**
 * GravityView module component for Divi Visual Builder.
 */
class GravityView extends Component {
	/**
	 * Module slug - must match the PHP module slug.
	 *
	 * @type {string}
	 */
	static slug = 'gk_gravityview';

	/**
	 * Load styles when component mounts or updates.
	 */
	async componentDidMount() {
		this._isMounted = true;
		this._injectedStyles = [];
		await waitForAssetLoader();
		if ( this._isMounted ) {
			this.loadAssets();
		}
	}

	/**
	 * Clean up injected styles when component unmounts.
	 */
	componentWillUnmount() {
		this._isMounted = false;

		// Remove all styles injected by this component.
		if ( this._injectedStyles && this._injectedStyles.length ) {
			this._injectedStyles.forEach( ( styleEl ) => {
				if ( styleEl.parentNode ) {
					styleEl.parentNode.removeChild( styleEl );
				}
			} );
			this._injectedStyles = [];
		}
	}

	/**
	 * Load styles when props change.
	 */
	async componentDidUpdate( prevProps ) {
		if ( prevProps.__view_content !== this.props.__view_content ) {
			await waitForAssetLoader();
			if ( this._isMounted ) {
				this.loadAssets();
			}
		}
	}

	/**
	 * Parse the view content and load any assets using the shared asset loader.
	 */
	loadAssets() {
		const viewContent = this.props.__view_content;

		if ( ! viewContent ) {
			return;
		}

		let data = viewContent;

		// If it's a string, try to parse as JSON.
		if ( typeof viewContent === 'string' ) {
			try {
				data = JSON.parse( viewContent );
			} catch ( e ) {
				// Content is not JSON, assets may be inline in HTML.
				return;
			}
		}

		// Use the shared asset loader from gv-builder-asset-loader.js
		if ( window.gkGravityViewLoadAssets ) {
			window.gkGravityViewLoadAssets( {
				styles: data.styles || [],
				scripts: data.scripts || [],
			} );
		}

		// Extract and inject inline styles from HTML content (Visual Builder only).
		if ( data.content && this.isVisualBuilder() ) {
			this.injectInlineStyles( data.content );
		}
	}

	/**
	 * Check if we're in the Visual Builder context.
	 *
	 * @return {boolean} True if in Visual Builder.
	 */
	isVisualBuilder() {
		return typeof window !== 'undefined' &&
		       ( window.et_fb || window.ET_Builder );
	}

	/**
	 * Extract inline style tags from HTML and inject them into the document head.
	 *
	 * @param {string} html The HTML content containing inline styles.
	 */
	injectInlineStyles( html ) {
		// Remove previously injected styles before re-injecting on updates.
		if ( this._injectedStyles && this._injectedStyles.length ) {
			this._injectedStyles.forEach( ( styleEl ) => {
				if ( styleEl.parentNode ) {
					styleEl.parentNode.removeChild( styleEl );
				}
			} );
			this._injectedStyles = [];
		}

		// Create a temporary container to parse the HTML.
		const temp = document.createElement( 'div' );
		temp.innerHTML = html;

		// Find all style tags.
		const styleTags = temp.querySelectorAll( 'style' );

		styleTags.forEach( ( styleTag ) => {
			const styleId = styleTag.id;

			// Skip if already injected by another component instance.
			if ( styleId && document.getElementById( styleId ) ) {
				return;
			}

			// Clone and inject into document head.
			const newStyle = document.createElement( 'style' );
			newStyle.setAttribute( 'data-gv-inline-style', 'true' );
			if ( styleId ) {
				newStyle.id = styleId;
			}
			newStyle.textContent = styleTag.textContent;
			document.head.appendChild( newStyle );

			// Track for cleanup on unmount.
			if ( this._injectedStyles ) {
				this._injectedStyles.push( newStyle );
			}
		} );
	}

	/**
	 * Get the HTML content to render.
	 *
	 * @return {string|null} The HTML content.
	 */
	getContent() {
		const viewContent = this.props.__view_content;

		if ( ! viewContent ) {
			return null;
		}

		// If it's already an object, extract content directly.
		if ( typeof viewContent === 'object' ) {
			return viewContent.content || null;
		}

		// Try to parse as JSON string.
		if ( typeof viewContent === 'string' ) {
			try {
				const data = JSON.parse( viewContent );
				return data.content || null;
			} catch ( e ) {
				// Not JSON, return the string as-is.
				return viewContent;
			}
		}

		return null;
	}

	/**
	 * Render the module content.
	 *
	 * @return {JSX.Element} The rendered component.
	 */
	render() {
		const content = this.getContent();

		// If no content available, show placeholder.
		if ( ! content ) {
			return (
				<div className="gk-gravityview-placeholder" style={{
					textAlign: 'center',
					padding: '40px 20px',
					border: '2px dashed #ccc',
					borderRadius: '4px',
					backgroundColor: '#f9f9f9',
					color: '#666',
				}}>
					<div style={{
						fontSize: '16px',
						fontWeight: '500',
						marginBottom: '8px',
					}}>
						GravityView
					</div>
					<div style={{
						fontSize: '14px',
					}}>
						{ __( 'Please select a View from the module settings.', 'gk-gravityview' ) }
					</div>
				</div>
			);
		}

		const adjusted = this.isVisualBuilder()
			? applyBuilderPreviewFallbacks( content, {
				dataTablesNotice: __( 'Entries from the DataTables layout are not available in the Visual Builder preview.', 'gk-gravityview' ),
				mapsNotice: __( 'Map is not available in the Visual Builder preview.', 'gk-gravityview' ),
			} )
			: content;

		// Render the View HTML content.
		return (
			<div
				className="gk-gravityview-divi-module"
				dangerouslySetInnerHTML={{ __html: adjusted }}
			/>
		);
	}
}

/**
 * Create a module component for a given Divi module slug.
 *
 * Divi's Visual Builder requires each registered component to have its own `render`
 * method (it does not walk the prototype chain). This factory copies all prototype
 * methods from the base GravityView class onto each new subclass so Divi can find them.
 *
 * @param {string} moduleSlug The Divi module slug.
 * @return {Function} A React component class with the given slug.
 */
function createModuleComponent( moduleSlug ) {
	class Module extends GravityView {
		static slug = moduleSlug;
	}

	// Copy all prototype methods from GravityView so Divi sees them as own properties.
	Object.getOwnPropertyNames( GravityView.prototype ).forEach( ( method ) => {
		if ( method !== 'constructor' ) {
			Module.prototype[ method ] = GravityView.prototype[ method ];
		}
	} );

	return Module;
}

const GravityViewEntry = createModuleComponent( 'gk_gravityview_entry' );
const GravityViewEntryField = createModuleComponent( 'gk_gravityview_entry_field' );
const GravityViewEntryLink = createModuleComponent( 'gk_gravityview_entry_link' );
const GravityViewViewDetails = createModuleComponent( 'gk_gravityview_view_details' );

// Register all modules with Divi's Visual Builder API.
if ( typeof jQuery !== 'undefined' ) {
	jQuery( window ).on( 'et_builder_api_ready', function( _event, api ) {
		const modules = [
			GravityView,
			GravityViewEntry,
			GravityViewEntryField,
			GravityViewEntryLink,
			GravityViewViewDetails,
		];

		if ( api && api.registerModules ) {
			api.registerModules( modules );
		}
	} );
}

export default GravityView;
