<?php
/**
 * AJAX Controller.
 *
 * Endpoints (all master-admin gated):
 *   - platform_helm_lk_scan          — Scan orphan URLs, return suggestions
 *   - platform_helm_lk_save_approve  — Persist approval list between tabs
 *   - platform_helm_lk_init_execute  — Create a job from approvals, return ID + total
 *   - platform_helm_lk_process_batch — Process next BATCH_SIZE rows
 *   - platform_helm_lk_restore       — One-click rollback
 *   - platform_helm_lk_delete_backup — Delete backup (post stays cleaned)
 *
 * @package Platform\Helm\Modules\Link_Architect
 */

declare( strict_types = 1 );

namespace Platform\Helm\Modules\Link_Architect;

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

final class AjaxController {

	public const NONCE_ACTION = 'platform_helm_lk_nonce';

	/** Bulk content modification is an editor-level action. */
	private const CAPABILITY = 'edit_others_posts';

	public function __construct(
		private KeywordExtractor $extractor,
		private PostScanner $scanner,
		private BatchProcessor $processor,
		private BackupManager $backups,
		private JobStore $jobs
	) {}

	public function register_hooks(): void {
		add_action( 'wp_ajax_platform_helm_lk_scan',          [ $this, 'ajax_scan' ] );
		add_action( 'wp_ajax_platform_helm_lk_save_approve',  [ $this, 'ajax_save_approvals' ] );
		add_action( 'wp_ajax_platform_helm_lk_init_execute',  [ $this, 'ajax_init_execute' ] );
		add_action( 'wp_ajax_platform_helm_lk_process_batch', [ $this, 'ajax_process_batch' ] );
		add_action( 'wp_ajax_platform_helm_lk_restore',       [ $this, 'ajax_restore' ] );
		add_action( 'wp_ajax_platform_helm_lk_delete_backup', [ $this, 'ajax_delete_backup' ] );
	}

	/* =========================================================================
	 * SCAN
	 * ========================================================================= */

	public function ajax_scan(): void {
		$this->verify();

		$urls_raw = isset( $_POST['urls'] ) ? wp_unslash( (string) $_POST['urls'] ) : '';
		$urls     = $this->parse_url_list( $urls_raw );

		if ( empty( $urls ) ) {
			wp_send_json_error( [ 'message' => __( 'Please paste at least one orphan URL.', 'platform-helm' ) ], 400 );
		}
		if ( count( $urls ) > 100 ) {
			wp_send_json_error( [
				'message' => sprintf(
					/* translators: %d: max URLs */
					__( 'Maximum %d URLs per scan. Split into smaller batches.', 'platform-helm' ),
					100
				),
			], 400 );
		}

		$groups = [];
		foreach ( $urls as $url ) {
			$post_id = url_to_postid( $url );
			if ( $post_id <= 0 ) {
				$groups[] = [
					'url'         => $url,
					'post_id'     => 0,
					'post_title'  => '',
					'candidates'  => [],
					'suggestions' => [],
					'error'       => __( 'Could not resolve URL to a local post.', 'platform-helm' ),
				];
				continue;
			}
			$post = get_post( $post_id );
			if ( ! $post ) {
				$groups[] = [
					'url'         => $url,
					'post_id'     => $post_id,
					'post_title'  => '',
					'candidates'  => [],
					'suggestions' => [],
					'error'       => __( 'Resolved post not found.', 'platform-helm' ),
				];
				continue;
			}

			$candidates = $this->extractor->candidates_for( $post_id );
			// Enrich each candidate with a match count.
			foreach ( $candidates as $i => $c ) {
				$candidates[ $i ]['match_count'] = $this->scanner->count_matches( $c['phrase'], $post_id, $url );
			}

			// Pick the BEST candidate: prefer SEO focus > cleaned_title > content_phrase,
			// among those that have a sensible match count (3–50 ideal).
			$best_idx = $this->pick_best_candidate_index( $candidates );

			$suggestions = [];
			if ( null !== $best_idx ) {
				$best   = $candidates[ $best_idx ];
				$source_posts = $this->scanner->find_source_posts(
					(string) $best['phrase'],
					$post_id,
					$url,
					10
				);
				foreach ( $source_posts as $sp ) {
					$suggestions[] = array_merge( $sp, [
						'phrase' => (string) $best['phrase'],
					] );
				}
			}

			$groups[] = [
				'url'             => $url,
				'post_id'         => $post_id,
				'post_title'      => $post->post_title,
				'edit_link'       => (string) get_edit_post_link( $post_id ),
				'candidates'      => array_values( $candidates ),
				'best_candidate'  => $best_idx,
				'suggestions'     => $suggestions,
				'error'           => '',
			];
		}

		wp_send_json_success( [ 'groups' => $groups ] );
	}

	/**
	 * @param array<int,array{strategy:string,match_count:int}> $candidates
	 */
	private function pick_best_candidate_index( array $candidates ): ?int {
		if ( empty( $candidates ) ) {
			return null;
		}
		// First pass: prefer SEO focus keyword if it has any matches.
		foreach ( $candidates as $i => $c ) {
			if ( in_array( $c['strategy'], [ 'yoast_focus', 'rankmath_focus' ], true ) && (int) $c['match_count'] >= 1 ) {
				return $i;
			}
		}
		// Second pass: best in sweet spot (3–50 matches).
		foreach ( $candidates as $i => $c ) {
			$mc = (int) $c['match_count'];
			if ( $mc >= 3 && $mc <= 50 ) {
				return $i;
			}
		}
		// Third pass: anything with at least 1 match.
		foreach ( $candidates as $i => $c ) {
			if ( (int) $c['match_count'] >= 1 ) {
				return $i;
			}
		}
		return 0;
	}

	/* =========================================================================
	 * APPROVALS
	 * ========================================================================= */

	public function ajax_save_approvals(): void {
		$this->verify();

		$raw = isset( $_POST['approvals'] ) ? wp_unslash( (string) $_POST['approvals'] ) : '[]';
		$decoded = json_decode( $raw, true );
		if ( ! is_array( $decoded ) ) {
			wp_send_json_error( [ 'message' => __( 'Invalid approval payload.', 'platform-helm' ) ], 400 );
		}

		$clean = [];
		foreach ( $decoded as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			$source_post_id = (int) ( $row['source_post_id'] ?? 0 );
			$orphan_url     = esc_url_raw( (string) ( $row['orphan_url'] ?? '' ) );
			$phrase         = trim( (string) ( $row['phrase'] ?? '' ) );
			if ( $source_post_id <= 0 || '' === $orphan_url || '' === $phrase ) {
				continue;
			}
			$clean[] = [
				'source_post_id' => $source_post_id,
				'orphan_url'     => $orphan_url,
				'phrase'         => $phrase,
			];
		}

		$this->jobs->save_approvals( get_current_user_id(), $clean );
		wp_send_json_success( [ 'count' => count( $clean ) ] );
	}

	/* =========================================================================
	 * EXECUTE
	 * ========================================================================= */

	public function ajax_init_execute(): void {
		$this->verify();

		$approvals = $this->jobs->get_approvals( get_current_user_id() );
		if ( empty( $approvals ) ) {
			wp_send_json_error( [ 'message' => __( 'No approved suggestions on file. Scan and approve first.', 'platform-helm' ) ], 400 );
		}

		$job_id = $this->jobs->init_job( $approvals );
		wp_send_json_success( [ 'job_id' => $job_id, 'total' => count( $approvals ) ] );
	}

	public function ajax_process_batch(): void {
		$this->verify();

		$job_id = isset( $_POST['job_id'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['job_id'] ) ) : '';
		if ( '' === $job_id ) {
			wp_send_json_error( [ 'message' => __( 'Missing job ID.', 'platform-helm' ) ], 400 );
		}

		$state = $this->jobs->get_state( $job_id );
		if ( null === $state ) {
			wp_send_json_error( [ 'message' => __( 'Job expired or not found. Please restart from Tab 1.', 'platform-helm' ) ], 410 );
		}

		$cursor     = (int) $state['cursor'];
		$total      = (int) $state['total'];
		$approvals  = is_array( $state['approvals'] ) ? $state['approvals'] : [];
		$batch_size = LinkArchitectModule::BATCH_SIZE;

		$batch_log = [];
		$end = min( $cursor + $batch_size, $total );
		for ( $i = $cursor; $i < $end; $i++ ) {
			if ( ! isset( $approvals[ $i ] ) ) {
				continue;
			}
			$result = $this->processor->process_one( $approvals[ $i ] );
			switch ( $result['status'] ) {
				case 'success': $state['succeeded']++; break;
				case 'skipped': $state['skipped']++;   break;
				default:        $state['failed']++;    break;
			}
			$batch_log[] = $result;
		}

		$state['cursor'] = $end;
		// Keep only the last 50 log lines to bound memory.
		$state['log'] = array_slice(
			array_merge( is_array( $state['log'] ) ? $state['log'] : [], $batch_log ),
			-50
		);
		$this->jobs->save_state( $job_id, $state );

		$done = $end >= $total;
		if ( $done ) {
			// Job complete — clear the approval list so a fresh scan doesn't double-up.
			$this->jobs->clear_approvals( get_current_user_id() );
		}

		wp_send_json_success( [
			'processed' => $end,
			'total'     => $total,
			'succeeded' => (int) $state['succeeded'],
			'skipped'   => (int) $state['skipped'],
			'failed'    => (int) $state['failed'],
			'log'       => $batch_log,
			'done'      => $done,
		] );
	}

	/* =========================================================================
	 * RESTORE / DELETE
	 * ========================================================================= */

	public function ajax_restore(): void {
		$this->verify();
		$post_id  = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		$meta_key = isset( $_POST['meta_key'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['meta_key'] ) ) : '';
		if ( $post_id <= 0 || '' === $meta_key ) {
			wp_send_json_error( [ 'message' => __( 'Missing post or backup reference.', 'platform-helm' ) ], 400 );
		}
		$result = $this->backups->restore( $post_id, $meta_key );
		if ( ! $result['ok'] ) {
			wp_send_json_error( [ 'message' => $result['message'] ], 400 );
		}
		wp_send_json_success( [ 'message' => $result['message'] ] );
	}

	public function ajax_delete_backup(): void {
		$this->verify();
		$post_id  = isset( $_POST['post_id'] ) ? (int) $_POST['post_id'] : 0;
		$meta_key = isset( $_POST['meta_key'] ) ? sanitize_text_field( wp_unslash( (string) $_POST['meta_key'] ) ) : '';
		if ( $post_id <= 0 || '' === $meta_key ) {
			wp_send_json_error( [ 'message' => __( 'Missing post or backup reference.', 'platform-helm' ) ], 400 );
		}
		$ok = $this->backups->delete( $post_id, $meta_key );
		if ( ! $ok ) {
			wp_send_json_error( [ 'message' => __( 'Could not delete backup.', 'platform-helm' ) ], 400 );
		}
		wp_send_json_success( [ 'message' => __( 'Backup deleted.', 'platform-helm' ) ] );
	}

	/* =========================================================================
	 * INTERNALS
	 * ========================================================================= */

	private function verify(): void {
		check_ajax_referer( self::NONCE_ACTION, 'nonce' );
		if ( ! current_user_can( self::CAPABILITY ) ) {
			wp_send_json_error( [ 'message' => __( 'Permission denied.', 'platform-helm' ) ], 403 );
		}
	}

	/**
	 * @return string[]
	 */
	private function parse_url_list( string $raw ): array {
		$lines = preg_split( '/\r?\n/', $raw ) ?: [];
		$out   = [];
		foreach ( $lines as $line ) {
			$url = esc_url_raw( trim( $line ) );
			if ( '' === $url ) {
				continue;
			}
			$out[] = $url;
		}
		return array_values( array_unique( $out ) );
	}
}
