<?php
/**
 * Favicon module. Injects a custom favicon at priority 9999 on the frontend,
 * admin, and login head — overriding theme defaults (notably Kadence).
 *
 * Ported from the standalone `bw-favicon` plugin. The original was missing a
 * `sanitize_callback` on its registered setting; this version sanitizes via
 * `esc_url_raw` on save and `esc_url` on output.
 *
 * @package BW_Dev
 */

defined( 'ABSPATH' ) || exit;

class BW_Dev_Module_Favicon implements BW_Dev_Module_Interface {

	public function slug(): string {
		return 'favicon';
	}

	public function label(): string {
		return __( 'Favicon', 'bw-dev' );
	}

	public function group(): string {
		return 'frontend';
	}

	public function default_settings(): array {
		return array( 'url' => '' );
	}

	public function sanitize( array $data ): array {
		$url = isset( $data['url'] ) ? wp_unslash( $data['url'] ) : '';
		// esc_url_raw rejects javascript: and other unsafe protocols while
		// accepting an empty string (used to clear the favicon).
		$url = esc_url_raw( (string) $url );
		return array( 'url' => $url );
	}

	public function register(): void {
		add_action( 'init',        array( $this, 'maybe_suppress_competitors' ), 0 );
		add_action( 'wp_head',     array( $this, 'output_favicon' ), 9999 );
		add_action( 'admin_head',  array( $this, 'output_favicon' ), 9999 );
		add_action( 'login_head',  array( $this, 'output_favicon' ), 9999 );
		add_action( 'admin_enqueue_scripts', array( $this, 'maybe_enqueue_media' ) );
	}

	/**
	 * When a BW Dev favicon URL is set we own the favicon — so suppress WP
	 * core's own `wp_site_icon` output (which Kadence and other themes can
	 * also pipe through). Without this, browsers may pick the wrong tag and
	 * the BW Dev favicon silently loses to whatever the theme/core injected
	 * earlier in the head.
	 *
	 * Only fires when there's a configured URL — when blank, WP's normal
	 * site-icon flow is left alone.
	 */
	public function maybe_suppress_competitors(): void {
		$url = (string) bw_dev()->settings()->get( $this->slug(), 'url', '' );
		if ( '' === $url ) {
			return;
		}
		// `wp_site_icon` is the canonical core hook that emits the WP Customizer
		// Site Icon tags (icon-32, icon-192, apple-touch-icon, msapplication-TileImage).
		// Hooked at priority 99 on all three head actions — match exactly to remove.
		remove_action( 'wp_head',    'wp_site_icon', 99 );
		remove_action( 'admin_head', 'wp_site_icon', 99 );
		remove_action( 'login_head', 'wp_site_icon', 99 );
	}

	/**
	 * Enqueue wp.media only when our settings tab is the active one, so the
	 * library doesn't load on unrelated admin pages.
	 */
	public function maybe_enqueue_media( $hook ): void {
		if ( 'settings_page_' . BW_Dev_Admin_Page::MENU_SLUG !== $hook ) {
			return;
		}
		$query = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$tab   = isset( $query['tab'] ) ? sanitize_key( (string) $query['tab'] ) : 'modules';
		if ( $this->slug() !== $tab ) {
			return;
		}
		wp_enqueue_media();
	}

	public function output_favicon(): void {
		$url = (string) bw_dev()->settings()->get( $this->slug(), 'url', '' );
		if ( '' === $url ) {
			return;
		}
		$safe = esc_url( $url );
		echo "\n";
		echo '<link rel="icon" href="' . $safe . '" />' . "\n";
		echo '<link rel="shortcut icon" href="' . $safe . '" />' . "\n";
		echo '<link rel="apple-touch-icon" href="' . $safe . '" />' . "\n";
	}

	public function render_tab(): void {
		$url      = (string) bw_dev()->settings()->get( $this->slug(), 'url', '' );
		$is_set   = '' !== $url;
		$field_id = 'bw-dev-favicon-url';
		$field_nm = BW_Dev_Settings::OPTION . '[' . $this->slug() . '][url]';
		?>
		<div style="background:#fff;border-left:4px solid #2271b1;padding:12px 16px;margin:14px 0;max-width:720px;">
			<strong><?php esc_html_e( 'Why this exists', 'bw-dev' ); ?></strong>
			<p style="margin:6px 0 0;">
				<?php esc_html_e( 'This module exists to forcefully set a favicon when Kadence (or another theme) fails to render one. The normal route is WordPress\'s built-in "Site Icon" in the Customizer — use that first. Set a URL here only when that doesn\'t take effect.', 'bw-dev' ); ?>
			</p>
		</div>

		<p class="description">
			<?php esc_html_e( 'Upload or paste a square PNG or ICO. When set, the favicon is forcefully injected on every page (frontend, admin, login) using all three of:', 'bw-dev' ); ?>
		</p>
		<ul style="list-style:disc;margin-left:24px;">
			<li><code>&lt;link rel="icon"&gt;</code></li>
			<li><code>&lt;link rel="shortcut icon"&gt;</code></li>
			<li><code>&lt;link rel="apple-touch-icon"&gt;</code></li>
		</ul>
		<p class="description">
			<?php esc_html_e( 'Output runs at priority 9999 so it lands after every theme + plugin. WordPress core\'s own "Site Icon" output (wp_site_icon) is also suppressed while this URL is set, so the two don\'t fight for the browser\'s attention.', 'bw-dev' ); ?>
		</p>

		<?php if ( $is_set ) : ?>
			<div style="background:#fff;border-left:4px solid #00a32a;padding:12px 16px;margin:14px 0;max-width:720px;">
				<strong><?php esc_html_e( 'Currently active', 'bw-dev' ); ?></strong>
				<p style="margin:6px 0 0;">
					<?php esc_html_e( 'A favicon is set — view source on the homepage and look for the BW Dev <link rel="icon"> tag near the bottom of the <head> to confirm it\'s rendering.', 'bw-dev' ); ?>
				</p>
			</div>
		<?php else : ?>
			<div style="background:#fff;border-left:4px solid #dba617;padding:12px 16px;margin:14px 0;max-width:720px;">
				<strong><?php esc_html_e( 'Feature inactive', 'bw-dev' ); ?></strong>
				<p style="margin:6px 0 0;">
					<?php esc_html_e( 'No favicon URL set — the module does nothing. Theme/Customizer favicon (if any) works normally.', 'bw-dev' ); ?>
				</p>
			</div>
		<?php endif; ?>
		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row">
						<label for="<?php echo esc_attr( $field_id ); ?>"><?php esc_html_e( 'Favicon image', 'bw-dev' ); ?></label>
					</th>
					<td>
						<div class="bw-dev-favicon-row" style="display:flex; align-items:center; gap:10px;">
							<input
								type="text"
								id="<?php echo esc_attr( $field_id ); ?>"
								name="<?php echo esc_attr( $field_nm ); ?>"
								value="<?php echo esc_attr( $url ); ?>"
								class="regular-text"
								placeholder="https://..."
							/>
							<button type="button" id="bw-dev-favicon-upload" class="button button-secondary">
								<?php esc_html_e( 'Upload / Select', 'bw-dev' ); ?>
							</button>
							<button type="button" id="bw-dev-favicon-clear" class="button button-link-delete">
								<?php esc_html_e( 'Clear', 'bw-dev' ); ?>
							</button>
						</div>
						<div id="bw-dev-favicon-preview" style="margin-top:15px;">
							<?php if ( '' !== $url ) : ?>
								<p><strong><?php esc_html_e( 'Preview:', 'bw-dev' ); ?></strong></p>
								<img src="<?php echo esc_url( $url ); ?>" alt="" style="max-width:64px; border:1px solid #ccc; padding:5px; background:#fff;" />
							<?php endif; ?>
						</div>
					</td>
				</tr>
			</tbody>
		</table>
		<script>
		(function ($) {
			$(function () {
				var $url     = $('#<?php echo esc_js( $field_id ); ?>');
				var $preview = $('#bw-dev-favicon-preview');
				var frame;

				$('#bw-dev-favicon-upload').on('click', function (e) {
					e.preventDefault();
					if (frame) { frame.open(); return; }
					frame = wp.media({
						title:    <?php echo wp_json_encode( __( 'Choose favicon', 'bw-dev' ) ); ?>,
						button:   { text: <?php echo wp_json_encode( __( 'Use as favicon', 'bw-dev' ) ); ?> },
						multiple: false
					});
					frame.on('select', function () {
						var attachment = frame.state().get('selection').first().toJSON();
						$url.val(attachment.url).trigger('change');
					});
					frame.open();
				});

				$('#bw-dev-favicon-clear').on('click', function (e) {
					e.preventDefault();
					$url.val('').trigger('change');
				});

				$url.on('change', function () {
					var val = $url.val();
					if (val) {
						$preview.html(
							'<p><strong><?php echo esc_js( __( 'Preview:', 'bw-dev' ) ); ?></strong></p>' +
							'<img src="' + val + '" alt="" style="max-width:64px; border:1px solid #ccc; padding:5px; background:#fff;" />'
						);
					} else {
						$preview.empty();
					}
				});
			});
		})(jQuery);
		</script>
		<?php
	}

	public function uninstall(): void {
		// No-op. The root option drop in uninstall.php removes persisted state.
	}
}
