<?php
/**
 * Admin Columns module.
 *
 * Configure custom columns on the admin post-list screens, per post type.
 * Three column kinds:
 *  - Featured image with click-to-change quick-edit (AJAX-powered media picker)
 *  - Taxonomy columns (any registered taxonomy)
 *  - Meta-key columns (scanned from wp_postmeta, optionally including private keys)
 *
 * Ported from the standalone `bw-admin-column` plugin. Settings UI uses a
 * sub-tab nav inside the Admin Columns tab so each post type has its own
 * panel. Re-prefixed throughout (AJAX actions, nonces, column IDs, CSS
 * classes, localized globals) — see KNOWN-ISSUES.md.
 *
 * @package BW_Dev
 */

defined( 'ABSPATH' ) || exit;

class BW_Dev_Module_Admin_Columns implements BW_Dev_Module_Interface {

	const AJAX_ACTION_SET_IMAGE  = 'bw_dev_set_featured_image';
	const AJAX_ACTION_SCAN_META  = 'bw_dev_scan_meta_keys';
	const AJAX_NONCE             = 'bw_dev_admin_columns_nonce';
	const COL_FEATURED_IMAGE     = 'bw_dev_featured_image';
	const COL_TAX_PREFIX         = 'bw_dev_tax_';
	const COL_META_PREFIX        = 'bw_dev_meta_';

	/**
	 * Cached module settings (per post type).
	 *
	 * @var array
	 */
	private $cache = null;

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

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

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

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

	public function sanitize( array $data ): array {
		$sanitized   = array();
		$valid_types = array_keys( $this->get_public_post_types() );

		foreach ( $valid_types as $pt_slug ) {
			if ( ! isset( $data[ $pt_slug ] ) || ! is_array( $data[ $pt_slug ] ) ) {
				continue;
			}
			$pt = $data[ $pt_slug ];
			$sanitized[ $pt_slug ] = array(
				'enabled'           => ! empty( $pt['enabled'] ),
				'taxonomies'        => $this->sanitize_string_array( $pt['taxonomies'] ?? array() ),
				'meta_keys'         => $this->sanitize_string_array( $pt['meta_keys'] ?? array() ),
				'featured_image'    => ! empty( $pt['featured_image'] ),
				'show_private_meta' => ! empty( $pt['show_private_meta'] ),
			);
		}

		return $sanitized;
	}

	private function sanitize_string_array( $arr ): array {
		if ( ! is_array( $arr ) ) {
			return array();
		}
		return array_values( array_map( 'sanitize_text_field', array_map( 'wp_unslash', $arr ) ) );
	}

	public function register(): void {
		add_action( 'init', array( $this, 'register_column_hooks' ), 99 );
		add_action( 'pre_get_posts', array( $this, 'handle_column_sorting' ) );
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_assets' ) );
		add_action( 'wp_ajax_' . self::AJAX_ACTION_SET_IMAGE, array( $this, 'ajax_set_featured_image' ) );
		add_action( 'wp_ajax_' . self::AJAX_ACTION_SCAN_META, array( $this, 'ajax_scan_meta_keys' ) );
	}

	/* ---------------------------------------------------------------------
	 * Column registration
	 * ------------------------------------------------------------------- */

	public function register_column_hooks(): void {
		foreach ( $this->settings() as $post_type => $pt_settings ) {
			if ( empty( $pt_settings['enabled'] ) ) {
				continue;
			}
			add_filter( "manage_{$post_type}_posts_columns",       array( $this, 'add_columns' ) );
			add_action( "manage_{$post_type}_posts_custom_column", array( $this, 'render_column' ), 10, 2 );
			add_filter( "manage_edit-{$post_type}_sortable_columns", array( $this, 'add_sortable_columns' ) );
		}
	}

	public function add_columns( $columns ): array {
		$screen = get_current_screen();
		if ( ! $screen ) {
			return $columns;
		}
		$pt          = $screen->post_type;
		$pt_settings = $this->settings( $pt );

		if ( ! empty( $pt_settings['featured_image'] ) ) {
			$rebuilt = array();
			foreach ( $columns as $key => $label ) {
				if ( 'title' === $key ) {
					$rebuilt[ self::COL_FEATURED_IMAGE ] = __( 'Image', 'bw-dev' );
				}
				$rebuilt[ $key ] = $label;
			}
			$columns = $rebuilt;
		}

		if ( ! empty( $pt_settings['taxonomies'] ) ) {
			foreach ( $pt_settings['taxonomies'] as $taxonomy ) {
				$tax_obj = get_taxonomy( $taxonomy );
				if ( ! $tax_obj ) {
					continue;
				}
				// Skip if WP already added it (e.g. categories/tags on `post`).
				if ( isset( $columns[ 'taxonomy-' . $taxonomy ] ) ) {
					continue;
				}
				$columns[ self::COL_TAX_PREFIX . $taxonomy ] = $tax_obj->labels->name;
			}
		}

		if ( ! empty( $pt_settings['meta_keys'] ) ) {
			foreach ( $pt_settings['meta_keys'] as $meta_key ) {
				$columns[ self::COL_META_PREFIX . $meta_key ] = $this->format_meta_key_label( $meta_key );
			}
		}

		return $columns;
	}

	public function render_column( $column_name, $post_id ): void {
		if ( self::COL_FEATURED_IMAGE === $column_name ) {
			$this->render_featured_image_column( (int) $post_id );
			return;
		}
		if ( 0 === strpos( $column_name, self::COL_TAX_PREFIX ) ) {
			$this->render_taxonomy_column( (int) $post_id, substr( $column_name, strlen( self::COL_TAX_PREFIX ) ) );
			return;
		}
		if ( 0 === strpos( $column_name, self::COL_META_PREFIX ) ) {
			$this->render_meta_column( (int) $post_id, substr( $column_name, strlen( self::COL_META_PREFIX ) ) );
		}
	}

	private function render_featured_image_column( int $post_id ): void {
		echo $this->featured_image_cell_html( $post_id ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- HTML built with escaped values below.
	}

	private function featured_image_cell_html( int $post_id ): string {
		$thumbnail_id = get_post_thumbnail_id( $post_id );
		$nonce        = wp_create_nonce( self::AJAX_ACTION_SET_IMAGE . '_' . $post_id );
		ob_start();
		?>
		<div class="bw-dev-admin-columns-featured-image-cell"
		     data-post-id="<?php echo esc_attr( (string) $post_id ); ?>"
		     data-nonce="<?php echo esc_attr( $nonce ); ?>"
		     title="<?php esc_attr_e( 'Click to change featured image', 'bw-dev' ); ?>">
			<?php if ( $thumbnail_id ) : ?>
				<?php echo get_the_post_thumbnail( $post_id, array( 50, 50 ), array( 'class' => 'bw-dev-admin-columns-thumb' ) ); ?>
			<?php else : ?>
				<span class="dashicons dashicons-format-image bw-dev-admin-columns-no-thumb-icon"></span>
			<?php endif; ?>
		</div>
		<?php
		return (string) ob_get_clean();
	}

	private function render_taxonomy_column( int $post_id, string $taxonomy ): void {
		$terms = get_the_terms( $post_id, $taxonomy );
		if ( empty( $terms ) || is_wp_error( $terms ) ) {
			echo '<span aria-hidden="true">&#8212;</span>';
			return;
		}
		$links = array();
		foreach ( $terms as $term ) {
			$url     = add_query_arg(
				array(
					'post_type' => get_post_type( $post_id ),
					$taxonomy   => $term->slug,
				),
				admin_url( 'edit.php' )
			);
			$links[] = '<a href="' . esc_url( $url ) . '">' . esc_html( $term->name ) . '</a>';
		}
		echo wp_kses(
			implode( ', ', $links ),
			array(
				'a' => array( 'href' => true ),
			)
		);
	}

	private function render_meta_column( int $post_id, string $meta_key ): void {
		$value = get_post_meta( $post_id, $meta_key, true );
		if ( '' === $value || null === $value || false === $value ) {
			echo '<span aria-hidden="true">&#8212;</span>';
			return;
		}
		$display = wp_strip_all_tags( is_array( $value ) ? (string) wp_json_encode( $value ) : (string) $value );
		if ( strlen( $display ) > 80 ) {
			$display = substr( $display, 0, 77 ) . '...';
		}
		printf(
			'<span title="%1$s">%2$s</span>',
			esc_attr( $display ),
			esc_html( $display )
		);
	}

	public function add_sortable_columns( $columns ): array {
		$screen = get_current_screen();
		if ( ! $screen ) {
			return $columns;
		}
		$pt_settings = $this->settings( $screen->post_type );
		if ( empty( $pt_settings['meta_keys'] ) ) {
			return $columns;
		}
		foreach ( $pt_settings['meta_keys'] as $meta_key ) {
			$col              = self::COL_META_PREFIX . $meta_key;
			$columns[ $col ] = $col;
		}
		return $columns;
	}

	public function handle_column_sorting( $query ): void {
		if ( ! is_admin() || ! $query->is_main_query() ) {
			return;
		}
		$orderby = $query->get( 'orderby' );
		if ( ! is_string( $orderby ) || 0 !== strpos( $orderby, self::COL_META_PREFIX ) ) {
			return;
		}
		$meta_key = substr( $orderby, strlen( self::COL_META_PREFIX ) );
		$query->set( 'meta_key', $meta_key );
		$query->set( 'orderby', 'meta_value' );
		$query->set( 'meta_query', array(
			'relation' => 'OR',
			array( 'key' => $meta_key, 'compare' => 'EXISTS' ),
			array( 'key' => $meta_key, 'compare' => 'NOT EXISTS' ),
		) );
	}

	/* ---------------------------------------------------------------------
	 * Asset enqueue
	 * ------------------------------------------------------------------- */

	public function enqueue_admin_assets( $hook ): void {
		// Settings tab assets — only on Settings → BW Dev → Admin Columns.
		if ( 'settings_page_' . BW_Dev_Admin_Page::MENU_SLUG === $hook ) {
			$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_style(
				'bw-dev-admin-columns-settings',
				BW_DEV_URL . 'assets/css/admin-columns-settings.css',
				array(),
				BW_DEV_VERSION
			);
			wp_enqueue_script(
				'bw-dev-admin-columns-settings',
				BW_DEV_URL . 'assets/js/admin-columns-settings.js',
				array( 'jquery' ),
				BW_DEV_VERSION,
				true
			);
			wp_localize_script(
				'bw-dev-admin-columns-settings',
				'bwDevAdminColumnsSettings',
				array(
					'ajaxUrl' => admin_url( 'admin-ajax.php' ),
					'nonce'   => wp_create_nonce( self::AJAX_NONCE ),
					'scanAction' => self::AJAX_ACTION_SCAN_META,
					'fieldNamePrefix' => BW_Dev_Settings::OPTION . '[' . $this->slug() . ']',
					'i18n'    => array(
						'scanning' => __( 'Scanning meta keys…', 'bw-dev' ),
						'noKeys'   => __( 'No custom fields found for this post type.', 'bw-dev' ),
					),
				)
			);
			return;
		}

		// List-screen assets — only on edit.php for an enabled post type.
		if ( 'edit.php' !== $hook ) {
			return;
		}
		$screen = get_current_screen();
		if ( ! $screen ) {
			return;
		}
		$pt_settings = $this->settings( $screen->post_type );
		if ( empty( $pt_settings['enabled'] ) ) {
			return;
		}
		wp_enqueue_style(
			'bw-dev-admin-columns-list',
			BW_DEV_URL . 'assets/css/admin-columns-list.css',
			array(),
			BW_DEV_VERSION
		);
		if ( ! empty( $pt_settings['featured_image'] ) ) {
			wp_enqueue_media();
			wp_enqueue_script(
				'bw-dev-admin-columns-list',
				BW_DEV_URL . 'assets/js/admin-columns-list.js',
				array( 'jquery' ),
				BW_DEV_VERSION,
				true
			);
			wp_localize_script(
				'bw-dev-admin-columns-list',
				'bwDevAdminColumnsList',
				array(
					'ajaxUrl'   => admin_url( 'admin-ajax.php' ),
					'setAction' => self::AJAX_ACTION_SET_IMAGE,
					'i18n'      => array(
						'selectImage' => __( 'Select featured image', 'bw-dev' ),
						'useImage'    => __( 'Set as featured image', 'bw-dev' ),
					),
				)
			);
		}
	}

	/* ---------------------------------------------------------------------
	 * AJAX handlers
	 * ------------------------------------------------------------------- */

	public function ajax_set_featured_image(): void {
		$post_data    = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked below.
		$post_id      = isset( $post_data['post_id'] ) ? absint( $post_data['post_id'] ) : 0;
		$thumbnail_id = isset( $post_data['thumbnail_id'] ) ? (int) $post_data['thumbnail_id'] : 0;
		$nonce        = isset( $post_data['nonce'] ) ? (string) $post_data['nonce'] : '';

		if ( ! $post_id ) {
			wp_send_json_error( array( 'message' => __( 'Invalid post ID.', 'bw-dev' ) ) );
		}
		if ( ! wp_verify_nonce( $nonce, self::AJAX_ACTION_SET_IMAGE . '_' . $post_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed.', 'bw-dev' ) ) );
		}
		if ( ! current_user_can( 'edit_post', $post_id ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'bw-dev' ) ) );
		}

		if ( $thumbnail_id <= 0 ) {
			delete_post_thumbnail( $post_id );
			wp_send_json_success( array(
				'html'         => $this->featured_image_cell_html( $post_id ),
				'thumbnail_id' => 0,
			) );
		}

		if ( set_post_thumbnail( $post_id, $thumbnail_id ) ) {
			wp_send_json_success( array(
				'html'         => $this->featured_image_cell_html( $post_id ),
				'thumbnail_id' => $thumbnail_id,
			) );
		}
		wp_send_json_error( array( 'message' => __( 'Could not set featured image.', 'bw-dev' ) ) );
	}

	public function ajax_scan_meta_keys(): void {
		$post_data = wp_unslash( $_POST ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce checked below.
		$nonce     = isset( $post_data['nonce'] ) ? (string) $post_data['nonce'] : '';
		if ( ! wp_verify_nonce( $nonce, self::AJAX_NONCE ) ) {
			wp_send_json_error( array( 'message' => __( 'Security check failed.', 'bw-dev' ) ) );
		}
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_send_json_error( array( 'message' => __( 'Permission denied.', 'bw-dev' ) ) );
		}
		$post_type    = isset( $post_data['post_type'] ) ? sanitize_key( (string) $post_data['post_type'] ) : '';
		$show_private = ! empty( $post_data['show_private'] );

		if ( '' === $post_type || ! post_type_exists( $post_type ) ) {
			wp_send_json_error( array( 'message' => __( 'Invalid post type.', 'bw-dev' ) ) );
		}
		$meta_keys = $this->get_meta_keys_for_post_type( $post_type, $show_private );
		$selected  = (array) ( $this->settings( $post_type )['meta_keys'] ?? array() );
		wp_send_json_success( array(
			'meta_keys' => $meta_keys,
			'selected'  => $selected,
		) );
	}

	/* ---------------------------------------------------------------------
	 * Settings UI (sub-tab nav inside the Admin Columns tab)
	 * ------------------------------------------------------------------- */

	public function render_tab(): void {
		$post_types = $this->get_public_post_types();
		if ( empty( $post_types ) ) {
			echo '<p>' . esc_html__( 'No public post types are registered.', 'bw-dev' ) . '</p>';
			return;
		}

		$slugs   = array_keys( $post_types );
		$query   = wp_unslash( $_GET ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$active  = isset( $query['ptype'] ) ? sanitize_key( (string) $query['ptype'] ) : '';
		if ( '' === $active || ! in_array( $active, $slugs, true ) ) {
			$active = reset( $slugs );
		}

		$settings = $this->settings();
		?>
		<nav class="bw-dev-admin-columns-subtabs" style="margin:14px 0 18px;border-bottom:1px solid #c3c4c7;">
			<?php foreach ( $post_types as $pt ) :
				$url = add_query_arg(
					array(
						'page'  => BW_Dev_Admin_Page::MENU_SLUG,
						'tab'   => $this->slug(),
						'ptype' => $pt->name,
					),
					admin_url( 'options-general.php' )
				);
				$active_class = $pt->name === $active ? ' nav-tab-active' : '';
				?>
				<a href="<?php echo esc_url( $url ); ?>" class="nav-tab<?php echo esc_attr( $active_class ); ?>" style="margin-bottom:-1px;">
					<?php echo esc_html( $pt->labels->name ); ?>
				</a>
			<?php endforeach; ?>
		</nav>

		<p>
			<input type="hidden" name="<?php echo esc_attr( BW_Dev_Settings::OPTION . '[' . $this->slug() . '][__active_ptype]' ); ?>" value="<?php echo esc_attr( $active ); ?>" />
		</p>

		<?php
		// Preserve other post types' data via hidden fields.
		foreach ( $post_types as $pt ) {
			if ( $pt->name === $active ) {
				continue;
			}
			$this->render_hidden_fields( $pt->name, $settings );
		}
		$this->render_post_type_panel( $active, $settings );
	}

	private function render_hidden_fields( string $post_type, array $settings ): void {
		$pt_settings = $settings[ $post_type ] ?? array();
		$prefix      = BW_Dev_Settings::OPTION . '[' . $this->slug() . '][' . $post_type . ']';

		if ( ! empty( $pt_settings['enabled'] ) ) {
			echo '<input type="hidden" name="' . esc_attr( $prefix . '[enabled]' ) . '" value="1" />';
		}
		if ( ! empty( $pt_settings['featured_image'] ) ) {
			echo '<input type="hidden" name="' . esc_attr( $prefix . '[featured_image]' ) . '" value="1" />';
		}
		if ( ! empty( $pt_settings['show_private_meta'] ) ) {
			echo '<input type="hidden" name="' . esc_attr( $prefix . '[show_private_meta]' ) . '" value="1" />';
		}
		foreach ( (array) ( $pt_settings['taxonomies'] ?? array() ) as $tax ) {
			echo '<input type="hidden" name="' . esc_attr( $prefix . '[taxonomies][]' ) . '" value="' . esc_attr( $tax ) . '" />';
		}
		foreach ( (array) ( $pt_settings['meta_keys'] ?? array() ) as $mk ) {
			echo '<input type="hidden" name="' . esc_attr( $prefix . '[meta_keys][]' ) . '" value="' . esc_attr( $mk ) . '" />';
		}
	}

	private function render_post_type_panel( string $post_type, array $settings ): void {
		$pt_settings    = $settings[ $post_type ] ?? array();
		$enabled        = ! empty( $pt_settings['enabled'] );
		$show_private   = ! empty( $pt_settings['show_private_meta'] );
		$selected_taxes = (array) ( $pt_settings['taxonomies'] ?? array() );
		$selected_meta  = (array) ( $pt_settings['meta_keys'] ?? array() );
		$featured_img   = ! empty( $pt_settings['featured_image'] );

		$taxonomies   = $this->get_taxonomies_for_post_type( $post_type );
		$meta_keys    = $this->get_meta_keys_for_post_type( $post_type, $show_private );
		$field_prefix = BW_Dev_Settings::OPTION . '[' . $this->slug() . '][' . $post_type . ']';
		$pt_object    = get_post_type_object( $post_type );
		$pt_label     = $pt_object ? $pt_object->labels->name : $post_type;
		?>
		<div class="bw-dev-admin-columns-panel">
			<h2>
				<?php
				/* translators: %s: post type label */
				printf( esc_html__( 'Column settings for "%s"', 'bw-dev' ), esc_html( $pt_label ) );
				?>
			</h2>

			<table class="form-table" role="presentation">
				<tr>
					<th scope="row"><?php esc_html_e( 'Enable column management', 'bw-dev' ); ?></th>
					<td>
						<label>
							<input type="checkbox" name="<?php echo esc_attr( $field_prefix . '[enabled]' ); ?>" value="1" <?php checked( $enabled ); ?> class="bw-dev-admin-columns-enable-toggle" />
							<?php esc_html_e( 'Manage columns for this post type', 'bw-dev' ); ?>
						</label>
					</td>
				</tr>
			</table>

			<div class="bw-dev-admin-columns-options" <?php echo $enabled ? '' : 'style="display:none;"'; ?>>
				<h3><?php esc_html_e( 'Featured image', 'bw-dev' ); ?></h3>
				<table class="form-table" role="presentation">
					<tr>
						<th scope="row"><?php esc_html_e( 'Show featured image column', 'bw-dev' ); ?></th>
						<td>
							<label>
								<input type="checkbox" name="<?php echo esc_attr( $field_prefix . '[featured_image]' ); ?>" value="1" <?php checked( $featured_img ); ?> />
								<?php esc_html_e( 'Display thumbnail with click-to-change quick edit', 'bw-dev' ); ?>
							</label>
						</td>
					</tr>
				</table>

				<h3><?php esc_html_e( 'Taxonomy columns', 'bw-dev' ); ?></h3>
				<?php if ( empty( $taxonomies ) ) : ?>
					<p class="description"><?php esc_html_e( 'No public taxonomies registered for this post type.', 'bw-dev' ); ?></p>
				<?php else : ?>
					<fieldset class="bw-dev-admin-columns-fieldset">
						<legend class="screen-reader-text"><?php esc_html_e( 'Select taxonomy columns', 'bw-dev' ); ?></legend>
						<?php foreach ( $taxonomies as $tax ) : ?>
							<label class="bw-dev-admin-columns-checkbox-label">
								<input type="checkbox" name="<?php echo esc_attr( $field_prefix . '[taxonomies][]' ); ?>" value="<?php echo esc_attr( $tax->name ); ?>" <?php checked( in_array( $tax->name, $selected_taxes, true ) ); ?> />
								<?php echo esc_html( $tax->labels->name ); ?>
								<span class="description">(<?php echo esc_html( $tax->name ); ?>)</span>
							</label>
						<?php endforeach; ?>
					</fieldset>
				<?php endif; ?>

				<h3><?php esc_html_e( 'Custom field columns', 'bw-dev' ); ?></h3>
				<table class="form-table" role="presentation">
					<tr>
						<th scope="row"><?php esc_html_e( 'Show private meta keys', 'bw-dev' ); ?></th>
						<td>
							<label>
								<input type="checkbox" name="<?php echo esc_attr( $field_prefix . '[show_private_meta]' ); ?>" value="1" <?php checked( $show_private ); ?> class="bw-dev-admin-columns-show-private" data-post-type="<?php echo esc_attr( $post_type ); ?>" />
								<?php esc_html_e( 'Include meta keys starting with _ (underscore)', 'bw-dev' ); ?>
							</label>
						</td>
					</tr>
				</table>

				<div class="bw-dev-admin-columns-meta-keys-list" data-post-type="<?php echo esc_attr( $post_type ); ?>">
					<?php if ( empty( $meta_keys ) ) : ?>
						<p class="description"><?php esc_html_e( 'No custom fields found for this post type. Create some posts with custom fields first.', 'bw-dev' ); ?></p>
					<?php else : ?>
						<fieldset>
							<legend class="screen-reader-text"><?php esc_html_e( 'Select custom field columns', 'bw-dev' ); ?></legend>
							<?php foreach ( $meta_keys as $meta_key ) : ?>
								<label class="bw-dev-admin-columns-checkbox-label">
									<input type="checkbox" name="<?php echo esc_attr( $field_prefix . '[meta_keys][]' ); ?>" value="<?php echo esc_attr( $meta_key ); ?>" <?php checked( in_array( $meta_key, $selected_meta, true ) ); ?> />
									<code><?php echo esc_html( $meta_key ); ?></code>
								</label>
							<?php endforeach; ?>
						</fieldset>
					<?php endif; ?>
				</div>
			</div>
		</div>
		<?php
	}

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

	/**
	 * Whole module setting (or one post type's slice).
	 */
	private function settings( ?string $post_type = null ): array {
		if ( null === $this->cache ) {
			$this->cache = (array) bw_dev()->settings()->get( $this->slug(), null, array() );
		}
		if ( null === $post_type ) {
			return $this->cache;
		}
		return is_array( $this->cache[ $post_type ] ?? null ) ? $this->cache[ $post_type ] : array();
	}

	public function get_public_post_types(): array {
		return get_post_types(
			array(
				'public'  => true,
				'show_ui' => true,
			),
			'objects'
		);
	}

	public function get_taxonomies_for_post_type( string $post_type ): array {
		$taxonomies = get_object_taxonomies( $post_type, 'objects' );
		return array_filter(
			$taxonomies,
			static function ( $tax ) {
				return $tax->public && $tax->show_ui;
			}
		);
	}

	public function get_meta_keys_for_post_type( string $post_type, bool $show_private = false ): array {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$keys = $wpdb->get_col( $wpdb->prepare(
			"SELECT DISTINCT pm.meta_key
			 FROM {$wpdb->postmeta} pm
			 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
			 WHERE p.post_type = %s
			 ORDER BY pm.meta_key
			 LIMIT 200",
			$post_type
		) );
		if ( ! $show_private ) {
			$keys = array_filter(
				$keys,
				static function ( $key ) {
					return 0 !== strpos( (string) $key, '_' );
				}
			);
		}
		return array_values( $keys );
	}

	private function format_meta_key_label( string $meta_key ): string {
		$label = ltrim( $meta_key, '_' );
		$label = str_replace( array( '_', '-' ), ' ', $label );
		return ucwords( $label );
	}

	public function uninstall(): void {
		// No-op. Root option drop handles persisted state.
	}
}
