<?php
/**
 * Admin notices system.
 *
 * @package Woody_Code_Snippets
 */

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

/**
 * Class WINP_Notices
 */
class WINP_Notices {

	/**
	 * Class instance.
	 * 
	 * @var WINP_Notices
	 */
	private static $instance = null;

	/**
	 * Prefix.
	 * 
	 * @var string
	 */
	private static $prefix = 'winp_notice_';

	/**
	 * Notices storage.
	 * 
	 * @var array<int, array<string, mixed>> Notices data
	 */
	private static $notices = [];

	/**
	 * Private constructor to prevent direct instantiation.
	 */
	private function __construct() {
		add_action( 'admin_notices', [ $this, 'display_notices' ] );
		add_action( 'wp_ajax_winp_dismiss_notice', [ $this, 'ajax_dismiss_notice' ] );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
	}

	/**
	 * Get singleton instance
	 *
	 * @return WINP_Notices
	 */
	public static function instance() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	/**
	 * Enqueue scripts for dismissible notices
	 * 
	 * @return void
	 */
	public function enqueue_scripts() {
		// Only enqueue if there are notices to display.
		if ( empty( self::$notices ) ) {
			return;
		}
		
		// Add CSS for error notice styling.
		wp_add_inline_style(
			'common',
			'.winp-admin-notice { 
				padding: 12px 16px !important; 
			}
			.winp-admin-notice.is-dismissible {
				padding-right: 38px !important;
			}
			.winp-admin-notice p { margin: 0.5em 0; }
			.winp-admin-notice p:first-child { margin-top: 0; }
			.winp-admin-notice p:last-child { margin-bottom: 0; }
			.winp-error-notice-header { 
				margin-bottom: 10px; 
				font-size: 14px;
				line-height: 1.5;
			}
			.winp-snippet-edit-link {
				color: #2271b1;
				text-decoration: none;
				font-weight: 500;
				border-bottom: 1px solid transparent;
				transition: all 0.15s ease;
			}
			.winp-snippet-edit-link:hover {
				color: #135e96;
				border-bottom-color: #135e96;
			}
			.winp-snippet-edit-link:focus {
				box-shadow: 0 0 0 2px #2271b1;
				outline: 2px solid transparent;
				border-radius: 2px;
			}
			.winp-admin-notice details { 
				margin: 10px 0 4px; 
				border: 1px solid rgba(0, 0, 0, 0.15);
				border-radius: 4px;
				background: #fff;
			}
			.winp-admin-notice details summary { 
				cursor: pointer; 
				font-weight: 500;
				color: #2271b1;
				padding: 10px 12px;
				user-select: none;
				font-size: 13px;
				display: flex;
				align-items: center;
				transition: all 0.15s ease;
			}
			.winp-admin-notice details summary::-webkit-details-marker {
				display: none;
			}
			.winp-admin-notice details summary::before {
				content: "▶";
				display: inline-block;
				margin-right: 8px;
				font-size: 10px;
				transition: transform 0.2s ease;
			}
			.winp-admin-notice details[open] summary::before {
				transform: rotate(90deg);
			}
			.winp-admin-notice details summary:hover { 
				color: #135e96;
				background: rgba(0, 0, 0, 0.03);
			}
			.winp-admin-notice details[open] summary { 
				border-bottom: 1px solid rgba(0, 0, 0, 0.1);
				background: rgba(0, 0, 0, 0.02);
			}
			.winp-error-details {
				padding: 14px;
			}
			.winp-error-message,
			.winp-error-location {
				margin-bottom: 14px;
			}
			.winp-error-location {
				margin-bottom: 0;
			}
			.winp-error-message strong,
			.winp-error-location strong {
				display: block;
				margin-bottom: 6px;
				font-size: 11px;
				text-transform: uppercase;
				color: #646970;
				font-weight: 600;
				letter-spacing: 0.5px;
			}
			.winp-error-message code {
				display: block;
				padding: 10px 12px;
				background: #f6f7f7;
				border-left: 3px solid #d63638;
				font-size: 13px;
				line-height: 1.6;
				color: #d63638;
				word-break: break-word;
				font-family: Consolas, Monaco, monospace;
			}
			.winp-error-location {
				font-size: 13px;
				color: #2c3338;
				line-height: 1.5;
			}
			.winp-line-number {
				color: #646970;
				font-style: italic;
			}
			.winp-error-notice-footer {
				margin-top: 12px;
				padding-top: 12px;
				border-top: 1px solid rgba(0, 0, 0, 0.1);
				display: flex;
				align-items: center;
				justify-content: space-between;
				gap: 12px;
			}
			.winp-dismiss-help {
				margin: 0 !important;
				font-size: 13px;
				color: #646970;
				flex: 1;
			}
			.winp-manual-dismiss-btn {
				background: #f0f0f1;
				border-color: #2271b1;
				color: #2271b1;
				flex-shrink: 0;
				font-weight: 500;
			}
			.winp-manual-dismiss-btn:hover {
				background: #2271b1;
				border-color: #2271b1;
				color: #fff;
			}'
		);
		
		wp_add_inline_script(
			'common',
			'(function($) {
				// Handle X button dismiss
				$(document).on("click", ".winp-admin-notice.is-dismissible .notice-dismiss", function(e) {
					var notice = $(this).closest(".winp-admin-notice");
					var noticeId = notice.data("notice-id");
					var nonce = notice.data("nonce");
					
					if (noticeId && nonce && typeof ajaxurl !== "undefined") {
						$.post(ajaxurl, {
							action: "winp_dismiss_notice",
							notice_id: noticeId,
							nonce: nonce
						});
					}
				});
				
				// Handle manual dismiss button
				$(document).on("click", ".winp-manual-dismiss-btn", function(e) {
					e.preventDefault();
					var notice = $(this).closest(".winp-admin-notice");
					var noticeId = notice.data("notice-id");
					var nonce = notice.data("nonce");
					
					if (noticeId && nonce && typeof ajaxurl !== "undefined") {
						$(this).prop("disabled", true).text("Dismissing...");
						
						$.post(ajaxurl, {
							action: "winp_dismiss_notice",
							notice_id: noticeId,
							nonce: nonce
						}).done(function() {
							notice.fadeOut(300, function() {
								$(this).remove();
							});
						}).fail(function() {
							alert("Failed to dismiss notice. Please try again.");
							$(this).prop("disabled", false).text("Dismiss Notice");
						});
					}
				});
			})(jQuery);'
		);
	}

	/**
	 * Display admin notices
	 * 
	 * @return void
	 */
	public function display_notices() {
		if ( ! WINP_Plugin::app()->current_user_car() ) {
			return;
		}

		$current_screen = get_current_screen();

		// Display all registered notices.
		foreach ( self::$notices as $notice ) {
			// Check if notice should be shown on current screen.
			if ( ! empty( $notice['where'] ) && $current_screen ) {
				if ( ! in_array( $current_screen->base, $notice['where'] ) ) {
					continue;
				}
			}

			// Check if notice is dismissed.
			if ( $notice['dismissible'] && $this->is_notice_dismissed( $notice['id'] ) ) {
				continue;
			}

			$this->render_notice( $notice );
		}
	}

	/**
	 * Render a single notice
	 *
	 * @param array<string, mixed> $notice Notice data.
	 * 
	 * @return void
	 */
	private function render_notice( $notice ) {
		$type         = $notice['type'];
		$dismissible  = $notice['dismissible'] ? 'is-dismissible' : '';
		$notice_class = "notice notice-{$type} {$dismissible}";
		$notice_text  = is_string( $notice['text'] ) ? $notice['text'] : '';

		?>
		<div
			class="<?php echo esc_attr( $notice_class ); ?> winp-admin-notice"
			<?php if ( $notice['dismissible'] ) : ?>
				data-notice-id="<?php echo esc_attr( $notice['id'] ); ?>"
				data-nonce="<?php echo esc_attr( wp_create_nonce( 'winp_dismiss_notice_' . $notice['id'] ) ); ?>"
			<?php endif; ?>
		>
			<?php echo wp_kses( $notice_text, $this->get_allowed_notice_html() ); ?>
		</div>
		<?php
	}

	/**
	 * Allowed HTML markup for notice messages.
	 *
	 * @return array<string, array<string, bool>>
	 */
	private function get_allowed_notice_html() {
		$allowed_html = wp_kses_allowed_html( 'post' );

		$allowed_html['a']['class']    = true;
		$allowed_html['a']['target']   = true;
		$allowed_html['a']['rel']      = true;
		$allowed_html['div']['class']  = true;
		$allowed_html['span']['class'] = true;
		$allowed_html['details']       = [
			'class' => true,
			'open'  => true,
		];
		$allowed_html['summary']       = [ 'class' => true ];
		$allowed_html['code']['class'] = true;
		$allowed_html['button']        = [
			'class'    => true,
			'type'     => true,
			'disabled' => true,
		];

		return $allowed_html;
	}

	/**
	 * Check if notice is dismissed
	 *
	 * @param string $notice_id Notice ID.
	 * 
	 * @return bool
	 */
	private function is_notice_dismissed( $notice_id ) {
		$dismissed_notices = get_option( 'winp_dismissed_notices', [] );
		
		if ( ! is_array( $dismissed_notices ) || ! isset( $dismissed_notices[ $notice_id ] ) ) {
			return false;
		}
		
		$expires = (int) $dismissed_notices[ $notice_id ];
		
		// If expires is 0, dismissed forever.
		// If expires is set and still in future, still dismissed.
		return 0 === $expires || $expires > time();
	}

	/**
	 * Handle AJAX dismiss request
	 * 
	 * @return void
	 */
	public function ajax_dismiss_notice() {
		$notice_id = isset( $_POST['notice_id'] ) ? sanitize_key( $_POST['notice_id'] ) : '';
		$nonce     = isset( $_POST['nonce'] ) ? sanitize_text_field( $_POST['nonce'] ) : '';

		if ( ! $notice_id || ! wp_verify_nonce( $nonce, 'winp_dismiss_notice_' . $notice_id ) ) {
			wp_send_json_error();
		}

		if ( ! WINP_Plugin::app()->current_user_car() ) {
			wp_send_json_error();
		}

		// Find the notice to get its expiration time.
		$expire_time = 0; // Default: dismiss forever.
		foreach ( self::$notices as $notice ) {
			if ( $notice['id'] === $notice_id ) {
				$expire_time = $notice['dismiss_expires'] > 0 ? time() + $notice['dismiss_expires'] : 0;
				break;
			}
		}
		
		// Get existing dismissed notices.
		$dismissed_notices = get_option( 'winp_dismissed_notices', [] );
		if ( ! is_array( $dismissed_notices ) ) {
			$dismissed_notices = [];
		}
		
		// Clean up expired dismissals.
		foreach ( $dismissed_notices as $id => $expires ) {
			if ( 0 !== $expires && $expires < time() ) {
				unset( $dismissed_notices[ $id ] );
			}
		}
		
		// Limit size to prevent excessive growth (keep last 10 dismissals).
		if ( count( $dismissed_notices ) > 10 ) {
			// Sort by expiration time (oldest first).
			asort( $dismissed_notices );
			// Remove oldest entries beyond limit.
			$dismissed_notices = array_slice( $dismissed_notices, -10, null, true );
		}
		
		// Add this dismissal.
		$dismissed_notices[ $notice_id ] = $expire_time;
		
		// Update option.
		update_option( 'winp_dismissed_notices', $dismissed_notices, false );

		wp_send_json_success();
	}

	/**
	 * Add notice to be displayed
	 *
	 * @param string        $id              ID of the notice.
	 * @param string|null   $message         Message of the notice.
	 * @param string        $type            Type of the notice. Possible values: 'error', 'warning', 'success', 'info'.
	 * @param bool          $dismissible     Whether the notice can be dismissed.
	 * @param int           $dismiss_expires Time in seconds, after which the notice will be shown again. 0 - forever.
	 * @param array<string> $where           Where to show the notice. Screen base values: 'post', 'post-new', 'edit', etc.
	 * 
	 * @return void
	 */
	public static function add_notice( $id, $message, $type = 'warning', $dismissible = true, $dismiss_expires = 0, $where = [] ) {
		if ( is_null( $message ) || empty( $message ) ) {
			return;
		}

		// Prevent duplicate notices.
		foreach ( self::$notices as $notice ) {
			if ( $notice['id'] === self::$prefix . $id ) {
				return;
			}
		}

		self::$notices[] = [
			'id'              => self::$prefix . $id,
			'type'            => $type,
			'dismissible'     => (bool) $dismissible,
			'dismiss_expires' => (int) $dismiss_expires,
			'where'           => $where,
			'text'            => $message,
		];
	}
}
