<?php
defined( 'ABSPATH' ) || exit;

/**
 * Extracts and normalizes image prompt inventories generated by the design agent.
 */
final class Costudio_Publisher_Image_Prompts {

	private function __construct() {
	}

	public static function extract( string $design, ?callable $on_missing = null ): array {
		if ( empty( $design ) ) {
			return array();
		}

		$has_visual_plan = false !== stripos( $design, '<VISUAL_PLAN>' );
		$parsed          = self::parse_visual_plan( $design );
		// Never let invalid/truncated VISUAL_PLAN JSON fall through to the generic
		// inventory parser. Legacy prose after the contract remains eligible.
		$legacy_design = $has_visual_plan && class_exists( 'Costudio_Visual_Plan' )
			? Costudio_Visual_Plan::strip_block( $design )
			: $design;
		if ( null === $parsed ) {
			$parsed = self::parse_json_inventory( $legacy_design );
		}
		if ( null === $parsed ) {
			$parsed = self::parse_markdown_sections( $legacy_design );
		}

		if ( null === $parsed ) {
			if ( is_callable( $on_missing ) ) {
				$on_missing( function_exists( 'mb_substr' ) ? mb_substr( $design, 0, 300 ) : substr( $design, 0, 300 ) );
			}
			return array();
		}

		$images = array();
		foreach ( $parsed as $img ) {
			if ( empty( $img['filename'] ) || empty( $img['prompt'] ) ) {
				continue;
			}

			$filename = self::normalize_filename( (string) $img['filename'], count( $images ) + 1 );

			$images[] = array(
				'filename' => $filename,
				'prompt'   => trim( (string) $img['prompt'] ),
				'alt'      => self::first_non_empty( $img, array( 'alt_text', 'alt', 'altText', 'alt_texto', 'texto_alternativo' ), $filename ),
				'caption'  => self::first_non_empty( $img, array( 'caption', 'legenda', 'leyenda' ), '' ),
				'role'     => self::first_non_empty( $img, array( 'role' ), 1 === count( $images ) + 1 ? 'cover' : 'inline' ),
				'position' => self::first_non_empty( $img, array( 'position', 'context' ), '' ),
			);
		}

		return $images;
	}

	private static function parse_visual_plan( string $design ): ?array {
		if ( ! class_exists( 'Costudio_Visual_Plan' ) ) {
			return null;
		}

		$plan = Costudio_Visual_Plan::extract( $design );
		if ( ! is_array( $plan ) ) {
			return null;
		}

		$validation = Costudio_Visual_Plan::validate_and_normalize( $plan );
		if ( empty( $validation['valid'] ) ) {
			return null;
		}
		$assets     = $validation['plan']['assets'] ?? array();
		return is_array( $assets ) && array() !== $assets ? $assets : null;
	}

	private static function parse_json_inventory( string $design ): ?array {
		foreach ( self::json_candidates( $design ) as $json_string ) {
			$try = json_decode( $json_string, true );
			if ( ! is_array( $try ) || array() === $try ) {
				continue;
			}

			$items = self::unwrap_inventory( $try );
			if ( null !== $items ) {
				return $items;
			}
		}

		return null;
	}

	private static function json_candidates( string $design ): array {
		$candidates = array();

		if ( preg_match_all( '/```(?:json)?\s*(.*?)\s*```/is', $design, $blocks ) ) {
			foreach ( $blocks[1] as $block ) {
				if ( false !== strpos( $block, '"filename"' ) || false !== strpos( $block, "'filename'" ) ) {
					$candidates[] = trim( $block );
				}
			}
		}

		if ( preg_match_all( '/\[\s*\{.*?"filename".*?\}\s*\]/is', $design, $arrays ) ) {
			foreach ( $arrays[0] as $array_json ) {
				$candidates[] = trim( $array_json );
			}
		}

		if ( preg_match_all( '/\{\s*"(?:images|image_prompts|visual_prompts|prompts|assets|items|data)"\s*:\s*\[.*?\]\s*\}/is', $design, $objects ) ) {
			foreach ( $objects[0] as $object_json ) {
				$candidates[] = trim( $object_json );
			}
		}

		return array_values( array_unique( array_filter( $candidates ) ) );
	}

	private static function unwrap_inventory( array $decoded ): ?array {
		if ( self::looks_like_inventory( $decoded ) ) {
			return $decoded;
		}

		foreach ( array( 'images', 'image_prompts', 'visual_prompts', 'prompts', 'assets', 'items', 'data' ) as $key ) {
			if ( isset( $decoded[ $key ] ) && is_array( $decoded[ $key ] ) && self::looks_like_inventory( $decoded[ $key ] ) ) {
				return $decoded[ $key ];
			}
		}

		return null;
	}

	private static function looks_like_inventory( array $items ): bool {
		$first = reset( $items );
		return is_array( $first ) && ! empty( $first['filename'] ) && ! empty( $first['prompt'] );
	}

	private static function parse_markdown_sections( string $design ): ?array {
		if (
			! preg_match_all(
				'/^#{3,6}\s+([a-z0-9._-]+\.webp)\b[^\n]*\R(.*?)(?=^#{3,6}\s+[a-z0-9._-]+\.webp\b|^---\s*$|^###\s+(?:PART|PARTE)\s+2\b|\z)/ims',
				$design,
				$sections,
				PREG_SET_ORDER
			)
		) {
			return null;
		}

		$items = array();
		foreach ( $sections as $section ) {
			$body   = trim( (string) $section[2] );
			$prompt = self::extract_labeled_value( $body, array( 'Prompt' ) );
			if ( '' === $prompt ) {
				continue;
			}

			$items[] = array(
				'filename' => trim( (string) $section[1] ),
				'prompt'   => $prompt,
				'alt_text' => self::extract_labeled_value( $body, array( 'Alt Text', 'Alt', 'Texto alternativo', 'Texto Alt' ) ),
				'caption'  => self::extract_labeled_value( $body, array( 'Caption', 'Legenda', 'Leyenda' ) ),
			);
		}

		return array() === $items ? null : $items;
	}

	private static function extract_labeled_value( string $body, array $labels ): string {
		foreach ( $labels as $label ) {
			$pattern = '/(?:\*\*)?' . preg_quote( $label, '/' ) . '(?:\*\*)?\s*:\s*(?:\*\*)?\s*(.*?)(?=\R\s*(?:\*\*)?(?:Prompt|Alt Text|Alt|Texto alternativo|Texto Alt|Caption|Legenda|Leyenda)(?:\*\*)?\s*:|\R\s*---|\z)/isu';
			if ( preg_match( $pattern, $body, $m ) ) {
				return trim( preg_replace( '/\s+/', ' ', (string) $m[1] ) );
			}
		}

		return '';
	}

	private static function normalize_filename( string $filename, int $position ): string {
		$raw_name  = strtolower( $filename );
		$raw_name  = str_replace( array( "\r", "\n", '\\', '/', ' ' ), '-', $raw_name );
		$base_name = pathinfo( $raw_name, PATHINFO_FILENAME );
		$base_name = function_exists( 'sanitize_file_name' )
			? sanitize_file_name( $base_name )
			: preg_replace( '/[^a-z0-9._-]+/i', '-', $base_name );
		$base_name = trim( (string) $base_name, '.-_' );

		if ( '' === $base_name ) {
			$base_name = 'imagem-' . $position;
		}

		if ( self::is_cover_alias( $base_name, $position ) ) {
			$base_name = 'imagem1';
		}

		return $base_name . '.webp';
	}

	private static function is_cover_alias( string $base_name, int $position ): bool {
		$compact = preg_replace( '/[^a-z0-9]+/', '', $base_name );
		if ( 1 === $position && in_array( $compact, array( 'image1', 'img1', 'cover', 'featured', 'featuredimage', 'hero', 'thumbnail', 'capa', 'portada' ), true ) ) {
			return true;
		}

		return (bool) preg_match( '/^(?:imagem|image|img)[-_]?0*1$/i', $base_name );
	}

	private static function first_non_empty( array $item, array $keys, string $fallback ): string {
		foreach ( $keys as $key ) {
			if ( isset( $item[ $key ] ) && '' !== trim( (string) $item[ $key ] ) ) {
				return trim( (string) $item[ $key ] );
			}
		}

		return $fallback;
	}
}
