<?php
/**
 * Image workspace API bridge.
 *
 * @package Pendid_AI_Studio
 */

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

/**
 * Talks to the Pendid WordPress image contract without keeping local pricing logic.
 */
class Pendid_AI_Studio_Image_API {

	const API_BASE_URL = 'https://studio.pendid.com/api/wp';

	/**
	 * Fetches the current image capabilities and Admin-controlled pricing.
	 *
	 * Capability IDs, access rules and prices remain server-authoritative. Only
	 * human-readable UI labels are localized inside WordPress.
	 *
	 * @param string $access_token Plugin access token.
	 * @return array<string,mixed>
	 */
	public function get_config( $access_token ) {
		$config = $this->request_json( 'GET', '/config', $access_token );

		if ( ! empty( $config['ok'] ) && ! empty( $config['image'] ) && is_array( $config['image'] ) ) {
			$config['image'] = $this->localize_image_config( $config['image'] );
		}

		return $config;
	}

	/**
	 * Replaces server presentation labels with WordPress-localized labels while
	 * preserving every server-controlled identifier and capability value.
	 *
	 * @param array<string,mixed> $image Image config.
	 * @return array<string,mixed>
	 */
	private function localize_image_config( $image ) {
		if ( isset( $image['pricing'] ) && is_array( $image['pricing'] ) && isset( $image['brand_kit'] ) && is_array( $image['brand_kit'] ) ) {
			$image['pricing']['brand_kit'] = $image['brand_kit'];
		}

		if ( ! empty( $image['output_sizes'] ) && is_array( $image['output_sizes'] ) ) {
			foreach ( $image['output_sizes'] as $index => $size ) {
				if ( ! is_array( $size ) ) {
					continue;
				}

				$id = isset( $size['id'] ) ? sanitize_key( (string) $size['id'] ) : '';
				$labels = $this->get_size_labels();
				if ( isset( $labels[ $id ] ) ) {
					$image['output_sizes'][ $index ]['title'] = $labels[ $id ];
				}

				if ( isset( $size['modelvalue'] ) && is_scalar( $size['modelvalue'] ) ) {
					$model_value = sanitize_text_field( (string) $size['modelvalue'] );
					$image['output_sizes'][ $index ]['ratio'] = false !== strpos( $model_value, 'x' )
						? str_replace( 'x', '×', $model_value )
						: $model_value;
				}
			}
		}

		if ( ! empty( $image['tones'] ) && is_array( $image['tones'] ) ) {
			$tone_labels = $this->get_tone_labels();
			foreach ( $image['tones'] as $index => $tone ) {
				if ( ! is_array( $tone ) ) {
					continue;
				}
				$id = isset( $tone['id'] ) ? sanitize_key( (string) $tone['id'] ) : '';
				if ( isset( $tone_labels[ $id ] ) ) {
					$image['tones'][ $index ]['title'] = $tone_labels[ $id ];
				}
			}
		}

		return $image;
	}

	/**
	 * WordPress-localized labels for server output-size IDs.
	 *
	 * @return array<string,string>
	 */
	private function get_size_labels() {
		return array(
			'square'           => __( 'Square', 'pendid-ai-studio' ),
			'landscape'        => __( 'Landscape', 'pendid-ai-studio' ),
			'portrait'         => __( 'Portrait', 'pendid-ai-studio' ),
			'widescreen'       => __( 'Widescreen', 'pendid-ai-studio' ),
			'vertical-screen'  => __( 'Vertical full-screen', 'pendid-ai-studio' ),
			'square-1024'      => __( 'Standard square', 'pendid-ai-studio' ),
			'landscape-1536'   => __( 'Standard landscape', 'pendid-ai-studio' ),
			'portrait-1536'    => __( 'Standard portrait', 'pendid-ai-studio' ),
			'square-2048'      => __( 'Large square', 'pendid-ai-studio' ),
			'landscape-2048'   => __( 'Large landscape', 'pendid-ai-studio' ),
			'portrait-2048'    => __( 'Large portrait', 'pendid-ai-studio' ),
			'landscape-4k'     => __( '4K landscape', 'pendid-ai-studio' ),
			'portrait-4k'      => __( '4K portrait', 'pendid-ai-studio' ),
		);
	}

	/**
	 * WordPress-localized labels for server tone IDs.
	 *
	 * @return array<string,string>
	 */
	private function get_tone_labels() {
		return array(
			'free'         => __( 'Free style', 'pendid-ai-studio' ),
			'professional' => __( 'Professional', 'pendid-ai-studio' ),
			'sales'        => __( 'Sales focused', 'pendid-ai-studio' ),
			'luxury'       => __( 'Luxury', 'pendid-ai-studio' ),
			'modern'       => __( 'Modern', 'pendid-ai-studio' ),
			'minimal'      => __( 'Minimal', 'pendid-ai-studio' ),
			'friendly'     => __( 'Friendly', 'pendid-ai-studio' ),
			'happy'        => __( 'Bright and playful', 'pendid-ai-studio' ),
			'playful'      => __( 'Playful', 'pendid-ai-studio' ),
			'bold'         => __( 'Bold', 'pendid-ai-studio' ),
			'formal'       => __( 'Formal', 'pendid-ai-studio' ),
			'tech'         => __( 'Technology', 'pendid-ai-studio' ),
			'natural'      => __( 'Natural', 'pendid-ai-studio' ),
			'editorial'    => __( 'Editorial', 'pendid-ai-studio' ),
		);
	}

	/**
	 * Generates or edits an image through the v1.2 WordPress contract.
	 *
	 * @param string              $access_token Plugin access token.
	 * @param array<string,mixed> $args Request values.
	 * @return array<string,mixed>
	 */
	public function generate( $access_token, $args ) {
		$access_token = is_scalar( $access_token ) ? sanitize_text_field( (string) $access_token ) : '';
		$args         = is_array( $args ) ? $args : array();

		if ( '' === $access_token ) {
			return array( 'ok' => false, 'reason' => 'invalid_token', 'status' => 401 );
		}

		$description = isset( $args['description'] ) && is_scalar( $args['description'] )
			? trim( sanitize_textarea_field( (string) $args['description'] ) )
			: '';

		if ( strlen( $description ) < 8 ) {
			return array( 'ok' => false, 'reason' => 'description_required', 'status' => 400 );
		}

		$fields = array(
			'mode'             => isset( $args['mode'] ) ? sanitize_key( (string) $args['mode'] ) : 'generate',
			'description'      => $description,
			'imageType'        => isset( $args['image_type'] ) ? sanitize_key( (string) $args['image_type'] ) : 'intro-cover',
			'format'           => isset( $args['format'] ) ? sanitize_key( (string) $args['format'] ) : 'intro-square',
			'outputSize'       => isset( $args['output_size'] ) ? sanitize_text_field( (string) $args['output_size'] ) : '1:1',
			'tone'             => isset( $args['tone'] ) ? sanitize_key( (string) $args['tone'] ) : 'professional',
			'modelTier'        => isset( $args['model_tier'] ) ? sanitize_key( (string) $args['model_tier'] ) : 'standard',
			'outputFormat'     => isset( $args['output_format'] ) ? sanitize_key( (string) $args['output_format'] ) : 'png',
			'editAspectRatio'  => isset( $args['edit_aspect_ratio'] ) ? sanitize_text_field( (string) $args['edit_aspect_ratio'] ) : 'auto',
			'sourceImageUrl'   => isset( $args['source_image_url'] ) ? esc_url_raw( (string) $args['source_image_url'] ) : '',
			'sourceR2Key'      => isset( $args['source_r2_key'] ) ? sanitize_text_field( (string) $args['source_r2_key'] ) : '',
			'brandKit'         => ! empty( $args['brand_kit'] ) ? '1' : '0',
		);

		$files = array();
		$this->append_file( $files, 'editFile', isset( $args['edit_file'] ) ? $args['edit_file'] : array() );
		$this->append_file( $files, 'referenceFile', isset( $args['reference_file'] ) ? $args['reference_file'] : array() );

		if ( ! empty( $args['input_files'] ) && is_array( $args['input_files'] ) ) {
			foreach ( $args['input_files'] as $file ) {
				$this->append_file( $files, 'inputImage', $file );
			}
		}

		$multipart = $this->build_multipart_body( $fields, $files );
		if ( is_wp_error( $multipart ) ) {
			return array(
				'ok'     => false,
				'reason' => 'invalid_input_image',
				'status' => 400,
				'error'  => $multipart->get_error_message(),
			);
		}

		$response = wp_remote_post(
			esc_url_raw( self::API_BASE_URL . '/generate' ),
			array(
				'timeout' => 620,
				'headers' => array(
					'Accept'        => 'application/json',
					'Authorization' => 'Bearer ' . $access_token,
					'Content-Type'  => 'multipart/form-data; boundary=' . $multipart['boundary'],
				),
				'body'    => $multipart['body'],
			)
		);

		if ( is_wp_error( $response ) ) {
			return array( 'ok' => false, 'reason' => 'service_unavailable', 'status' => 0, 'error' => $response->get_error_message() );
		}

		$status = (int) wp_remote_retrieve_response_code( $response );
		$data   = json_decode( wp_remote_retrieve_body( $response ), true );

		if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $data ) ) {
			return array( 'ok' => false, 'reason' => 'invalid_response', 'status' => $status );
		}

		$data = $this->sanitize_response_data( $data );
		if ( 200 > $status || 299 < $status || empty( $data['ok'] ) ) {
			$reason = isset( $data['error'] ) && is_scalar( $data['error'] ) ? sanitize_key( (string) $data['error'] ) : 'generation_failed';
			return array_merge( $data, array( 'ok' => false, 'reason' => $reason, 'status' => $status ) );
		}

		$data['status'] = $status;
		return $data;
	}

	/**
	 * Makes a no-cache JSON request.
	 *
	 * @param string $method HTTP method.
	 * @param string $path Endpoint path.
	 * @param string $access_token Plugin access token.
	 * @return array<string,mixed>
	 */
	private function request_json( $method, $path, $access_token ) {
		$access_token = is_scalar( $access_token ) ? sanitize_text_field( (string) $access_token ) : '';
		if ( '' === $access_token ) {
			return array( 'ok' => false, 'reason' => 'invalid_token', 'status' => 401 );
		}

		$response = wp_remote_request(
			esc_url_raw( self::API_BASE_URL . $path ),
			array(
				'method'  => $method,
				'timeout' => 30,
				'headers' => array(
					'Accept'        => 'application/json',
					'Authorization' => 'Bearer ' . $access_token,
					'Cache-Control' => 'no-cache',
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			return array( 'ok' => false, 'reason' => 'service_unavailable', 'status' => 0 );
		}

		$status = (int) wp_remote_retrieve_response_code( $response );
		$data   = json_decode( wp_remote_retrieve_body( $response ), true );
		if ( JSON_ERROR_NONE !== json_last_error() || ! is_array( $data ) ) {
			return array( 'ok' => false, 'reason' => 'invalid_response', 'status' => $status );
		}

		$data = $this->sanitize_response_data( $data );
		if ( 200 > $status || 299 < $status || empty( $data['ok'] ) ) {
			$reason = isset( $data['error'] ) && is_scalar( $data['error'] ) ? sanitize_key( (string) $data['error'] ) : 'request_failed';
			return array_merge( $data, array( 'ok' => false, 'reason' => $reason, 'status' => $status ) );
		}

		$data['status'] = $status;
		return $data;
	}

	/**
	 * Adds one validated file definition to a repeated multipart field list.
	 *
	 * @param array<int,array<string,mixed>> $files File list.
	 * @param string                         $field_name Multipart field name.
	 * @param mixed                          $file File descriptor.
	 * @return void
	 */
	private function append_file( &$files, $field_name, $file ) {
		if ( is_array( $file ) && ! empty( $file['path'] ) ) {
			$files[] = array( 'field_name' => $field_name, 'file' => $file );
		}
	}

	/**
	 * Builds multipart body including repeated inputImage fields.
	 *
	 * @param array<string,scalar>           $fields Scalar fields.
	 * @param array<int,array<string,mixed>> $files File entries.
	 * @return array{boundary:string,body:string}|WP_Error
	 */
	private function build_multipart_body( $fields, $files ) {
		$boundary = '--------------------------' . wp_generate_password( 24, false, false );
		$eol      = "\r\n";
		$body     = '';

		foreach ( $fields as $name => $value ) {
			$body .= '--' . $boundary . $eol;
			$body .= 'Content-Disposition: form-data; name="' . sanitize_key( $name ) . '"' . $eol . $eol;
			$body .= (string) $value . $eol;
		}

		foreach ( $files as $entry ) {
			$field_name = isset( $entry['field_name'] ) ? sanitize_key( (string) $entry['field_name'] ) : '';
			$file       = isset( $entry['file'] ) && is_array( $entry['file'] ) ? $entry['file'] : array();
			$path       = isset( $file['path'] ) && is_scalar( $file['path'] ) ? (string) $file['path'] : '';
			if ( '' === $field_name || ! $path || ! is_readable( $path ) ) {
				return new WP_Error( 'pendid_image_unreadable', __( 'An input image could not be read.', 'pendid-ai-studio' ) );
			}

			$bytes = file_get_contents( $path );
			if ( false === $bytes ) {
				return new WP_Error( 'pendid_image_unreadable', __( 'An input image could not be read.', 'pendid-ai-studio' ) );
			}

			$name = isset( $file['name'] ) ? sanitize_file_name( (string) $file['name'] ) : 'image';
			$type = isset( $file['type'] ) ? sanitize_text_field( (string) $file['type'] ) : 'application/octet-stream';
			$body .= '--' . $boundary . $eol;
			$body .= 'Content-Disposition: form-data; name="' . $field_name . '"; filename="' . $name . '"' . $eol;
			$body .= 'Content-Type: ' . $type . $eol . $eol;
			$body .= $bytes . $eol;
		}

		$body .= '--' . $boundary . '--' . $eol;
		return array( 'boundary' => $boundary, 'body' => $body );
	}

	/**
	 * Sanitizes remote JSON recursively.
	 *
	 * @param mixed $data Remote data.
	 * @return mixed
	 */
	private function sanitize_response_data( $data ) {
		if ( is_array( $data ) ) {
			$clean = array();
			foreach ( $data as $key => $value ) {
				$clean_key           = is_string( $key ) ? sanitize_key( $key ) : $key;
				$clean[ $clean_key ] = $this->sanitize_response_data( $value );
			}
			return $clean;
		}
		if ( is_bool( $data ) || is_int( $data ) || is_float( $data ) || null === $data ) {
			return $data;
		}
		if ( ! is_scalar( $data ) ) {
			return '';
		}
		$value = (string) $data;
		return preg_match( '#^https?://#i', $value ) ? esc_url_raw( $value ) : sanitize_text_field( $value );
	}
}
