<?php

namespace WPDRMS\ASP\AI\Service;

use RuntimeException;
use WPDRMS\ASP\AI\AbstractCachedAIFeatureService;
use WPDRMS\ASP\AI\AIInsufficientDataException;
use WPDRMS\ASP\Index\ORM\IndexTableOptions;
use WPDRMS\ASP\Statistics\ORM\Search as StatSearch;
use WPDRMS\PluginCore\Traits\SingletonTrait;
use WPDRMS\Utils\MB;

if ( ! defined( 'ABSPATH' ) ) {
	die( '-1' );
}

class AIStopwordService extends AbstractCachedAIFeatureService {
	use SingletonTrait;

	private function __construct() {}

	/**
	 * @param int  $asp_id  0 = all instances
	 * @param int  $days
	 * @param bool $force
	 * @return array<string, mixed>
	 */
	public function analyze( int $asp_id, int $days, bool $force = false ): array {
		if ( ! IndexTableOptions::instance()->use_stopwords->value ) {
			return array(
				'stopwords_enabled' => false,
				'conflicts'         => array(),
				'generated_at'      => null,
				'cached'            => false,
				'tokens_used'       => 0,
			);
		}

		try {
			return array_merge(
				array( 'stopwords_enabled' => true ),
				$this->runCached(
					array(
						'asp_id' => $asp_id,
						'days'   => $days,
					),
					$force 
				)
			);
		} catch ( AIInsufficientDataException $e ) {
			return array(
				'stopwords_enabled' => true,
				'conflicts'         => array(),
				'generated_at'      => null,
				'cached'            => false,
				'tokens_used'       => 0,
			);
		}
	}

	/**
	 * Removes a word from the IndexTableOptions stopword list.
	 *
	 * @param string $word
	 * @return array{success: true, remaining_count: int}
	 */
	public function removeWord( string $word ): array {
		$opts     = IndexTableOptions::instance();
		$current  = $opts->stopwords->value;
		$filtered = array_values(
			array_filter(
				$current,
				static function ( $w ) use ( $word ) {
					return strcasecmp( (string) $w, $word ) !== 0;
				}
			)
		);

		$opts->setArgs( array( 'stopwords' => array( 'value' => $filtered ) ) )->save();

		return array(
			'success'         => true,
			'remaining_count' => count( $filtered ),
		);
	}

	protected function getFeatureSlug(): string {
		return 'stopword_detector';
	}

	protected function getAgentId(): string {
		return 'stopword_detector';
	}

	protected function getTtl(): int {
		return DAY_IN_SECONDS;
	}

	protected function buildCacheKey( array $args ): string {
		return md5( $args['asp_id'] . '_' . $args['days'] );
	}

	protected function collectData( array $args ): array {
		return array( 'conflicts' => $this->buildStopwordConflicts( $args['asp_id'], $args['days'] ) );
	}

	protected function validateData( array $data ): void {
		if ( empty( $data['conflicts'] ) ) {
			throw new AIInsufficientDataException( 'No stopword conflicts detected.' );
		}
	}

	protected function buildPrompt( array $data ): string {
		return $this->buildStopwordPrompt( $data['conflicts'] );
	}

	protected function buildPayload( array $data, string $ai_result ): array {
		$conflicts   = $data['conflicts'];
		$ai_analysis = json_decode( $ai_result, true );

		if ( is_array( $ai_analysis ) ) {
			$ai_map = array();
			foreach ( $ai_analysis as $item ) {
				if ( isset( $item['word'] ) ) {
					$ai_map[ strtolower( (string) $item['word'] ) ] = $item;
				}
			}
			foreach ( $conflicts as &$conflict ) {
				$word = strtolower( $conflict['word'] );
				if ( isset( $ai_map[ $word ] ) ) {
					$ai_entry                   = $ai_map[ $word ];
					$conflict['explanation']    = (string) ( $ai_entry['explanation'] ?? '' );
					$conflict['safe_to_remove'] = (bool) ( $ai_entry['safe_to_remove'] ?? false );
					$conflict['risk']           = (string) ( $ai_entry['risk'] ?? 'medium' );
					$conflict['recommendation'] = (string) ( $ai_entry['recommendation'] ?? '' );
				}
			}
			unset( $conflict );
		}

		return array(
			'conflicts'    => $conflicts,
			'generated_at' => current_time( 'mysql' ),
		);
	}

	/**
	 * Cross-references zero-result phrases with the active stopword list.
	 *
	 * @param int $asp_id  0 = all instances
	 * @param int $days
	 * @return array<int, array<string, mixed>>
	 */
	private function buildStopwordConflicts( int $asp_id, int $days ): array {
		global $wpdb;

		$it_opts   = IndexTableOptions::instance();
		$min_len   = max( 1, (int) $it_opts->min_word_length->value );
		$raw_words = $it_opts->stopwords->value;

		// Mirror Tokenizer::getStopWords() — discard words shorter than min_word_length
		$active_words = array();
		foreach ( $raw_words as $w ) {
			$w = strtolower( trim( (string) $w ) );
			if ( $w !== '' && MB::strlen( $w ) >= $min_len ) {
				$active_words[ $w ] = true;
			}
		}

		if ( empty( $active_words ) ) {
			return array();
		}

		$stat_table = StatSearch::getTableName();
		$asp_cond   = $asp_id > 0 ? $wpdb->prepare( ' AND asp_id = %d', $asp_id ) : '';

		// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
		$rows = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.NoCaching
			$wpdb->prepare(
				"SELECT phrase, COUNT(*) AS count
				 FROM `{$stat_table}`
				 WHERE found_results = 0
				   AND date >= DATE_SUB(NOW(), INTERVAL %d DAY)
				   AND CHAR_LENGTH(phrase) > 1
				   {$asp_cond}
				 GROUP BY phrase
				 ORDER BY count DESC
				 LIMIT 100",
				$days
			),
			ARRAY_A
		) ?? array();
		// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared

		// For each phrase, find which active stopwords appear in it
		$word_data = array();
		foreach ( $rows as $row ) {
			$phrase = strtolower( trim( (string) $row['phrase'] ) );
			$count  = (int) $row['count'];
			$tokens = array_filter( explode( ' ', $phrase ) );

			foreach ( $tokens as $token ) {
				$token = trim( $token );
				if ( isset( $active_words[ $token ] ) ) {
					if ( ! isset( $word_data[ $token ] ) ) {
						$word_data[ $token ] = array(
							'word'        => $token,
							'phrases'     => array(),
							'total_count' => 0,
						);
					}
					$word_data[ $token ]['phrases'][]    = array(
						'phrase' => (string) $row['phrase'],
						'count'  => $count,
					);
					$word_data[ $token ]['total_count'] += $count;
				}
			}
		}

		if ( empty( $word_data ) ) {
			return array();
		}

		// Sort by total_count DESC, take top 15
		usort(
			$word_data,
			static function ( $a, $b ) {
				return (int) $b['total_count'] - (int) $a['total_count'];
			}
		);

		$conflicts = array();
		foreach ( array_slice( $word_data, 0, 15 ) as $item ) {
			$conflicts[] = array(
				'word'           => $item['word'],
				'affected_count' => $item['total_count'],
				'phrases'        => $item['phrases'],
			);
		}

		return $conflicts;
	}

	/**
	 * @param array<int, array<string, mixed>> $conflicts
	 * @return string
	 */
	private function buildStopwordPrompt( array $conflicts ): string {
		$data = array();
		foreach ( $conflicts as $c ) {
			$data[] = array(
				'word'             => $c['word'],
				'affected_phrases' => array_slice( $c['phrases'], 0, 10 ),
				'total'            => $c['affected_count'],
			);
		}

		return (string) wp_json_encode( $data );
	}
}
