<?php
/**
 * Title Override module.
 *
 * Adds a "Page Title Override" meta box to the post-edit screen for any
 * configured post type and replaces the front-end H1 hero title with the
 * override when set.
 *
 * Theme-agnostic: hooks the standard WordPress `the_title` filter, which
 * Kadence's title template (`the_title( '<h1 class="entry-title">', '</h1>' )`)
 * respects directly. Works on any theme that calls `the_title()` for the
 * page heading.
 *
 * Strict gating keeps the override confined to the main H1 only — it does
 * NOT change the browser tab title, OG/Twitter meta tags, admin list-table
 * titles, nav-menu item labels, or breadcrumb segments.
 *
 * Allowed HTML in the override (sanitized at save time via wp_kses):
 *   <em>, <strong>, <i>, <b>, <br>, <span class="…">
 *
 * Storage: post meta key `_bw_dev_title_override` — underscore prefix hides
 * it from the Custom Fields UI; default-state posts store nothing.
 *
 * @package BW_Dev
 */

defined( 'ABSPATH' ) || exit;

class BW_Dev_Module_Title_Override implements BW_Dev_Module_Interface {

	const META_KEY    = '_bw_dev_title_override';
	const NONCE_FIELD = 'bw_dev_title_override_nonce';
	const NONCE_ACTION = 'bw_dev_title_override_save';

	/**
	 * Defaults: enabled on `page` only. Posts and CPTs are opt-in via the
	 * settings tab — most blogs don't want the override on every blog post.
	 */
	private const DEFAULTS = array(
		'post_types' => array( 'page' ),
	);

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

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

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

	public function default_settings(): array {
		return self::DEFAULTS;
	}

	public function sanitize( array $data ): array {
		$valid_pts = array_keys( $this->available_post_types() );
		$selected  = isset( $data['post_types'] ) && is_array( $data['post_types'] ) ? $data['post_types'] : array();
		$out       = array();
		foreach ( $selected as $pt ) {
			$pt = sanitize_key( (string) $pt );
			if ( in_array( $pt, $valid_pts, true ) ) {
				$out[] = $pt;
			}
		}
		return array( 'post_types' => array_values( array_unique( $out ) ) );
	}

	public function register(): void {
		add_action( 'add_meta_boxes', array( $this, 'add_meta_box' ), 10, 2 );
		add_action( 'save_post',      array( $this, 'save_meta_box' ), 10, 2 );
		add_filter( 'the_title',      array( $this, 'filter_title' ), 10, 2 );
	}

	/* ---------------------------------------------------------------------
	 * Helpers
	 * ------------------------------------------------------------------- */

	private function enabled_post_types(): array {
		$value = bw_dev()->settings()->get( $this->slug(), 'post_types', self::DEFAULTS['post_types'] );
		return is_array( $value ) ? array_values( $value ) : self::DEFAULTS['post_types'];
	}

	private function is_post_type_enabled( string $post_type ): bool {
		return in_array( $post_type, $this->enabled_post_types(), true );
	}

	/**
	 * Public post types offered in the settings tab. Filterable in case a
	 * site wants to expose a private CPT.
	 */
	public function available_post_types(): array {
		$types = get_post_types( array( 'public' => true ), 'objects' );
		unset( $types['attachment'] );
		$out = array();
		foreach ( $types as $slug => $obj ) {
			$out[ $slug ] = $obj->labels->name;
		}
		return (array) apply_filters( 'bw_dev_title_override_post_types', $out );
	}

	private function allowed_html(): array {
		return array(
			'em'     => array(),
			'strong' => array(),
			'i'      => array(),
			'b'      => array(),
			'br'     => array(),
			'span'   => array( 'class' => array() ),
		);
	}

	/* ---------------------------------------------------------------------
	 * Meta box
	 * ------------------------------------------------------------------- */

	public function add_meta_box( $post_type, $post ): void {
		unset( $post );
		if ( ! $this->is_post_type_enabled( (string) $post_type ) ) {
			return;
		}
		add_meta_box(
			'bw_dev_title_override',
			__( 'Page Title Override (BW Dev)', 'bw-dev' ),
			array( $this, 'render_meta_box' ),
			$post_type,
			'side',
			'high'
		);
	}

	public function render_meta_box( $post ): void {
		wp_nonce_field( self::NONCE_ACTION, self::NONCE_FIELD );
		$value = (string) get_post_meta( $post->ID, self::META_KEY, true );
		?>
		<p>
			<label for="bw-dev-title-override" style="font-weight:600;">
				<?php esc_html_e( 'Override H1 page title', 'bw-dev' ); ?>
			</label>
		</p>
		<textarea id="bw-dev-title-override" name="bw_dev_title_override" rows="3" class="widefat" placeholder="<?php echo esc_attr( get_the_title( $post ) ); ?>"><?php echo esc_textarea( $value ); ?></textarea>
		<p class="description" style="margin-top:8px;">
			<?php esc_html_e( 'Leave blank to use the default post title. Allowed HTML: <em>, <strong>, <i>, <b>, <br>, <span class="…">.', 'bw-dev' ); ?>
		</p>
		<p class="description">
			<?php esc_html_e( 'Examples: "About <em>Our</em> Company" or "About<br>Our Company".', 'bw-dev' ); ?>
		</p>
		<p class="description" style="color:#646970;font-style:italic;">
			<?php esc_html_e( 'Affects only the H1 page heading. The post slug, browser tab title, social/SEO meta tags, admin list, and nav menu labels all remain the original title.', 'bw-dev' ); ?>
		</p>
		<?php
	}

	public function save_meta_box( $post_id, $post ): void {
		// Read $_POST through a single unslashed local; nonce-checked just below.
		$post_data = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked below.
		$nonce     = isset( $post_data[ self::NONCE_FIELD ] ) ? (string) $post_data[ self::NONCE_FIELD ] : '';
		if ( '' === $nonce || ! wp_verify_nonce( $nonce, self::NONCE_ACTION ) ) {
			return;
		}
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			return;
		}
		if ( ! $this->is_post_type_enabled( (string) $post->post_type ) ) {
			return;
		}

		$raw = isset( $post_data['bw_dev_title_override'] ) ? trim( (string) $post_data['bw_dev_title_override'] ) : '';

		if ( '' === $raw ) {
			delete_post_meta( $post_id, self::META_KEY );
			return;
		}

		$clean = wp_kses( $raw, $this->allowed_html() );
		update_post_meta( $post_id, self::META_KEY, $clean );
	}

	/* ---------------------------------------------------------------------
	 * the_title filter — gated to the main H1 only
	 * ------------------------------------------------------------------- */

	public function filter_title( $title, $post_id = null ) {
		// Frontend-only: bail in admin.
		if ( is_admin() ) {
			return $title;
		}
		// Skip non-rendering contexts.
		if ( wp_doing_ajax() ) {
			return $title;
		}
		if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
			return $title;
		}
		// Skip during wp_head — that's when OG/Twitter/SEO plugins build the
		// document title and social meta tags, and we don't want to override
		// those. (We can't gate on in_the_loop() because some themes — Kadence
		// included — render the H1 hero in `kadence_hero_header` BEFORE the
		// `while ( have_posts() )` loop body, so in_the_loop() is false at
		// the moment our filter fires.)
		if ( doing_action( 'wp_head' ) ) {
			return $title;
		}
		// Need a singular main-query context.
		if ( ! is_singular() || ! is_main_query() ) {
			return $title;
		}
		// Only target the queried object's title — not embedded posts, related-
		// post widgets, or anything else that happens to filter through the_title.
		if ( ! $post_id || (int) $post_id !== (int) get_queried_object_id() ) {
			return $title;
		}
		// Only on enabled post types.
		$post_type = get_post_type( (int) $post_id );
		if ( ! is_string( $post_type ) || ! $this->is_post_type_enabled( $post_type ) ) {
			return $title;
		}

		$override = get_post_meta( (int) $post_id, self::META_KEY, true );
		if ( ! is_string( $override ) || '' === trim( $override ) ) {
			return $title;
		}

		// Stored value is already kses-cleaned at save time. Trust it on render.
		return $override;
	}

	/* ---------------------------------------------------------------------
	 * Settings tab
	 * ------------------------------------------------------------------- */

	public function render_tab(): void {
		$prefix     = BW_Dev_Settings::OPTION . '[' . $this->slug() . ']';
		$enabled_pt = $this->enabled_post_types();
		$pts        = $this->available_post_types();
		?>
		<p class="description">
			<?php esc_html_e( 'Adds a "Page Title Override" meta box to the post-edit screen for the selected post types. The override replaces the H1 page heading on the front-end and supports limited HTML for emphasis or line breaks.', 'bw-dev' ); ?>
		</p>

		<table class="form-table" role="presentation">
			<tbody>
				<tr>
					<th scope="row"><?php esc_html_e( 'Show meta box on', 'bw-dev' ); ?></th>
					<td>
						<fieldset>
							<?php foreach ( $pts as $pt_slug => $pt_label ) :
								$id = 'bw-dev-titleovr-pt-' . sanitize_html_class( $pt_slug );
								?>
								<label for="<?php echo esc_attr( $id ); ?>" style="display:inline-block;margin-right:14px;">
									<input type="checkbox" id="<?php echo esc_attr( $id ); ?>" name="<?php echo esc_attr( $prefix . '[post_types][]' ); ?>" value="<?php echo esc_attr( $pt_slug ); ?>" <?php checked( in_array( $pt_slug, $enabled_pt, true ) ); ?> />
									<?php echo esc_html( $pt_label ); ?>
									<span style="color:#646970;font-size:11px;">(<?php echo esc_html( $pt_slug ); ?>)</span>
								</label>
							<?php endforeach; ?>
						</fieldset>
						<p class="description"><?php esc_html_e( 'Pages have the meta box by default. Posts and custom post types are opt-in.', 'bw-dev' ); ?></p>
					</td>
				</tr>
			</tbody>
		</table>

		<h3><?php esc_html_e( 'How the override behaves', 'bw-dev' ); ?></h3>
		<ul style="list-style:disc;margin-left:20px;">
			<li><?php esc_html_e( 'Replaces only the front-end H1 page heading via the standard WordPress the_title filter.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'Theme-agnostic: works with Kadence and any other theme that uses the_title() for the page heading.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'Does NOT change: the post slug, the browser tab title, OG/Twitter/SEO meta tags, the admin post list, nav menu item labels, or breadcrumb segments. Those keep the original post title.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'Allowed HTML: <em>, <strong>, <i>, <b>, <br>, <span class="…">. Anything else is stripped on save.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'Stored under the post-meta key _bw_dev_title_override. Empty values are deleted to keep the postmeta table tidy.', 'bw-dev' ); ?></li>
			<li><?php esc_html_e( 'Filterable: bw_dev_title_override_post_types lets a site expose private CPTs in the post-types list above.', 'bw-dev' ); ?></li>
		</ul>
		<?php
	}

	public function uninstall(): void {
		global $wpdb;
		$wpdb->delete( $wpdb->postmeta, array( 'meta_key' => self::META_KEY ), array( '%s' ) ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
	}
}
