<?php
/**
 * Shared vision image encoder.
 *
 * @package AIPT\Api\Helpers
 */

namespace AIPT\Api\Helpers;

/**
 * Fetches vision images from WordPress and converts them to provider-ready base64.
 */
class VisionImageEncoder {

    /**
     * Maximum image size accepted by vision providers.
     *
     * @var int
     */
    private const MAX_IMAGE_BYTES = 20971520;

    /**
     * Fetch an image URL and encode it as base64.
     *
     * Data URIs are parsed without fetching so existing encoded callers remain idempotent.
     *
     * @param string $image_url Image URL or data URI.
     * @param int    $timeout   Request timeout in seconds.
     * @return array{data:string,mime_type:string,size:int}
     *
     * @throws \RuntimeException When the image cannot be fetched or encoded.
     */
    public static function fetchAndEncode( string $image_url, int $timeout = 15 ): array {
        $image_url = trim( $image_url );

        if ( '' === $image_url ) {
            throw new \RuntimeException( esc_html__( 'Image URL is empty.', 'ai-product-tools' ) );
        }

        if ( self::isDataUri( $image_url ) ) {
            return self::parseDataUri( $image_url );
        }

        $sanitized_url = (string) filter_var( $image_url, FILTER_SANITIZE_URL );
        $scheme        = parse_url( $sanitized_url, PHP_URL_SCHEME ); 

        if ( ! in_array( $scheme, [ 'http', 'https' ], true ) ) {
            throw new \RuntimeException( esc_html__( 'Vision image URL must use http or https.', 'ai-product-tools' ) );
        }

        $local_file = self::resolveLocalUploadFile( $sanitized_url );
        if ( '' !== $local_file ) {
            $body = file_get_contents( $local_file ); 
            if ( false === $body || '' === $body ) {
                throw new \RuntimeException( esc_html__( 'Empty image body.', 'ai-product-tools' ) );
            }

            $size = strlen( $body );
            self::assertSupportedSize( $size );

            return [
                'data'      => base64_encode( $body ), 
                'mime_type' => self::detectImageMime( $body ),
                'size'      => $size,
            ];
        }

        if ( ! filter_var( $sanitized_url, FILTER_VALIDATE_URL ) ) {
            throw new \RuntimeException( esc_html__( 'Vision image URL is not allowed.', 'ai-product-tools' ) );
        }

        $fetch_timeout = (int) \apply_filters( 'aipt_vision_fetch_timeout', $timeout );
        if ( $fetch_timeout <= 0 ) {
            $fetch_timeout = $timeout;
        }

        $response = \wp_remote_get(
            $sanitized_url,
            [
                'timeout'            => $fetch_timeout,
                'reject_unsafe_urls' => true,
                'sslverify'          => (bool) \apply_filters( 'aipt_vision_sslverify', true ), 
            ]
        );

        if ( $response instanceof \WP_Error ) {
            throw new \RuntimeException(
                sprintf(
                    /* translators: %s: WordPress HTTP API error message. */
                    esc_html__( 'Failed to fetch image: %s', 'ai-product-tools' ),
                    esc_html( $response->get_error_message() )
                )
            );
        }

        $response_code = (int) \wp_remote_retrieve_response_code( $response );
        if ( $response_code < 200 || $response_code >= 300 ) {
            throw new \RuntimeException(
                sprintf(
                    /* translators: %d: HTTP status code. */
                    esc_html__( 'HTTP %d fetching image.', 'ai-product-tools' ),
                    absint( $response_code )
                )
            );
        }

        $body = (string) \wp_remote_retrieve_body( $response );
        if ( '' === $body ) {
            throw new \RuntimeException( esc_html__( 'Empty image body.', 'ai-product-tools' ) );
        }

        $size = strlen( $body );
        self::assertSupportedSize( $size );

        return [
            'data'      => base64_encode( $body ), 
            'mime_type' => self::detectImageMime( $body ),
            'size'      => $size,
        ];
    }

    /**
     * Determine whether a value is a base64 data URI.
     *
     * @param string $image_url Image URL value.
     * @return bool
     */
    private static function isDataUri( string $image_url ): bool {
        return 0 === strpos( $image_url, 'data:image/' );
    }

    /**
     * Resolve WordPress upload URLs to local files when possible.
     *
     * @param string $image_url Sanitized image URL.
     * @return string Local file path, or empty string when the URL is not a local upload.
     */
    private static function resolveLocalUploadFile( string $image_url ): string {
        $uploads = \wp_upload_dir();
        if ( ! is_array( $uploads ) || ! empty( $uploads['error'] ) ) {
            return '';
        }

        $base_url = isset( $uploads['baseurl'] ) ? rtrim( (string) $uploads['baseurl'], '/' ) : '';
        $base_dir = isset( $uploads['basedir'] ) ? rtrim( str_replace( '\\', '/', (string) $uploads['basedir'] ), '/' ) : '';
        if ( '' === $base_url || '' === $base_dir || 0 !== strpos( $image_url, $base_url . '/' ) ) {
            return '';
        }

        $relative = ltrim( substr( $image_url, strlen( $base_url ) ), '/' );
        $relative = str_replace( '\\', '/', $relative );
        if ( '' === $relative || preg_match( '#(^|/)\.\.(/|$)#', $relative ) ) {
            return '';
        }

        $file = $base_dir . '/' . $relative;
        if ( 0 !== strpos( $file, $base_dir . '/' ) || ! is_file( $file ) || ! is_readable( $file ) ) {
            return '';
        }

        return $file;
    }

    /**
     * Parse an image data URI into provider-ready fields.
     *
     * @param string $data_uri Data URI.
     * @return array{data:string,mime_type:string,size:int}
     *
     * @throws \RuntimeException When the data URI is invalid.
     */
    private static function parseDataUri( string $data_uri ): array {
        if ( ! preg_match( '/^data:(image\/(?:jpeg|png|gif|webp));base64,([A-Za-z0-9+\/=\r\n]+)$/', $data_uri, $matches ) ) {
            throw new \RuntimeException( esc_html__( 'Invalid image data URI.', 'ai-product-tools' ) );
        }

        $base64 = preg_replace( '/\s+/', '', $matches[2] );
        if ( ! is_string( $base64 ) || '' === $base64 ) {
            throw new \RuntimeException( esc_html__( 'Invalid image data URI.', 'ai-product-tools' ) );
        }

        $binary = base64_decode( $base64, true ); 
        if ( false === $binary || '' === $binary ) {
            throw new \RuntimeException( esc_html__( 'Invalid image data URI.', 'ai-product-tools' ) );
        }

        $size = strlen( $binary );
        self::assertSupportedSize( $size );

        return [
            'data'      => $base64,
            'mime_type' => self::detectImageMime( $binary ),
            'size'      => $size,
        ];
    }

    /**
     * Ensure the image fits provider upload limits.
     *
     * @param int $size Image size in bytes.
     * @return void
     *
     * @throws \RuntimeException When the image is too large.
     */
    private static function assertSupportedSize( int $size ): void {
        if ( $size > self::MAX_IMAGE_BYTES ) {
            throw new \RuntimeException( esc_html__( 'Image too large (>20MB).', 'ai-product-tools' ) );
        }
    }

    /**
     * Detect image MIME type from binary headers.
     *
     * @param string $binary Image binary data.
     * @return string
     *
     * @throws \RuntimeException When the image signature is unsupported.
     */
    private static function detectImageMime( string $binary ): string {
        $header = substr( $binary, 0, 12 );

        if ( 0 === strpos( $header, "\xFF\xD8\xFF" ) ) {
            return 'image/jpeg';
        }

        if ( 0 === strpos( $header, "\x89PNG\r\n\x1A\n" ) ) {
            return 'image/png';
        }

        if ( 0 === strpos( $header, 'GIF87a' ) || 0 === strpos( $header, 'GIF89a' ) ) {
            return 'image/gif';
        }

        if ( 'RIFF' === substr( $header, 0, 4 ) && 'WEBP' === substr( $header, 8, 4 ) ) {
            return 'image/webp';
        }

        throw new \RuntimeException( esc_html__( 'Unsupported image type.', 'ai-product-tools' ) );
    }
}
