<?php
/**
 * Provider for Custom Post Types.
 *
 * @package Replyan_AI
 */

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

class Replyan_AI_CPT_Source_Provider implements Replyan_AI_Source_Provider_Interface {
	private string $post_type;

	public function __construct( string $post_type ) {
		$this->post_type = $post_type;
	}

	public function getDocuments(): array {
		if ( ! post_type_exists( $this->post_type ) ) {
			return array();
		}

		$settings           = Replyan_AI_Settings::get();
		$global_permalinks  = ! isset( $settings['index_permalinks'] ) || '0' !== (string) $settings['index_permalinks'];
		$cpt_config         = isset( $settings['cpt_fields_config'][ $this->post_type ] ) ? $settings['cpt_fields_config'][ $this->post_type ] : null;
		$include_content    = ( null === $cpt_config ) || ! empty( $cpt_config['include_content'] );
		$include_permalink  = ( null === $cpt_config ) ? $global_permalinks : ( ( isset( $cpt_config['include_permalink'] ) ? ! empty( $cpt_config['include_permalink'] ) : true ) && $global_permalinks );
		$allowed_taxonomies = ( null !== $cpt_config && isset( $cpt_config['taxonomies'] ) && is_array( $cpt_config['taxonomies'] ) ) ? $cpt_config['taxonomies'] : null;
		$allowed_fields     = ( null !== $cpt_config && isset( $cpt_config['custom_fields'] ) && is_array( $cpt_config['custom_fields'] ) ) ? $cpt_config['custom_fields'] : null;

		$posts = get_posts( array(
			'post_type'      => $this->post_type,
			'post_status'    => 'publish',
			'posts_per_page' => -1,
		) );

		$documents = array();
		foreach ( $posts as $post ) {
			$content_blocks = array();

			// 1. Post Title (always included as primary entity name)
			if ( ! empty( $post->post_title ) ) {
				$content_blocks[] = 'Title: ' . trim( $post->post_title );
			}

			// 2. Post Link / Permalink
			if ( $include_permalink ) {
				$permalink = get_permalink( $post->ID );
				if ( $permalink ) {
					$content_blocks[] = 'Link: ' . $permalink;
				}
			}

			// 2. Taxonomies (Categories, Locations, Tags, Services, etc.)
			$tax_blocks = $this->get_taxonomy_blocks( $post->ID, $allowed_taxonomies );
			if ( ! empty( $tax_blocks ) ) {
				$content_blocks = array_merge( $content_blocks, $tax_blocks );
			}

			// 3. GeoDirectory Custom Fields (Address, City, Region, Zip, etc.)
			$geodir_blocks = $this->get_geodir_blocks( $post->ID, $allowed_fields );
			if ( ! empty( $geodir_blocks ) ) {
				$content_blocks = array_merge( $content_blocks, $geodir_blocks );
			}

			// 4. ACF Fields (if active)
			$acf_blocks = $this->get_acf_blocks( $post->ID, $allowed_fields );
			if ( ! empty( $acf_blocks ) ) {
				$content_blocks = array_merge( $content_blocks, $acf_blocks );
			}

			// 5. Custom Fields / Post Meta (Zip code, address, phone, highlights, amenities, etc.)
			$meta_blocks = $this->get_meta_blocks( $post->ID, $allowed_fields );
			if ( ! empty( $meta_blocks ) ) {
				$content_blocks = array_merge( $content_blocks, $meta_blocks );
			}

			// 6. Post Content / Description (if enabled)
			if ( $include_content ) {
				$raw_content = wp_strip_all_tags( strip_shortcodes( $post->post_content ) );
				if ( ! empty( trim( $raw_content ) ) ) {
					$content_blocks[] = "Description:\n" . trim( $raw_content );
				}
			}

			$content_blocks = array_values( array_unique( $content_blocks ) );

			/**
			 * Filter the extracted content blocks for a CPT document.
			 *
			 * @param array   $content_blocks Array of formatted content text blocks.
			 * @param WP_Post $post           The post object.
			 * @param string  $post_type      The post type slug.
			 */
			$content_blocks = apply_filters( 'replyan_ai_cpt_document_content', $content_blocks, $post, $this->post_type );

			// Skip posts with no extractable text content or metadata.
			if ( empty( $content_blocks ) ) {
				continue;
			}

			$full_content = implode( "\n\n", $content_blocks );

			$documents[] = array(
				'source_type' => 'cpt:' . $this->post_type,
				'source_id'   => $post->ID,
				'title'       => $post->post_title,
				'url'         => get_permalink( $post->ID ),
				'content'     => $full_content,
				'modified_at' => get_post_modified_time( 'Y-m-d H:i:s', false, $post->ID ),
			);
		}

		return $documents;
	}

	/**
	 * Extract public taxonomies and their terms for a given post.
	 *
	 * @param int        $post_id            Post ID.
	 * @param array|null $allowed_taxonomies Optional list of allowed taxonomy slugs.
	 * @return array
	 */
	private function get_taxonomy_blocks( int $post_id, ?array $allowed_taxonomies = null ): array {
		$taxonomies = get_object_taxonomies( $this->post_type, 'objects' );
		if ( empty( $taxonomies ) ) {
			return array();
		}

		$ignored_taxonomies = array( 'nav_menu', 'link_category', 'post_format', 'wp_theme', 'wp_template_part_area' );
		$blocks             = array();

		foreach ( $taxonomies as $tax ) {
			if ( in_array( $tax->name, $ignored_taxonomies, true ) || ! $tax->public ) {
				continue;
			}

			if ( null !== $allowed_taxonomies && ! in_array( $tax->name, $allowed_taxonomies, true ) ) {
				continue;
			}

			$terms = wp_get_post_terms( $post_id, $tax->name, array( 'fields' => 'names' ) );
			if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
				$label    = ! empty( $tax->label ) ? $tax->label : ucwords( str_replace( array( '-', '_' ), ' ', $tax->name ) );
				$blocks[] = $label . ': ' . implode( ', ', $terms );
			}
		}

		return $blocks;
	}

	/**
	 * Extract safe custom fields (post meta) for a given post.
	 * Excludes private keys, system/audit keys, and sensitive data.
	 *
	 * @param int        $post_id        Post ID.
	 * @param array|null $allowed_fields Optional list of allowed custom field keys.
	 * @return array
	 */
	private function get_meta_blocks( int $post_id, ?array $allowed_fields = null ): array {
		$meta_data = get_post_meta( $post_id );
		if ( empty( $meta_data ) ) {
			return array();
		}

		// Exact meta keys to exclude (internal, audit, system, timestamps)
		$excluded_exact_keys = array(
			'created_at',
			'created_date',
			'created_time',
			'creation_date',
			'updated_at',
			'updated_date',
			'updated_time',
			'modified_date',
			'edit_date',
			'post_date',
			'revision_id',
			'import_id',
			'sync_status',
			'import_source',
			'author_id',
			'user_id',
			'views',
			'views_count',
			'post_views_count',
			'rating_count',
			'menu_order',
			'rule',
			'layout',
			'format',
		);

		/**
		 * Filter the excluded meta keys for CPT indexing.
		 *
		 * @param array  $excluded_exact_keys List of exact meta keys to exclude.
		 * @param string $post_type           The post type slug.
		 */
		$excluded_exact_keys = apply_filters( 'replyan_ai_cpt_excluded_meta_keys', $excluded_exact_keys, $this->post_type );

		// Sensitive / system substrings to exclude
		$sensitive_patterns = array(
			'password',
			'secret',
			'token',
			'api_key',
			'auth',
			'nonce',
			'card',
			'cvv',
			'ssn',
			'private',
			'internal',
			'billing_',
			'stripe',
			'paypal',
			'session',
			'elementor',
			'divi_',
			'vc_',
			'yoast',
			'aioseo',
			'rank_math',
			'schema',
		);

		$blocks = array();

		foreach ( $meta_data as $key => $values ) {
			// 1. Skip private / hidden WordPress & plugin meta (starts with _)
			if ( str_starts_with( $key, '_' ) ) {
				continue;
			}

			if ( null !== $allowed_fields && ! in_array( $key, $allowed_fields, true ) ) {
				continue;
			}

			$lower_key = strtolower( $key );

			// 2. Skip exact audit/system keys
			if ( in_array( $lower_key, $excluded_exact_keys, true ) ) {
				continue;
			}

			// 3. Skip sensitive / security / page-builder patterns
			$is_sensitive = false;
			foreach ( $sensitive_patterns as $pattern ) {
				if ( str_contains( $lower_key, $pattern ) ) {
					$is_sensitive = true;
					break;
				}
			}
			if ( $is_sensitive ) {
				continue;
			}

			// 4. Process and format values
			$formatted_values = array();
			foreach ( $values as $raw_val ) {
				$formatted = $this->format_meta_value( maybe_unserialize( $raw_val ) );
				if ( null !== $formatted && '' !== $formatted ) {
					$formatted_values[] = $formatted;
				}
			}

			if ( ! empty( $formatted_values ) ) {
				$label    = ucwords( str_replace( array( '-', '_' ), ' ', $key ) );
				$blocks[] = $label . ': ' . implode( ', ', $formatted_values );
			}
		}

		return $blocks;
	}

	/**
	 * Format a raw meta value into a clean, human-readable string.
	 *
	 * @param mixed $value Raw meta value.
	 * @return string|null
	 */
	private function format_meta_value( mixed $value ): ?string {
		if ( is_null( $value ) ) {
			return null;
		}

		// Handle array values (e.g. multi-selects, checkboxes, tags)
		if ( is_array( $value ) ) {
			$flattened = array();
			foreach ( $value as $item ) {
				if ( is_scalar( $item ) ) {
					$str = trim( wp_strip_all_tags( (string) $item ) );
					if ( '' !== $str ) {
						$flattened[] = $str;
					}
				}
			}
			return ! empty( $flattened ) ? implode( ', ', $flattened ) : null;
		}

		// Handle scalar values (string, int, float, bool)
		if ( is_scalar( $value ) ) {
			// Handle boolean-like values
			if ( is_bool( $value ) ) {
				return $value ? 'Yes' : 'No';
			}

			$str = trim( wp_strip_all_tags( (string) $value ) );

			// Skip JSON payloads or large code blocks
			if ( str_starts_with( $str, '{' ) || str_starts_with( $str, '[' ) ) {
				$decoded = json_decode( $str, true );
				if ( json_last_error() === JSON_ERROR_NONE && is_array( $decoded ) ) {
					return $this->format_meta_value( $decoded );
				}
			}

			// Clean and return
			return '' !== $str ? $str : null;
		}

		return null;
	}

	/**
	 * Extract GeoDirectory custom fields and location details if applicable.
	 *
	 * @param int        $post_id        Post ID.
	 * @param array|null $allowed_fields Optional list of allowed custom field keys.
	 * @return array
	 */
	private function get_geodir_blocks( int $post_id, ?array $allowed_fields = null ): array {
		global $wpdb;
		$blocks = array();

		$detail_table = $wpdb->prefix . 'geodir_' . $this->post_type . '_detail';
		$row          = null;

		if ( function_exists( 'geodir_get_post_info' ) ) {
			$info = geodir_get_post_info( $post_id );
			if ( ! empty( $info ) ) {
				$row = (array) $info;
			}
		}

		if ( empty( $row ) ) {
			// Check if detail table exists in DB directly
			// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
			$table_exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $detail_table ) );
			if ( $table_exists === $detail_table ) {
				// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
				$row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$detail_table} WHERE post_id = %d", $post_id ), ARRAY_A );
			}
		}

		if ( empty( $row ) ) {
			return $blocks;
		}

		$ignored_keys = array(
			'ID',
			'post_id',
			'post_title',
			'post_content',
			'post_excerpt',
			'post_status',
			'comment_status',
			'ping_status',
			'post_password',
			'post_name',
			'to_ping',
			'pinged',
			'post_modified',
			'post_modified_gmt',
			'post_date',
			'post_date_gmt',
			'post_content_filtered',
			'post_parent',
			'guid',
			'menu_order',
			'post_type',
			'post_mime_type',
			'comment_count',
			'post_author',
			'_search_title',
			'post_tags',
			'post_category',
			'default_category',
			'submit_ip',
			'featured',
			'featured_image',
			'overall_rating',
			'rating_count',
			'latitude',
			'longitude',
			'mapview',
			'mapzoom',
		);

		$labels = array(
			'street'  => 'Street Address',
			'street2' => 'Address Line 2',
			'city'    => 'City',
			'region'  => 'Region / State',
			'country' => 'Country',
			'zip'     => 'Zip / Postal Code',
			'phone'   => 'Phone',
			'email'   => 'Email',
			'website' => 'Website',
			'price'   => 'Price',
		);

		foreach ( $row as $key => $val ) {
			if ( in_array( $key, $ignored_keys, true ) ) {
				continue;
			}
			if ( null !== $allowed_fields && ! in_array( $key, $allowed_fields, true ) ) {
				continue;
			}
			if ( is_null( $val ) || '' === $val ) {
				continue;
			}

			$label = isset( $labels[ $key ] ) ? $labels[ $key ] : ucwords( str_replace( array( '-', '_' ), ' ', $key ) );
			if ( is_scalar( $val ) ) {
				$val_str = trim( wp_strip_all_tags( (string) $val ) );
				if ( '' !== $val_str ) {
					$blocks[] = $label . ': ' . $val_str;
				}
			}
		}

		return $blocks;
	}

	/**
	 * Extract ACF (Advanced Custom Fields) if available.
	 *
	 * @param int        $post_id        Post ID.
	 * @param array|null $allowed_fields Optional list of allowed custom field keys.
	 * @return array
	 */
	private function get_acf_blocks( int $post_id, ?array $allowed_fields = null ): array {
		if ( ! function_exists( 'get_fields' ) ) {
			return array();
		}

		$fields = get_fields( $post_id );
		if ( empty( $fields ) || ! is_array( $fields ) ) {
			return array();
		}

		$blocks = array();
		foreach ( $fields as $name => $value ) {
			if ( null !== $allowed_fields && ! in_array( $name, $allowed_fields, true ) ) {
				continue;
			}
			$formatted = $this->format_meta_value( $value );
			if ( null !== $formatted && '' !== $formatted ) {
				$label    = ucwords( str_replace( array( '-', '_' ), ' ', $name ) );
				$blocks[] = $label . ': ' . $formatted;
			}
		}

		return $blocks;
	}
}
