<?php
namespace Pay4All\Pro\Admin\Newsletter;

use Pay4All\Pro\Domain\Newsletter;
use Pay4All\Pro\PostTypes;
use Pay4All\Pro\Services\NewsletterTemplates;

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

/**
 * Editor screen for the p4all_newsletter CPT.
 *
 * Two-step flow :
 *
 *   STEP 1 — brand-new post (kind not yet confirmed) : only the Template
 *            metabox is rendered. The merchant picks the kind (free /
 *            product / promo) + optional source, clicks « Validate », and
 *            the save handler seeds the Content bag with the default
 *            templates for every active language then flips the confirm
 *            flag so the next render lands on step 2.
 *
 *   STEP 2 — kind confirmed. The layout becomes :
 *              main col   : [Template (kind grayed, pickers editable)]
 *                           [Content (per-language editors + variables)]
 *              side col   : [Status  (progress + test / queue / pause)]
 */
final class MetaBoxes {

	public const NONCE = 'p4all_pro_newsletter_save';

	public static function register() : void {
		add_action( 'add_meta_boxes',                     [ self::class, 'add_boxes' ], 10, 2 );
		add_action( 'save_post_' . PostTypes::NEWSLETTER, [ self::class, 'save' ], 10, 2 );
		// The « Screen Options » tab exposes columns / metabox toggles
		// that are meaningless on the newsletter editor — everything the
		// merchant needs is already visible. Hide the button entirely
		// on both the new-post and edit-post screens for our CPT.
		add_filter( 'screen_options_show_screen',         [ self::class, 'hide_screen_options' ], 10, 2 );
	}

	/**
	 * @param bool       $show
	 * @param \WP_Screen $screen
	 */
	public static function hide_screen_options( $show, $screen ) : bool {
		if ( $screen && isset( $screen->post_type ) && $screen->post_type === PostTypes::NEWSLETTER ) {
			return false;
		}
		return (bool) $show;
	}

	/**
	 * @param string   $post_type
	 * @param \WP_Post $post
	 */
	public static function add_boxes( $post_type, $post ) : void {
		if ( $post_type !== PostTypes::NEWSLETTER ) { return; }

		// Template metabox always renders — it's the entry point of the
		// editor (step 1 shows the picker, step 2 shows the locked kind
		// + editable pickers).
		add_meta_box(
			'p4all_newsletter_template',
			__( 'Template', 'pay4all-pro' ),
			[ self::class, 'render_template_box' ],
			PostTypes::NEWSLETTER,
			'normal',
			'high'
		);

		// Content + Status only appear once the kind is confirmed so
		// step 1 stays visually clean (one decision at a time).
		if ( $post instanceof \WP_Post && Newsletter::is_kind_confirmed( (int) $post->ID ) ) {
			add_meta_box(
				'p4all_newsletter_content',
				__( 'Content', 'pay4all-pro' ),
				[ self::class, 'render_content_box' ],
				PostTypes::NEWSLETTER,
				'normal',
				'default'
			);
			add_meta_box(
				'p4all_newsletter_status',
				__( 'Status', 'pay4all-pro' ),
				[ self::class, 'render_status_box' ],
				PostTypes::NEWSLETTER,
				'side',
				'high'
			);
		}
	}

	// ---------------------------------------------------------------
	// Template metabox : kind picker + source picker
	// ---------------------------------------------------------------
	public static function render_template_box( \WP_Post $post ) : void {
		wp_nonce_field( self::NONCE, '_p4all_nl_nonce' );

		$confirmed = Newsletter::is_kind_confirmed( (int) $post->ID );

		// URL-forwarded pre-selection (from the « Create newsletter »
		// buttons on the product / promo edit screens). Only applied
		// when the post isn't already confirmed — the picker is locked
		// on step 2 anyway.
		$forwarded_kind = isset( $_GET['p4all_nl_template'] ) ? sanitize_key( wp_unslash( (string) $_GET['p4all_nl_template'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		$forwarded_id   = isset( $_GET['p4all_nl_source'] )   ? absint( wp_unslash( $_GET['p4all_nl_source'] ) ) : 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended

		$kind = Newsletter::kind( (int) $post->ID );
		if ( ! $confirmed && $forwarded_kind !== '' && in_array( $forwarded_kind, Newsletter::KINDS, true ) ) {
			$kind = $forwarded_kind;
		}
		$product_id = Newsletter::product_id( (int) $post->ID );
		$promo_id   = Newsletter::promo_id( (int) $post->ID );
		if ( ! $confirmed && $product_id === 0 && $forwarded_kind === Newsletter::KIND_PRODUCT ) { $product_id = $forwarded_id; }
		if ( ! $confirmed && $promo_id   === 0 && $forwarded_kind === Newsletter::KIND_PROMO )   { $promo_id   = $forwarded_id; }
		?>
		<?php if ( ! $confirmed ) : ?>
			<p class="description" style="margin-top:0;">
				<?php esc_html_e( 'Pick the type of campaign, then click Validate to open the content editor. The type is locked afterwards to keep the variables in sync with the body.', 'pay4all-pro' ); ?>
			</p>
		<?php endif; ?>

		<table class="form-table">
			<tr>
				<th style="width:200px;"><label for="p4all_nl_kind"><?php esc_html_e( 'Type of campaign', 'pay4all-pro' ); ?></label></th>
				<td>
					<select id="p4all_nl_kind" name="p4all_nl_kind" <?php disabled( $confirmed ); ?>>
						<option value="<?php echo esc_attr( Newsletter::KIND_FREE ); ?>"    <?php selected( $kind, Newsletter::KIND_FREE ); ?>>
							<?php esc_html_e( 'Free content', 'pay4all-pro' ); ?>
						</option>
						<option value="<?php echo esc_attr( Newsletter::KIND_PRODUCT ); ?>" <?php selected( $kind, Newsletter::KIND_PRODUCT ); ?>>
							<?php esc_html_e( 'About a product', 'pay4all-pro' ); ?>
						</option>
						<option value="<?php echo esc_attr( Newsletter::KIND_PROMO ); ?>"   <?php selected( $kind, Newsletter::KIND_PROMO ); ?>>
							<?php esc_html_e( 'About a promo code', 'pay4all-pro' ); ?>
						</option>
					</select>
					<?php if ( $confirmed ) : ?>
						<input type="hidden" name="p4all_nl_kind" value="<?php echo esc_attr( $kind ); ?>">
						<p class="description"><?php esc_html_e( 'Type is locked once validated so the variable catalogue stays consistent with the body you wrote.', 'pay4all-pro' ); ?></p>
					<?php endif; ?>
				</td>
			</tr>
			<tr class="p4all-nl-picker p4all-nl-picker-product" style="<?php echo $kind === Newsletter::KIND_PRODUCT ? '' : 'display:none;'; ?>">
				<th><label for="p4all_nl_product_id"><?php esc_html_e( 'Product', 'pay4all-pro' ); ?></label></th>
				<td><?php self::render_post_picker( 'p4all_nl_product_id', 'p4all_product', $product_id ); ?></td>
			</tr>
			<tr class="p4all-nl-picker p4all-nl-picker-promo" style="<?php echo $kind === Newsletter::KIND_PROMO ? '' : 'display:none;'; ?>">
				<th><label for="p4all_nl_promo_id"><?php esc_html_e( 'Promo code', 'pay4all-pro' ); ?></label></th>
				<td><?php self::render_post_picker( 'p4all_nl_promo_id', 'p4all_promo', $promo_id ); ?></td>
			</tr>
		</table>

		<?php if ( ! $confirmed ) : ?>
			<input type="hidden" name="p4all_nl_validate" value="1">
			<p style="margin-top:12px;">
				<?php // The button name matters : « publish » is what WP's
				// wp-admin/post.php inspects to route the save through the
				// normal Publish flow (post_status = publish + redirect to
				// the edit screen with ?message=6). A custom name here made
				// WP think the merchant clicked no known save button, which
				// broke the redirect back to the editor. ?>
				<?php // id="publish" is what wp-admin/js/post.js listens to
					// in order to clear the « unsaved changes » beforeunload
					// warning. Without it, WP fires the browser prompt on
					// our custom submit even though we're saving normally.
					// The original « Publish » metabox is hidden above so
					// there's no id collision with WP's default button. ?>
				<button type="submit" name="publish" value="Publish" id="publish" class="button button-primary">
					<?php esc_html_e( 'Validate template', 'pay4all-pro' ); ?>
				</button>
			</p>
			<script>
			(function () {
				// Hide the WordPress-native Publish metabox on step 1 : the
				// merchant should validate the template first, not save an
				// empty post. Re-appears automatically after the reload
				// once kind_confirmed is set.
				var pub = document.getElementById('submitdiv');
				if (pub) { pub.style.display = 'none'; }

				// Title is mandatory at validation time — a nameless
				// campaign would show up as « (auto draft) » in the list
				// which is confusing. Gate the click on a non-empty title
				// and focus the field when missing.
				var validateBtn = document.querySelector('#p4all_newsletter_template button[name="publish"]');
				if (validateBtn) {
					validateBtn.addEventListener('click', function (ev) {
						var titleEl = document.getElementById('title');
						if (!titleEl || titleEl.value.trim() === '') {
							ev.preventDefault();
							if (titleEl) { titleEl.focus(); }
							window.alert(<?php echo wp_json_encode( __( 'Please give the newsletter a title before validating the template.', 'pay4all-pro' ) ); ?>);
							return;
						}
						// Defuse the « Leave site? » beforeunload warning
						// that WP's post.js registers with the jQuery
						// namespace « beforeunload.edit-post ». Setting
						// the legacy property to null covers the old-style
						// hook, and jQuery.off() strips the namespaced
						// listener that modern WP core installs. Without
						// this the browser prompts the merchant on every
						// Validate click even though we're saving normally.
						window.onbeforeunload = null;
						if (window.jQuery) {
							window.jQuery(window).off('beforeunload.edit-post');
						}
					});
				}
			})();
			</script>
		<?php endif; ?>

		<script>
		(function () {
			var sel = document.getElementById('p4all_nl_kind');
			if (!sel) { return; }
			function refresh () {
				document.querySelectorAll('.p4all-nl-picker').forEach(function (el) { el.style.display = 'none'; });
				var el = document.querySelector('.p4all-nl-picker-' + sel.value);
				if (el) { el.style.display = ''; }
			}
			sel.addEventListener('change', refresh);
			refresh();
		})();
		</script>
		<?php
	}

	private static function render_post_picker( string $name, string $post_type, int $selected_id ) : void {
		$posts = get_posts( [
			'post_type'      => $post_type,
			'posts_per_page' => 500,
			'post_status'    => 'publish',
			'orderby'        => 'title',
			'order'          => 'ASC',
		] );
		?>
		<select id="<?php echo esc_attr( $name ); ?>" name="<?php echo esc_attr( $name ); ?>" style="min-width:260px;">
			<option value="0">- <?php esc_html_e( 'None', 'pay4all-pro' ); ?> -</option>
			<?php foreach ( $posts as $p ) : ?>
				<option value="<?php echo esc_attr( (string) $p->ID ); ?>" <?php selected( $selected_id, (int) $p->ID ); ?>>
					<?php echo esc_html( $p->post_title !== '' ? $p->post_title : '#' . $p->ID ); ?>
				</option>
			<?php endforeach; ?>
		</select>
		<?php
	}

	// ---------------------------------------------------------------
	// Status metabox : progress + test + queue / pause
	// ---------------------------------------------------------------
	public static function render_status_box( \WP_Post $post ) : void {
		$status  = Newsletter::status( (int) $post->ID );
		$sent    = \Pay4All\Pro\Domain\NewsletterSendLog::count_sent( (int) $post->ID );
		$total   = Newsletter::audience_total( (int) $post->ID );
		if ( $total <= 0 && class_exists( '\\Pay4All\\Form\\Domain\\Customer' ) ) {
			$total = \Pay4All\Form\Domain\Customer::count_active_recipients();
		}
		$status_labels = [
			Newsletter::STATUS_DRAFT   => __( 'Draft',   'pay4all-pro' ),
			Newsletter::STATUS_QUEUED  => __( 'Queued',  'pay4all-pro' ),
			Newsletter::STATUS_SENDING => __( 'Sending', 'pay4all-pro' ),
			Newsletter::STATUS_DONE    => __( 'Done',    'pay4all-pro' ),
			Newsletter::STATUS_PAUSED  => __( 'Paused',  'pay4all-pro' ),
		];
		?>
		<p>
			<strong><?php esc_html_e( 'Status', 'pay4all-pro' ); ?> :</strong>
			<code><?php echo esc_html( $status_labels[ $status ] ?? $status ); ?></code>
		</p>
		<p><?php
			echo esc_html( sprintf(
				/* translators: 1: number sent, 2: total audience */
				__( 'Sent : %1$d / %2$d recipients', 'pay4all-pro' ),
				$sent, $total
			) );
		?></p>

		<?php
		// Actions rendered as nonced links (not nested <form>s — HTML
		// forbids form-in-form which would break the outer post-edit
		// form). The admin_post_ hook fires on GET too, so a signed URL
		// is enough for these merchant-triggered operations.
		// Build URLs manually rather than through wp_nonce_url() : that
		// helper runs esc_html() on its output, so `&` becomes `&amp;`.
		// Once such a URL is fed to window.location via JS, the browser
		// sends the literal `&amp;` as part of the query string and the
		// server sees param names like « amp;campaign_id » instead of
		// « campaign_id ». Building the URL from add_query_arg keeps the
		// separators as raw `&`, which the browser then encodes correctly.
		$make_url = static function ( $op, array $extra = [] ) use ( $post ) {
			return add_query_arg(
				array_merge(
					[
						'action'                 => 'p4all_nl_action',
						'campaign_id'            => (int) $post->ID,
						'p4all_nl_op'            => $op,
						'_p4all_nl_action_nonce' => wp_create_nonce( 'p4all_nl_action_' . $post->ID ),
					],
					$extra
				),
				admin_url( 'admin-post.php' )
			);
		};

		// « Scheduled for » hint — visible when the merchant already
		// picked a future date on this campaign but the cron hasn't
		// caught up yet, so they see when to expect the broadcast to
		// start.
		$scheduled_at = (string) get_post_meta( (int) $post->ID, Newsletter::META_SCHEDULED_AT, true );
		if ( $scheduled_at !== '' ) {
			$fmt   = trim( (string) get_option( 'date_format', 'Y-m-d' ) . ' ' . (string) get_option( 'time_format', 'H:i' ) );
			$label = wp_date( $fmt, (int) strtotime( $scheduled_at . ' UTC' ) );
			echo '<p><strong>' . esc_html__( 'Scheduled for', 'pay4all-pro' ) . ' :</strong> ' . esc_html( (string) $label ) . '</p>';
		}
		?>
		<p style="margin-top:12px;">
			<a href="<?php echo esc_url( $make_url( 'test' ) ); ?>" class="button">
				<?php esc_html_e( 'Send a test to me', 'pay4all-pro' ); ?>
			</a>
		</p>
		<?php if ( $status === Newsletter::STATUS_DRAFT || $status === Newsletter::STATUS_PAUSED ) :
			// Pre-compute the two URLs the modal will navigate to :
			//   « Send now »  → queue op with no scheduled_at param
			//   « Schedule »  → queue op with scheduled_at forwarded from
			//                    the datetime pickers (JS assembles it
			//                    from the two <input>s + injects into a
			//                    template URL literal below).
			$queue_now_url      = $make_url( 'queue' );
			$queue_template_url = $make_url( 'queue', [ 'scheduled_at' => '__P4ALL_NL_DT__' ] );
			// Today, in the merchant's timezone — used as the <input
			// type="date"> min so past dates aren't picker-selectable.
			$today_min = wp_date( 'Y-m-d' );
		?>
			<p>
				<button type="button" class="button button-primary p4all-nl-open-schedule">
					<?php esc_html_e( 'Schedule broadcast', 'pay4all-pro' ); ?>
				</button>
			</p>
			<?php // display:none as the initial state — the previous
				// `hidden` attribute was overridden by our inline
				// `display:flex`, so the modal popped up on page load
				// instead of on button click. JS swaps between `none` and
				// `flex` on open / close. ?>
			<div class="p4all-nl-schedule-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:99999;align-items:center;justify-content:center;padding:24px;">
				<div style="background:#fff;border-radius:8px;max-width:420px;width:100%;padding:24px;box-shadow:0 4px 24px rgba(0,0,0,.2);">
					<h3 style="margin-top:0;"><?php esc_html_e( 'Schedule broadcast', 'pay4all-pro' ); ?></h3>
					<p>
						<label style="display:flex;align-items:center;gap:8px;margin-bottom:8px;">
							<input type="radio" name="p4all_nl_when" value="now" checked>
							<?php esc_html_e( 'Send now', 'pay4all-pro' ); ?>
						</label>
						<label style="display:flex;align-items:center;gap:8px;">
							<input type="radio" name="p4all_nl_when" value="later">
							<?php esc_html_e( 'Schedule for a later date', 'pay4all-pro' ); ?>
						</label>
					</p>
					<div class="p4all-nl-datetime" style="display:none;gap:8px;margin:12px 0;">
						<input type="date" class="p4all-nl-date" min="<?php echo esc_attr( $today_min ); ?>" style="flex:1;">
						<input type="time" class="p4all-nl-time" style="flex:1;">
					</div>
					<p style="margin-top:20px;display:flex;justify-content:flex-end;gap:8px;">
						<button type="button" class="button p4all-nl-cancel"><?php esc_html_e( 'Cancel', 'pay4all-pro' ); ?></button>
						<button type="button" class="button button-primary p4all-nl-confirm"><?php esc_html_e( 'Confirm', 'pay4all-pro' ); ?></button>
					</p>
				</div>
			</div>
			<script>
			(function () {
				var openBtn  = document.querySelector('.p4all-nl-open-schedule');
				var modal    = document.querySelector('.p4all-nl-schedule-modal');
				if (!openBtn || !modal) { return; }
				var dtBlock  = modal.querySelector('.p4all-nl-datetime');
				var dateEl   = modal.querySelector('.p4all-nl-date');
				var timeEl   = modal.querySelector('.p4all-nl-time');
				var cancel   = modal.querySelector('.p4all-nl-cancel');
				var confirm  = modal.querySelector('.p4all-nl-confirm');
				var radios   = modal.querySelectorAll('input[name="p4all_nl_when"]');
				var urlNow   = <?php echo wp_json_encode( $queue_now_url ); ?>;
				var urlTpl   = <?php echo wp_json_encode( $queue_template_url ); ?>;

				function pickedWhen () {
					for (var i = 0; i < radios.length; i++) { if (radios[i].checked) { return radios[i].value; } }
					return 'now';
				}
				function refreshDatetime () {
					dtBlock.style.display = pickedWhen() === 'later' ? 'flex' : 'none';
				}
				openBtn.addEventListener('click', function () {
					refreshDatetime();
					modal.style.display = 'flex';
				});
				cancel.addEventListener('click',  function () { modal.style.display = 'none'; });
				modal.addEventListener('click', function (ev) { if (ev.target === modal) { modal.style.display = 'none'; } });
				for (var i = 0; i < radios.length; i++) { radios[i].addEventListener('change', refreshDatetime); }
				confirm.addEventListener('click', function () {
					var dt = '';
					if (pickedWhen() === 'later') {
						var d = (dateEl.value || '').trim();
						var t = (timeEl.value || '').trim();
						if (!d || !t) {
							window.alert(<?php echo wp_json_encode( __( 'Please pick a date and a time.', 'pay4all-pro' ) ); ?>);
							return;
						}
						dt = d + 'T' + t;
					}
					// Piggy-back on the standard post save : add hidden
					// fields to the outer #post form + submit through the
					// normal Publish flow so the merchant's unsaved TinyMCE
					// content / subject / CTA edits are persisted BEFORE
					// the campaign flips to queued. The save handler
					// detects the p4all_nl_queue_after_save flag and
					// performs the queue op right after wp_update_post().
					var form = document.getElementById('post');
					if (!form) { return; }
					function addHidden ( name, value ) {
						var input = document.createElement('input');
						input.type = 'hidden';
						input.name = name;
						input.value = value;
						form.appendChild(input);
					}
					addHidden('p4all_nl_queue_after_save', '1');
					addHidden('p4all_nl_scheduled_at',     dt);
					addHidden('p4all_nl_queue_nonce',      <?php echo wp_json_encode( wp_create_nonce( 'p4all_nl_queue_after_save_' . $post->ID ) ); ?>);
					// Defuse WP's « Leave site? » beforeunload prompt so
					// the merchant isn't asked to confirm right after they
					// just clicked Confirm.
					window.onbeforeunload = null;
					if (window.jQuery) { window.jQuery(window).off('beforeunload.edit-post'); }
					// Route through the standard Publish button so WP's
					// dirty-tracking gets cleared and the save flows
					// through save_post_p4all_newsletter as usual.
					var pub = document.getElementById('publish');
					if (pub) { pub.click(); return; }
					form.submit();
				});
			})();
			</script>
		<?php elseif ( in_array( $status, [ Newsletter::STATUS_QUEUED, Newsletter::STATUS_SENDING ], true ) ) : ?>
			<p>
				<a href="<?php echo esc_url( $make_url( 'pause' ) ); ?>" class="button">
					<?php esc_html_e( 'Pause', 'pay4all-pro' ); ?>
				</a>
			</p>
		<?php elseif ( $status === Newsletter::STATUS_DONE ) : ?>
			<?php // Broadcast finished — no more actions to take.
				// Render a disabled primary-styled button so the merchant
				// sees a clear « finished » state where the schedule
				// button used to be. ?>
			<p>
				<button type="button" class="button button-primary" disabled style="opacity:.9;cursor:default;">
					<span class="dashicons dashicons-yes" style="vertical-align:middle;font-size:16px;line-height:1.4;"></span>
					<?php esc_html_e( 'Send finished', 'pay4all-pro' ); ?>
				</button>
			</p>
		<?php endif; ?>
		<?php
	}

	// ---------------------------------------------------------------
	// Content metabox : per-language accordions with TinyMCE + variables
	// ---------------------------------------------------------------
	public static function render_content_box( \WP_Post $post ) : void {
		$languages = self::active_languages();
		$lang_labels = [
			'fr' => __( 'French',  'pay4all-pro' ),
			'de' => __( 'German',  'pay4all-pro' ),
			'it' => __( 'Italian', 'pay4all-pro' ),
			'en' => __( 'English', 'pay4all-pro' ),
		];
		$content = Newsletter::content( (int) $post->ID );
		$kind    = Newsletter::kind( (int) $post->ID );
		?>
		<p class="description" style="margin-top:0;">
			<?php esc_html_e( 'Write one version per language. Click a variable button below the editor to insert it at the caret. Empty languages fall back to the site\'s primary language at send time.', 'pay4all-pro' ); ?>
		</p>

		<?php foreach ( $languages as $i => $code ) :
			$row = $content[ $code ] ?? [];
			$editor_id = 'p4all_nl_body_' . $code;
			?>
			<details class="p4all-nl-lang" <?php echo $i === 0 ? 'open' : ''; ?> style="border:1px solid #dcdcde;border-radius:6px;margin-bottom:10px;">
				<summary style="cursor:pointer;padding:10px 14px;background:#f6f7f7;border-radius:6px 6px 0 0;font-weight:600;">
					<?php echo esc_html( $lang_labels[ $code ] ?? strtoupper( $code ) ); ?>
				</summary>
				<div style="padding:14px;">
					<p>
						<label for="p4all_nl_subject_<?php echo esc_attr( $code ); ?>"><strong><?php esc_html_e( 'Subject', 'pay4all-pro' ); ?></strong></label><br>
						<input type="text"
						       id="p4all_nl_subject_<?php echo esc_attr( $code ); ?>"
						       name="p4all_nl_content[<?php echo esc_attr( $code ); ?>][subject]"
						       value="<?php echo esc_attr( (string) ( $row['subject'] ?? '' ) ); ?>"
						       class="widefat">
					</p>

					<p><strong><?php esc_html_e( 'Body', 'pay4all-pro' ); ?></strong></p>
					<?php
					wp_editor(
						(string) ( $row['body'] ?? '' ),
						$editor_id,
						[
							'textarea_name' => 'p4all_nl_content[' . $code . '][body]',
							'textarea_rows' => 10,
							'media_buttons' => false,
							'teeny'         => false,
							'tinymce'       => [
								'toolbar1'      => 'formatselect,bold,italic,underline,bullist,numlist,link,unlink,alignleft,aligncenter,alignright,undo,redo',
								'toolbar2'      => '',
								'block_formats' => 'Paragraph=p; Heading 2=h2; Heading 3=h3',
							],
							'quicktags'     => true,
						]
					);
					?>

					<p class="p4all-nl-tokens" data-editor="<?php echo esc_attr( $editor_id ); ?>" style="margin-top:8px;">
						<strong><?php esc_html_e( 'Insert variable :', 'pay4all-pro' ); ?></strong>
						<?php self::render_token_buttons( $editor_id, $kind ); ?>
					</p>

					<table class="form-table">
						<tr>
							<th style="width:180px;"><label for="p4all_nl_cta_label_<?php echo esc_attr( $code ); ?>"><?php esc_html_e( 'CTA label', 'pay4all-pro' ); ?></label></th>
							<td><input type="text"
							           id="p4all_nl_cta_label_<?php echo esc_attr( $code ); ?>"
							           name="p4all_nl_content[<?php echo esc_attr( $code ); ?>][cta_label]"
							           value="<?php echo esc_attr( (string) ( $row['cta_label'] ?? '' ) ); ?>"
							           class="regular-text"></td>
						</tr>
						<tr>
							<th><label for="p4all_nl_cta_url_<?php echo esc_attr( $code ); ?>"><?php esc_html_e( 'CTA URL', 'pay4all-pro' ); ?></label></th>
							<td><input type="url"
							           id="p4all_nl_cta_url_<?php echo esc_attr( $code ); ?>"
							           name="p4all_nl_content[<?php echo esc_attr( $code ); ?>][cta_url]"
							           value="<?php echo esc_attr( (string) ( $row['cta_url'] ?? '' ) ); ?>"
							           class="regular-text code"></td>
						</tr>
					</table>
				</div>
			</details>
		<?php endforeach; ?>

		<script>
		(function () {
			// Click a token button → insert into the matching TinyMCE
			// editor at the caret. Falls back to appending to the textarea
			// when TinyMCE isn't active (quicktags / plain-text mode).
			document.addEventListener('click', function (ev) {
				var btn = ev.target.closest ? ev.target.closest('.p4all-nl-token') : null;
				if (!btn) { return; }
				ev.preventDefault();
				var token   = btn.getAttribute('data-token');
				var editor  = btn.getAttribute('data-editor');
				var snippet = '{' + token + '}';
				if (window.tinymce) {
					var ed = window.tinymce.get(editor);
					if (ed && !ed.isHidden()) { ed.execCommand('mceInsertContent', false, snippet); return; }
				}
				var ta = document.getElementById(editor);
				if (ta) {
					var start = ta.selectionStart || ta.value.length;
					var end   = ta.selectionEnd   || ta.value.length;
					ta.value = ta.value.substring(0, start) + snippet + ta.value.substring(end);
					ta.focus();
					ta.selectionStart = ta.selectionEnd = start + snippet.length;
				}
			});
		})();
		</script>
		<?php
	}

	private static function render_token_buttons( string $editor_id, string $kind ) : void {
		$bag = NewsletterTemplates::available_tokens( $kind );
		$all = array_merge( $bag['common'], $bag['extra'], $bag['system'] );
		foreach ( $all as $token ) {
			echo '<button type="button" class="button button-small p4all-nl-token" data-token="' . esc_attr( $token ) . '" data-editor="' . esc_attr( $editor_id ) . '" style="margin-right:4px;margin-bottom:4px;">{' . esc_html( $token ) . '}</button>';
		}
	}

	private static function active_languages() : array {
		if ( ! class_exists( '\\Pay4All\\Form\\Admin\\Settings\\Schema' ) ) { return [ 'en' ]; }
		$langs = (array) \Pay4All\Form\Admin\Settings\Schema::get( 'languages', [] );
		$langs = array_values( array_intersect( $langs, \Pay4All\Form\Admin\Settings\Schema::SUPPORTED_LANGUAGES ) );
		if ( empty( $langs ) ) { $langs = \Pay4All\Form\Admin\Settings\Schema::default_languages(); }
		return $langs;
	}

	// ---------------------------------------------------------------
	// Save handler — two-step aware.
	// ---------------------------------------------------------------
	public static function save( int $post_id, \WP_Post $post ) : void {
		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { return; }
		if ( $post->post_type !== PostTypes::NEWSLETTER ) { return; }
		if ( ! current_user_can( 'edit_post', $post_id ) ) { return; }
		$nonce = isset( $_POST['_p4all_nl_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['_p4all_nl_nonce'] ) ) : '';
		if ( ! wp_verify_nonce( $nonce, self::NONCE ) ) { return; }

		$kind       = isset( $_POST['p4all_nl_kind'] ) ? sanitize_key( wp_unslash( (string) $_POST['p4all_nl_kind'] ) ) : Newsletter::KIND_FREE;
		if ( ! in_array( $kind, Newsletter::KINDS, true ) ) { $kind = Newsletter::KIND_FREE; }
		$product_id = isset( $_POST['p4all_nl_product_id'] ) ? absint( wp_unslash( $_POST['p4all_nl_product_id'] ) ) : 0;
		$promo_id   = isset( $_POST['p4all_nl_promo_id'] )   ? absint( wp_unslash( $_POST['p4all_nl_promo_id'] ) )   : 0;
		$is_validate = ! empty( $_POST['p4all_nl_validate'] );

		if ( ! Newsletter::is_kind_confirmed( $post_id ) ) {
			// STEP 1 — merchant just validated the template picker. Seed
			// the content bag with the default templates for every active
			// language so step 2 lands on an editor that matches the DB,
			// then lock the kind. Content POST vars aren't posted at this
			// stage (the editors aren't rendered yet) so we ignore them.
			$seeded = [];
			foreach ( self::active_languages() as $lang ) {
				$seeded[ $lang ] = NewsletterTemplates::for_kind( $kind, $lang );
			}
			Newsletter::save( $post_id, $kind, $product_id, $promo_id, $seeded );
			// Title is mandatory before we can confirm — the JS gate
			// blocks the click but a tampered POST could still land here.
			// Refuse to flip kind_confirmed if the post has no title so
			// the merchant sees the step-1 screen again on the reload.
			if ( $is_validate && trim( (string) $post->post_title ) === '' ) {
				return;
			}
			if ( $is_validate ) {
				update_post_meta( $post_id, Newsletter::META_KIND_CONFIRMED, '1' );
				// Force the post to publish so the CPT list picks it up
				// AND the metaboxes on the next render include Content +
				// Status (they gate on kind_confirmed via add_boxes).
				if ( $post->post_status !== 'publish' ) {
					// Avoid recursion : remove our own save hook while we
					// flip the status, then re-add. wp_update_post fires
					// save_post_p4all_newsletter again otherwise.
					remove_action( 'save_post_' . PostTypes::NEWSLETTER, [ self::class, 'save' ], 10 );
					wp_update_post( [ 'ID' => $post_id, 'post_status' => 'publish' ] );
					add_action( 'save_post_' . PostTypes::NEWSLETTER, [ self::class, 'save' ], 10, 2 );
				}
			}
			return;
		}

		// STEP 2 — normal save. Kind is locked (kind hidden field mirrors
		// the disabled select), so we honour whatever the current DB says
		// even if a tampered POST tried to change it.
		$kind = Newsletter::kind( $post_id );
		// phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- each nested value sanitized inside Newsletter::save() via sanitize_text_field / wp_kses_post / esc_url_raw.
		$content_in = isset( $_POST['p4all_nl_content'] ) && is_array( $_POST['p4all_nl_content'] )
			? wp_unslash( $_POST['p4all_nl_content'] )
			: [];
		// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized

		Newsletter::save( $post_id, $kind, $product_id, $promo_id, $content_in );

		// « Queue after save » shortcut : the schedule-broadcast modal
		// piggy-backs on the standard Publish save (so unsaved TinyMCE
		// content / subject / CTA edits are persisted first) and asks
		// us to flip the campaign to queued afterwards. Nonce-gated
		// separately from the main save nonce because the modal renders
		// its own hidden field.
		if ( ! empty( $_POST['p4all_nl_queue_after_save'] ) ) {
			$queue_nonce = isset( $_POST['p4all_nl_queue_nonce'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['p4all_nl_queue_nonce'] ) ) : '';
			if ( wp_verify_nonce( $queue_nonce, 'p4all_nl_queue_after_save_' . $post_id ) ) {
				self::flip_to_queued( $post_id );
			}
		}
	}

	/**
	 * Shared « send now vs. schedule for later » branching, used both
	 * from the modal's queue-after-save path (this file) and the direct
	 * queue link handled by Page::handle_action(). Kept here so the
	 * scheduling logic lives next to the metabox that renders the modal.
	 */
	private static function flip_to_queued( int $post_id ) : void {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- called from the save_post handler which has already verified the metabox nonce.
		$sched_raw = isset( $_POST['p4all_nl_scheduled_at'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['p4all_nl_scheduled_at'] ) ) : '';
		$sched_utc = '';
		if ( $sched_raw !== '' && preg_match( '/^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})$/', $sched_raw, $m ) ) {
			try {
				$tz     = wp_timezone();
				$picked = new \DateTimeImmutable( $m[1] . 'T' . $m[2] . ':00', $tz );
				if ( $picked->getTimestamp() > time() + 60 ) {
					$sched_utc = gmdate( 'Y-m-d H:i:s', $picked->getTimestamp() );
				}
			} catch ( \Throwable $e ) {
				// Malformed date : ignore + fall back to send-now.
			}
		}
		if ( $sched_utc !== '' ) {
			update_post_meta( $post_id, Newsletter::META_SCHEDULED_AT, $sched_utc );
			Newsletter::set_status( $post_id, Newsletter::STATUS_QUEUED );
		} else {
			delete_post_meta( $post_id, Newsletter::META_SCHEDULED_AT );
			Newsletter::set_status( $post_id, Newsletter::STATUS_QUEUED );
			// Trigger an immediate first batch so the merchant sees
			// progress right on the redirect-back edit screen.
			if ( class_exists( '\\Pay4All\\Pro\\Services\\NewsletterSpooler' ) ) {
				\Pay4All\Pro\Services\NewsletterSpooler::process_batch( $post_id );
			}
		}
	}
}
