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

trait Calliope_SP_Master_Gutenberg {
    private function get_gutenberg_supported_post_types() {
        $post_types = apply_filters('calliope_sp_gutenberg_post_types', ['post', 'page']);
        if (!is_array($post_types)) {
            return ['post', 'page'];
        }

        $sanitized = [];
        foreach ($post_types as $post_type) {
            $post_type = sanitize_key((string) $post_type);
            if ($post_type !== '') {
                $sanitized[] = $post_type;
            }
        }

        if (empty($sanitized)) {
            return ['post', 'page'];
        }

        return array_values(array_unique($sanitized));
    }

    private function get_gutenberg_destination_choices() {
        $choices = [
            ['key' => 'bsky', 'label' => 'Bluesky'],
            ['key' => 'mastodon', 'label' => 'Mastodon'],
            ['key' => 'tumblr', 'label' => 'Tumblr'],
            ['key' => 'pinterest', 'label' => 'Pinterest'],
            ['key' => 'threads', 'label' => 'Threads'],
            ['key' => 'instagram', 'label' => 'Instagram'],
            ['key' => 'linkedin', 'label' => 'LinkedIn'],
            ['key' => 'blogger', 'label' => 'Blogger'],
            ['key' => 'telegram', 'label' => 'Telegram'],
            ['key' => 'facebook', 'label' => 'Facebook Page'],
        ];

        $choices = apply_filters('calliope_sp_destination_choices', $choices);
        if (!is_array($choices)) {
            return [];
        }

        $normalized = [];
        $seen = [];
        foreach ($choices as $choice) {
            if (!is_array($choice)) {
                continue;
            }

            $key = sanitize_key((string) ($choice['key'] ?? ''));
            $label = sanitize_text_field((string) ($choice['label'] ?? ''));
            if ($key === '' || $label === '' || isset($seen[$key])) {
                continue;
            }

            $seen[$key] = true;
            $normalized[] = ['key' => $key, 'label' => $label];
        }

        return $normalized;
    }

    private function get_gutenberg_allowed_destinations() {
        $keys = [];
        foreach ($this->get_gutenberg_destination_choices() as $destination) {
            if (!empty($destination['key'])) {
                $keys[] = sanitize_key((string) $destination['key']);
            }
        }

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

    private function get_publish_checkbox_map() {
        $map = [
            'post_to_bsky'      => 'bsky',
            'post_to_mastodon'  => 'mastodon',
            'post_to_tumblr'    => 'tumblr',
            'post_to_pinterest' => 'pinterest',
            'post_to_threads'   => 'threads',
            'post_to_instagram' => 'instagram',
            'post_to_linkedin'  => 'linkedin',
            'post_to_blogger'   => 'blogger',
            'post_to_telegram'  => 'telegram',
            'post_to_facebook'  => 'facebook',
        ];

        $map = apply_filters('calliope_sp_publish_checkbox_map', $map);
        if (!is_array($map)) {
            return [];
        }

        $normalized = [];
        foreach ($map as $field => $network) {
            $field = sanitize_key((string) $field);
            $network = sanitize_key((string) $network);
            if ($field !== '' && $network !== '') {
                $normalized[$field] = $network;
            }
        }

        return $normalized;
    }

    private function get_gutenberg_autopost_done_meta_key() {
        return '_calliope_sp_autopost_done';
    }

    private function get_gutenberg_autopost_pending_meta_key() {
        return '_calliope_sp_autopost_pending';
    }

    public function register_gutenberg_meta() {
        $post_types = $this->get_gutenberg_supported_post_types();
        static $rest_hooks_registered = [];

        foreach ($post_types as $pt) {
            register_post_meta($pt, 'calliope_sp_gb_enabled', [
                'type'              => 'boolean',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'boolean',
                        'default' => false,
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_boolean_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => false,
            ]);

            register_post_meta($pt, 'calliope_sp_gb_title', [
                'type'              => 'string',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_text_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => '',
            ]);

            register_post_meta($pt, 'calliope_sp_gb_text', [
                'type'              => 'string',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_textarea_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => '',
            ]);

            register_post_meta($pt, 'calliope_sp_gb_hashtags', [
                'type'              => 'string',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_hashtags_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => '',
            ]);

            register_post_meta($pt, 'calliope_sp_gb_destinations', [
                'type'              => 'array',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'array',
                        'default' => [],
                        'items'   => [
                            'type' => 'string',
                        ],
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_destinations_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => [],
            ]);

            register_post_meta($pt, 'calliope_sp_gb_media_mode', [
                'type'              => 'string',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'string',
                        'default' => 'single_image',
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_media_mode_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => 'single_image',
            ]);

            register_post_meta($pt, 'calliope_sp_gb_media_ids', [
                'type'              => 'array',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'array',
                        'default' => [],
                        'items'   => ['type' => 'integer'],
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_media_ids_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => [],
            ]);

            register_post_meta($pt, 'calliope_sp_gb_external_metadata', [
                'type'              => 'string',
                'single'            => true,
                'show_in_rest'      => ['schema' => ['type' => 'string', 'default' => '']],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_external_metadata_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => '',
            ]);

            register_post_meta($pt, 'calliope_sp_gb_posted', [
                'type'              => 'boolean',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'boolean',
                        'default' => false,
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_boolean_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => false,
            ]);

            register_post_meta($pt, 'calliope_sp_gb_last_log', [
                'type'              => 'string',
                'single'            => true,
                'show_in_rest'      => [
                    'schema' => [
                        'type'    => 'string',
                        'default' => '',
                    ],
                ],
                'sanitize_callback' => [$this, 'sanitize_gutenberg_last_log_meta'],
                'auth_callback'     => [$this, 'gutenberg_meta_auth_callback'],
                'default'           => '',
            ]);

            if (empty($rest_hooks_registered[$pt])) {
                add_action("rest_after_insert_{$pt}", [$this, 'maybe_autopost_after_rest_insert'], 10, 3);
                $rest_hooks_registered[$pt] = true;
            }
        }
    }

    public function gutenberg_meta_auth_callback($allowed, $meta_key, $post_id, $user_id, $cap, $caps) {
        $post_id = (int) $post_id;
        if ($post_id > 0) {
            return current_user_can('edit_post', $post_id);
        }

        return current_user_can('edit_posts');
    }

    public function sanitize_gutenberg_boolean_meta($value) {
        if (function_exists('rest_sanitize_boolean')) {
            return (bool) rest_sanitize_boolean($value);
        }

        if (is_bool($value)) {
            return $value;
        }

        $value = strtolower(trim((string) $value));
        return in_array($value, ['1', 'true', 'yes', 'on'], true);
    }

    public function sanitize_gutenberg_text_meta($value) {
        return sanitize_text_field((string) $value);
    }

    public function sanitize_gutenberg_textarea_meta($value) {
        return sanitize_textarea_field((string) $value);
    }

    public function sanitize_gutenberg_hashtags_meta($value) {
        $value = sanitize_text_field((string) $value);
        if ($value === '') {
            return '';
        }

        if (class_exists('Calliope_SP_Publish_Helper')) {
            return (string) Calliope_SP_Publish_Helper::format_hashtags($value);
        }

        return $value;
    }

    public function sanitize_gutenberg_destinations_meta($value) {
        if (is_string($value)) {
            $decoded = json_decode($value, true);
            if (is_array($decoded)) {
                $value = $decoded;
            } else {
                $value = preg_split('/[\s,;]+/', $value);
            }
        }

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

        $allowed = $this->get_gutenberg_allowed_destinations();
        $sanitized = [];

        foreach ($value as $item) {
            $item = sanitize_key((string) $item);
            if ($item !== '' && in_array($item, $allowed, true)) {
                $sanitized[] = $item;
            }
        }

        return array_values(array_unique($sanitized));
    }

    public function sanitize_gutenberg_media_mode_meta($value) {
        $mode = sanitize_key((string) $value);
        return in_array($mode, ['single_image', 'multiple_images', 'video', 'none'], true)
            ? $mode
            : 'single_image';
    }

    public function sanitize_gutenberg_media_ids_meta($value) {
        if (!is_array($value)) {
            return [];
        }

        // Preserve an existing selection when Pro is temporarily unavailable.
        // The Free/Pro bridge and every Pro execution gate still ignore these IDs
        // unless the add-on is compatible and the license is usable.
        $ids = [];
        foreach ($value as $item) {
            $id = absint($item);
            if ($id > 0 && !in_array($id, $ids, true)) {
                $ids[] = $id;
            }
            if (count($ids) >= 10) {
                break;
            }
        }
        return $ids;
    }

    public function sanitize_gutenberg_external_metadata_meta($value) {
        if (is_array($value)) { $value = wp_json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); }
        if (!is_string($value)) { return ''; }
        $decoded = json_decode($value, true);
        if (!is_array($decoded)) { return ''; }
        $clean = class_exists('Calliope_SP_Extensions')
            ? Calliope_SP_Extensions::sanitize_metadata_value($decoded)
            : [];
        if (!is_array($clean)) { return ''; }
        $encoded = wp_json_encode($clean, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        return is_string($encoded) ? substr($encoded, 0, 10000) : '';
    }

    public function sanitize_gutenberg_last_log_meta($value) {
        if (is_array($value)) {
            $value = wp_json_encode($value, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        }

        if (!is_string($value)) {
            return '';
        }

        $value = trim($value);
        if ($value === '') {
            return '';
        }

        if (function_exists('mb_substr')) {
            return mb_substr($value, 0, 20000);
        }

        return substr($value, 0, 20000);
    }

    public function enqueue_editor_assets() {
        if (!current_user_can('edit_posts')) {
            return;
        }

        if (function_exists('get_current_screen')) {
            $screen = get_current_screen();
            if ($screen && !in_array($screen->base, ['post', 'post-new'], true)) {
                return;
            }
        }

        wp_enqueue_media();

        $handle = 'calliope-social-publisher-editor';
        $src    = CALLIOPE_SP_URL . 'assets/js/editor-sidebar.js';
        $deps   = ['wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data', 'wp-api-fetch', 'wp-i18n', 'wp-block-editor', 'wp-core-data'];
        $version = method_exists($this, 'get_asset_version') ? $this->get_asset_version('assets/js/editor-sidebar.js') : CALLIOPE_SP_VERSION;
        wp_enqueue_script($handle, $src, $deps, $version, true);

        $style_handle  = 'calliope-social-publisher-editor-style';
        $style_src     = CALLIOPE_SP_URL . 'assets/css/editor-sidebar.css';
        $style_version = method_exists($this, 'get_asset_version') ? $this->get_asset_version('assets/css/editor-sidebar.css') : CALLIOPE_SP_VERSION;
        wp_enqueue_style($style_handle, $style_src, ['wp-components'], $style_version);

        if (function_exists('wp_set_script_translations')) {
            wp_set_script_translations($handle, 'calliope-social-publisher', CALLIOPE_SP_PATH . 'languages');
        }

        $editor_config = [
            'supportedPostTypes' => $this->get_gutenberg_supported_post_types(),
            'metaKeys'           => [
                'enabled'      => 'calliope_sp_gb_enabled',
                'title'        => 'calliope_sp_gb_title',
                'text'         => 'calliope_sp_gb_text',
                'hashtags'     => 'calliope_sp_gb_hashtags',
                'destinations' => 'calliope_sp_gb_destinations',
                'posted'       => 'calliope_sp_gb_posted',
                'lastLog'      => 'calliope_sp_gb_last_log',
                'mediaMode'    => 'calliope_sp_gb_media_mode',
                'mediaIds'     => 'calliope_sp_gb_media_ids',
                'externalMetadata' => 'calliope_sp_gb_external_metadata',
            ],
            'i18n'               => [
                'alreadyAttempted' => __('Automatic publishing already ran for this publish cycle. To publish again automatically, move the post out of publish and publish it again.', 'calliope-social-publisher'),
                'enabled'          => __('Publish to social networks when this post is published', 'calliope-social-publisher'),
                'title'            => __('Title (optional)', 'calliope-social-publisher'),
                'content'          => __('Content (optional)', 'calliope-social-publisher'),
                'contentHelp'      => __('If left empty, the excerpt will be used.', 'calliope-social-publisher'),
                'hashtags'         => __('Hashtags', 'calliope-social-publisher'),
                'hashtagsHelp'     => __('You can separate hashtags with spaces, commas, semicolons, or line breaks. They will be normalized automatically.', 'calliope-social-publisher'),
                'normalizedTags'   => __('Normalized preview:', 'calliope-social-publisher'),
                'destinations'     => __('Destinations', 'calliope-social-publisher'),
                'pluginName'       => __('Social Publisher', 'calliope-social-publisher'),
                'partialFailure'   => __('Some destinations failed during the last automatic publishing attempt. Review the plugin logs or use the manual publisher if you want to retry.', 'calliope-social-publisher'),
                'completedSuccess' => __('Social Publisher already published this post automatically to the selected destinations.', 'calliope-social-publisher'),
                'media'             => __('Media', 'calliope-social-publisher'),
                'oneImage'          => __('One image', 'calliope-social-publisher'),
                'multipleImages'    => __('Multiple images / Carousel', 'calliope-social-publisher'),
                'video'             => __('Video', 'calliope-social-publisher'),
                'proRequired'       => __('Requires Pro and an active license', 'calliope-social-publisher'),
                'selectImages'      => __('Select images', 'calliope-social-publisher'),
                'selectVideo'       => __('Select video', 'calliope-social-publisher'),
                'replaceVideo'      => __('Replace video', 'calliope-social-publisher'),
                'remove'            => __('Remove', 'calliope-social-publisher'),
                'moveUp'            => __('Move up', 'calliope-social-publisher'),
                'moveDown'          => __('Move down', 'calliope-social-publisher'),
            ],
            'destinations'        => $this->get_gutenberg_destination_choices(),
            'features'            => class_exists('Calliope_SP_Extensions') ? Calliope_SP_Extensions::get_feature_registry() : [],
            'proStatus'           => class_exists('Calliope_SP_Extensions') ? Calliope_SP_Extensions::get_pro_status() : [],
            'mediaCapabilities'   => class_exists('Calliope_SP_Extensions') ? Calliope_SP_Extensions::get_media_network_capabilities() : [],
        ];
        $editor_config = apply_filters('calliope_sp_editor_config', $editor_config);
        wp_add_inline_script($handle, 'window.CALLIOPE_SP_EDITOR = ' . wp_json_encode($editor_config) . ';', 'before');
    }

    public function maybe_autopost_from_gutenberg($new_status, $old_status, $post) {
        if (!$this->is_supported_gutenberg_post($post)) {
            return;
        }

        if ($new_status === 'publish' && $old_status !== 'publish') {
            if (defined('REST_REQUEST') && REST_REQUEST) {
                update_post_meta($post->ID, $this->get_gutenberg_autopost_pending_meta_key(), 1);
                return;
            }

            $this->maybe_run_gutenberg_autopost($post, 'transition_post_status', [
                'old_status' => sanitize_key((string) $old_status),
                'new_status' => sanitize_key((string) $new_status),
            ]);
            return;
        }

        if ($old_status === 'publish' && $new_status !== 'publish') {
            $this->reset_gutenberg_autopost_state($post->ID, true);
        }
    }

    public function maybe_autopost_after_rest_insert($post, $request, $creating) {
        if (!$this->is_supported_gutenberg_post($post)) {
            return;
        }

        $process_deferred_update = (bool) apply_filters('calliope_sp_should_process_gutenberg_update', false, $post, $request, $creating);
        if ($post->post_status !== 'publish' && !$process_deferred_update) {
            delete_post_meta($post->ID, $this->get_gutenberg_autopost_pending_meta_key());
            return;
        }

        $pending = (bool) get_post_meta($post->ID, $this->get_gutenberg_autopost_pending_meta_key(), true);
        if (!$pending && !$process_deferred_update) {
            return;
        }

        $this->maybe_run_gutenberg_autopost($post, 'rest_after_insert', [
            'creating' => !empty($creating),
        ]);
    }

    private function is_supported_gutenberg_post($post) {
        if (!($post instanceof WP_Post)) {
            return false;
        }

        if (wp_is_post_revision($post->ID) || wp_is_post_autosave($post->ID)) {
            return false;
        }

        return in_array($post->post_type, $this->get_gutenberg_supported_post_types(), true);
    }

    private function reset_gutenberg_autopost_state($post_id, $clear_ui_state = false) {
        $post_id = (int) $post_id;
        if ($post_id <= 0) {
            return;
        }

        delete_post_meta($post_id, $this->get_gutenberg_autopost_done_meta_key());
        delete_post_meta($post_id, $this->get_gutenberg_autopost_pending_meta_key());

        if ($clear_ui_state) {
            update_post_meta($post_id, 'calliope_sp_gb_posted', false);
            update_post_meta($post_id, 'calliope_sp_gb_last_log', '');
        }
    }

    private function maybe_run_gutenberg_autopost($post, $source, array $context = []) {
        if (!$this->is_supported_gutenberg_post($post)) {
            return;
        }

        $post_id = (int) $post->ID;
        $done_key = $this->get_gutenberg_autopost_done_meta_key();
        $pending_key = $this->get_gutenberg_autopost_pending_meta_key();

        $already_done = (bool) get_post_meta($post_id, $done_key, true);
        $deferred_refresh = (bool) apply_filters('calliope_sp_allow_deferred_gutenberg_refresh', false, $post, $source, $context);
        if ($already_done && !$deferred_refresh) {
            delete_post_meta($post_id, $pending_key);
            return;
        }

        $enabled = (bool) get_post_meta($post_id, 'calliope_sp_gb_enabled', true);
        if (!$enabled) {
            delete_post_meta($post_id, $pending_key);
            return;
        }

        $destinations = $this->sanitize_gutenberg_destinations_meta(get_post_meta($post_id, 'calliope_sp_gb_destinations', true));
        if (empty($destinations)) {
            $report = $this->build_publish_error_report(
                __('You did not select any destination.', 'calliope-social-publisher'),
                'no_destinations',
                false,
                'gutenberg'
            );

            $this->log_publish_report('gutenberg', [], $report, array_merge($context, [
                'post_id' => $post_id,
                'source'  => sanitize_key((string) $source),
            ]));
            $this->store_gutenberg_autopost_report($post_id, $report);
            return;
        }

        // Mark this publish cycle as already attempted before contacting external APIs.
        // This prevents duplicate social posts if the admin request times out, is retried,
        // or Gutenberg fires more than one publish-related hook for the same post.
        if (!$already_done && !add_post_meta($post_id, $done_key, current_time('mysql'), true)) {
            delete_post_meta($post_id, $pending_key);
            return;
        }
        delete_post_meta($post_id, $pending_key);

        $payload = $this->build_gutenberg_publish_payload($post);
        $deferred = apply_filters('calliope_sp_maybe_defer_gutenberg_publication', null, $post, $payload, $destinations, $source, $context);
        if (is_wp_error($deferred)) {
            $error_data = $deferred->get_error_data();
            $preserve_done = is_array($error_data) && !empty($error_data['preserve_done']);
            if (!$preserve_done) {
                delete_post_meta($post_id, $done_key);
            }
            delete_post_meta($post_id, $pending_key);
            $report = $this->build_publish_error_report($deferred->get_error_message(), (string) $deferred->get_error_code(), false, 'gutenberg');
            $this->store_gutenberg_autopost_report($post_id, $report);
            if (!$preserve_done) {
                delete_post_meta($post_id, $done_key);
            }
            return;
        }
        if (is_array($deferred) && !empty($deferred['reset_done'])) {
            delete_post_meta($post_id, $done_key);
        }
        if (is_array($deferred) && !empty($deferred['handled'])) {
            delete_post_meta($post_id, $pending_key);
            return;
        }
        $queued_destinations = apply_filters('calliope_sp_force_queue_destinations', [], $destinations, $payload, 'gutenberg');
        $queued_destinations = array_values(array_intersect($destinations, $this->sanitize_gutenberg_destinations_meta($queued_destinations)));
        $direct_destinations = array_values(array_diff($destinations, $queued_destinations));
        $combined_results = [];

        if ($direct_destinations !== []) {
            $direct_report = $this->run_publish_pipeline($payload, $direct_destinations, 'gutenberg', array_merge($context, [
                'post_id' => $post_id,
                'source'  => sanitize_key((string) $source),
            ]));
            if (is_wp_error($direct_report)) {
                $direct_report = $this->build_publish_error_report($direct_report->get_error_message(), (string) $direct_report->get_error_code(), false, 'gutenberg');
            }
            $combined_results = is_array($direct_report['results'] ?? null) ? $direct_report['results'] : [];
        }

        if ($queued_destinations !== []) {
            $queued_payload = apply_filters('calliope_sp_prepare_publish_payload', $payload, $queued_destinations, 'gutenberg');
            if (!is_array($queued_payload)) {
                $report = $this->build_publish_error_report(__('The queued provider payload could not be prepared safely.', 'calliope-social-publisher'), 'invalid_extension_payload', false, 'gutenberg');
            } else {
                if (class_exists('Calliope_SP_Extensions')) { $queued_payload = Calliope_SP_Extensions::sanitize_publish_payload($queued_payload); }
                $fingerprint = $this->build_manual_publish_fingerprint($queued_payload, $queued_destinations);
                $job_id = $this->queue_manual_publication($queued_payload, $queued_destinations, $fingerprint, [
                    'source'       => sanitize_key((string) $source) ?: 'gutenberg',
                    'trigger_type' => 'automatic',
                    'post_id'      => $post_id,
                    'ai_used'      => false,
                    'user_id'      => get_current_user_id(),
                ]);
                if (is_wp_error($job_id)) {
                    foreach ($queued_destinations as $network) {
                        $combined_results[$network] = ['network'=>$network,'ok'=>false,'error_code'=>$job_id->get_error_code(),'error'=>$job_id->get_error_message()];
                    }
                } else {
                    foreach ($queued_destinations as $network) {
                        $combined_results[$network] = [
                            'network'=>$network,'ok'=>false,'pending'=>true,'error_code'=>'provider_queued','code'=>'provider_queued',
                            'retry_after'=>5,'progress'=>0,'ui_status'=>'warning','short_message'=>__('Upload queued.', 'calliope-social-publisher'),
                        ];
                    }
                }
                $report = class_exists('Calliope_SP_Publish_Helper')
                    ? Calliope_SP_Publish_Helper::build_report($combined_results)
                    : $this->build_publish_report_from_results($combined_results);
                if (!is_wp_error($job_id)) {
                    $report['partial'] = true;
                    $report['notice_type'] = 'warning';
                    $report['headline'] = __('Upload queued', 'calliope-social-publisher');
                    $report['summary'] = __('Media upload queued. Open Social Publisher > Publisher to continue processing.', 'calliope-social-publisher');
                }
            }
        } else {
            $report = class_exists('Calliope_SP_Publish_Helper')
                ? Calliope_SP_Publish_Helper::build_report($combined_results)
                : $this->build_publish_report_from_results($combined_results);
        }

        if (is_wp_error($report)) {
            $report = $this->build_publish_error_report($report->get_error_message(), (string) $report->get_error_code(), false, 'gutenberg');
            $this->log_publish_report('gutenberg', $destinations, $report, array_merge($context, [
                'post_id' => $post_id,
                'source'  => sanitize_key((string) $source),
            ]));
        }

        $this->store_gutenberg_autopost_report($post_id, $report);
    }

    private function build_gutenberg_publish_payload($post) {
        $title = trim((string) get_post_meta($post->ID, 'calliope_sp_gb_title', true));
        if ($title === '') {
            $title = get_the_title($post);
        }

        $content = trim((string) get_post_meta($post->ID, 'calliope_sp_gb_text', true));
        if ($content === '') {
            $content = wp_strip_all_tags((string) get_the_excerpt($post));
        }
        if ($content === '') {
            $content = wp_trim_words(wp_strip_all_tags((string) $post->post_content), 40, '...');
        }

        $hashtags = trim((string) get_post_meta($post->ID, 'calliope_sp_gb_hashtags', true));
        $image_b64 = $this->sanitize_image_data($this->get_featured_image_b64($post->ID));
        $media_mode = $this->sanitize_gutenberg_media_mode_meta(get_post_meta($post->ID, 'calliope_sp_gb_media_mode', true));
        $media_ids = $this->sanitize_gutenberg_media_ids_meta(get_post_meta($post->ID, 'calliope_sp_gb_media_ids', true));
        $single_attachment_id = (int) get_post_thumbnail_id($post->ID);
        $external_metadata = [];
        $external_raw = (string) get_post_meta($post->ID, 'calliope_sp_gb_external_metadata', true);
        if ($external_raw !== '') {
            $decoded = json_decode($external_raw, true);
            if (is_array($decoded)) {
                $external_metadata = $decoded;
            }
        }

        return [
            'title'        => $title,
            'content'      => $content,
            'link'         => get_permalink($post),
            'hashtags'     => $hashtags,
            'alt_text'     => $this->get_featured_image_alt_text($post->ID),
            'image_b64'    => $image_b64,
            'image_format' => 'original',
            'media_mode'   => $media_mode,
            'media_ids'    => $media_ids,
            'single_image_attachment_id' => $single_attachment_id,
            'metadata'     => $external_metadata,
        ];
    }

    private function get_featured_image_alt_text($post_id) {
        $thumb_id = (int) get_post_thumbnail_id($post_id);
        if ($thumb_id <= 0) {
            return '';
        }

        return sanitize_text_field((string) get_post_meta($thumb_id, '_wp_attachment_image_alt', true));
    }

    private function store_gutenberg_autopost_report($post_id, array $report) {
        $post_id = (int) $post_id;
        if ($post_id <= 0) {
            return;
        }

        $report['attempted_at'] = current_time('mysql');

        update_post_meta($post_id, 'calliope_sp_gb_posted', !empty($report['all_ok']));
        update_post_meta($post_id, 'calliope_sp_gb_last_log', wp_json_encode($report, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
        update_post_meta($post_id, $this->get_gutenberg_autopost_done_meta_key(), 1);
        delete_post_meta($post_id, $this->get_gutenberg_autopost_pending_meta_key());
    }

    private function run_publish_pipeline(array $payload, array $selected, $context = 'publisher', array $context_data = []) {
        $selected = $this->sanitize_gutenberg_destinations_meta($selected);
        if (empty($selected)) {
            return new WP_Error('no_destinations', __('You did not select any destination.', 'calliope-social-publisher'));
        }

        $payload = apply_filters('calliope_sp_prepare_publish_payload', $payload, $selected, sanitize_key((string) $context));
        if (!is_array($payload)) {
            return new WP_Error('invalid_extension_payload', __('The publication data could not be prepared safely.', 'calliope-social-publisher'));
        }
        if (class_exists('Calliope_SP_Extensions')) {
            $payload = Calliope_SP_Extensions::sanitize_publish_payload($payload);
        }

        $results = [];
        $history_context = [
            'publication_uuid' => class_exists('Calliope_SP_History') ? Calliope_SP_History::new_publication_uuid() : wp_generate_uuid4(),
            'source'           => sanitize_key((string) ($context_data['source'] ?? $context)),
            'trigger_type'     => sanitize_key((string) $context) === 'gutenberg' ? 'automatic' : 'manual',
            'post_id'          => absint($context_data['post_id'] ?? 0),
            'ai_used'          => !empty($context_data['ai_used']),
            'user_id'          => absint($context_data['user_id'] ?? get_current_user_id()),
        ];

        foreach ($selected as $network) {
            $network = sanitize_key((string) $network);
            if ($network === '') {
                continue;
            }

            $results[$network] = $this->run_single_network_publish($network, $payload, $history_context);
        }

        $report = class_exists('Calliope_SP_Publish_Helper')
            ? Calliope_SP_Publish_Helper::build_report($results)
            : $this->build_publish_report_from_results($results);

        foreach ($report['results'] as $network => $network_result) {
            $status = (($network_result['ui_status'] ?? '') === 'warning')
                ? 'warning'
                : (!empty($network_result['ok']) ? 'success' : 'error');
            Calliope_SP_Logger::log(
                $network,
                $status,
                wp_json_encode($network_result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
            );
        }

        $this->log_publish_report($context, $selected, $report, $context_data);

        return $report;
    }


    private function run_single_network_publish($network, array $payload, array $history_context = []) {
        $network = sanitize_key((string) $network);
        if (class_exists('Calliope_SP_Tracking')) {
            $payload = Calliope_SP_Tracking::apply_to_payload($payload, $network, $history_context);
        }
        if (class_exists('Calliope_SP_Short_Link_Service')) {
            $payload = Calliope_SP_Short_Link_Service::apply_to_payload($payload, $network, $history_context);
        }
        /**
         * Allow compatible add-ons to prepare network-specific copy after UTM
         * tracking has resolved the final post link but before Social Templates
         * render. Free remains unaware of any Pro-only feature implementation.
         *
         * @param array<string,mixed> $payload
         * @param string              $network
         * @param array<string,mixed> $history_context
         */
        $prepared_payload = apply_filters('calliope_sp_prepare_network_template_payload', $payload, $network, $history_context);
        if (is_array($prepared_payload)) {
            $payload = $prepared_payload;
        }
        if (class_exists('Calliope_SP_Social_Templates')) {
            $payload = Calliope_SP_Social_Templates::apply_to_payload($payload, $network, $history_context);
        }
        $network_fingerprint = $this->build_network_publish_fingerprint($network, $payload);
        $history_id = absint($history_context['history_id'] ?? 0);
        if (class_exists('Calliope_SP_History') && $history_id <= 0) {
            $history_id = Calliope_SP_History::ensure_pending_row($network, $payload, $history_context);
        }

        if (!$this->reserve_network_publish_attempt($network, $network_fingerprint)) {
            if (class_exists('Calliope_SP_Logger')) {
                Calliope_SP_Logger::log(
                    $network,
                    'warning',
                    wp_json_encode([
                        'event' => 'network_duplicate_guard_blocked',
                        'network' => $network,
                        'duplicate_key' => substr((string) $network_fingerprint, 0, 12),
                        'media_mode' => sanitize_key((string) ($payload['media_mode'] ?? 'none')),
                        'media_count' => isset($payload['media']) && is_array($payload['media']) ? count($payload['media']) : 0,
                        'video_source' => isset($payload['metadata']) && is_array($payload['metadata']) ? sanitize_key((string) ($payload['metadata']['video_source'] ?? '')) : '',
                        'youtube_link_set' => isset($payload['metadata']) && is_array($payload['metadata']) && !empty($payload['metadata']['youtube_link_url']),
                    ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
                );
            }
            $duplicate_result = [
                'network'       => $network,
                'ok'            => false,
                'skipped'       => true,
                'ui_status'     => 'warning',
                'error_code'    => 'duplicate_skipped',
                'duplicate_key' => substr((string) $network_fingerprint, 0, 12),
                'message'       => __('Skipped because this network already received the same publication recently.', 'calliope-social-publisher'),
                'short_message' => __('skipped duplicate', 'calliope-social-publisher'),
                'user_message'  => __('This network was skipped because the same publication was already sent or started recently. It was not counted as published.', 'calliope-social-publisher'),
            ];
            if (class_exists('Calliope_SP_History') && $history_id > 0) {
                Calliope_SP_History::record_result($history_id, $network, $payload, $duplicate_result, $history_context);
            }
            return $duplicate_result;
        }

        try {
            $this->log_network_payload_before_publish($network, $payload);
            $network_result = $this->normalize_publish_result($network, $this->publish_one($network, $payload));

            if (!empty($network_result['ok']) && empty($network_result['pending']) && $this->network_requires_video_confirmation($network, $payload) && !$this->network_result_confirms_video($network, $network_result)) {
                $this->release_network_publish_attempt($network, $network_fingerprint);
                if (class_exists('Calliope_SP_Logger')) {
                    Calliope_SP_Logger::log(
                        $network,
                        'error',
                        wp_json_encode([
                            'event' => 'video_publish_unconfirmed',
                            'network' => $network,
                            'media_mode' => sanitize_key((string) ($payload['media_mode'] ?? 'none')),
                            'result_keys' => array_values(array_map('sanitize_key', array_keys($network_result))),
                            'media_type' => sanitize_key((string) ($network_result['media_type'] ?? '')),
                            'status' => sanitize_key((string) ($network_result['status'] ?? '')),
                        ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
                    );
                }
                $network_result = [
                    'network' => $network,
                    'ok' => false,
                    'error_code' => 'video_publish_not_confirmed',
                    'error' => __('The network accepted the request but did not confirm that a video was attached. It was not counted as a video publication.', 'calliope-social-publisher'),
                    'ui_status' => 'error',
                    'media_type' => 'video',
                    'retryable' => true,
                ];
            }

            if (!empty($network_result['ok']) || !empty($network_result['needs_review'])) {
                $this->mark_network_publish_success($network, $network_fingerprint, $network_result);
            } else {
                $this->release_network_publish_attempt($network, $network_fingerprint);
            }

            if (class_exists('Calliope_SP_History') && $history_id > 0) {
                Calliope_SP_History::record_result($history_id, $network, $payload, $network_result, $history_context);
            }

            return $network_result;
        } catch (Throwable $e) {
            $this->release_network_publish_attempt($network, $network_fingerprint);

            $exception_result = [
                'network'    => $network,
                'ok'         => false,
                'error_code' => 'publish_exception',
                'error'      => __('The network publication failed safely.', 'calliope-social-publisher'),
            ];
            if (class_exists('Calliope_SP_History') && $history_id > 0) {
                Calliope_SP_History::record_result($history_id, $network, $payload, $exception_result, $history_context);
            }
            return $exception_result;
        }
    }

    private function network_requires_video_confirmation($network, array $payload) {
        $network = sanitize_key((string) $network);
        $mode = sanitize_key((string) ($payload['media_mode'] ?? 'none'));
        if ($mode !== 'video') {
            return false;
        }

        return in_array($network, [
            'bsky',
            'bluesky',
            'mastodon',
            'tumblr',
            'telegram',
            'facebook',
            'instagram',
            'threads',
            'youtube',
            'youtube_shorts',
        ], true);
    }

    private function network_result_confirms_video($network, array $result) {
        $network = sanitize_key((string) $network);
        if (!empty($result['video_confirmed']) || !empty($result['provider_media_confirmed'])) {
            return true;
        }

        if ($network === 'tumblr') {
            return false;
        }

        if (sanitize_key((string) ($result['media_type'] ?? '')) === 'video') {
            return true;
        }

        foreach (['video_id', 'video_file_id', 'media_id', 'post_uri'] as $key) {
            if (isset($result[$key]) && is_scalar($result[$key]) && trim((string) $result[$key]) !== '') {
                return true;
            }
        }

        return false;
    }

    private function log_network_payload_before_publish($network, array $payload) {
        if (!class_exists('Calliope_SP_Logger')) {
            return;
        }

        $network = sanitize_key((string) $network);
        $media = isset($payload['media']) && is_array($payload['media']) ? $payload['media'] : [];
        $media_mode = sanitize_key((string) ($payload['media_mode'] ?? 'none'));
        if ($media_mode !== 'video' && $media === [] && empty($payload['youtube'])) {
            return;
        }

        $items = [];
        foreach ($media as $item) {
            if (!is_array($item)) {
                continue;
            }
            $items[] = [
                'id' => absint($item['id'] ?? 0),
                'type' => sanitize_key((string) ($item['type'] ?? '')),
                'mime_type' => sanitize_mime_type((string) ($item['mime_type'] ?? '')),
                'position' => absint($item['position'] ?? 0),
                'url_set' => trim((string) ($item['url'] ?? '')) !== '',
                'alt_set' => trim((string) ($item['alt'] ?? '')) !== '',
                'caption_set' => trim((string) ($item['caption'] ?? '')) !== '',
            ];
        }

        $youtube = is_array($payload['youtube'] ?? null) ? $payload['youtube'] : [];
        Calliope_SP_Logger::log(
            $network,
            'info',
            wp_json_encode([
                'event' => 'network_payload_before_publish',
                'network' => $network,
                'publication_type' => sanitize_key((string) ($payload['publication_type'] ?? 'standard')),
                'media_mode' => $media_mode,
                'media_count' => count($items),
                'media' => $items,
                'has_image_b64' => !empty($payload['image_b64']),
                'has_youtube_block' => $youtube !== [],
                'youtube' => $youtube === [] ? [] : [
                    'video_attachment_id' => absint($youtube['video_attachment_id'] ?? 0),
                    'format' => sanitize_key((string) ($youtube['format'] ?? '')),
                    'account_profile_set' => trim((string) ($youtube['account_profile_uuid'] ?? '')) !== '',
                    'made_for_kids_present' => array_key_exists('made_for_kids', $youtube),
                    'made_for_kids' => array_key_exists('made_for_kids', $youtube) ? (bool) $youtube['made_for_kids'] : null,
                    'audience_confirmed' => !empty($youtube['audience_confirmed']),
                    'category_id_set' => trim((string) ($youtube['category_id'] ?? '')) !== '',
                    'privacy_status' => sanitize_key((string) ($youtube['privacy_status'] ?? '')),
                ],
            ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
        );
    }

    private function build_publish_report_from_results(array $results) {
        $all_ok = !array_filter($results, static function ($result) {
            return empty($result['ok']);
        });

        return [
            'all_ok'        => $all_ok,
            'partial'       => false,
            'notice_type'   => $all_ok ? 'success' : 'error',
            'headline'      => $all_ok ? 'Published successfully' : 'Publishing failed',
            'summary'       => '',
            'lines'         => [],
            'results'       => $results,
            'success_count' => count(array_filter($results, static function ($result) {
                return !empty($result['ok']);
            })),
            'failed_count'  => count(array_filter($results, static function ($result) {
                return empty($result['ok']);
            })),
        ];
    }

    private function build_publish_error_report($message, $error_code = 'publish_error', $retryable = false, $result_network = 'publisher') {
        $message = trim((string) $message);
        if ($message === '') {
            $message = __('Unknown error', 'calliope-social-publisher');
        }

        $headline = __('Publishing failed', 'calliope-social-publisher');
        $result_network = sanitize_key((string) $result_network);
        if ($result_network === '') {
            $result_network = 'publisher';
        }

        return [
            'all_ok'        => false,
            'partial'       => false,
            'notice_type'   => 'error',
            'headline'      => $headline,
            'summary'       => $headline . ' | ' . $message,
            'lines'         => [$message],
            'results'       => [
                $result_network => [
                    'network'        => $result_network,
                    'ok'             => false,
                    'error'          => $message,
                    'error_code'     => sanitize_key((string) $error_code),
                    'ui_status'      => 'error',
                    'classification' => strtoupper(sanitize_key((string) $error_code)),
                    'retryable'      => (bool) $retryable,
                    'user_message'   => $message,
                    'short_message'  => $message,
                ],
            ],
            'success_count' => 0,
            'failed_count'  => 1,
        ];
    }

    private function log_publish_report($context, array $selected, array $report, array $context_data = []) {
        $status = (($report['notice_type'] ?? 'error') === 'warning') ? 'warning' : (!empty($report['all_ok']) ? 'success' : 'error');
        $payload = array_merge([
            'destinations' => array_values($selected),
            'all_ok'       => !empty($report['all_ok']),
            'partial'      => !empty($report['partial']),
            'summary'      => (string) ($report['summary'] ?? ''),
            'results'      => isset($report['results']) && is_array($report['results']) ? $report['results'] : [],
        ], $context_data);

        Calliope_SP_Logger::log(
            sanitize_key((string) $context),
            $status,
            wp_json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
        );
    }

    private function get_featured_image_b64($post_id) {
        $thumb_id = get_post_thumbnail_id($post_id);
        if (!$thumb_id) {
            return '';
        }

        $file = get_attached_file($thumb_id);
        if (!$file || !file_exists($file)) {
            return '';
        }

        $mime = get_post_mime_type($thumb_id);
        if (!$mime) {
            $mime = 'image/jpeg';
        }

        $raw = @file_get_contents($file);
        if ($raw === false) {
            return '';
        }

        return 'data:' . $mime . ';base64,' . base64_encode($raw);
    }

    private function normalize_publish_result($network, $result) {
        $normalized = [
            'network' => (string) $network,
            'ok'      => false,
        ];

        if (is_wp_error($result)) {
            $normalized['error'] = $result->get_error_message();
            $normalized['error_code'] = $result->get_error_code();
            $data = $result->get_error_data();
            if ($data !== null && $data !== '') {
                $normalized['data'] = $data;
                if (is_array($data)) {
                    if (array_key_exists('retryable', $data)) $normalized['retryable'] = (bool) $data['retryable'];
                    foreach (['failure_kind', 'phase'] as $safe_key) {
                        if (isset($data[$safe_key]) && is_scalar($data[$safe_key])) $normalized[$safe_key] = sanitize_key((string) $data[$safe_key]);
                    }
                    if (isset($data['http_code']) && is_scalar($data['http_code'])) $normalized['http_code'] = absint($data['http_code']);
                }
            }
            return $normalized;
        }

        if ($result === true || $result === null) {
            $normalized['ok'] = true;
            return $normalized;
        }

        if ($result === false) {
            $normalized['error'] = 'Unknown error';
            return $normalized;
        }

        if (is_array($result)) {
            $normalized['ok'] = isset($result['ok']) ? (bool) $result['ok'] : true;
            foreach ($result as $key => $value) {
                if (is_object($value)) {
                    continue;
                }
                $normalized[$key] = $value;
            }
            if (empty($normalized['ok']) && empty($normalized['pending']) && empty($normalized['error'])) {
                $normalized['error'] = 'Unknown error';
            }
            return $normalized;
        }

        $normalized['ok'] = true;
        $normalized['result'] = is_scalar($result) ? $result : 'non_scalar_result';
        return $normalized;
    }

    private function publish_one($network, $payload) {
        $network = sanitize_key((string) $network);
        $preflight = apply_filters('calliope_sp_preflight_network_publish', true, $network, (array) $payload);
        if (is_wp_error($preflight)) {
            return $preflight;
        }
        if ($preflight !== true) {
            return new WP_Error('calliope_sp_media_network_not_supported', __('The selected media mode is not supported by this network.', 'calliope-social-publisher'));
        }
        $client_class = $this->resolve_network_client_class($network);

        if (!$client_class || !$this->ensure_network_client_loaded($network, $client_class)) {
            return new WP_Error('unknown_network', 'Unknown network: ' . $network);
        }

        $client = new $client_class();

        if (!method_exists($client, 'publish')) {
            return new WP_Error('invalid_network_client', 'Invalid network client for: ' . $network);
        }

        if (class_exists('Calliope_SP_Publish_Helper')) {
            $payload = Calliope_SP_Publish_Helper::normalize_payload((array) $payload, $network);
        }
        $payload = apply_filters('calliope_sp_network_publish_payload', (array) $payload, $network);
        if (!is_array($payload)) {
            return new WP_Error('invalid_network_payload', __('The network publication payload is invalid.', 'calliope-social-publisher'));
        }

        return $client->publish($payload);
    }

    private function resolve_network_client_class($network) {
        $network = sanitize_key((string) $network);
        $client_class = apply_filters("calliope_sp_network_client_{$network}", null, $network);

        if (is_string($client_class) && $client_class !== '') {
            return $client_class;
        }

        $internal_mapping = [
            'bsky'      => 'Calliope_SP_Bluesky',
            'bluesky'   => 'Calliope_SP_Bluesky',
            'mastodon'  => 'Calliope_SP_Mastodon',
            'tumblr'    => 'Calliope_SP_Tumblr',
            'pinterest' => 'Calliope_SP_Pinterest',
            'threads'   => 'Calliope_SP_Threads',
            'instagram' => 'Calliope_SP_Instagram',
            'linkedin'  => 'Calliope_SP_LinkedIn',
            'blogger'   => 'Calliope_SP_Blogger',
            'telegram'  => 'Calliope_SP_Telegram',
            'facebook'  => 'Calliope_SP_Facebook',
        ];

        return $internal_mapping[$network] ?? null;
    }

    private function ensure_network_client_loaded($network, $client_class) {
        $network = sanitize_key((string) $network);
        $client_class = is_string($client_class) ? trim($client_class) : '';

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

        if (class_exists($client_class, true)) {
            return true;
        }

        $file_mapping = [
            'bsky'      => 'class-bluesky.php',
            'bluesky'   => 'class-bluesky.php',
            'mastodon'  => 'class-mastodon.php',
            'tumblr'    => 'class-tumblr.php',
            'pinterest' => 'class-pinterest.php',
            'threads'   => 'class-threads.php',
            'instagram' => 'class-instagram.php',
            'linkedin'  => 'class-linkedin.php',
            'blogger'   => 'class-blogger.php',
            'telegram'  => 'class-telegram.php',
            'facebook'  => 'class-facebook.php',
        ];

        if (isset($file_mapping[$network])) {
            $file = CALLIOPE_SP_PATH . 'classes/' . $file_mapping[$network];
            if (is_readable($file)) {
                require_once $file;
            }
        }

        return class_exists($client_class, true);
    }
}
