<?php
/**
 * Campaigns Module
 *
 * Manages campaign custom post type and related functionality.
 *
 * @package AmbassadorHub\Campaigns
 */

namespace AmbassadorHub\Campaigns;

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

use AmbassadorHub\Frontend\BlockTemplateLoader;

/**
 * Campaigns module class.
 */
final class CampaignsModule {

	public const POST_TYPE = 'ambh_campaign';

	/**
	 * Slug of the hidden campaign detail/stats admin page.
	 *
	 * A single literal constant rather than POST_TYPE . '_stats' built at
	 * each call site — see FundraisersModule::STATS_PAGE_SLUG for why.
	 */
	public const STATS_PAGE_SLUG = 'ambh_campaign_stats';

	/**
	 * Initialize the module.
	 *
	 * @return void
	 */
	public static function init(): void {

		// Register custom post type and status.
		add_action( 'init', array( self::class, 'register_post_type' ) );

		// Activation hook to flush rewrite rules and perform other setup tasks.
		add_action( 'ambh_activate', array( self::class, 'register_post_type' ) );

		// Register post meta for REST API exposure.
		add_action( 'init', array( self::class, 'register_post_meta' ) );

		CampaignsCapabilities::register();

		Admin\Menu::init();
		Admin\CampaignDataMetaBox::init();
		Admin\MetaBoxes::init();
		Admin\NestingMetaBox::init();
		Admin\ListTable::init();

		Automations::init();

		// Enforce status policy server-side.
		add_filter( 'wp_insert_post_data', array( self::class, 'enforce_status_policy' ) );

		// Campaign end date scheduling.
		add_action( 'save_post_' . self::POST_TYPE, array( self::class, 'handle_campaign_end_scheduling' ), 20, 2 );
		add_action( 'before_delete_post', array( self::class, 'handle_campaign_deletion_cleanup' ), 10, 2 );
		add_action( 'ambh_end_campaign', array( self::class, 'end_campaign' ) );

		// Status transitions drive automation triggers.
		add_action( 'transition_post_status', array( self::class, 'on_campaign_status_transition' ), 10, 3 );

		// Register the FSE/classic page template — deferred to 'init' so __() does
		// not fire during plugins_loaded and trigger the just-in-time notice.
		add_action( 'init', array( self::class, 'register_templates' ), 5 );

		// Disable the block editor for campaigns — use the classic editor.
		add_filter( 'use_block_editor_for_post_type', array( self::class, 'disable_block_editor' ), 10, 2 );

		// Register all campaign-scoped Gutenberg blocks.
		self::register_blocks();

		// Responsive layout overrides for the single-campaign template.
		add_action( 'wp_enqueue_scripts', array( self::class, 'enqueue_template_styles' ), 20 );

		/**
		 * Fires when campaigns module is initialized.
		 *
		 * @since 0.1.0
		 */
		do_action( 'ambh_campaigns_module_init' );
	}

	/**
	 * Inject responsive CSS for the single-campaign template.
	 *
	 * The featured image is already full-width above the grid, so only a simple
	 * grid collapse + order swap is needed: sidebar (stats/form) appears before
	 * content (title/description/fundraiser grid) at tablet breakpoint.
	 *
	 * @return void
	 */
	public static function enqueue_template_styles(): void {
		if ( ! is_singular( self::POST_TYPE ) ) {
			return;
		}

		wp_add_inline_style(
			'ambh-theme-vars',
			'
			@media (max-width: 1024px) {

				/* Force a single-column grid — display:grid must be explicit or WP collapses it to block. */
				.ambh-campaign-layout {
					display: grid !important;
					grid-template-columns: minmax(0, 1fr) !important; /* mobile: 1 col */

					> * {
						grid-column: span 1;
					}
				}

				/* Reset columnSpan/rowSpan inline styles set by the block editor. */
				.ambh-campaign-content,
				.ambh-campaign-sidebar {
					grid-column: 1 / -1 !important;
					grid-row: auto !important;
					min-width: 0;
				}

				/* Content appears last by default; ambh-order-N classes override this. */
				.ambh-campaign-content {
					display: contents;
				}

				//* Left-column children become full-width grid items; appear last by default. */
				.ambh-campaign-content > * {
					order: 10;
					grid-column: 1 / -1 !important;
					width: 100% !important;
					max-width: 100% !important;
					min-width: 0 !important;
					margin-left: 0 !important;
					margin-right: 0 !important;
					box-sizing: border-box;
				}

				/* Force flex containers to wrap. */
				.ambh-campaign-content .is-layout-flex,
				.ambh-campaign-sidebar .is-layout-flex {
					flex-wrap: wrap !important;
				}

				/* Allow every descendant to shrink below its content size. */
				.ambh-campaign-content > * *,
				.ambh-campaign-sidebar * {
					min-width: 0;
					box-sizing: border-box;
				}

				/* Mobile order utilities — set via "Additional CSS class(es)" in the block editor. */
				.ambh-order-1 { order: 1 !important; }
				.ambh-order-2 { order: 2 !important; }
				.ambh-order-3 { order: 3 !important; }
				.ambh-order-4 { order: 4 !important; }
				.ambh-order-5 { order: 5 !important; }
				.ambh-order-6 { order: 6 !important; }
				.ambh-order-7 { order: 7 !important; }
				.ambh-order-8 { order: 8 !important; }
				.ambh-order-9 { order: 9 !important; }
			}
		'
		);
	}

	/**
	 * Register campaign-scoped Gutenberg blocks.
	 *
	 * @return void
	 */
	private static function register_blocks(): void {
		Blocks\Fundraisers\Block::init();
		Blocks\CampaignGrid\Block::init();
		Blocks\CampaignCountdown\Block::init();
	}

	/**
	 * Register the FSE/classic block template(s) for this module.
	 *
	 * Hooked to 'init' at priority 5 — must run before BlockTemplateLoader fires
	 * at priority 20, and must not be called directly during plugins_loaded.
	 *
	 * @return void
	 */
	public static function register_templates(): void {
		BlockTemplateLoader::register(
			array(
				'slug'        => 'single-campaign',
				'title'       => __( 'Single Campaign', 'ambassadorhub' ),
				'post_types'  => array( self::POST_TYPE ),
				'path'        => AMBASSADOR_HUB_PATH . 'src/Campaigns/block-templates/single-campaign.html',
				'theme_slugs' => array( 'single-campaign.php' ),
			)
		);
	}

	/**
	 * Disable the Gutenberg block editor for campaigns.
	 *
	 * @param bool   $use_block_editor Whether to use the block editor.
	 * @param string $post_type        The post type being checked.
	 *
	 * @return bool
	 */
	public static function disable_block_editor( bool $use_block_editor, string $post_type ): bool {
		if ( self::POST_TYPE === $post_type ) {
			return false;
		}

		return $use_block_editor;
	}

	/**
	 * Register post meta fields exposed via the REST API.
	 *
	 * @return void
	 */
	public static function register_post_meta(): void {
		register_post_meta(
			self::POST_TYPE,
			'_ambh_financial_goal',
			array(
				'type'              => 'number',
				'single'            => true,
				'default'           => 0,
				'show_in_rest'      => true,
				'sanitize_callback' => static function ( $val ) {
					return max( 0.0, (float) $val ); },
				'auth_callback'     => static function () {
					return current_user_can( 'edit_posts' ); },
			)
		);
	}

	/**
	 * Register campaign post type.
	 *
	 * @return void
	 */
	public static function register_post_type(): void {
		$labels = array(
			'name'          => __( 'Campaigns', 'ambassadorhub' ),
			'singular_name' => __( 'Campaign', 'ambassadorhub' ),
			'add_new'       => __( 'Add New', 'ambassadorhub' ),
			'add_new_item'  => __( 'Add New Campaign', 'ambassadorhub' ),
			'edit_item'     => __( 'Edit Campaign', 'ambassadorhub' ),
			'new_item'      => __( 'New Campaign', 'ambassadorhub' ),
			'view_item'     => __( 'View Campaign', 'ambassadorhub' ),
			'search_items'  => __( 'Search Campaigns', 'ambassadorhub' ),
			'not_found'     => __( 'No campaigns found', 'ambassadorhub' ),
			'menu_name'     => __( 'Campaigns', 'ambassadorhub' ),
		);

		$args = array(
			'labels'          => $labels,
			'public'          => true,
			'has_archive'     => true,
			'menu_icon'       => 'dashicons-megaphone',
			'supports'        => array( 'title', 'editor', 'thumbnail' ),
			'show_in_rest'    => true,
			'show_in_menu'    => false,
			'show_ui'         => true,
			'capability_type' => self::POST_TYPE,
			'map_meta_cap'    => true,
			'rewrite'         => array( 'slug' => 'campaign' ),

		);

		register_post_type( self::POST_TYPE, $args );

		register_post_status(
			'ended',
			array(
				'label'                     => _x( 'Ended', 'post status', 'ambassadorhub' ),
				'public'                    => true, // still public so it can be queried and shown on the front end
				'exclude_from_search'       => false, // still show in search results
				'show_in_admin_all_list'    => true,
				'show_in_admin_status_list' => true,
				// translators: %s: number of posts with this status.
				'label_count'               => _n_noop(
					'Ended <span class="count">(%s)</span>',
					'Ended <span class="count">(%s)</span>',
					'ambassadorhub'
				),
			)
		);
	}



	/**
	 * Enforce allowed statuses and visibility for automation posts.
	 *
	 * @param array $data    Sanitized post data.
	 * @param array $postarr Raw post data.
	 *
	 * @return array
	 */
	public static function enforce_status_policy( array $data ): array {

		if ( self::POST_TYPE !== ( $data['post_type'] ?? '' ) ) {
			return $data;
		}

		// 'trash' must be allowed through so WordPress can delete campaigns.
		// It is not a user-selectable campaign status.
		$allowed_statuses = array( 'publish', 'future', 'draft', 'ended', 'trash' );

		if ( ! in_array( $data['post_status'] ?? '', $allowed_statuses, true ) ) {
			$data['post_status'] = 'draft';
		}

		return $data;
	}

	/**
	 *
	 *
	 * @param mixed $new_status
	 * @param mixed $old_status
	 * @param mixed $post
	 * @return void
	 */
	public static function on_campaign_status_transition( $new_status, $old_status, $post ) {

		// Only trigger for campaign post type
		if ( self::POST_TYPE !== $post->post_type ) {
			return;
		}

		// If status didn't actually change, do nothing
		if ( $new_status === $old_status ) {
			return;
		}

		if ( 'publish' === $new_status && 'publish' !== $old_status ) {
			do_action( 'ambh_campaign_started', $post->ID );
		}

		if ( 'ended' === $new_status && 'ended' !== $old_status ) {
			do_action( 'ambh_campaign_ended', $post->ID );
		}
	}

	public static function handle_campaign_end_scheduling( $post_id, $post ) {

		// Bail on autosave / revisions / wrong status
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
			return;
		}
		if ( 'auto-draft' === $post->post_status ) {
			return;
		}
		if ( self::POST_TYPE !== $post->post_type ) {
			return;
		}

		// If the campaign is not published, ensure any existing end scheduling is removed
		as_unschedule_all_actions(
			'ambh_end_campaign',
			array( $post_id ),
			'AmbassadorHub\Campaigns\CampaignsModule'
		);

		// If the campaign is published, check if there's an end date and schedule the end action if so
		$end_date = get_post_meta( $post_id, '_ambh_end_date', true );
		if ( $end_date && 'publish' === $post->post_status ) {
			$end_timestamp = strtotime( $end_date );

			if ( $end_timestamp && $end_timestamp > time() ) {
				as_schedule_single_action(
					$end_timestamp,
					'ambh_end_campaign',
					array( $post_id ),
					'AmbassadorHub\Campaigns\CampaignsModule'
				);
			} elseif ( $end_timestamp ) {
				// End date is already in the past — end immediately.
				// Safe to call directly: end_campaign() triggers save_post with status 'ended',
				// which skips this block on re-entry so there is no loop.
				self::end_campaign( $post_id );
			}
		}
	}

	public static function handle_campaign_deletion_cleanup( $post_id, $post ) {
		if ( ! $post || self::POST_TYPE !== $post->post_type ) {
			return;
		}

		// Unschedule any pending end campaign actions for this campaign
		as_unschedule_all_actions(
			'ambh_end_campaign',
			array( $post_id ),
			'AmbassadorHub\Campaigns\CampaignsModule'
		);
	}

	public static function end_campaign( $post_id ) {
		$post = get_post( $post_id );
		if ( ! $post || self::POST_TYPE !== $post->post_type ) {
			return;
		}

		do_action( 'ambh_campaign_before_auto_ended', $post_id );

		// Update campaign status to 'ended'
		wp_update_post(
			array(
				'ID'          => $post_id,
				'post_status' => 'ended',
			)
		);
	}
}
