<?php

namespace Limb_Chatbot\Includes\Services;

use Limb_Chatbot\Includes\Data_Objects\Chatbot;
use Limb_Chatbot\Includes\Data_Objects\Dataset_Entry;
use Limb_Chatbot\Includes\Data_Objects\Dataset_Meta;
use Limb_Chatbot\Includes\Data_Objects\Message;
use Limb_Chatbot\Includes\Services\Knowledge\Knowledge_Source_Store;

/**
 * Prepares assistant response content according to attachment settings.
 */
class Assistant_Response_Preparer {

	/**
	 * Markdown image pattern: ![alt](url "optional title")
	 */
	private const MARKDOWN_IMAGE_PATTERN = '/!\[([^\]]*)\]\(([^)\s]+)(?:\s+"([^"]*)")?\)/';

	/**
	 * Maximum number of attachments to append to a response.
	 */
	private const MAX_ATTACHMENTS = 3;

	/**
	 * Main entry point to prepare a message's content.
	 *
	 * Scans text blocks for markdown image tokens, extracts them as attachments,
	 * and cleans the text while preserving structure. If no markdown images are found,
	 * it falls back to source attachment URLs from the knowledge base.
	 *
	 * @param Message $message The message object to prepare.
	 * @param Chatbot $chatbot The chatbot instance (not used in current implementation but available for context).
	 *
	 * @return Message The prepared message with attachments extracted and text cleaned.
	 */
	public function prepare( Message $message, Chatbot $chatbot ): Message {
		$content = $message->get_content();
		if ( ! is_array( $content ) || empty( $content ) ) {
			return $message;
		}

		$attachments_setting = (int) $chatbot->get_parameter( 'attachments' );
		$should_attach       = 1 === $attachments_setting;
		$attachment_items = [];
		foreach ( $content as $index => $item ) {
			if ( ! isset( $item['type'] ) || Message::CONTENT_TYPE_TEXT !== $item['type'] ) {
				continue;
			}

			$text_value = $item['text']['value'] ?? '';
			if ( ! is_string( $text_value ) || '' === trim( $text_value ) ) {
				continue;
			}

			$extracted = $this->extract_markdown_image_attachments( $text_value );
			$content[ $index ]['text']['value'] = $extracted['text'];
			if ( $should_attach ) {
				$attachment_items = array_merge( $attachment_items, $extracted['attachments'] );
			}
		}

		// If no markdown images are found in model text, fall back to source attachment URLs.
		if ( $should_attach && empty( $attachment_items ) ) {
			$attachment_items = $this->limit_attachment_items( $this->build_attachment_items_from_source_store() );
		}

		if ( $should_attach && ! empty( $attachment_items ) ) {
			$content = array_merge( $content, $this->limit_attachment_items( $attachment_items ) );
		}

		$message->set_content( $content );

		return $message;
	}

	/**
	 * Extract markdown images from text and return cleaned text + attachment blocks.
	 */
	private function extract_markdown_image_attachments( string $text ): array {
		$attachments = [];

		if ( preg_match_all( self::MARKDOWN_IMAGE_PATTERN, $text, $matches, PREG_SET_ORDER ) ) {
			foreach ( $matches as $match ) {
				$alt   = isset( $match[1] ) ? trim( $match[1] ) : '';
				$url   = isset( $match[2] ) ? trim( $match[2] ) : '';
				$title = isset( $match[3] ) ? trim( $match[3] ) : '';

				if ( '' === $url || false === filter_var( $url, FILTER_VALIDATE_URL ) ) {
					continue;
				}

				$attachments[] = $this->build_attachment_item( $url, $title, $alt );
			}
		}

		$cleaned_text = $this->remove_markdown_images_preserving_structure( $text );

		return [
			'text'        => $cleaned_text,
			'attachments' => $this->dedupe_attachment_items( $attachments ),
		];
	}

	/**
	 * Remove markdown image tokens while keeping markdown structure stable.
	 */
	private function remove_markdown_images_preserving_structure( string $text ): string {
		$lines         = preg_split( "/\r\n|\r|\n/", $text );
		$cleaned_lines = [];

		foreach ( $lines as $line ) {
			$line_without_images = preg_replace( self::MARKDOWN_IMAGE_PATTERN, '', $line );
			$trimmed_line        = trim( (string) $line_without_images );

			// Drop list markers that became empty after image removal (e.g. "- ![...](...)").
			if ( '' !== $trimmed_line && preg_match( '/^(?:[-*+]|\d+\.)\s*$/', $trimmed_line ) ) {
				continue;
			}

			if ( '' === $trimmed_line ) {
				$cleaned_lines[] = '';
				continue;
			}

			$line_without_images = preg_replace( '/\s{2,}/', ' ', (string) $line_without_images );
			$cleaned_lines[]     = rtrim( (string) $line_without_images );
		}

		$cleaned_text = implode( "\n", $cleaned_lines );
		$cleaned_text = preg_replace( "/\n{3,}/", "\n\n", $cleaned_text );

		return trim( (string) $cleaned_text );
	}

	/**
	 * Build fallback attachment content items from knowledge source dataset metas.
	 */
	private function build_attachment_items_from_source_store(): array {
		$source_store = Knowledge_Source_Store::instance();
		$sources      = $source_store->get_sources();
		$urls         = [];

		foreach ( $sources as $source ) {
			if ( ! $source instanceof Dataset_Entry ) {
				continue;
			}

			$dataset_id = $source->get_dataset_id();
			if ( empty( $dataset_id ) ) {
				continue;
			}

			$meta = Dataset_Meta::where(
				[
					'meta_key'   => 'source_attachment_urls',
					'dataset_id' => $dataset_id,
				]
			)->first();

			if ( ! $meta instanceof Dataset_Meta ) {
				continue;
			}

			$meta_value = $meta->get_meta_value();
			$urls       = array_merge( $urls, $this->normalize_urls( $meta_value ) );
		}

		$urls = array_values( array_unique( $urls ) );

		return array_map(
			function ( string $url ) {
				return $this->build_attachment_item( $url );
			},
			$urls
		);
	}

	/**
	 * Build one attachment content item.
	 */
	private function build_attachment_item( string $url, string $title = '', string $alt = '' ): array {
		$resolved_title = '' !== $title ? $title : ( '' !== $alt ? $alt : $this->extract_url_title( $url ) );

		return [
			'type'       => 'attachment',
			'attachment' => [
				'title'     => $resolved_title,
				'mime_type' => $this->resolve_mime_type( $url ),
				'value'     => $url,
			],
		];
	}

	/**
	 * Resolve mime type from URL path.
	 */
	private function resolve_mime_type( string $url ): string {
		$path      = (string) wp_parse_url( $url, PHP_URL_PATH );
		$check     = wp_check_filetype( $path );
		$mime_type = $check['type'] ?? '';

		return '' !== $mime_type ? $mime_type : 'application/octet-stream';
	}

	/**
	 * Normalize URLs from meta value (array or JSON string).
	 */
	private function normalize_urls( $meta_value ): array {
		if ( is_string( $meta_value ) ) {
			$decoded = json_decode( $meta_value, true );
			$meta_value = is_array( $decoded ) ? $decoded : [ $meta_value ];
		}

		if ( ! is_array( $meta_value ) ) {
			return [];
		}

		$urls = array_values(
			array_filter(
				array_map(
					static function ( $item ) {
						return is_string( $item ) ? trim( $item ) : '';
					},
					$meta_value
				),
				static function ( string $url ): bool {
					return '' !== $url && false !== filter_var( $url, FILTER_VALIDATE_URL );
				}
			)
		);

		return array_values( array_unique( $urls ) );
	}

	/**
	 * Remove duplicate attachment blocks by URL.
	 */
	private function dedupe_attachment_items( array $items ): array {
		$seen   = [];
		$unique = [];

		foreach ( $items as $item ) {
			$url = $item['attachment']['value'] ?? '';
			if ( ! is_string( $url ) || '' === $url || isset( $seen[ $url ] ) ) {
				continue;
			}

			$seen[ $url ] = true;
			$unique[]     = $item;
		}

		return $unique;
	}

	/**
	 * Limit attachments to the maximum allowed per response.
	 */
	private function limit_attachment_items( array $items ): array {
		return array_slice( $items, 0, self::MAX_ATTACHMENTS );
	}

	/**
	 * Generate title from URL path.
	 */
	private function extract_url_title( string $url ): string {
		$path = (string) wp_parse_url( $url, PHP_URL_PATH );
		$name = basename( $path );
		$name = urldecode( $name );

		return '' !== $name ? $name : $url;
	}
}
