<?php
if (!defined('ABSPATH')) {
    exit;
}

class Calliope_SP_Media_Helper {
    /**
     * Decode a base64 image, save it to uploads, optionally convert it to a preferred
     * image MIME, and verify that the public URL serves a valid image.
     *
     * @param string $b64  Data URL or raw base64 image.
     * @param array  $args {
     *     Optional. Save/validation options.
     *
     *     @type string $context        Uploads subdirectory name.
     *     @type string $filename_prefix Base filename prefix.
     *     @type string $preferred_mime Preferred output MIME, for example image/jpeg.
     *     @type string $network        Network key used only for logs.
     *     @type int    $max_attempts   Public URL validation attempts.
     * }
     * @return array<string,mixed>|WP_Error
     */
    public static function create_public_image_from_b64($b64, array $args = []) {
        $args = wp_parse_args($args, [
            'context'         => 'shared',
            'filename_prefix' => 'calliope-social-publisher',
            'preferred_mime'  => '',
            'network'         => '',
            'max_attempts'    => 6,
            'url_strategy'        => 'rest_proxy',
            'url_ttl'             => 6 * HOUR_IN_SECONDS,
            'required_public_mime'=> '',
            'require_head_200'    => false,
            'allow_partial_get'   => true,
            'force_jpeg_reencode' => false,
            'skip_public_validation' => false,
            'trust_local_on_public_validation_failure' => false,
        ]);

        $decoded = self::decode_base64_image($b64);
        if (is_wp_error($decoded)) {
            return $decoded;
        }

        $uploads = wp_upload_dir();
        if (!empty($uploads['error'])) {
            return new WP_Error('upload_failed', 'Could not access the uploads directory: ' . $uploads['error']);
        }

        $context = sanitize_key((string) $args['context']);
        if ($context === '') {
            $context = 'shared';
        }

        $url_strategy = sanitize_key((string) $args['url_strategy']);
        if ($url_strategy === '') {
            $url_strategy = 'static_upload';
        }

        $use_meta_static_store = in_array($url_strategy, ['meta_static_safe', 'meta_static_passthrough'], true);

        $filename_prefix = sanitize_file_name((string) $args['filename_prefix']);
        if ($filename_prefix === '') {
            $filename_prefix = 'calliope-social-publisher';
        }

        $dir = trailingslashit($uploads['basedir']) . 'calliope-social-publisher/' . $context . '/';

        if (!wp_mkdir_p($dir)) {
            return new WP_Error('upload_failed', 'Could not create the uploads directory.');
        }

        $filename = $filename_prefix . '-' . gmdate('Ymd-His') . '-' . wp_generate_password(6, false, false) . '.' . $decoded['extension'];
        $path = $dir . $filename;

        if (file_put_contents($path, $decoded['binary']) === false) {
            return new WP_Error('upload_failed', 'Could not write the image file to uploads.');
        }

        $preferred_mime = strtolower((string) $args['preferred_mime']);
        $converted_to_preferred_mime = false;
        if ($preferred_mime !== '' && $preferred_mime !== strtolower((string) $decoded['mime'])) {
            $converted = self::convert_image_file($path, $preferred_mime);
            if (is_wp_error($converted)) {
                self::delete_file($path);
                return $converted;
            }
            $path = $converted['path'];
            $converted_to_preferred_mime = true;
        }

        $force_jpeg_reencode = !empty($args['force_jpeg_reencode']) || ($use_meta_static_store && $preferred_mime === 'image/jpeg');
        if ($force_jpeg_reencode && $preferred_mime === 'image/jpeg' && !$converted_to_preferred_mime) {
            $reencoded = self::reencode_image_file($path, 'image/jpeg');
            if (is_wp_error($reencoded)) {
                self::delete_file($path);
                return $reencoded;
            }
            $path = $reencoded['path'];
        }

        $file_meta = self::inspect_image_file($path);
        if (is_wp_error($file_meta)) {
            self::delete_file($path);
            return $file_meta;
        }

        if ($preferred_mime === 'image/jpeg' && !self::file_starts_with_jpeg_magic($path)) {
            self::delete_file($path);
            return new WP_Error('invalid_image_file', 'The generated JPEG file does not start with valid JPEG bytes.');
        }

        $static_url = self::build_public_url_from_upload_path($path);
        if (is_wp_error($static_url)) {
            self::delete_file($path);
            return $static_url;
        }
        $static_url = (string) $static_url;

        $meta_static_path = '';
        $meta_static_url = '';
        $meta_file_meta = [];
        $passthrough_url = '';

        if ($use_meta_static_store) {
            $meta_copy = self::copy_image_to_meta_static_store($path, $context, $filename_prefix);
            if (is_wp_error($meta_copy)) {
                self::delete_file($path);
                return $meta_copy;
            }

            $meta_static_path = (string) ($meta_copy['path'] ?? '');
            $meta_static_url = (string) ($meta_copy['url'] ?? '');
            $meta_file_meta = isset($meta_copy['meta']) && is_array($meta_copy['meta']) ? $meta_copy['meta'] : [];

            if ($url_strategy === 'meta_static_passthrough') {
                $passthrough = self::create_meta_static_proxy_url(
                    $meta_static_path,
                    $context,
                    $filename_prefix,
                    max(300, (int) $args['url_ttl'])
                );
                $passthrough_url = is_wp_error($passthrough) ? '' : (string) $passthrough;
            }
        }

        $proxy_path = $meta_static_path !== '' ? $meta_static_path : $path;
        $proxy_meta = !empty($meta_file_meta) ? $meta_file_meta : $file_meta;
        $proxy_url = self::create_signed_public_media_url(
            $proxy_path,
            (string) ($proxy_meta['mime'] ?? 'image/jpeg'),
            max(300, (int) $args['url_ttl'])
        );
        $proxy_url = is_wp_error($proxy_url) ? '' : (string) $proxy_url;

        $candidates = [];
        if ($url_strategy === 'meta_static_passthrough') {
            if ($passthrough_url !== '') {
                $candidates[] = [
                    'url'       => $passthrough_url,
                    'strategy'  => 'meta_static_passthrough',
                    'path'      => $meta_static_path,
                    'file_meta' => $meta_file_meta,
                ];
            }
            if ($meta_static_url !== '') {
                $candidates[] = [
                    'url'       => $meta_static_url,
                    'strategy'  => 'meta_static_safe',
                    'path'      => $meta_static_path,
                    'file_meta' => $meta_file_meta,
                ];
            }
            if ($static_url !== '' && $static_url !== $meta_static_url) {
                $candidates[] = [
                    'url'       => $static_url,
                    'strategy'  => 'static_upload',
                    'path'      => $path,
                    'file_meta' => $file_meta,
                ];
            }
            if ($proxy_url !== '') {
                $candidates[] = [
                    'url'       => $proxy_url,
                    'strategy'  => 'rest_proxy',
                    'path'      => $proxy_path,
                    'file_meta' => $proxy_meta,
                ];
            }
        } elseif ($url_strategy === 'meta_static_safe') {
            if ($meta_static_url !== '') {
                $candidates[] = [
                    'url'       => $meta_static_url,
                    'strategy'  => 'meta_static_safe',
                    'path'      => $meta_static_path,
                    'file_meta' => $meta_file_meta,
                ];
            }
            if ($static_url !== '' && $static_url !== $meta_static_url) {
                $candidates[] = [
                    'url'       => $static_url,
                    'strategy'  => 'static_upload',
                    'path'      => $path,
                    'file_meta' => $file_meta,
                ];
            }
            if ($proxy_url !== '') {
                $candidates[] = [
                    'url'       => $proxy_url,
                    'strategy'  => 'rest_proxy',
                    'path'      => $proxy_path,
                    'file_meta' => $proxy_meta,
                ];
            }
        } elseif ($url_strategy === 'rest_proxy' || $url_strategy === 'proxy_then_static') {
            if ($proxy_url !== '') {
                $candidates[] = ['url' => $proxy_url, 'strategy' => 'rest_proxy', 'path' => $proxy_path, 'file_meta' => $proxy_meta];
            }
            $candidates[] = ['url' => $static_url, 'strategy' => 'static_upload', 'path' => $path, 'file_meta' => $file_meta];
        } elseif ($url_strategy === 'static_then_proxy') {
            $candidates[] = ['url' => $static_url, 'strategy' => 'static_upload', 'path' => $path, 'file_meta' => $file_meta];
            if ($proxy_url !== '') {
                $candidates[] = ['url' => $proxy_url, 'strategy' => 'rest_proxy', 'path' => $proxy_path, 'file_meta' => $proxy_meta];
            }
        } else {
            $candidates[] = ['url' => $static_url, 'strategy' => 'static_upload', 'path' => $path, 'file_meta' => $file_meta];
            if ($url_strategy === 'static_upload_with_proxy_fallback' && $proxy_url !== '') {
                $candidates[] = ['url' => $proxy_url, 'strategy' => 'rest_proxy', 'path' => $proxy_path, 'file_meta' => $proxy_meta];
            }
        }

        $network = sanitize_key((string) $args['network']);
        if ($network !== '' && class_exists('Calliope_SP_Logger')) {
            $prepared_log = array_merge([
                'message'           => 'Meta media URL diagnostics: prepared local image and candidate public URLs.',
                'local_path'        => $path,
                'static_url'        => $static_url,
                'meta_static_path'  => $meta_static_path,
                'meta_static_url'   => $meta_static_url,
                'passthrough_url'   => $passthrough_url,
                'proxy_url'         => $proxy_url,
                'requested_strategy'=> $url_strategy,
                'required_public_mime' => (string) $args['required_public_mime'],
                'require_head_200'  => (bool) $args['require_head_200'],
                'allow_partial_get' => (bool) $args['allow_partial_get'],
                'candidate_count'   => count($candidates),
                'site_url'          => site_url('/'),
                'home_url'          => home_url('/'),
            ], self::get_local_file_diagnostics($meta_static_path !== '' ? $meta_static_path : $path, !empty($meta_file_meta) ? $meta_file_meta : $file_meta));

            Calliope_SP_Logger::log($network, 'info', wp_json_encode($prepared_log, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        }

        $url = $passthrough_url !== '' ? $passthrough_url : ($meta_static_url !== '' ? $meta_static_url : $static_url);
        $resolved_strategy = $passthrough_url !== '' ? 'meta_static_passthrough' : ($meta_static_url !== '' ? 'meta_static_safe' : 'static_upload');
        $selected_path = $meta_static_path !== '' ? $meta_static_path : $path;
        $selected_file_meta = !empty($meta_file_meta) ? $meta_file_meta : $file_meta;
        $public_meta = null;
        $last_error = null;
        $candidate_diagnostics = [];
        $skip_public_validation = !empty($args['skip_public_validation']);
        $trust_local_on_public_validation_failure = !empty($args['trust_local_on_public_validation_failure']);

        foreach ($candidates as $candidate) {
            $candidate_url = esc_url_raw((string) ($candidate['url'] ?? ''));
            $candidate_strategy = sanitize_key((string) ($candidate['strategy'] ?? 'static_upload'));
            if ($candidate_url === '') {
                continue;
            }

            $candidate_path = (string) ($candidate['path'] ?? '');
            $candidate_file_meta = isset($candidate['file_meta']) && is_array($candidate['file_meta']) ? $candidate['file_meta'] : [];
            $candidate_local_diagnostics = self::get_local_file_diagnostics($candidate_path, $candidate_file_meta);

            if ($skip_public_validation) {
                $url = $candidate_url;
                $resolved_strategy = $candidate_strategy;
                $selected_path = (string) ($candidate['path'] ?? $selected_path);
                $selected_file_meta = isset($candidate['file_meta']) && is_array($candidate['file_meta']) ? $candidate['file_meta'] : $selected_file_meta;
                $public_meta = self::build_trusted_local_public_meta($candidate_local_diagnostics, 'skipped');
                $candidate_diagnostics[] = [
                    'strategy' => $candidate_strategy,
                    'url'      => $candidate_url,
                    'local_path' => $selected_path,
                    'ok'       => true,
                    'trusted_local' => true,
                    'meta'     => $public_meta,
                ];
                break;
            }

            $candidate_meta = self::wait_for_public_image_url($candidate_url, (string) $args['network'], (int) $args['max_attempts'], [
                'required_mime'     => (string) $args['required_public_mime'],
                'require_head_200'  => (bool) $args['require_head_200'],
                'allow_partial_get' => (bool) $args['allow_partial_get'],
                'expected_content_length' => (int) ($candidate_local_diagnostics['filesize'] ?? 0),
                'expected_first_12_bytes_hex' => (string) ($candidate_local_diagnostics['first_12_bytes_hex'] ?? ''),
                'require_public_body_match' => $candidate_strategy === 'meta_static_passthrough',
            ]);
            if (!is_wp_error($candidate_meta)) {
                $url = $candidate_url;
                $resolved_strategy = $candidate_strategy;
                $selected_path = (string) ($candidate['path'] ?? $selected_path);
                $selected_file_meta = isset($candidate['file_meta']) && is_array($candidate['file_meta']) ? $candidate['file_meta'] : $selected_file_meta;
                $public_meta = $candidate_meta;
                $candidate_diagnostics[] = [
                    'strategy' => $candidate_strategy,
                    'url'      => $candidate_url,
                    'local_path' => $selected_path,
                    'ok'       => true,
                    'meta'     => $candidate_meta,
                ];
                break;
            }

            $candidate_diagnostics[] = [
                'strategy' => $candidate_strategy,
                'url'      => $candidate_url,
                'ok'       => false,
                'error'    => $candidate_meta->get_error_message(),
                'data'     => $candidate_meta->get_error_data(),
            ];
            $last_error = $candidate_meta;
        }

        if ($public_meta === null) {
            if ($trust_local_on_public_validation_failure && !empty($candidates)) {
                $trusted_candidate = $candidates[0];
                $url = esc_url_raw((string) ($trusted_candidate['url'] ?? $url));
                $resolved_strategy = sanitize_key((string) ($trusted_candidate['strategy'] ?? $resolved_strategy));
                $selected_path = (string) ($trusted_candidate['path'] ?? $selected_path);
                $selected_file_meta = isset($trusted_candidate['file_meta']) && is_array($trusted_candidate['file_meta']) ? $trusted_candidate['file_meta'] : $selected_file_meta;
                $public_meta = self::build_trusted_local_public_meta(self::get_local_file_diagnostics($selected_path, $selected_file_meta), 'self_validation_failed');

                if ($network !== '' && class_exists('Calliope_SP_Logger')) {
                    Calliope_SP_Logger::log($network, 'warning', wp_json_encode([
                        'message' => 'Public media self-check failed on this host, but the local JPEG is valid. Sending the direct public URL to the network instead of blocking the publish.',
                        'public_url' => $url,
                        'url_strategy' => $resolved_strategy,
                        'last_error_code' => $last_error instanceof WP_Error ? $last_error->get_error_code() : '',
                        'last_error_message' => $last_error instanceof WP_Error ? $last_error->get_error_message() : '',
                    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
                }
            } else {
                $error_data = [
                    'local_path'        => $path,
                    'meta_static_path'  => $meta_static_path,
                    'meta_static_url'   => $meta_static_url,
                    'passthrough_url'   => $passthrough_url,
                    'static_url'        => $static_url,
                    'proxy_url'         => $proxy_url,
                    'requested_strategy'=> $url_strategy,
                    'candidates'        => $candidate_diagnostics,
                ];
                if ($last_error instanceof WP_Error) {
                    $error_data['last_error_code'] = $last_error->get_error_code();
                    $error_data['last_error_message'] = $last_error->get_error_message();
                    $error_data['last_error_data'] = $last_error->get_error_data();
                }
                return new WP_Error(
                    'invalid_public_media_url',
                    'The public media URL is not accessible or does not return a valid image.',
                    $error_data
                );
            }
        }

        $result = array_merge($selected_file_meta, self::get_local_file_diagnostics($selected_path, $selected_file_meta), [
            'url'               => esc_url_raw($url),
            'passthrough_url'   => esc_url_raw($passthrough_url),
            'meta_static_url'   => esc_url_raw($meta_static_url),
            'static_url'        => esc_url_raw($static_url),
            'url_strategy'      => $resolved_strategy,
            'local_path'        => $selected_path,
            'http_head_status'  => (int) ($public_meta['http_head_status'] ?? 0),
            'http_content_type' => (string) ($public_meta['http_content_type'] ?? ''),
            'http_get_status'   => (int) ($public_meta['http_get_status'] ?? 0),
            'http_content_length' => (string) ($public_meta['http_content_length'] ?? ''),
            'http_accept_ranges' => (string) ($public_meta['http_accept_ranges'] ?? ''),
            'http_cache_control' => (string) ($public_meta['http_cache_control'] ?? ''),
            'http_x_content_type_options' => (string) ($public_meta['http_x_content_type_options'] ?? ''),
            'http_server' => (string) ($public_meta['http_server'] ?? ''),
            'http_via' => (string) ($public_meta['http_via'] ?? ''),
            'http_cf_cache_status' => (string) ($public_meta['http_cf_cache_status'] ?? ''),
            'http_x_litespeed_cache' => (string) ($public_meta['http_x_litespeed_cache'] ?? ''),
            'http_x_hcdn_cache_status' => (string) ($public_meta['http_x_hcdn_cache_status'] ?? ''),
            'http_x_hcdn_request_id' => (string) ($public_meta['http_x_hcdn_request_id'] ?? ''),
            'body_starts_with_jpeg' => (bool) ($public_meta['body_starts_with_jpeg'] ?? false),
            'body_detected_mime' => (string) ($public_meta['body_detected_mime'] ?? ''),
            'body_magic' => (string) ($public_meta['body_magic'] ?? ''),
            'body_matches_local' => (bool) ($public_meta['body_matches_expected'] ?? false),
            'body_size_matches_local' => (bool) ($public_meta['body_size_matches_expected'] ?? false),
            'body_magic_matches_local' => (bool) ($public_meta['body_magic_matches_expected'] ?? false),
            'http_content_length_matches_local' => (bool) ($public_meta['http_content_length_matches_expected'] ?? false),
        ]);

        if ($network !== '' && class_exists('Calliope_SP_Logger')) {
            if ($resolved_strategy === 'rest_proxy') {
                Calliope_SP_Logger::log($network, 'warning', wp_json_encode([
                    'message'      => 'Meta media URL diagnostics: falling back to REST proxy. Meta may reject wp-json media URLs.',
                    'public_url'   => $result['url'],
                    'url_strategy' => $resolved_strategy,
                    'static_url'   => $result['static_url'],
                    'meta_static_url' => $result['meta_static_url'],
                    'passthrough_url' => $result['passthrough_url'],
                ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
            }

            Calliope_SP_Logger::log($network, 'info', wp_json_encode([
                'message'           => 'Meta media URL diagnostics: selected public URL for API payload.',
                'local_path'        => $result['local_path'],
                'public_url'        => $result['url'],
                'passthrough_url'   => $result['passthrough_url'],
                'meta_static_url'   => $result['meta_static_url'],
                'static_url'        => $result['static_url'],
                'url_strategy'      => $result['url_strategy'],
                'mime'              => $result['mime'],
                'width'             => $result['width'],
                'height'            => $result['height'],
                'filesize'          => $result['filesize'],
                'first_12_bytes_hex'=> $result['first_12_bytes_hex'],
                'sha1'              => $result['sha1'],
                'http_head_status'  => $result['http_head_status'],
                'http_content_type' => $result['http_content_type'],
                'http_get_status'   => $result['http_get_status'],
                'http_content_length' => $result['http_content_length'],
                'http_accept_ranges' => $result['http_accept_ranges'],
                'http_cache_control' => $result['http_cache_control'],
                'http_x_content_type_options' => $result['http_x_content_type_options'],
                'http_server' => $result['http_server'],
                'http_via' => $result['http_via'],
                'http_cf_cache_status' => $result['http_cf_cache_status'],
                'http_x_litespeed_cache' => $result['http_x_litespeed_cache'],
                'http_x_hcdn_cache_status' => $result['http_x_hcdn_cache_status'],
                'http_x_hcdn_request_id' => $result['http_x_hcdn_request_id'],
                'body_starts_with_jpeg' => $result['body_starts_with_jpeg'],
                'body_detected_mime' => $result['body_detected_mime'],
                'body_magic' => $result['body_magic'],
                'body_matches_local' => $result['body_matches_local'],
                'body_size_matches_local' => $result['body_size_matches_local'],
                'body_magic_matches_local' => $result['body_magic_matches_local'],
                'http_content_length_matches_local' => $result['http_content_length_matches_local'],
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        }

        return $result;
    }

    /**
     * Build synthetic public metadata when the local file is already valid but the
     * host cannot self-fetch its own HTTPS URL (common on some shared hosts/CDNs).
     * This avoids blocking Meta/Telegram before the external network gets a chance
     * to fetch the media itself.
     *
     * @param array<string,mixed> $local_diagnostics Local file diagnostics.
     * @param string              $reason Reason this trusted result was used.
     * @return array<string,mixed>
     */
    private static function build_trusted_local_public_meta(array $local_diagnostics, $reason = '') {
        $mime = (string) ($local_diagnostics['mime'] ?? '');
        $filesize = (int) ($local_diagnostics['filesize'] ?? 0);

        return [
            'trusted_local' => true,
            'trusted_local_reason' => sanitize_key((string) $reason),
            'http_head_status' => 0,
            'http_head_type' => '',
            'http_head_length' => '',
            'http_head_location' => '',
            'http_content_type' => $mime,
            'http_content_length' => $filesize > 0 ? (string) $filesize : '',
            'http_get_status' => 0,
            'http_get_type' => '',
            'http_get_length' => '',
            'body_bytes' => $filesize,
            'body_detected_mime' => $mime,
            'body_magic' => (string) ($local_diagnostics['first_12_bytes_hex'] ?? ''),
            'body_starts_with_jpeg' => !empty($local_diagnostics['starts_with_jpeg_magic']),
            'body_matches_expected' => true,
            'body_size_matches_expected' => true,
            'body_magic_matches_expected' => true,
            'http_content_length_matches_expected' => true,
            'http_accept_ranges' => '',
            'http_cache_control' => '',
            'http_x_content_type_options' => '',
            'http_server' => '',
            'http_via' => '',
            'http_cf_cache_status' => '',
            'http_x_litespeed_cache' => '',
            'http_x_hcdn_cache_status' => '',
            'http_x_hcdn_request_id' => '',
            'attempts' => [],
        ];
    }

    /**
     * Wait until a public image URL responds with an image content type.
     *
     * @param string $url          Public media URL.
     * @param string $network      Network key used only for logs.
     * @param int    $max_attempts Number of validation attempts.
     * @return array<string,mixed>|WP_Error
     */
    public static function wait_for_public_image_url($url, $network = '', $max_attempts = 6, array $args = []) {
        $args = wp_parse_args($args, [
            'required_mime'     => '',
            'require_head_200'  => false,
            'allow_partial_get' => true,
            'expected_content_length' => 0,
            'expected_first_12_bytes_hex' => '',
            'require_public_body_match' => false,
        ]);

        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return new WP_Error('invalid_public_media_url', 'Missing public media URL.');
        }

        $required_mime = self::normalize_content_type((string) $args['required_mime']);
        $require_head_200 = (bool) $args['require_head_200'];
        $allow_partial_get = (bool) $args['allow_partial_get'];
        $expected_content_length = max(0, (int) $args['expected_content_length']);
        $expected_first_12_bytes_hex = strtolower(preg_replace('/[^a-f0-9]/', '', (string) $args['expected_first_12_bytes_hex']));
        $require_public_body_match = (bool) $args['require_public_body_match'];

        $last_meta = [
            'public_url'            => $url,
            'required_mime'         => $required_mime,
            'require_head_200'      => $require_head_200,
            'allow_partial_get'     => $allow_partial_get,
            'expected_content_length' => $expected_content_length,
            'expected_first_12_bytes_hex' => $expected_first_12_bytes_hex,
            'require_public_body_match' => $require_public_body_match,
            'http_head_status'      => 0,
            'http_head_type'        => '',
            'http_head_length'      => '',
            'http_head_location'    => '',
            'http_head_error'       => '',
            'http_head_accept_ranges' => '',
            'http_head_cache_control' => '',
            'http_head_x_content_type_options' => '',
            'http_head_server'      => '',
            'http_head_via'         => '',
            'http_head_cf_cache_status' => '',
            'http_head_x_litespeed_cache' => '',
            'http_head_x_hcdn_cache_status' => '',
            'http_head_x_hcdn_request_id' => '',
            'http_content_type'     => '',
            'http_content_length'   => '',
            'http_accept_ranges'    => '',
            'http_cache_control'    => '',
            'http_x_content_type_options' => '',
            'http_server'           => '',
            'http_via'              => '',
            'http_cf_cache_status'  => '',
            'http_x_litespeed_cache'=> '',
            'http_x_hcdn_cache_status' => '',
            'http_x_hcdn_request_id'=> '',
            'http_get_status'       => 0,
            'http_get_type'         => '',
            'http_get_length'       => '',
            'http_get_location'     => '',
            'http_get_error'        => '',
            'http_get_accept_ranges' => '',
            'http_get_cache_control' => '',
            'http_get_x_content_type_options' => '',
            'http_get_server'       => '',
            'http_get_via'          => '',
            'http_get_cf_cache_status' => '',
            'http_get_x_litespeed_cache' => '',
            'http_get_x_hcdn_cache_status' => '',
            'http_get_x_hcdn_request_id' => '',
            'body_preview'          => '',
            'body_bytes'            => 0,
            'body_detected_mime'    => '',
            'body_magic'            => '',
            'body_starts_with_jpeg' => false,
            'body_matches_expected' => false,
            'body_size_matches_expected' => false,
            'body_magic_matches_expected' => false,
            'http_content_length_matches_expected' => false,
            'attempts'              => [],
        ];

        $accept = $required_mime === 'image/jpeg'
            ? 'image/jpeg,*/*;q=0.8'
            : 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8';

        $request_args = [
            'timeout'     => 20,
            'redirection' => 3,
            'user-agent'  => 'facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)',
            'headers'     => [
                'Accept'        => $accept,
                'Cache-Control' => 'no-cache',
                'Pragma'        => 'no-cache',
            ],
        ];

        for ($attempt = 1; $attempt <= max(1, (int) $max_attempts); $attempt++) {
            $attempt_meta = [
                'attempt'            => $attempt,
                'head_status'        => 0,
                'head_type'          => '',
                'head_normalized'    => '',
                'head_length'        => '',
                'head_location'      => '',
                'head_error'         => '',
                'head_accept_ranges' => '',
                'head_cache_control' => '',
                'head_x_content_type_options' => '',
                'head_server'        => '',
                'head_via'           => '',
                'head_cf_cache_status' => '',
                'head_x_litespeed_cache' => '',
                'head_x_hcdn_cache_status' => '',
                'head_x_hcdn_request_id' => '',
                'head_ok'            => false,
                'get_status'         => 0,
                'get_type'           => '',
                'get_normalized'     => '',
                'get_length'         => '',
                'get_location'       => '',
                'get_error'          => '',
                'get_accept_ranges'  => '',
                'get_cache_control'  => '',
                'get_x_content_type_options' => '',
                'get_server'         => '',
                'get_via'            => '',
                'get_cf_cache_status' => '',
                'get_x_litespeed_cache' => '',
                'get_x_hcdn_cache_status' => '',
                'get_x_hcdn_request_id' => '',
                'get_body_bytes'     => 0,
                'get_body_preview'   => '',
                'get_body_detected'  => '',
                'get_body_magic'     => '',
                'get_body_starts_with_jpeg' => false,
                'get_body_matches_expected' => false,
                'get_body_size_matches_expected' => false,
                'get_body_magic_matches_expected' => false,
                'get_content_length_matches_expected' => false,
                'get_ok'             => false,
            ];

            $head = wp_remote_head($url, $request_args);
            if (!is_wp_error($head)) {
                $attempt_meta['head_status']     = (int) wp_remote_retrieve_response_code($head);
                $attempt_meta['head_type']       = strtolower((string) wp_remote_retrieve_header($head, 'content-type'));
                $attempt_meta['head_normalized'] = self::normalize_content_type($attempt_meta['head_type']);
                $attempt_meta['head_length']     = (string) wp_remote_retrieve_header($head, 'content-length');
                $attempt_meta['head_location']   = (string) wp_remote_retrieve_header($head, 'location');
                $attempt_meta['head_accept_ranges'] = (string) wp_remote_retrieve_header($head, 'accept-ranges');
                $attempt_meta['head_cache_control'] = (string) wp_remote_retrieve_header($head, 'cache-control');
                $attempt_meta['head_x_content_type_options'] = (string) wp_remote_retrieve_header($head, 'x-content-type-options');
                $attempt_meta['head_server'] = (string) wp_remote_retrieve_header($head, 'server');
                $attempt_meta['head_via'] = (string) wp_remote_retrieve_header($head, 'via');
                $attempt_meta['head_cf_cache_status'] = (string) wp_remote_retrieve_header($head, 'cf-cache-status');
                $attempt_meta['head_x_litespeed_cache'] = (string) wp_remote_retrieve_header($head, 'x-litespeed-cache');
                $attempt_meta['head_x_hcdn_cache_status'] = (string) wp_remote_retrieve_header($head, 'x-hcdn-cache-status');
                $attempt_meta['head_x_hcdn_request_id'] = (string) wp_remote_retrieve_header($head, 'x-hcdn-request-id');

                $last_meta['http_head_status']   = $attempt_meta['head_status'];
                $last_meta['http_head_type']     = $attempt_meta['head_type'];
                $last_meta['http_head_length']   = $attempt_meta['head_length'];
                $last_meta['http_head_location'] = $attempt_meta['head_location'];
                $last_meta['http_head_accept_ranges'] = $attempt_meta['head_accept_ranges'];
                $last_meta['http_head_cache_control'] = $attempt_meta['head_cache_control'];
                $last_meta['http_head_x_content_type_options'] = $attempt_meta['head_x_content_type_options'];
                $last_meta['http_head_server'] = $attempt_meta['head_server'];
                $last_meta['http_head_via'] = $attempt_meta['head_via'];
                $last_meta['http_head_cf_cache_status'] = $attempt_meta['head_cf_cache_status'];
                $last_meta['http_head_x_litespeed_cache'] = $attempt_meta['head_x_litespeed_cache'];
                $last_meta['http_head_x_hcdn_cache_status'] = $attempt_meta['head_x_hcdn_cache_status'];
                $last_meta['http_head_x_hcdn_request_id'] = $attempt_meta['head_x_hcdn_request_id'];
                $last_meta['http_content_type']  = $attempt_meta['head_type'];
                $last_meta['http_content_length']= $attempt_meta['head_length'];
                $last_meta['http_accept_ranges'] = $attempt_meta['head_accept_ranges'];
                $last_meta['http_cache_control'] = $attempt_meta['head_cache_control'];
                $last_meta['http_x_content_type_options'] = $attempt_meta['head_x_content_type_options'];
                $last_meta['http_server'] = $attempt_meta['head_server'];
                $last_meta['http_via'] = $attempt_meta['head_via'];
                $last_meta['http_cf_cache_status'] = $attempt_meta['head_cf_cache_status'];
                $last_meta['http_x_litespeed_cache'] = $attempt_meta['head_x_litespeed_cache'];
                $last_meta['http_x_hcdn_cache_status'] = $attempt_meta['head_x_hcdn_cache_status'];
                $last_meta['http_x_hcdn_request_id'] = $attempt_meta['head_x_hcdn_request_id'];

                $attempt_meta['head_ok'] = (
                    $attempt_meta['head_status'] === 200 &&
                    self::is_allowed_public_content_type($attempt_meta['head_type'], $required_mime) &&
                    ($attempt_meta['head_length'] === '' || (int) $attempt_meta['head_length'] > 0)
                );

                if ($attempt_meta['head_ok'] && !$require_head_200) {
                    $last_meta['attempts'][] = $attempt_meta;
                    return $last_meta;
                }
            } else {
                $attempt_meta['head_error'] = $head->get_error_message();
                $last_meta['http_head_error'] = $attempt_meta['head_error'];
            }

            $get_args = $request_args;
            if ($allow_partial_get) {
                $get_args['headers'] = array_merge($request_args['headers'], [
                    'Range' => 'bytes=0-2047',
                ]);
            }

            $get = wp_remote_get($url, $get_args);

            if (!is_wp_error($get)) {
                $attempt_meta['get_status']     = (int) wp_remote_retrieve_response_code($get);
                $attempt_meta['get_type']       = strtolower((string) wp_remote_retrieve_header($get, 'content-type'));
                $attempt_meta['get_normalized'] = self::normalize_content_type($attempt_meta['get_type']);
                $attempt_meta['get_length']     = (string) wp_remote_retrieve_header($get, 'content-length');
                $attempt_meta['get_location']   = (string) wp_remote_retrieve_header($get, 'location');
                $attempt_meta['get_accept_ranges'] = (string) wp_remote_retrieve_header($get, 'accept-ranges');
                $attempt_meta['get_cache_control'] = (string) wp_remote_retrieve_header($get, 'cache-control');
                $attempt_meta['get_x_content_type_options'] = (string) wp_remote_retrieve_header($get, 'x-content-type-options');
                $attempt_meta['get_server'] = (string) wp_remote_retrieve_header($get, 'server');
                $attempt_meta['get_via'] = (string) wp_remote_retrieve_header($get, 'via');
                $attempt_meta['get_cf_cache_status'] = (string) wp_remote_retrieve_header($get, 'cf-cache-status');
                $attempt_meta['get_x_litespeed_cache'] = (string) wp_remote_retrieve_header($get, 'x-litespeed-cache');
                $attempt_meta['get_x_hcdn_cache_status'] = (string) wp_remote_retrieve_header($get, 'x-hcdn-cache-status');
                $attempt_meta['get_x_hcdn_request_id'] = (string) wp_remote_retrieve_header($get, 'x-hcdn-request-id');
                $body = (string) wp_remote_retrieve_body($get);
                $attempt_meta['get_body_bytes']    = strlen($body);
                $attempt_meta['get_body_detected'] = self::detect_image_mime_from_binary($body);
                $attempt_meta['get_body_magic']    = bin2hex(substr($body, 0, 12));
                $attempt_meta['get_body_starts_with_jpeg'] = strncmp($body, "\xFF\xD8\xFF", 3) === 0;
                $attempt_meta['get_body_size_matches_expected'] = $expected_content_length <= 0 || $attempt_meta['get_body_bytes'] === $expected_content_length;
                $attempt_meta['get_content_length_matches_expected'] = $expected_content_length <= 0 || $attempt_meta['get_length'] === '' || (int) $attempt_meta['get_length'] === $expected_content_length;
                $attempt_meta['get_body_magic_matches_expected'] = $expected_first_12_bytes_hex === '' || strtolower($attempt_meta['get_body_magic']) === substr($expected_first_12_bytes_hex, 0, 24);
                $attempt_meta['get_body_matches_expected'] = $attempt_meta['get_body_size_matches_expected'] && $attempt_meta['get_body_magic_matches_expected'];

                if (!self::is_allowed_public_content_type($attempt_meta['get_type'], $required_mime)) {
                    $attempt_meta['get_body_preview'] = self::preview_response_body($body);
                }

                $last_meta['http_get_status']     = $attempt_meta['get_status'];
                $last_meta['http_get_type']       = $attempt_meta['get_type'];
                $last_meta['http_get_length']     = $attempt_meta['get_length'];
                $last_meta['http_get_location']   = $attempt_meta['get_location'];
                $last_meta['http_get_accept_ranges'] = $attempt_meta['get_accept_ranges'];
                $last_meta['http_get_cache_control'] = $attempt_meta['get_cache_control'];
                $last_meta['http_get_x_content_type_options'] = $attempt_meta['get_x_content_type_options'];
                $last_meta['http_get_server'] = $attempt_meta['get_server'];
                $last_meta['http_get_via'] = $attempt_meta['get_via'];
                $last_meta['http_get_cf_cache_status'] = $attempt_meta['get_cf_cache_status'];
                $last_meta['http_get_x_litespeed_cache'] = $attempt_meta['get_x_litespeed_cache'];
                $last_meta['http_get_x_hcdn_cache_status'] = $attempt_meta['get_x_hcdn_cache_status'];
                $last_meta['http_get_x_hcdn_request_id'] = $attempt_meta['get_x_hcdn_request_id'];
                $last_meta['http_content_type']   = $attempt_meta['get_type'];
                $last_meta['http_content_length'] = $attempt_meta['get_length'];
                $last_meta['http_accept_ranges'] = $attempt_meta['get_accept_ranges'] !== '' ? $attempt_meta['get_accept_ranges'] : $last_meta['http_accept_ranges'];
                $last_meta['http_cache_control'] = $attempt_meta['get_cache_control'] !== '' ? $attempt_meta['get_cache_control'] : $last_meta['http_cache_control'];
                $last_meta['http_x_content_type_options'] = $attempt_meta['get_x_content_type_options'] !== '' ? $attempt_meta['get_x_content_type_options'] : $last_meta['http_x_content_type_options'];
                $last_meta['http_server'] = $attempt_meta['get_server'] !== '' ? $attempt_meta['get_server'] : $last_meta['http_server'];
                $last_meta['http_via'] = $attempt_meta['get_via'] !== '' ? $attempt_meta['get_via'] : $last_meta['http_via'];
                $last_meta['http_cf_cache_status'] = $attempt_meta['get_cf_cache_status'] !== '' ? $attempt_meta['get_cf_cache_status'] : $last_meta['http_cf_cache_status'];
                $last_meta['http_x_litespeed_cache'] = $attempt_meta['get_x_litespeed_cache'] !== '' ? $attempt_meta['get_x_litespeed_cache'] : $last_meta['http_x_litespeed_cache'];
                $last_meta['http_x_hcdn_cache_status'] = $attempt_meta['get_x_hcdn_cache_status'] !== '' ? $attempt_meta['get_x_hcdn_cache_status'] : $last_meta['http_x_hcdn_cache_status'];
                $last_meta['http_x_hcdn_request_id'] = $attempt_meta['get_x_hcdn_request_id'] !== '' ? $attempt_meta['get_x_hcdn_request_id'] : $last_meta['http_x_hcdn_request_id'];
                $last_meta['body_bytes']          = $attempt_meta['get_body_bytes'];
                $last_meta['body_preview']        = $attempt_meta['get_body_preview'];
                $last_meta['body_detected_mime']  = $attempt_meta['get_body_detected'];
                $last_meta['body_magic']          = $attempt_meta['get_body_magic'];
                $last_meta['body_starts_with_jpeg'] = $attempt_meta['get_body_starts_with_jpeg'];
                $last_meta['body_matches_expected'] = $attempt_meta['get_body_matches_expected'];
                $last_meta['body_size_matches_expected'] = $attempt_meta['get_body_size_matches_expected'];
                $last_meta['body_magic_matches_expected'] = $attempt_meta['get_body_magic_matches_expected'];
                $last_meta['http_content_length_matches_expected'] = $attempt_meta['get_content_length_matches_expected'];

                $valid_statuses = $allow_partial_get ? [200, 206] : [200];
                $attempt_meta['get_ok'] = (
                    in_array($attempt_meta['get_status'], $valid_statuses, true) &&
                    self::is_allowed_public_content_type($attempt_meta['get_type'], $required_mime) &&
                    ($required_mime === '' || $attempt_meta['get_body_detected'] === '' || $attempt_meta['get_body_detected'] === $required_mime) &&
                    ($required_mime !== 'image/jpeg' || $attempt_meta['get_body_starts_with_jpeg']) &&
                    (!$require_public_body_match || $attempt_meta['get_body_matches_expected']) &&
                    $body !== ''
                );

                if ($attempt_meta['get_ok'] && (!$require_head_200 || $attempt_meta['head_ok'])) {
                    $last_meta['attempts'][] = $attempt_meta;
                    return $last_meta;
                }
            } else {
                $attempt_meta['get_error'] = $get->get_error_message();
                $last_meta['http_get_error'] = $attempt_meta['get_error'];
            }

            $last_meta['attempts'][] = $attempt_meta;
            sleep(min(4, $attempt));
        }

        $network = sanitize_key((string) $network);
        if ($network !== '' && class_exists('Calliope_SP_Logger')) {
            Calliope_SP_Logger::log($network, 'error', wp_json_encode(array_merge([
                'message'    => 'Meta media URL diagnostics: public image URL validation failed before sending to API.',
                'public_url' => $url,
            ], $last_meta), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
        }

        return new WP_Error(
            'invalid_public_media_url',
            'The public media URL is not accessible or does not return a valid image that meets the required media MIME.',
            $last_meta
        );
    }

    /**
     * Build a safe, short preview of a non-image HTTP body for diagnostics.
     *
     * @param string $body Response body.
     * @return string
     */
    private static function preview_response_body($body) {
        $body = (string) $body;
        if ($body === '') {
            return '';
        }

        $snippet = substr($body, 0, 500);
        $snippet = preg_replace('/[^\P{C}\t\r\n]+/u', '', $snippet);
        if (!is_string($snippet)) {
            $snippet = substr($body, 0, 200);
        }
        $snippet = wp_strip_all_tags($snippet);
        $snippet = preg_replace('/\s+/', ' ', (string) $snippet);
        return trim(mb_substr((string) $snippet, 0, 300));
    }

    /**
     * Return local image metadata.
     *
     * @param string $path Absolute file path.
     * @return array<string,mixed>|WP_Error
     */
    public static function inspect_image_file($path) {
        $path = (string) $path;
        if ($path === '' || !file_exists($path) || !is_file($path)) {
            return new WP_Error('invalid_image_file', 'The generated image file does not exist.');
        }

        $size = @getimagesize($path);
        if (!is_array($size) || empty($size[0]) || empty($size[1])) {
            return new WP_Error('invalid_image_file', 'The generated file is not a valid image.');
        }

        $mime = '';
        if (!empty($size['mime'])) {
            $mime = strtolower((string) $size['mime']);
        }

        if ($mime === '' && function_exists('mime_content_type')) {
            $mime = strtolower((string) @mime_content_type($path));
        }

        if ($mime === '' && class_exists('finfo')) {
            $finfo = new finfo(FILEINFO_MIME_TYPE);
            $mime = strtolower((string) $finfo->file($path));
        }

        $wp_type = wp_check_filetype(basename($path));
        if ($mime === '' && !empty($wp_type['type'])) {
            $mime = strtolower((string) $wp_type['type']);
        }

        if (!self::is_image_content_type($mime)) {
            return new WP_Error('invalid_image_file', 'The generated file does not report a valid image MIME type.');
        }

        return [
            'path'      => $path,
            'width'     => (int) $size[0],
            'height'    => (int) $size[1],
            'filesize'  => (int) filesize($path),
            'mime'      => $mime,
            'extension' => !empty($wp_type['ext']) ? sanitize_key((string) $wp_type['ext']) : self::guess_extension_from_mime($mime),
        ];
    }

    /**
     * Return the shared media-preparation arguments for Instagram and Threads.
     *
     * @param string $network Network key.
     * @return array<string,mixed>
     */
    public static function get_meta_static_image_args($network) {
        $network = sanitize_key((string) $network);
        $context = $network === 'threads' ? 'threads' : 'instagram';

        return [
            'context'              => $context,
            'filename_prefix'      => $context === 'threads' ? 'csp-meta-threads' : 'csp-meta-ig',
            'preferred_mime'       => 'image/jpeg',
            'network'              => $context,
            'max_attempts'         => 2,
            'url_strategy'         => 'meta_static_safe',
            'required_public_mime' => 'image/jpeg',
            'require_head_200'     => true,
            'allow_partial_get'    => false,
            'force_jpeg_reencode'  => true,
            'trust_local_on_public_validation_failure' => true,
        ];
    }

    /**
     * Create a simple 1080x1080 JPEG data URL for admin media URL diagnostics.
     *
     * @return string|WP_Error
     */
    public static function create_sample_jpeg_data_url() {
        if (!function_exists('imagecreatetruecolor') || !function_exists('imagejpeg')) {
            return new WP_Error('image_editor_missing', 'The server cannot generate a sample JPEG because the GD image library is unavailable.');
        }

        $size = 1080;
        $image = imagecreatetruecolor($size, $size);
        if (!$image) {
            return new WP_Error('image_editor_missing', 'The server could not allocate a sample JPEG image.');
        }

        $bg = imagecolorallocate($image, 244, 247, 250);
        $ink = imagecolorallocate($image, 24, 34, 79);
        $accent = imagecolorallocate($image, 214, 41, 118);
        $soft = imagecolorallocate($image, 235, 238, 245);

        imagefilledrectangle($image, 0, 0, $size, $size, $bg);
        imagefilledellipse($image, 540, 540, 760, 760, $soft);
        imagefilledrectangle($image, 250, 250, 830, 830, $accent);
        imagefilledrectangle($image, 310, 310, 770, 770, $bg);
        imagefilledellipse($image, 540, 540, 250, 250, $ink);

        ob_start();
        imagejpeg($image, null, 90);
        $binary = (string) ob_get_clean();
        imagedestroy($image);

        if ($binary === '' || strncmp($binary, "\xFF\xD8\xFF", 3) !== 0) {
            return new WP_Error('image_save_failed', 'The server could not generate a valid sample JPEG.');
        }

        return 'data:image/jpeg;base64,' . base64_encode($binary);
    }

    /**
     * Build local-file diagnostics for Meta media logs.
     *
     * @param string              $path Absolute local path.
     * @param array<string,mixed> $meta Optional image metadata.
     * @return array<string,mixed>
     */
    public static function get_local_file_diagnostics($path, array $meta = []) {
        $path = (string) $path;
        $exists = $path !== '' && file_exists($path) && is_file($path);

        return [
            'file_exists'            => $exists,
            'file_readable'          => $exists && is_readable($path),
            'filesize'               => $exists ? (int) filesize($path) : 0,
            'mime'                   => (string) ($meta['mime'] ?? ''),
            'width'                  => (int) ($meta['width'] ?? 0),
            'height'                 => (int) ($meta['height'] ?? 0),
            'first_12_bytes_hex'     => $exists ? self::read_file_prefix_hex($path, 12) : '',
            'sha1'                   => $exists ? (string) sha1_file($path) : '',
            'starts_with_jpeg_magic' => $exists ? self::file_starts_with_jpeg_magic($path) : false,
        ];
    }

    /**
     * Build the direct public uploads URL for a generated image file.
     *
     * @param string $path Absolute local path.
     * @return string|WP_Error
     */
    public static function build_public_url_from_upload_path($path) {
        $path = (string) $path;
        if ($path === '' || !file_exists($path) || !is_file($path)) {
            return new WP_Error('invalid_image_file', 'The generated image file does not exist.');
        }

        $uploads = wp_upload_dir();
        if (!empty($uploads['error'])) {
            return new WP_Error('upload_failed', 'Could not access the uploads directory: ' . $uploads['error']);
        }

        $base_dir = trailingslashit(wp_normalize_path((string) $uploads['basedir']));
        $normalized_path = wp_normalize_path($path);
        if (strpos($normalized_path, $base_dir) !== 0) {
            return new WP_Error('invalid_image_file', 'The generated image was not saved inside uploads.');
        }

        $relative = ltrim(substr($normalized_path, strlen($base_dir)), '/');
        $relative_parts = array_map('rawurlencode', explode('/', $relative));
        $url = trailingslashit((string) $uploads['baseurl']) . implode('/', $relative_parts);

        return esc_url_raw(self::normalize_public_url($url));
    }

    /**
     * Keep media URLs on HTTPS when the site itself is HTTPS.
     * Meta's media fetchers are stricter with redirects and mixed schemes than WordPress' own self-check.
     *
     * @param string $url Public URL.
     * @return string
     */
    private static function normalize_public_url($url) {
        $url = esc_url_raw((string) $url);
        if ($url === '') {
            return '';
        }

        $home = home_url('/');
        if (is_ssl() || stripos((string) $home, 'https://') === 0) {
            $url = set_url_scheme($url, 'https');
        }

        return esc_url_raw($url);
    }

    /**
     * Delete a file created by the helper when it is no longer needed.
     *
     * @param string $path Absolute file path.
     * @return void
     */
    public static function delete_file($path) {
        $path = (string) $path;
        if ($path !== '' && file_exists($path) && is_file($path)) {
            wp_delete_file($path);
        }
    }

    /**
     * Copy a prepared JPEG into the direct public Meta-safe upload store.
     *
     * @param string $source_path Source image path.
     * @param string $context     Network subdirectory.
     * @param string $prefix      Filename prefix.
     * @return array<string,mixed>|WP_Error
     */
    private static function copy_image_to_meta_static_store($source_path, $context, $prefix) {
        $source_path = (string) $source_path;
        if ($source_path === '' || !file_exists($source_path) || !is_file($source_path)) {
            return new WP_Error('invalid_image_file', 'The generated image file does not exist.');
        }

        $source_meta = self::inspect_image_file($source_path);
        if (is_wp_error($source_meta)) {
            return $source_meta;
        }

        if (strtolower((string) ($source_meta['mime'] ?? '')) !== 'image/jpeg' || !self::file_starts_with_jpeg_magic($source_path)) {
            return new WP_Error('invalid_image_file', 'The Meta-safe media store only accepts true JPEG files.');
        }

        $store = self::ensure_meta_static_store($context);
        if (is_wp_error($store)) {
            return $store;
        }

        $prefix = sanitize_file_name((string) $prefix);
        if ($prefix === '') {
            $prefix = 'csp-meta';
        }

        $filename = $prefix . '-' . gmdate('Ymd-His') . '-' . wp_generate_password(8, false, false) . '.jpg';
        $target_path = trailingslashit((string) $store['dir']) . $filename;

        if (!@copy($source_path, $target_path)) {
            return new WP_Error('upload_failed', 'Could not copy the JPEG into the Meta-safe media store.');
        }

        @chmod($target_path, 0644);

        $meta = self::inspect_image_file($target_path);
        if (is_wp_error($meta)) {
            self::delete_file($target_path);
            return $meta;
        }

        if (!self::file_starts_with_jpeg_magic($target_path)) {
            self::delete_file($target_path);
            return new WP_Error('invalid_image_file', 'The Meta-safe JPEG file does not start with valid JPEG bytes.');
        }

        $url = self::build_public_url_from_upload_path($target_path);
        if (is_wp_error($url)) {
            self::delete_file($target_path);
            return $url;
        }

        return [
            'path' => $target_path,
            'url'  => (string) $url,
            'meta' => $meta,
        ];
    }

    /**
     * Create a static-looking .jpg URL that is served dynamically from WordPress.
     *
     * This avoids wp-json URLs while giving hosts/CDNs less opportunity to rewrite
     * the original static JPEG before Instagram fetches it.
     *
     * @param string $path    Absolute local JPEG path.
     * @param string $context Network subdirectory.
     * @param string $prefix  Filename prefix.
     * @param int    $ttl     URL lifetime in seconds.
     * @return string|WP_Error
     */
    public static function create_meta_static_proxy_url($path, $context, $prefix, $ttl = 21600) {
        $path = (string) $path;
        if ($path === '' || !file_exists($path) || !is_file($path) || !self::is_path_inside_plugin_uploads($path)) {
            return new WP_Error('invalid_image_file', 'The generated image file does not exist.');
        }

        $meta = self::inspect_image_file($path);
        if (is_wp_error($meta)) {
            return $meta;
        }

        if (strtolower((string) ($meta['mime'] ?? '')) !== 'image/jpeg' || !self::file_starts_with_jpeg_magic($path)) {
            return new WP_Error('invalid_image_file', 'The static-looking Meta URL only accepts true JPEG files.');
        }

        $store = self::ensure_meta_static_store($context);
        if (is_wp_error($store)) {
            return $store;
        }

        $uploads = wp_upload_dir();
        if (!empty($uploads['error'])) {
            return new WP_Error('upload_failed', 'Could not access the uploads directory: ' . $uploads['error']);
        }

        $context = sanitize_key((string) $context);
        $prefix = sanitize_file_name((string) $prefix);
        if ($context === '') {
            $context = 'shared';
        }
        if ($prefix === '') {
            $prefix = 'csp-meta';
        }

        $filename = $prefix . '-passthrough-' . gmdate('Ymd-His') . '-' . wp_generate_password(8, false, false) . '.jpg';
        $relative = $context . '/' . $filename;
        $key = self::get_meta_static_proxy_transient_key($relative);

        set_transient($key, [
            'path'     => wp_normalize_path($path),
            'mime'     => 'image/jpeg',
            'created'  => time(),
            'expires'  => time() + max(300, (int) $ttl),
            'filesize' => (int) filesize($path),
        ], max(300, (int) $ttl));

        $url = trailingslashit((string) $uploads['baseurl']) . 'calliope-social-publisher-meta/' . rawurlencode($context) . '/' . rawurlencode($filename);
        return esc_url_raw(self::normalize_public_url($url));
    }

    /**
     * Ensure the dedicated Meta media directory and protective files exist.
     *
     * @param string $context Network subdirectory.
     * @return array{dir:string}|WP_Error
     */
    private static function ensure_meta_static_store($context) {
        $uploads = wp_upload_dir();
        if (!empty($uploads['error'])) {
            return new WP_Error('upload_failed', 'Could not access the uploads directory: ' . $uploads['error']);
        }

        $context = sanitize_key((string) $context);
        if ($context === '') {
            $context = 'shared';
        }

        $base_dir = trailingslashit((string) $uploads['basedir']) . 'calliope-social-publisher-meta/';
        $dir = trailingslashit($base_dir) . $context . '/';

        if (!wp_mkdir_p($dir)) {
            return new WP_Error('upload_failed', 'Could not create the Meta-safe uploads directory.');
        }

        $htaccess = "# Calliope Social Publisher Meta-safe media store.\n"
            . "<IfModule mod_mime.c>\n"
            . "AddType image/jpeg .jpg .jpeg\n"
            . "</IfModule>\n\n"
            . "<IfModule mod_headers.c>\n"
            . "Header set Cache-Control \"public, max-age=3600, no-transform\"\n"
            . "Header set Accept-Ranges \"bytes\"\n"
            . "Header set Content-Disposition \"inline\"\n"
            . "Header set X-Content-Type-Options \"nosniff\"\n"
            . "Header unset Content-Encoding\n"
            . "</IfModule>\n\n"
            . "<IfModule mod_rewrite.c>\n"
            . "RewriteEngine On\n"
            . "RewriteCond %{REQUEST_FILENAME} !-f\n"
            . "RewriteRule ^(instagram|threads)/([^/]+\\.jpg)$ /index.php?calliope_sp_meta_media=$1/$2 [L,QSA]\n"
            . "</IfModule>\n";

        foreach ([$base_dir, $dir] as $target_dir) {
            if (!self::write_text_file_if_changed(trailingslashit($target_dir) . 'index.html', '')) {
                return new WP_Error('upload_failed', 'Could not write the Meta-safe uploads index file.');
            }

            if (!self::write_text_file_if_changed(trailingslashit($target_dir) . '.htaccess', $htaccess)) {
                return new WP_Error('upload_failed', 'Could not write the Meta-safe uploads .htaccess file.');
            }
        }

        return ['dir' => $dir];
    }

    /**
     * Write a small text file only when its contents differ.
     *
     * @param string $path     File path.
     * @param string $contents File contents.
     * @return bool
     */
    private static function write_text_file_if_changed($path, $contents) {
        $path = (string) $path;
        $contents = (string) $contents;

        if (file_exists($path) && is_file($path)) {
            $current = @file_get_contents($path);
            if ($current === $contents) {
                return true;
            }
        }

        $written = @file_put_contents($path, $contents, LOCK_EX);
        if ($written === false) {
            return false;
        }

        @chmod($path, 0644);
        return true;
    }

    /**
     * Register a signed public media endpoint used by Meta APIs when static uploads
     * are blocked by hotlink protection, CDN rules, or MIME/header quirks.
     *
     * The route is intentionally public because Meta must fetch it without a logged-in
     * WordPress session. Access is limited by an unguessable token stored in a short
     * lived transient and by strict uploads-directory path validation.
     *
     * @return void
     */
    public static function register_rest_routes() {
        register_rest_route('calliope-social-publisher/v1', '/media/(?P<token>[A-Za-z0-9]+)/(?P<filename>[^/]+)', [
            'methods'             => ['GET', 'HEAD'],
            'permission_callback' => '__return_true',
            'callback'            => [__CLASS__, 'serve_signed_public_media'],
        ]);

        register_rest_route('calliope-social-publisher/v1', '/media/(?P<token>[A-Za-z0-9]+)', [
            'methods'             => ['GET', 'HEAD'],
            'permission_callback' => '__return_true',
            'callback'            => [__CLASS__, 'serve_signed_public_media'],
        ]);
    }

    /**
     * Create a temporary signed URL that streams the already generated local image
     * through WordPress with explicit image headers.
     *
     * @param string $path Absolute local path.
     * @param string $mime Image MIME type.
     * @param int    $ttl  URL lifetime in seconds.
     * @return string|WP_Error
     */
    public static function create_signed_public_media_url($path, $mime, $ttl = 21600) {
        $path = (string) $path;
        if ($path === '' || !file_exists($path) || !is_file($path)) {
            return new WP_Error('invalid_image_file', 'The generated image file does not exist.');
        }

        if (!self::is_path_inside_plugin_uploads($path)) {
            return new WP_Error('invalid_image_file', 'The generated image is outside the plugin uploads directory.');
        }

        $mime = strtolower((string) $mime);
        if (!self::is_image_content_type($mime)) {
            $mime = 'image/jpeg';
        }

        $token = wp_generate_password(40, false, false);
        set_transient('calliope_sp_public_media_' . $token, [
            'path'      => wp_normalize_path($path),
            'mime'      => $mime,
            'created'   => time(),
            'expires'   => time() + max(300, (int) $ttl),
            'filesize'  => (int) filesize($path),
        ], max(300, (int) $ttl));

        $url = rest_url('calliope-social-publisher/v1/media/' . rawurlencode($token) . '/' . rawurlencode(basename($path)));
        return esc_url_raw(self::normalize_public_url($url));
    }

    /**
     * Serve a signed temporary media file to Meta crawlers.
     *
     * @param WP_REST_Request $request REST request.
     * @return WP_Error|void
     */
    public static function serve_signed_public_media($request) {
        $token = sanitize_text_field((string) $request->get_param('token'));
        if ($token === '') {
            return new WP_Error('calliope_sp_media_not_found', 'Media not found.', ['status' => 404]);
        }

        $data = get_transient('calliope_sp_public_media_' . $token);
        if (!is_array($data)) {
            return new WP_Error('calliope_sp_media_not_found', 'Media not found or expired.', ['status' => 404]);
        }

        $path = (string) ($data['path'] ?? '');
        if ($path === '' || !file_exists($path) || !is_file($path) || !self::is_path_inside_plugin_uploads($path)) {
            return new WP_Error('calliope_sp_media_not_found', 'Media file not found.', ['status' => 404]);
        }

        $meta = self::inspect_image_file($path);
        if (is_wp_error($meta)) {
            return new WP_Error('calliope_sp_invalid_media', 'Invalid media file.', ['status' => 404]);
        }

        $mime = strtolower((string) ($meta['mime'] ?? ($data['mime'] ?? 'image/jpeg')));
        $filesize = (int) filesize($path);

        status_header(200);
        header('Content-Type: ' . $mime);
        header('Content-Length: ' . $filesize);
        header('Cache-Control: public, max-age=21600, no-transform');
        header('Accept-Ranges: bytes');
        header('Access-Control-Allow-Origin: *');
        header('X-Content-Type-Options: nosniff');
        header('X-Robots-Tag: noindex, nofollow', true);
        header('Content-Disposition: inline; filename="' . basename($path) . '"');

        if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'HEAD') {
            readfile($path);
        }

        exit;
    }

    /**
     * Serve static-looking Meta media URLs rewritten from the uploads subfolder.
     *
     * @return void
     */
    public static function maybe_serve_meta_static_proxy() {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public media URL uses an unguessable transient-backed filename and strict path validation.
        $relative = isset($_GET['calliope_sp_meta_media']) ? sanitize_text_field(wp_unslash((string) $_GET['calliope_sp_meta_media'])) : '';
        if ($relative === '') {
            return;
        }

        $relative = str_replace('\\', '/', $relative);
        $relative = ltrim($relative, '/');
        if (!preg_match('#^(instagram|threads)/[A-Za-z0-9._-]+\.jpg$#', $relative)) {
            status_header(404);
            exit;
        }

        $data = get_transient(self::get_meta_static_proxy_transient_key($relative));
        if (!is_array($data)) {
            status_header(404);
            exit;
        }

        $path = (string) ($data['path'] ?? '');
        if ($path === '' || !file_exists($path) || !is_file($path) || !self::is_path_inside_plugin_uploads($path)) {
            status_header(404);
            exit;
        }

        $meta = self::inspect_image_file($path);
        if (is_wp_error($meta) || strtolower((string) ($meta['mime'] ?? '')) !== 'image/jpeg' || !self::file_starts_with_jpeg_magic($path)) {
            status_header(404);
            exit;
        }

        $filesize = (int) filesize($path);

        nocache_headers();
        header_remove('Expires');
        header_remove('Pragma');
        header_remove('Content-Encoding');
        status_header(200);
        header('Content-Type: image/jpeg');
        header('Content-Length: ' . $filesize);
        header('Cache-Control: public, max-age=3600, no-transform');
        header('Accept-Ranges: bytes');
        header('Access-Control-Allow-Origin: *');
        header('X-Content-Type-Options: nosniff');
        header('X-Robots-Tag: noindex, nofollow', true);
        header('Content-Disposition: inline; filename="' . basename($relative) . '"');

        if (strtoupper((string) ($_SERVER['REQUEST_METHOD'] ?? 'GET')) !== 'HEAD') {
            readfile($path);
        }

        exit;
    }

    /**
     * Get the transient key for a static-looking Meta media URL.
     *
     * @param string $relative Relative network/filename path.
     * @return string
     */
    private static function get_meta_static_proxy_transient_key($relative) {
        return 'calliope_sp_meta_static_proxy_' . sha1((string) $relative);
    }

    /**
     * Ensure signed media URLs can only stream files generated by this plugin.
     *
     * @param string $path Absolute path.
     * @return bool
     */
    private static function is_path_inside_plugin_uploads($path) {
        $uploads = wp_upload_dir();
        if (!empty($uploads['error'])) {
            return false;
        }

        $real = wp_normalize_path((string) $path);

        $base_dir = trailingslashit(wp_normalize_path((string) $uploads['basedir']));
        $allowed_bases = [
            $base_dir . 'calliope-social-publisher/',
            $base_dir . 'calliope-social-publisher-meta/',
        ];

        foreach ($allowed_bases as $base) {
            if (strpos($real, $base) === 0) {
                return true;
            }
        }

        return false;
    }

    /**
     * @param string $b64 Base64 payload.
     * @return array<string,mixed>|WP_Error
     */
    private static function decode_base64_image($b64) {
        if (!is_string($b64) || trim($b64) === '') {
            return new WP_Error('no_image', 'No image data was provided.');
        }

        $mime = '';
        $raw = trim($b64);

        if (preg_match('#^data:([^;]+);base64,(.*)$#s', $raw, $matches)) {
            $mime = strtolower(trim((string) $matches[1]));
            $raw = (string) $matches[2];
        }

        $raw = preg_replace('#\s+#', '', $raw);
        $binary = base64_decode((string) $raw, true);
        if ($binary === false || $binary === '') {
            return new WP_Error('bad_base64', 'The image data could not be decoded.');
        }

        $info = function_exists('getimagesizefromstring') ? @getimagesizefromstring($binary) : false;
        if (!is_array($info) || empty($info[0]) || empty($info[1])) {
            return new WP_Error('invalid_image_file', 'The decoded file is not a valid image.');
        }

        $detected_mime = !empty($info['mime']) ? strtolower((string) $info['mime']) : $mime;
        if (!self::is_supported_mime($detected_mime)) {
            return new WP_Error('invalid_image_type', 'The image type is not supported for publishing.');
        }

        return [
            'binary'    => $binary,
            'mime'      => $detected_mime,
            'extension' => self::guess_extension_from_mime($detected_mime),
            'width'     => (int) $info[0],
            'height'    => (int) $info[1],
        ];
    }

    /**
     * Convert an existing file to the preferred output MIME.
     *
     * @param string $path           Absolute file path.
     * @param string $preferred_mime Preferred MIME.
     * @return array{path:string,mime:string}|WP_Error
     */
    private static function convert_image_file($path, $preferred_mime) {
        if (!function_exists('wp_get_image_editor')) {
            return new WP_Error('image_editor_missing', 'WordPress image editing is not available on this site.');
        }

        $preferred_mime = strtolower((string) $preferred_mime);
        if (!self::is_supported_mime($preferred_mime)) {
            return new WP_Error('invalid_image_type', 'The requested output image type is not supported.');
        }

        $editor = wp_get_image_editor($path);
        if (is_wp_error($editor)) {
            return $editor;
        }

        if (method_exists($editor, 'set_quality')) {
            $editor->set_quality(90);
        }

        $target_path = preg_replace('/\.[^.]+$/', '', $path) . '.' . self::guess_extension_from_mime($preferred_mime);
        $saved = $editor->save($target_path, $preferred_mime);
        if (is_wp_error($saved) || empty($saved['path'])) {
            return is_wp_error($saved)
                ? $saved
                : new WP_Error('image_save_failed', 'The converted image file could not be saved.');
        }

        if ($target_path !== $path) {
            self::delete_file($path);
        }

        return [
            'path' => (string) $saved['path'],
            'mime' => !empty($saved['mime-type']) ? strtolower((string) $saved['mime-type']) : $preferred_mime,
        ];
    }

    /**
     * Re-encode a file through WordPress' image editor to get a fresh baseline JPEG.
     *
     * @param string $path           Absolute file path.
     * @param string $preferred_mime Preferred MIME.
     * @return array{path:string,mime:string}|WP_Error
     */
    private static function reencode_image_file($path, $preferred_mime) {
        if (!function_exists('wp_get_image_editor')) {
            return new WP_Error('image_editor_missing', 'WordPress image editing is not available on this site.');
        }

        $preferred_mime = strtolower((string) $preferred_mime);
        if ($preferred_mime !== 'image/jpeg') {
            return self::convert_image_file($path, $preferred_mime);
        }

        $editor = wp_get_image_editor($path);
        if (is_wp_error($editor)) {
            return $editor;
        }

        if (method_exists($editor, 'set_quality')) {
            $editor->set_quality(90);
        }

        $target_path = preg_replace('/\.[^.]+$/', '', (string) $path) . '-jpeg.jpg';
        $saved = $editor->save($target_path, 'image/jpeg');
        if (is_wp_error($saved) || empty($saved['path'])) {
            return is_wp_error($saved)
                ? $saved
                : new WP_Error('image_save_failed', 'The re-encoded JPEG file could not be saved.');
        }

        if ((string) $saved['path'] !== (string) $path) {
            self::delete_file($path);
        }

        return [
            'path' => (string) $saved['path'],
            'mime' => !empty($saved['mime-type']) ? strtolower((string) $saved['mime-type']) : 'image/jpeg',
        ];
    }

    /**
     * @param string $mime MIME type.
     * @return bool
     */
    private static function is_supported_mime($mime) {
        return in_array(strtolower((string) $mime), [
            'image/jpeg',
            'image/png',
            'image/webp',
            'image/gif',
        ], true);
    }

    /**
     * Normalize a Content-Type header into only the MIME value.
     *
     * @param string $content_type Raw Content-Type header.
     * @return string
     */
    private static function normalize_content_type($content_type) {
        $content_type = strtolower(trim((string) $content_type));
        if ($content_type === '') {
            return '';
        }

        $semi_pos = strpos($content_type, ';');
        if ($semi_pos !== false) {
            $content_type = substr($content_type, 0, $semi_pos);
        }

        return trim($content_type);
    }

    /**
     * Check whether a public response has an image Content-Type and, if requested,
     * the exact MIME required by Meta.
     *
     * @param string $content_type Raw Content-Type header.
     * @param string $required_mime Optional exact MIME, for example image/jpeg.
     * @return bool
     */
    private static function is_allowed_public_content_type($content_type, $required_mime = '') {
        $normalized = self::normalize_content_type($content_type);
        $required_mime = self::normalize_content_type($required_mime);

        if ($normalized === '') {
            return false;
        }

        if ($required_mime !== '') {
            return $normalized === $required_mime;
        }

        return strpos($normalized, 'image/') === 0;
    }

    /**
     * Detect the actual image MIME from response bytes, useful when a CDN changes
     * a .jpg URL into WebP while keeping a JPEG-looking filename.
     *
     * @param string $binary Response body bytes.
     * @return string
     */
    private static function detect_image_mime_from_binary($binary) {
        $binary = (string) $binary;
        if ($binary === '') {
            return '';
        }

        if (strncmp($binary, "\xFF\xD8\xFF", 3) === 0) {
            return 'image/jpeg';
        }
        if (strncmp($binary, "\x89PNG\r\n\x1a\n", 8) === 0) {
            return 'image/png';
        }
        if (strncmp($binary, 'GIF87a', 6) === 0 || strncmp($binary, 'GIF89a', 6) === 0) {
            return 'image/gif';
        }
        if (substr($binary, 0, 4) === 'RIFF' && substr($binary, 8, 4) === 'WEBP') {
            return 'image/webp';
        }

        return '';
    }

    /**
     * Read a prefix of a file as lowercase hex.
     *
     * @param string $path  Absolute path.
     * @param int    $bytes Number of bytes.
     * @return string
     */
    private static function read_file_prefix_hex($path, $bytes = 12) {
        $path = (string) $path;
        if ($path === '' || !is_readable($path)) {
            return '';
        }

        $handle = @fopen($path, 'rb');
        if (!$handle) {
            return '';
        }

        $prefix = (string) fread($handle, max(1, (int) $bytes));
        fclose($handle);

        return bin2hex($prefix);
    }

    /**
     * Check the local file magic bytes for JPEG.
     *
     * @param string $path Absolute path.
     * @return bool
     */
    private static function file_starts_with_jpeg_magic($path) {
        $path = (string) $path;
        if ($path === '' || !is_readable($path)) {
            return false;
        }

        $handle = @fopen($path, 'rb');
        if (!$handle) {
            return false;
        }

        $prefix = (string) fread($handle, 3);
        fclose($handle);

        return strncmp($prefix, "\xFF\xD8\xFF", 3) === 0;
    }

    /**
     * @param string $content_type HTTP content type.
     * @return bool
     */
    private static function is_image_content_type($content_type) {
        $content_type = self::normalize_content_type($content_type);
        if ($content_type === '') {
            return false;
        }

        return strpos($content_type, 'image/') === 0;
    }

    /**
     * @param string $mime MIME type.
     * @return string
     */
    private static function guess_extension_from_mime($mime) {
        $map = [
            'image/jpeg' => 'jpg',
            'image/png'  => 'png',
            'image/webp' => 'webp',
            'image/gif'  => 'gif',
        ];

        $mime = strtolower((string) $mime);
        return $map[$mime] ?? 'jpg';
    }
}
