<?php

defined( 'ABSPATH' ) || exit;

/**
 * Admin Note module.
 *
 * Adds an internal "Admin Note" to posts and pages, visible only to logged-in
 * users in the block editor. Notes appear:
 *  - In a sidebar Document panel (PluginDocumentSettingPanel) for editing.
 *  - As a prominent yellow banner above the first block when set.
 *
 * Useful for editor instructions, page documentation, warnings to future
 * editors, etc. Notes are never rendered on the frontend.
 *
 * Ported from the standalone `bw-admin-note` plugin (deployed on regent and
 * promobix today; the bw-plugins copy is an empty placeholder).
 *
 * **Compatibility decision**: the post-meta key `_bw_admin_note` is
 * intentionally NOT re-prefixed to `_bw_dev_admin_note`. Sites switching
 * from the standalone plugin to bw-dev keep their existing notes without
 * any data migration — same prefix that bw-ai-schema-pro uses for the
 * grandfathered `bw_schema_*` option keys.
 *
 * @package BW_Dev
 */

class BW_Dev_Module_Admin_Note implements BW_Dev_Module_Interface {

	const META_KEY = '_bw_admin_note';

	/**
	 * Post types we have already registered meta for, to avoid double
	 * registration when `registered_post_type` fires for ones we already
	 * caught at init.
	 *
	 * @var string[]
	 */
	private $registered_meta_for = array();

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

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

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

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

	public function sanitize( array $data ): array {
		$types = isset( $data['post_types'] ) && is_array( $data['post_types'] )
			? array_values( array_filter( array_map( 'sanitize_key', $data['post_types'] ) ) )
			: array();
		// Fall back to 'page' when the user unchecks everything — matches
		// the legacy plugin's default and prevents the meta box from
		// disappearing site-wide by accident.
		if ( empty( $types ) ) {
			$types = array( 'page' );
		}
		return array( 'post_types' => $types );
	}

	public function register(): void {
		add_action( 'init',                       array( $this, 'register_meta_for_all_types' ) );
		add_action( 'registered_post_type',       array( $this, 'register_meta_for_post_type' ), 10, 2 );
		add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_editor_assets' ) );
		add_action( 'admin_enqueue_scripts',      array( $this, 'enqueue_admin_styles' ) );
	}

	/* ---------------------------------------------------------------------
	 * Meta registration — has to happen for every public REST post type so
	 * the editor can read/write _bw_admin_note via core/editor meta.
	 * ------------------------------------------------------------------- */

	public function register_meta_for_all_types(): void {
		$types = get_post_types( array( 'public' => true, 'show_in_rest' => true ), 'names' );
		unset( $types['attachment'] );
		foreach ( $types as $type ) {
			$this->register_meta_for_single_type( $type );
		}
	}

	public function register_meta_for_post_type( $post_type, $post_type_object ): void {
		if ( ! $post_type_object instanceof WP_Post_Type ) {
			return;
		}
		if ( ! $post_type_object->public || ! $post_type_object->show_in_rest ) {
			return;
		}
		if ( 'attachment' === $post_type ) {
			return;
		}
		$this->register_meta_for_single_type( (string) $post_type );
	}

	private function register_meta_for_single_type( string $post_type ): void {
		if ( in_array( $post_type, $this->registered_meta_for, true ) ) {
			return;
		}
		$this->registered_meta_for[] = $post_type;

		register_post_meta(
			$post_type,
			self::META_KEY,
			array(
				'show_in_rest'      => true,
				'single'            => true,
				'type'              => 'string',
				'default'           => '',
				'sanitize_callback' => 'wp_kses_post',
				'auth_callback'     => static function () {
					return current_user_can( 'edit_posts' );
				},
			)
		);
	}

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

	public function enqueue_editor_assets(): void {
		global $post;
		if ( ! isset( $post ) || ! $this->is_post_type_enabled( $post->post_type ) ) {
			return;
		}
		wp_enqueue_script(
			'bw-dev-admin-note-editor',
			BW_DEV_URL . 'assets/js/admin-note-editor.js',
			array( 'wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data', 'wp-i18n', 'wp-compose', 'wp-hooks' ),
			BW_DEV_VERSION,
			true
		);
		wp_localize_script(
			'bw-dev-admin-note-editor',
			'bwDevAdminNoteData',
			array(
				'metaKey' => self::META_KEY,
				'labels'  => array(
					'panelTitle'  => __( 'Admin Note', 'bw-dev' ),
					'placeholder' => __( 'Add a note for editors (only visible in admin)…', 'bw-dev' ),
					'helpText'    => __( 'This note is only visible to logged-in users in the editor. It will not appear on the frontend.', 'bw-dev' ),
					'bannerLabel' => __( 'Admin note (visible to logged-in users only)', 'bw-dev' ),
				),
			)
		);
		wp_enqueue_style(
			'bw-dev-admin-note',
			BW_DEV_URL . 'assets/css/admin-note.css',
			array(),
			BW_DEV_VERSION
		);
	}

	public function enqueue_admin_styles( $hook ): void {
		// Settings tab assets handle their own CSS via enqueue_editor_assets
		// when the user is in the block editor; here we only need the CSS
		// on our settings page so the Notes Index table is styled.
		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_style(
			'bw-dev-admin-note',
			BW_DEV_URL . 'assets/css/admin-note.css',
			array(),
			BW_DEV_VERSION
		);
	}

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

	private function is_post_type_enabled( string $post_type ): bool {
		$enabled = (array) bw_dev()->settings()->get( $this->slug(), 'post_types', array( 'page' ) );
		return in_array( $post_type, $enabled, true );
	}

	/**
	 * Distinct posts that have a non-empty `_bw_admin_note`. Direct SQL —
	 * a WP_Query meta_query against this would be slower at scale.
	 */
	private function posts_with_notes(): array {
		global $wpdb;
		// phpcs:ignore WordPress.DB.DirectDatabaseQuery
		$rows = $wpdb->get_results( $wpdb->prepare(
			"SELECT p.ID, p.post_title, p.post_type, p.post_status, p.post_author, pm.meta_value AS note
			   FROM {$wpdb->posts} p
			   INNER JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id
			  WHERE pm.meta_key = %s
			    AND pm.meta_value <> ''
			    AND p.post_status IN ('publish', 'draft', 'pending', 'private', 'future')
			  ORDER BY p.post_type ASC, p.post_title ASC",
			self::META_KEY
		) );
		if ( empty( $rows ) ) {
			return array();
		}

		$out = array();
		foreach ( $rows as $row ) {
			$pt_obj   = get_post_type_object( $row->post_type );
			$status   = get_post_status_object( $row->post_status );
			$author   = get_userdata( (int) $row->post_author );
			$preview  = wp_strip_all_tags( (string) $row->note );
			$truncate = strlen( $preview ) > 150;
			if ( $truncate ) {
				$preview = substr( $preview, 0, 150 );
			}
			$out[] = array(
				'id'              => (int) $row->ID,
				'title'           => $row->post_title !== '' ? $row->post_title : __( '(no title)', 'bw-dev' ),
				'post_type'       => $row->post_type,
				'post_type_label' => $pt_obj ? $pt_obj->labels->singular_name : $row->post_type,
				'author_name'     => $author ? $author->display_name : __( 'Unknown', 'bw-dev' ),
				'status'          => $row->post_status,
				'status_label'    => $status ? $status->label : ucfirst( $row->post_status ),
				'note_preview'    => $preview,
				'truncated'       => $truncate,
				'edit_link'       => get_edit_post_link( (int) $row->ID, 'raw' ),
				'view_link'       => get_permalink( (int) $row->ID ),
			);
		}
		return $out;
	}

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

	public function render_tab(): void {
		$enabled    = (array) bw_dev()->settings()->get( $this->slug(), 'post_types', array( 'page' ) );
		$post_types = get_post_types( array( 'public' => true ), 'objects' );
		unset( $post_types['attachment'] );

		$field_name = BW_Dev_Settings::OPTION . '[' . $this->slug() . '][post_types][]';
		?>
		<p class="description">
			<?php esc_html_e( 'Adds an internal "Admin Note" to posts and pages, edited from a sidebar panel and shown as a banner above the first block in the editor. Visible only to logged-in users in the editor — never on the frontend.', 'bw-dev' ); ?>
		</p>

		<h3><?php esc_html_e( 'Enabled post types', 'bw-dev' ); ?></h3>
		<fieldset class="bw-dev-admin-note-fieldset">
			<legend class="screen-reader-text"><?php esc_html_e( 'Post types that allow admin notes', 'bw-dev' ); ?></legend>
			<?php foreach ( $post_types as $pt ) :
				$checked = in_array( $pt->name, $enabled, true );
				?>
				<label class="bw-dev-admin-note-post-type-option">
					<input type="checkbox" name="<?php echo esc_attr( $field_name ); ?>" value="<?php echo esc_attr( $pt->name ); ?>" <?php checked( $checked ); ?> />
					<?php echo esc_html( $pt->labels->singular_name ); ?>
					<span class="description">(<?php echo esc_html( $pt->name ); ?>)</span>
				</label>
			<?php endforeach; ?>
		</fieldset>
		<p class="description"><?php esc_html_e( 'If no boxes are checked, settings fall back to "page" so the editor panel does not disappear site-wide accidentally.', 'bw-dev' ); ?></p>

		<div class="bw-dev-admin-note-info-box" style="background:#fff;padding:14px 18px;margin-top:24px;border:1px solid #c3c4c7;border-left:4px solid #2271b1;max-width:720px;">
			<h3 style="margin-top:0;"><?php esc_html_e( 'How it works', 'bw-dev' ); ?></h3>
			<p><?php esc_html_e( 'Editors open the "Admin Note" panel in the editor sidebar and write a note. When a note exists, it shows as a yellow banner above the first block. Useful for:', 'bw-dev' ); ?></p>
			<ul style="list-style:disc; margin-left:20px;">
				<li><?php esc_html_e( 'Instructions for content editors', 'bw-dev' ); ?></li>
				<li><?php esc_html_e( 'Documenting how a page works', 'bw-dev' ); ?></li>
				<li><?php esc_html_e( 'Warnings about important considerations', 'bw-dev' ); ?></li>
				<li><?php esc_html_e( 'Notes for future reference', 'bw-dev' ); ?></li>
			</ul>
			<p><strong><?php esc_html_e( 'Notes are never shown on the frontend — only in the editor.', 'bw-dev' ); ?></strong></p>
			<p style="margin:8px 0 0;color:#646970;font-size:12px;">
				<?php
				printf(
					/* translators: %s: post-meta key in code formatting */
					esc_html__( 'Storage: post meta %s (kept un-re-prefixed for compat with the legacy bw-admin-note plugin so existing notes survive migration).', 'bw-dev' ),
					'<code>' . esc_html( self::META_KEY ) . '</code>'
				);
				?>
			</p>
		</div>

		<?php $this->render_notes_index(); ?>
		<?php
	}

	private function render_notes_index(): void {
		$rows = $this->posts_with_notes();
		?>
		<div class="bw-dev-admin-note-index" style="margin-top:30px;">
			<h3><?php esc_html_e( 'Notes index', 'bw-dev' ); ?></h3>
			<p class="description"><?php esc_html_e( 'All posts, pages, and CPTs that currently have an admin note.', 'bw-dev' ); ?></p>

			<?php if ( empty( $rows ) ) : ?>
				<div style="background:#f0f0f1;padding:18px;border-radius:4px;margin-top:12px;">
					<p style="margin:0;color:#50575e;">
						<span class="dashicons dashicons-info" style="margin-right:5px;"></span>
						<?php esc_html_e( 'No posts with admin notes found.', 'bw-dev' ); ?>
					</p>
				</div>
			<?php else : ?>
				<table class="wp-list-table widefat fixed striped" style="margin-top:12px;">
					<thead>
						<tr>
							<th scope="col" style="width:22%;"><?php esc_html_e( 'Title', 'bw-dev' ); ?></th>
							<th scope="col" style="width:10%;"><?php esc_html_e( 'Type', 'bw-dev' ); ?></th>
							<th scope="col" style="width:10%;"><?php esc_html_e( 'Author', 'bw-dev' ); ?></th>
							<th scope="col" style="width:8%;"><?php esc_html_e( 'Status', 'bw-dev' ); ?></th>
							<th scope="col" style="width:50%;"><?php esc_html_e( 'Preview', 'bw-dev' ); ?></th>
						</tr>
					</thead>
					<tbody>
						<?php
						$status_colors = array(
							'publish' => '#00a32a',
							'draft'   => '#d63638',
							'pending' => '#dba617',
							'private' => '#2271b1',
							'future'  => '#8c8f94',
						);
						foreach ( $rows as $r ) :
							$color = $status_colors[ $r['status'] ] ?? '#8c8f94';
							?>
							<tr>
								<td>
									<strong>
										<a href="<?php echo esc_url( $r['edit_link'] ); ?>"><?php echo esc_html( $r['title'] ); ?></a>
									</strong>
									<div class="row-actions">
										<span class="edit"><a href="<?php echo esc_url( $r['edit_link'] ); ?>"><?php esc_html_e( 'Edit', 'bw-dev' ); ?></a> | </span>
										<span class="view"><a href="<?php echo esc_url( $r['view_link'] ); ?>" target="_blank"><?php esc_html_e( 'View', 'bw-dev' ); ?></a></span>
									</div>
								</td>
								<td>
									<span style="background:#f0f0f1;padding:3px 8px;border-radius:3px;font-size:12px;">
										<?php echo esc_html( $r['post_type_label'] ); ?>
									</span>
								</td>
								<td><?php echo esc_html( $r['author_name'] ); ?></td>
								<td><span style="color:<?php echo esc_attr( $color ); ?>;font-weight:500;"><?php echo esc_html( $r['status_label'] ); ?></span></td>
								<td>
									<div style="color:#50575e;font-size:13px;line-height:1.5;max-height:60px;overflow:hidden;">
										<?php echo esc_html( $r['note_preview'] ); ?><?php if ( $r['truncated'] ) : ?><span style="color:#2271b1;">…</span><?php endif; ?>
									</div>
								</td>
							</tr>
						<?php endforeach; ?>
					</tbody>
				</table>
				<p style="margin-top:12px;color:#50575e;">
					<?php
					printf(
						/* translators: %d: count */
						esc_html( _n( '%d post with an admin note.', '%d posts with admin notes.', count( $rows ), 'bw-dev' ) ),
						count( $rows )
					);
					?>
				</p>
			<?php endif; ?>
		</div>
		<?php
	}

	public function uninstall(): void {
		// Intentionally leave _bw_admin_note post meta intact on uninstall:
		// notes are valuable editorial content, and if the user reinstalls
		// or switches back to the standalone plugin the notes survive.
	}
}
