<?php
/**
 * File Path: includes/admin/class-mbs-metabox-arc.php
 *
 * Pre-Release / ARC Metabox — Core (Hook Registration & Save)
 *
 * Wires save_post and instantiates the two delegate classes:
 *   MBS_Metabox_Arc_Render — metabox registration, enqueue, and render.
 *   MBS_Metabox_Arc_Ajax   — AJAX export, approve, and reject handlers.
 *
 * Stories: ARC-2, ARC-3, ARC-4, F-027.
 * REF-015 (Session 137): extracted render + AJAX into dedicated classes.
 *
 * Tier enforcement:
 *   FREE  — 1 book Active at a time; 5 reviewers per book; no export.
 *   PRO   — unlimited books Active; 25 reviewers per book; XLSX + CSV + JSON.
 *   PUB   — unlimited books Active; unlimited reviewers; XLSX + CSV + JSON.
 *
 * @package MyBookShowroom
 * @since   1.1.0
 */

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

// phpcs:disable WordPress.Security.NonceVerification -- nonce verified in parent class verify_save() / check_admin_referer() before $_POST access; PHPCS cannot trace cross-method nonce verification
// phpcs:disable WordPress.Security.ValidatedSanitizedInput -- input sanitized at point of use (sanitize_text_field, absint, wp_kses_post, etc.); false positives from PHPCS not tracing sanitization helpers called before this method
class MBS_Metabox_Arc extends MBS_Metabox_Base { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedClassFound -- mbs_/MBS_ prefix is correct; scanner not configured with plugin prefix

	/** Register hooks and instantiate delegate classes. */
	public function __construct() {
		add_action( 'save_post_' . MBS_POST_TYPE, array( $this, 'save' ), 10, 2 );
		new MBS_Metabox_Arc_Render();
		new MBS_Metabox_Arc_Ajax();
	}

	// =========================================================================
	// Save
	// =========================================================================

	/**
	 * Save ARC postmeta and reviewer rows.
	 *
	 * @param int     $post_id Post ID.
	 * @param WP_Post $post    Post object.
	 */
	public function save( int $post_id, WP_Post $post ): void {
		if ( ! $this->verify_save( $post_id, 'mbs_arc_nonce', 'mbs_arc_nonce_' . $post_id ) ) {
			return;
		}

		$is_pro = mbs_is_pro();
		$is_pub = mbs_is_pub();

		$status = $this->save_arc_status( $post_id, $is_pro );
		$dates  = $this->save_arc_dates( $post_id );
		MBS_Arc_Recipient_DAO::save_campaign( $post_id, array_merge( array( 'status' => $status ), $dates ) );
		$this->save_arc_reviewers( $post_id, $is_pro, $is_pub );

		// F-026-6: Schedule async ESP sync 60 s out so the DB transaction
		// has committed before the cron handler reads it. PRO + enabled only.
		if ( mbs_is_pro() && '1' === mbs_get_setting( 'email', 'arc_sync_enabled', '0' ) ) {
			wp_schedule_single_event( time() + 60, 'mbs_esp_arc_reviewer_sync', array( $post_id ) );
		}
	}

	/**
	 * Save ARC status, enforcing the FREE one-active-book limit.
	 *
	 * @param int  $post_id Book post ID.
	 * @param bool $is_pro  Whether the site holds a PRO license.
	 * @return string The final validated ARC status stored for this book.
	 */
	private function save_arc_status( int $post_id, bool $is_pro ): string {
		$requested = sanitize_text_field( wp_unslash( $_POST['mbs_arc_status'] ?? 'not_started' ) );
		$valid     = array( 'not_started', 'active', 'closed' );
		if ( ! in_array( $requested, $valid, true ) ) {
			$requested = 'not_started';
		}

		if ( ! $is_pro && 'active' === $requested ) {
			$other_active = MBS_Arc_Recipient_DAO::count_active_books( $post_id );
			if ( $other_active > 0 ) {
				$campaign  = MBS_Arc_Recipient_DAO::get_campaign( $post_id );
			$requested = $campaign ? $campaign->status : 'not_started';
				set_transient(
					'mbs_arc_block_' . get_current_user_id(),
					__( 'ARC status not saved: another book is already Active. Upgrade to PRO to run unlimited books in ARC simultaneously.', 'my-book-showroom' ),
					30
				);
			}
		}
		return $requested;
	}

	/**
	 * Save ARC copy count and date fields.
	 *
	 * @param int $post_id Book post ID.
	 * @return array Validated campaign field values keyed by DB column name.
	 */
	private function save_arc_dates( int $post_id ): array {
		$pattern    = '/^\\d{4}-\\d{2}-\\d{2}$/';
		$copy_count  = absint( $_POST['mbs_arc_copy_count'] ?? 0 );
		$dist_date   = sanitize_text_field( wp_unslash( $_POST['mbs_arc_distribution_date']  ?? '' ) );
		$close_date  = sanitize_text_field( wp_unslash( $_POST['mbs_arc_close_date']         ?? '' ) );
		$tgt_date    = sanitize_text_field( wp_unslash( $_POST['mbs_arc_target_review_date'] ?? '' ) );

		if ( ! preg_match( $pattern, $dist_date ) )  { $dist_date  = ''; }
		if ( ! preg_match( $pattern, $close_date ) ) { $close_date = ''; }
		if ( ! preg_match( $pattern, $tgt_date ) )   { $tgt_date   = ''; }

		return array(
			'copy_count'         => $copy_count,
			'distribution_date'  => $dist_date,
			'close_date'         => $close_date,
			'target_review_date' => $tgt_date,
		);
	}

	/**
	 * Save, update, and delete ARC reviewer rows.
	 *
	 * @param int  $post_id Book post ID.
	 * @param bool $is_pro  Whether the site holds a PRO license.
	 * @param bool $is_pub  Whether the site holds a Publisher license.
	 */
	private function save_arc_reviewers( int $post_id, bool $is_pro, bool $is_pub ): void {
		$pattern   = '/^\\d{4}-\\d{2}-\\d{2}$/';
		$submitted = isset( $_POST['mbs_arc_reviewers'] ) && is_array( $_POST['mbs_arc_reviewers'] )
			? $_POST['mbs_arc_reviewers']
			: array();
		$cap           = $is_pub ? PHP_INT_MAX : ( $is_pro ? 25 : 5 );
		$existing_rows = MBS_Arc_Recipient_DAO::get_by_book( $post_id );
		$existing_ids  = array_map( static fn( $r ) => (int) $r->id, $existing_rows );
		$submitted_ids = array();
		$rows_saved    = 0;

		foreach ( $submitted as $row_data ) {
			$row_id = absint( $row_data['id'] ?? 0 );
			$name   = sanitize_text_field( wp_unslash( $row_data['reviewer_name'] ?? '' ) );
			if ( '' === $name )        { continue; }
			if ( $rows_saved >= $cap ) { break; }

			$allowed_types = array( '', 'bookfunnel', 'email', 'netgalley', 'booksirenreads', 'direct', 'other' );
			$contact_type  = sanitize_key( $row_data['contact_type'] ?? '' );
			// V5-B-004 fix: use sanitize_email() when contact_type is 'email'.
			$contact = ( 'email' === $contact_type )
				? sanitize_email( wp_unslash( $row_data['contact_method'] ?? '' ) )
				: sanitize_text_field( wp_unslash( $row_data['contact_method'] ?? '' ) );
			if ( ! in_array( $contact_type, $allowed_types, true ) ) { $contact_type = ''; }
			$d_sent = sanitize_text_field( wp_unslash( $row_data['date_sent'] ?? '' ) );
			if ( ! preg_match( $pattern, $d_sent ) ) { $d_sent = ''; }
			$posted = absint( $row_data['review_posted'] ?? 0 ) ? 1 : 0;

			// Parse multi-URL array; first URL also kept in review_url for backward compat.
			$urls_raw   = ( $posted && isset( $row_data['review_urls'] ) && is_array( $row_data['review_urls'] ) )
				? $row_data['review_urls']
				: array();
			$urls_clean = array();
			foreach ( $urls_raw as $raw_url ) {
				$u = esc_url_raw( wp_unslash( (string) $raw_url ) );
				if ( '' !== $u ) {
					$urls_clean[] = $u;
				}
			}
			// Backward-compat: keep review_url = first URL.
			$url = $urls_clean[0] ?? '';

			$row = array(
				'post_id'        => $post_id,
				'reviewer_name'  => $name,
				'contact_method' => $contact,
				'contact_type'   => $contact_type ?: null,
				'date_sent'      => $d_sent ?: null,
				'review_posted'  => $posted,
				'review_url'     => $url ?: null,
				// review_urls JSON column no longer written -- child table used instead (R2-004).
			);

			if ( $row_id > 0 && in_array( $row_id, $existing_ids, true ) ) {
				MBS_Arc_Recipient_DAO::update( $row_id, $row );
				MBS_Arc_Recipient_DAO::save_urls( $row_id, $urls_clean );  // R2-004
				$submitted_ids[] = $row_id;
			} else {
				$new_id = MBS_Arc_Recipient_DAO::insert( $row );
				if ( $new_id ) {
					MBS_Arc_Recipient_DAO::save_urls( $new_id, $urls_clean );  // R2-004
					$submitted_ids[] = $new_id;
					}
			}
			$rows_saved++;
		}

		// Delete reviewer rows that were removed from the form.
		$to_delete = array_diff( $existing_ids, $submitted_ids );
		foreach ( $to_delete as $del_id ) {
			MBS_Arc_Recipient_DAO::delete( (int) $del_id );
		}
	}
}
