<?php
/**
 * Script and style registration handler.
 *
 * @package PDFDraft
 */

namespace PDFDraft;

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

use PDFDraft\Traits\Hooks;

/**
 * Registers and manages plugin scripts and styles.
 */
class ScriptStyle {

	use Hooks;

	/**
	 * Whether the plugin is in development mode.
	 *
	 * @var bool
	 */
	private bool $isDev = false;

	/**
	 * Registered scripts configuration.
	 *
	 * @var array<string,array{deps: array<string>,version: string,src: string,callback: callable|null,i18n: bool}>
	 */
	protected array $scripts = array();

	/**
	 * Registered styles configuration.
	 *
	 * @var array<string,array{deps: array<string>,version: string,src: string,callback: callable|null}>
	 */
	protected array $styles = array();

	/**
	 * Default script configuration values.
	 *
	 * @var array
	 */
	private array $scriptDefaults = array(
		'deps'     => array(),
		'version'  => PDFDRAFT_VERSION,
		'src'      => '',
		'callback' => null,
		'i18n'     => false,
	);

	/**
	 * Default style configuration values.
	 *
	 * @var array
	 */
	private array $styleDefaults = array(
		'deps'     => array(),
		'version'  => PDFDRAFT_VERSION,
		'src'      => '',
		'callback' => null,
	);

	/**
	 * Constructor.
	 *
	 * @return void
	 */
	public function __construct() {
		$this->isDev = defined( 'PDFDRAFT_IS_DEVELOPMENT' ) && PDFDRAFT_IS_DEVELOPMENT;
	}

	/**
	 * Register the init action hook.
	 *
	 * @return void
	 */
	public function init() {
		$this->addAction( 'init', array( $this, 'onInit' ), 0 );
	}

	/**
	 * Handle the init action to set up scripts and styles.
	 *
	 * @return void
	 */
	public function onInit() {
		$this->initLibs();

		$editorAsset            = $this->getAsset( PDFDRAFT_DIST_DIR . '/editor.asset.php' );
		$settingsAsset          = $this->getAsset( PDFDRAFT_DIST_DIR . '/settings.asset.php' );
		$embedPdfBlockAsset     = $this->getAsset( PDFDRAFT_DIST_DIR . '/embed-pdf-block.asset.php' );
		$downloadPdfBlockAsset  = $this->getAsset( PDFDRAFT_DIST_DIR . '/download-pdf-block.asset.php' );
		$embedPdfViewerAsset    = $this->getAsset( PDFDRAFT_DIST_DIR . '/embed-pdf-viewer.asset.php' );
		$runtimePath            = PDFDRAFT_DIST_DIR . '/runtime.js';
		$runtimeOnDisk          = file_exists( $runtimePath );
		// Dev server may load runtime from localhost without dist/runtime.js; embed viewer always uses dist file.
		$hasRuntime             = $this->isDev || $runtimeOnDisk;
		if ( $hasRuntime ) {
			$this->scripts['runtime'] = array(
				'src'      => $this->isDev ? 'http://localhost:8887/runtime.js' : trailingslashit( PDFDRAFT_DIST_URI ) . 'runtime.js',
				'deps'     => array(),
				'version'  => $this->isDev ? md5( time() . wp_rand( 0, 1000 ) ) : (string) filemtime( $runtimePath ),
				'i18n'     => false,
				'callback' => $this->isDev ? null : static fn (): bool => file_exists( PDFDRAFT_DIST_DIR . '/runtime.js' ),
			);
		}
		if ( $runtimeOnDisk ) {
			$this->scripts['embed-runtime'] = array(
				'src'     => trailingslashit( PDFDRAFT_DIST_URI ) . 'runtime.js',
				'deps'    => array(),
				'version' => (string) filemtime( $runtimePath ),
				'i18n'    => false,
			);
		}

		$this->scripts['editor'] = array(
			'src'     => $this->getAssetUri( 'editor.js' ),
			'deps'    => array_merge( $editorAsset['dependencies'], $hasRuntime ? array( 'pdfdraft-runtime' ) : array() ),
			'version' => $editorAsset['version'],
			'i18n'    => true,
		);

		$this->styles['editor'] = array(
			'src'     => $this->getAssetUri( 'editor.css' ),
			'deps'    => array(),
			'version' => $editorAsset['version'],
		);

		$this->scripts['settings'] = array(
			'src'     => $this->getAssetUri( 'settings.js' ),
			'deps'    => array_merge( $settingsAsset['dependencies'], $hasRuntime ? array( 'pdfdraft-runtime' ) : array() ),
			'version' => $settingsAsset['version'],
			'i18n'    => true,
		);

		$this->styles['settings'] = array(
			'src'     => $this->getAssetUri( 'settings.css' ),
			'deps'    => array(),
			'version' => $settingsAsset['version'],
		);

		$this->styles['settings-shell'] = array(
			'src'     => trailingslashit( PDFDRAFT_PLUGIN_DIR_URL ) . 'assets/settings-shell.css',
			'deps'    => array(),
			'version' => PDFDRAFT_VERSION,
		);

		$embedPdfBlockDeps                 = array(
			'wp-blocks',
			'wp-element',
			'wp-i18n',
			'wp-components',
			'wp-block-editor',
			'wp-api-fetch',
		);
		$this->scripts['embed-pdf-block']  = array(
			// Use dist asset for post editor compatibility even in dev mode.
			'src'     => trailingslashit( PDFDRAFT_DIST_URI ) . 'embed-pdf-block.js',
			'deps'    => array_values(
				array_unique(
					array_merge(
						$embedPdfBlockAsset['dependencies'],
						$embedPdfBlockDeps
					)
				)
			),
			'version' => $embedPdfBlockAsset['version'],
			'i18n'    => true,
		);

		$this->scripts['download-pdf-block'] = array(
			'src'     => trailingslashit( PDFDRAFT_DIST_URI ) . 'download-pdf-block.js',
			'deps'    => array_values(
				array_unique(
					array_merge(
						$downloadPdfBlockAsset['dependencies'],
						$embedPdfBlockDeps
					)
				)
			),
			'version' => $downloadPdfBlockAsset['version'],
			'i18n'    => true,
		);
		$this->scripts['embed-pdf-viewer'] = array(
			'src'     => trailingslashit( PDFDRAFT_DIST_URI ) . 'embed-pdf-viewer.js',
			'deps'    => array_values(
				array_unique(
					array_merge(
						$embedPdfViewerAsset['dependencies'],
						$runtimeOnDisk ? array( 'pdfdraft-embed-runtime' ) : array()
					)
				)
			),
			'version' => $embedPdfViewerAsset['version'],
			'i18n'    => false,
		);
		$this->styles['embed-pdf-viewer']  = array(
			'src'     => trailingslashit( PDFDRAFT_DIST_URI ) . 'embed-pdf-viewer.css',
			'deps'    => array(),
			'version' => $embedPdfViewerAsset['version'],
		);
		// Posts to PDF frontend bundle
		$postsFrontendAssetPath = PDFDRAFT_DIST_DIR . '/posts-to-pdf-frontend.asset.php';
		if ( $this->isDev || file_exists( $postsFrontendAssetPath ) ) {
			$postsFrontendAsset                       = file_exists( $postsFrontendAssetPath ) ? $this->getAsset( $postsFrontendAssetPath ) : array( 'dependencies' => array(), 'version' => md5( (string) time() ) );
			$this->scripts['posts-to-pdf-frontend']   = array(
				'src'     => $this->getAssetUri( 'posts-to-pdf-frontend.js' ),
				'deps'    => array_merge( $postsFrontendAsset['dependencies'], $hasRuntime ? array( 'pdfdraft-runtime' ) : array() ),
				'version' => $this->isDev ? md5( time() . wp_rand( 0, 1000 ) ) : $postsFrontendAsset['version'],
				'i18n'    => true,
				// Only register here; PostsToPdf::enqueue_frontend_scripts() handles conditional enqueue.
				'callback' => static fn (): bool => true,
			);
		}

		// Posts to PDF Gutenberg sidebar panel
		$gutenbergAssetPath = PDFDRAFT_DIST_DIR . '/gutenberg-posts-to-pdf.asset.php';
		if ( $this->isDev || file_exists( $gutenbergAssetPath ) ) {
			$gutenbergAsset                              = file_exists( $gutenbergAssetPath ) ? $this->getAsset( $gutenbergAssetPath ) : array( 'dependencies' => array(), 'version' => md5( (string) time() ) );
			$this->scripts['gutenberg-posts-to-pdf']     = array(
				'src'     => $this->getAssetUri( 'gutenberg-posts-to-pdf.js' ),
				'deps'    => array_merge( $gutenbergAsset['dependencies'], $hasRuntime ? array( 'pdfdraft-runtime' ) : array() ),
				'version' => $this->isDev ? md5( time() . wp_rand( 0, 1000 ) ) : $gutenbergAsset['version'],
				'i18n'    => true,
				'callback' => static fn (): bool => true,
			);
		}

		$this->scripts                     = $this->applyFilters(
			'pdfdraft_scripts',
			$this->scripts
		);
		$this->styles                      = $this->applyFilters(
			'pdfdraft_styles',
			$this->styles
		);

		$this->register();
	}

	/**
	 * Initialize library scripts from the lib directory.
	 *
	 * @return void
	 */
	private function initLibs() {
		$libDir = PDFDRAFT_PLUGIN_DIR . '/lib';
		if ( ! is_dir( $libDir ) ) {
			return;
		}
		$entries = scandir( $libDir );
		if ( false === $entries ) {
			return;
		}

		foreach ( $entries as $lib ) {
			if ( '.' === $lib || '..' === $lib ) {
				continue;
			}
			if ( ! is_dir( $libDir . DIRECTORY_SEPARATOR . $lib ) ) {
				continue;
			}
			$libAssetPath = $libDir . DIRECTORY_SEPARATOR . $lib . DIRECTORY_SEPARATOR . $lib . '.asset.php';
			if ( ! file_exists( $libAssetPath ) ) {
				continue;
			}
			$libAsset              = $this->getAsset( $libAssetPath );
			$this->scripts[ $lib ] = array(
				'src'     => plugin_dir_url( PDFDRAFT_PLUGIN_FILE ) . "lib/$lib/$lib.js",
				'deps'    => $libAsset['dependencies'] ?? array(),
				'version' => $libAsset['version'] ?? PDFDRAFT_VERSION,
				'i18n'    => true,
			);
		}
	}

	/**
	 * Register all scripts and styles.
	 *
	 * @return void
	 */
	private function register() {
		$this->registerScripts();
		$this->registerStyles();
	}

	/**
	 * Register plugin scripts with WordPress.
	 *
	 * @return void
	 */
	private function registerScripts() {
		if ( empty( $this->scripts ) ) {
			return;
		}
		foreach ( $this->scripts as $handle => $script ) {
			$script ??= array();
			$config   = array_merge(
				$this->scriptDefaults,
				$script,
				array( 'handle' => "pdfdraft-$handle" )
			);

			if ( ! $this->shouldRegister( $config ) ) {
				continue;
			}

			wp_register_script( // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NotInFooter
				$config['handle'],
				$config['src'],
				$config['deps'],
				$config['version'],
				array(
					'in_footer' => true,
				)
			);

			$config['i18n'] && wp_set_script_translations(
				$config['handle'],
				'pdfdraft',
				PDFDRAFT_LANGUAGE_DIR
			);
		}

		$this->doAction( 'pdfdraft_scripts_registered', $this->scripts );
	}

	/**
	 * Register plugin styles with WordPress.
	 *
	 * @return void
	 */
	private function registerStyles() {
		if ( empty( $this->styles ) ) {
			return;
		}
		foreach ( $this->styles as $handle => $style ) {
			$handle  = "pdfdraft-$handle";
			$style ??= array();
			$config = array_merge(
				$this->styleDefaults,
				$style
			);

			if ( ! $this->shouldRegister( $config ) ) {
				continue;
			}

			wp_register_style(
				$handle,
				$config['src'],
				$config['deps'],
				$config['version']
			);
		}

		$this->doAction( 'pdfdraft_styles_registered', $this->styles );
	}

	/**
	 * Determine whether a script or style should be registered.
	 *
	 * @param array $config Script or style configuration array.
	 * @return bool
	 */
	private function shouldRegister( array $config ) {
		if ( ! isset( $config['callback'] ) ) {
			return true;
		}

		if ( ! is_callable( $config['callback'] ) ) {
			return true;
		}

		return (bool) call_user_func_array( $config['callback'], array() );
	}

	/**
	 * Get asset metadata from a PHP asset file.
	 *
	 * @param string $filepath Path to the asset metadata file.
	 * @return array{dependencies: array<string>, version: string}
	 */
	public function getAsset( string $filepath ): array {
		$filepath = wp_normalize_path( $filepath );
		$handle   = basename( dirname( $filepath ) );
		return $this->applyFilters(
			"pdfdraft_asset_$handle",
			file_exists( $filepath )
			? include $filepath : array(
				'dependencies' => array(),
				'version'      => PDFDRAFT_VERSION,
			)
		);
	}

	/**
	 * Get the URI for an asset file.
	 *
	 * @param string $path Relative path to the asset.
	 * @return string
	 */
	public function getAssetUri( string $path ): string {
		if ( $this->isDev ) {
			return "http://localhost:8887/$path";
		}
		return trailingslashit( PDFDRAFT_DIST_URI ) . $path;
	}
}
