<?php

require_once __DIR__ . '/class-content-miner-collision-recommendation.php';
require_once __DIR__ . '/class-content-miner-prompt-page-evidence.php';

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

/** Build provider-neutral prompts from existing Content Miner evidence. */
class Content_Miner_Prompt_Builder {
	/** @var string */
	private $site_name;

	/** @var string */
	private $business_descriptor;

	/** @param string|null $site_name Site name. @param string|null $business_descriptor Optional site tagline. */
	public function __construct( $site_name = null, $business_descriptor = null ) {
		$this->site_name = null === $site_name && function_exists( 'get_bloginfo' ) ? get_bloginfo( 'name' ) : (string) $site_name;
		$this->business_descriptor = null === $business_descriptor && function_exists( 'get_bloginfo' ) ? get_bloginfo( 'description' ) : (string) $business_descriptor;
		$this->site_name = $this->clean( $this->site_name );
		$this->business_descriptor = $this->clean( $this->business_descriptor );
	}

	/** Build a prompt for one collision pair. @return string */
	public function build_collision_pair( array $pair, array $left_page, array $right_page ) {
		$pair = Content_Miner_Collision_Recommendation::resolve( $pair );
		$reason = ! empty( $pair['llm_verdict']['rationale'] ) ? (string) $pair['llm_verdict']['rationale'] : (string) $pair['reason'];
		$lines = array(
			$this->role_line(),
			'',
			__( 'A content analysis tool found two pages that may overlap:', 'content-miner' ),
		);
		$lines = array_merge( $lines, $this->page_block( __( 'Page A', 'content-miner' ), $left_page ) );
		$lines = array_merge( $lines, $this->page_block( __( 'Page B', 'content-miner' ), $right_page ) );
		$lines[] = '';
		/* translators: 1: similarity percentage, 2: resolved action, 3: resolved reason or AI rationale. */
		$lines[] = sprintf( __( 'The tool measured them %1$d%% similar and recommends: %2$s. Reason: %3$s', 'content-miner' ), (int) round( (float) $pair['score'] * 100 ), (string) $pair['action'], $reason );
		$lines[] = '';
		$lines[] = __( 'Please:', 'content-miner' );
		$lines[] = __( '1) Explain in plain language whether these pages are actually competing and what that could mean for their search performance.', 'content-miner' );
		$lines[] = __( '2) Recommend keep both and differentiate, combine, or leave alone, with simple trade-offs.', 'content-miner' );
		$lines[] = __( '3) Give 3–5 specific prioritized steps the owner or a web professional could take.', 'content-miner' );
		$lines[] = '';
		$lines[] = $this->guardrail();
		return implode( "\n", $lines );
	}

	/** Build a prompt for one page recommendation or recorded decision. @return string */
	public function build_single_page( array $page ) {
		$recommendation = isset( $page['recommendation'] ) && is_array( $page['recommendation'] ) ? $page['recommendation'] : array();
		$action = isset( $recommendation['action'] ) ? (string) $recommendation['action'] : __( 'Review', 'content-miner' );
		$reason = isset( $recommendation['reason'] ) ? (string) $recommendation['reason'] : __( 'The available evidence needs an operator review.', 'content-miner' );
		$lines = array(
			$this->role_line(),
			'',
			__( 'A content analysis tool reviewed this page:', 'content-miner' ),
		);
		$lines = array_merge( $lines, $this->page_block( __( 'Page', 'content-miner' ), $page ) );
		$lines[] = '';
		/* translators: 1: page action, 2: page reason. */
		$lines[] = sprintf( __( 'The tool recommends: %1$s. Reason: %2$s', 'content-miner' ), $action, $reason );
		$lines[] = '';
		$lines[] = __( 'Please:', 'content-miner' );
		$lines[] = __( '1) Explain what this recommendation means in plain language.', 'content-miner' );
		$lines[] = __( '2) Say whether the available search and visitor evidence supports it, and identify any evidence still needed.', 'content-miner' );
		$lines[] = __( '3) Give 3–5 specific prioritized next steps, including what to verify before changing the page.', 'content-miner' );
		$lines[] = '';
		$lines[] = $this->guardrail();
		return implode( "\n", $lines );
	}

	/** Build a prompt for the full report, optionally emphasizing one section. @return string */
	public function build_whole_report( array $report, $focus = '' ) {
		$lines = array(
			$this->role_line(),
			'',
			__( 'Use a proof-first review structure. Separate observed facts from assumptions, cite the source for each observation, state explicit limitations, and end with one readiness verdict: ship, caution, or block.', 'content-miner' ),
			'',
			__( 'Observed facts from Content Miner:', 'content-miner' ),
		);
		if ( '' !== trim( (string) $focus ) ) {
			/* translators: %s: report section name. */
			$lines[] = sprintf( __( 'Focus this explanation on the %s section while using the full report for context.', 'content-miner' ), $this->clean( $focus ) );
		}
		/* translators: 1: source content count, 2: remembered content count. */
		$lines[] = sprintf( __( '- Library: %1$d source pages; %2$d remembered pages', 'content-miner' ), (int) ( $report['source_total'] ?? 0 ), (int) ( $report['memory_indexed'] ?? 0 ) );
		/* translators: 1: scored page count, 2: excluded page count. */
		$lines[] = sprintf( __( '- Site scoring: %1$d pages scored; %2$d inventoried pages excluded', 'content-miner' ), (int) ( $report['scored_count'] ?? 0 ), (int) ( $report['excluded_count'] ?? 0 ) );
		$input_sources = isset( $report['input_sources'] ) && is_array( $report['input_sources'] ) ? $report['input_sources'] : array();
		/* translators: 1: rendered input count, 2: stored fallback count, 3: stored non-public count. */
		$lines[] = sprintf( __( '- Input sources: %1$d rendered; %2$d stored fallback; %3$d stored non-public', 'content-miner' ), (int) ( $input_sources['rendered'] ?? 0 ), (int) ( $input_sources['stored_fallback'] ?? 0 ), (int) ( $input_sources['stored_nonpublic'] ?? 0 ) );
		$summary = isset( $report['summary'] ) && is_array( $report['summary'] ) ? $report['summary'] : array();
		$overlap_pairs = max( 0, (int) ( $summary['overlap_pairs'] ?? 0 ) );
		$overlap_pairs_total = max( $overlap_pairs, (int) ( $summary['overlap_pairs_total'] ?? $overlap_pairs ) );
		/* translators: 1: keep count, 2: refresh count, 3: localize count, 4: review count, 5: collision count. */
		$lines[] = sprintf( __( '- Recommendations among scored pages: %1$d keep; %2$d refresh; %3$d localize; %4$d review; %5$d possible collisions', 'content-miner' ), (int) ( $summary['keep'] ?? 0 ), (int) ( $summary['refresh'] ?? 0 ), (int) ( $summary['localize'] ?? 0 ), (int) ( $summary['review'] ?? 0 ), $overlap_pairs );
		if ( $overlap_pairs_total > $overlap_pairs ) {
			/* translators: 1: displayed collision count, 2: total collision count. */
			$lines[] = sprintf( __( '- Collision detail is truncated: only %1$d of %2$d possible collisions are shown', 'content-miner' ), $overlap_pairs, $overlap_pairs_total );
		}
		$scores = isset( $report['readiness_scores'] ) && is_array( $report['readiness_scores'] ) ? $report['readiness_scores'] : array();
		if ( $scores ) {
			/* translators: 1: SEO score, 2: AEO score, 3: GEO score, 4: AIO score. */
			$lines[] = sprintf( __( '- Average readiness scores across scored pages: SEO %1$d; AEO %2$d; GEO %3$d; AIO %4$d', 'content-miner' ), (int) ( $scores['seo'] ?? 0 ), (int) ( $scores['aeo'] ?? 0 ), (int) ( $scores['geo'] ?? 0 ), (int) ( $scores['aio'] ?? 0 ) );
		}
		$issues = isset( $report['top_issues'] ) && is_array( $report['top_issues'] ) ? array_slice( $report['top_issues'], 0, 5, true ) : array();
		foreach ( $issues as $issue => $count ) {
			/* translators: 1: issue label, 2: affected-page count. */
			$lines[] = sprintf( __( '- Common gap among scored pages: %1$s (%2$d pages)', 'content-miner' ), $this->clean( $issue ), (int) $count );
		}
		$lines = array_merge( $lines, $this->report_evidence( $report ) );
		$lines = array_merge( $lines, $this->report_pages( $report ) );
		$lines[] = '';
		$lines[] = __( 'Assumptions and limitations to preserve:', 'content-miner' );
		$lines[] = __( '- Do not treat unavailable Search Console, visitor, rendered-input, not_stored, or not_measured evidence as a zero score.', 'content-miner' );
		$lines[] = __( '- Do not invent full-page facts; this prompt contains report evidence, not full page bodies.', 'content-miner' );
		$lines[] = __( '- Cite Content Miner report, page evidence, connected search evidence, connected visitor evidence, or explicit absence markers as the source for each observation.', 'content-miner' );
		$lines[] = '';
		$lines[] = __( 'Please:', 'content-miner' );
		$lines[] = __( '1) Explain the most important observed facts in plain language and define every technical acronym.', 'content-miner' );
		$lines[] = __( '2) Put assumptions and missing proof in their own section.', 'content-miner' );
		$lines[] = __( '3) Separate urgent risks, useful opportunities, and findings that only need monitoring.', 'content-miner' );
		$lines[] = __( '4) Give one readiness verdict: ship, caution, or block. Use caution when important proof is unavailable; use block only when the report shows a material readiness blocker.', 'content-miner' );
		$lines[] = __( '5) Produce a prioritized 30-day action plan with the first five practical steps.', 'content-miner' );
		$lines[] = '';
		$lines[] = $this->guardrail();
		return implode( "\n", $lines );
	}

	/** @return string[] */
	private function page_block( $label, array $page ) {
		$title = $this->clean( isset( $page['title'] ) ? $page['title'] : '' );
		$url = $this->clean( isset( $page['url'] ) ? $page['url'] : '' );
		/* translators: 1: page label, 2: page title, 3: page URL. */
		$lines = array( sprintf( __( '%1$s: "%2$s" (%3$s)', 'content-miner' ), $label, $title, $url ) );
		$input_source = isset( $page['input_source'] ) ? (string) $page['input_source'] : '';
		if ( '' !== $input_source ) {
			$source_label = 'rendered' === $input_source ? __( 'rendered', 'content-miner' ) : ( 'stored_nonpublic' === $input_source ? __( 'stored non-public', 'content-miner' ) : __( 'stored fallback', 'content-miner' ) );
			/* translators: %s: rendered, stored fallback, or stored non-public. */
			$lines[] = sprintf( __( '- Analysis input: %s', 'content-miner' ), $source_label );
			if ( 'stored_fallback' === $input_source && ! empty( $page['fallback_reason_code'] ) ) {
				/* translators: %s: stable rendered-fetch failure reason code. */
				$lines[] = sprintf( __( '- Input warning: rendered fetch unavailable (%s)', 'content-miner' ), $this->clean( $page['fallback_reason_code'] ) );
			}
		}
		$search = isset( $page['search_console'] ) && is_array( $page['search_console'] ) ? $page['search_console'] : array();
		if ( $search ) {
			/* translators: 1: clicks, 2: impressions, 3: average search position. */
			$lines[] = sprintf( __( '- Search 28d: %1$d clicks, %2$d impressions, avg position %3$s', 'content-miner' ), (int) ( $search['clicks'] ?? 0 ), (int) ( $search['impressions'] ?? 0 ), $this->number( (float) ( $search['position'] ?? 0 ), 1 ) );
		}
		$analytics = isset( $page['analytics'] ) && is_array( $page['analytics'] ) ? $page['analytics'] : array();
		if ( $analytics ) {
			/* translators: 1: sessions, 2: engagement percentage. */
			$lines[] = sprintf( __( '- Visitors 28d: %1$d sessions, %2$s%% engagement rate', 'content-miner' ), (int) ( $analytics['sessions'] ?? 0 ), $this->number( (float) ( $analytics['engagement_rate'] ?? 0 ) * 100, 1 ) );
		}
		return $lines;
	}

	/** @return string[] */
	private function report_evidence( array $report ) {
		$lines = array();
		$search = isset( $report['search_console'] ) && is_array( $report['search_console'] ) ? $report['search_console'] : array();
		if ( $this->search_available( $search ) ) {
			/* translators: 1: total clicks, 2: total impressions. */
			$lines[] = sprintf( __( '- Connected search evidence: %1$d clicks and %2$d impressions in the latest 28 days', 'content-miner' ), (int) ( $search['clicks'] ?? 0 ), (int) ( $search['impressions'] ?? 0 ) );
		}
		$analytics = isset( $report['analytics'] ) && is_array( $report['analytics'] ) ? $report['analytics'] : array();
		if ( ! empty( $analytics['connected'] ) && ! empty( $analytics['last_sync_at'] ) ) {
			/* translators: %d: total sessions. */
			$lines[] = sprintf( __( '- Connected visitor evidence: %d sessions in the latest 28 days', 'content-miner' ), (int) ( $analytics['sessions'] ?? 0 ) );
		}
		return $lines;
	}

	/** @return string[] */
	private function report_pages( array $report ) {
		$search = isset( $report['search_console'] ) && is_array( $report['search_console'] ) ? $report['search_console'] : array();
		$search_available = $this->search_available( $search );
		$candidates = isset( $report['prompt_pages'] ) && is_array( $report['prompt_pages'] ) ? $report['prompt_pages'] : array();
		$pages = Content_Miner_Prompt_Page_Evidence::rank(
			$candidates,
			$search_available
		);
		$shown = count( $pages );
		$total = max( $shown, (int) ( $report['scored_count'] ?? 0 ) );
		$lines = array( '' );

		if ( $search_available ) {
			$lines[] = sprintf(
				/* translators: 1: shown page count, 2: total scored-page count. */
				__( 'Page evidence: showing %1$d of %2$d scored pages. Pages with matched Search Console data are ranked first by highest impressions, then lowest overall readiness score, then oldest update; pages without matched data follow using readiness and age.', 'content-miner' ),
				$shown,
				$total
			);
		} else {
			$lines[] = sprintf(
				/* translators: 1: shown page count, 2: total scored-page count. */
				__( 'Page evidence: showing %1$d of %2$d scored pages. Search Console data is unavailable, so clicks and impressions are not shown; pages are ranked by lowest overall readiness score, then oldest update.', 'content-miner' ),
				$shown,
				$total
			);
		}

		foreach ( $pages as $page ) {
			$title = $this->clean( $page['title'] ?? '' );
			$path = $this->clean( $page['path'] ?? '/' );
			$path = '' === $path ? '/' : $path;
			$scores = isset( $page['scores'] ) && is_array( $page['scores'] ) ? $page['scores'] : array();
			$gap = $this->clean( $page['top_gap'] ?? '' );
			if ( '' === $gap ) {
				$gap = __( 'none detected by the readiness checks.', 'content-miner' );
			}
			$line = sprintf(
				/* translators: 1: page title, 2: URL path, 3: SEO score, 4: AEO score, 5: GEO score, 6: AIO score. */
				__( '- "%1$s" (%2$s) — readiness: SEO %3$d, AEO %4$d, GEO %5$d, AIO %6$d', 'content-miner' ),
				$title,
				$path,
				(int) ( $scores['seo'] ?? 0 ),
				(int) ( $scores['aeo'] ?? 0 ),
				(int) ( $scores['geo'] ?? 0 ),
				(int) ( $scores['aio'] ?? 0 )
			);
			if ( $search_available ) {
				if ( ! empty( $page['search_metrics_available'] ) ) {
					$clicks = max( 0, (int) ( $page['clicks'] ?? 0 ) );
					$page_search = sprintf(
						/* translators: 1: page clicks, 2: page impressions. */
						_n( '%1$d click, %2$d impressions', '%1$d clicks, %2$d impressions', $clicks, 'content-miner' ),
						$clicks,
						max( 0, (int) ( $page['impressions'] ?? 0 ) )
					);
				} else {
					$page_search = __( 'unavailable for this page', 'content-miner' );
				}
				/* translators: %s: page-level Search Console metrics or unavailable message. */
				$line .= sprintf( __( '; Search 28d: %s', 'content-miner' ), $page_search );
			}
			/* translators: %s: the page's most significant readiness gap. */
			$line .= sprintf( __( '; top gap: %s', 'content-miner' ), $gap );
			$input_source = isset( $page['input_source'] ) ? (string) $page['input_source'] : 'stored_fallback';
			$input_label = 'rendered' === $input_source ? __( 'rendered', 'content-miner' ) : ( 'stored_nonpublic' === $input_source ? __( 'stored non-public', 'content-miner' ) : __( 'stored fallback', 'content-miner' ) );
			/* translators: %s: rendered, stored fallback, or stored non-public. */
			$line .= sprintf( __( '; input: %s', 'content-miner' ), $input_label );
			if ( 'stored_fallback' === $input_source && ! empty( $page['fallback_reason_code'] ) ) {
				/* translators: %s: stable rendered-fetch failure reason code. */
				$line .= sprintf( __( ' (%s)', 'content-miner' ), $this->clean( $page['fallback_reason_code'] ) );
			}
			$evidence = isset( $page['evidence'] ) && is_array( $page['evidence'] ) ? $page['evidence'] : array();
			foreach ( array( 'author_source', 'published_source', 'modified_source' ) as $evidence_key ) {
				if ( ! empty( $evidence[ $evidence_key ] ) ) {
					/* translators: 1: evidence field name, 2: evidence field value. */
					$line .= sprintf( __( '; %1$s: %2$s', 'content-miner' ), $evidence_key, $this->clean( $evidence[ $evidence_key ] ) );
				}
			}
			if ( ! empty( $evidence['direct_answer_span'] ) && array_key_exists( 'direct_answer_passed', $evidence ) && null !== $evidence['direct_answer_passed'] ) {
				/* translators: %s: measured direct-answer opening excerpt. */
				$line .= sprintf( __( '; direct-answer excerpt: %s', 'content-miner' ), Content_Miner_Prompt_Page_Evidence::excerpt( $evidence['direct_answer_span'] ) );
			}
			$not_measured = isset( $page['not_measured'] ) && is_array( $page['not_measured'] ) ? $page['not_measured'] : array();
			if ( $not_measured ) {
				/* translators: %s: JSON-encoded honest absence markers. */
				$line .= sprintf( __( '; honest absence: %s', 'content-miner' ), wp_json_encode( $not_measured ) );
			}
			$lines[] = $line;
		}

		return $lines;
	}

	/** Whether a complete Search Console reporting window is available. */
	private function search_available( array $search ) {
		return ! empty( $search['connected'] )
			&& ! empty( $search['last_sync_at'] )
			&& 'complete' === (string) ( $search['sync_status'] ?? '' );
	}

	/** @return string */
	private function role_line() {
		$site = '' !== $this->site_name ? $this->site_name : __( 'this website', 'content-miner' );
		if ( '' !== $this->business_descriptor ) {
			/* translators: 1: site name, 2: site tagline used as a business description. */
			return sprintf( __( 'You are helping the owner of %1$s, a %2$s website. They are not an SEO expert — explain everything in plain language and define any technical term you use.', 'content-miner' ), $site, $this->business_descriptor );
		}
		/* translators: %s: site name. */
		return sprintf( __( 'You are helping the owner of %s. They are not an SEO expert — explain everything in plain language and define any technical term you use.', 'content-miner' ), $site );
	}

	/** @return string */
	private function guardrail() {
		return __( 'Do not assume they know SEO terminology. Prioritize clarity over completeness.', 'content-miner' );
	}

	/** @return string */
	private function clean( $value ) {
		$value = wp_strip_all_tags( (string) $value );
		return trim( preg_replace( '/\s+/u', ' ', $value ) );
	}

	/** @return string */
	private function number( $value, $decimals ) {
		return function_exists( 'number_format_i18n' ) ? number_format_i18n( $value, $decimals ) : number_format( $value, $decimals, '.', '' );
	}
}
