<?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() {
        return [
            ['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'],
        ];
    }

    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() {
        return [
            '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',
        ];
    }

    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_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_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;
            }
        }

        $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'];
        $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);

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

        wp_add_inline_script($handle, 'window.CALLIOPE_SP_EDITOR = ' . wp_json_encode([
            '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',
            ],
            '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'       => __('Calliope 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' => __('Calliope Social Publisher already published this post automatically to the selected destinations.', 'calliope-social-publisher'),
            ],
            'destinations'        => $this->get_gutenberg_destination_choices(),
        ]) . ';', '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;
        }

        if ($post->post_status !== 'publish') {
            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) {
            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();

        if ((bool) get_post_meta($post_id, $done_key, true)) {
            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 (!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);
        $report = $this->run_publish_pipeline($payload, $destinations, 'gutenberg', array_merge($context, [
            'post_id' => $post_id,
            'source'  => sanitize_key((string) $source),
        ]));

        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));

        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',
        ];
    }

    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'));
        }

        $results = [];

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

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

        $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 = !empty($network_result['ok']) ? 'success' : ((($network_result['ui_status'] ?? '') === 'warning') ? 'warning' : '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) {
        $network = sanitize_key((string) $network);
        $network_fingerprint = $this->build_network_publish_fingerprint($network, $payload);

        if (!$this->reserve_network_publish_attempt($network, $network_fingerprint)) {
            return [
                'network'       => $network,
                'ok'            => true,
                'skipped'       => true,
                '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'),
            ];
        }

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

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

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

            return [
                'network' => $network,
                'ok'      => false,
                'error'   => $e->getMessage(),
            ];
        }
    }

    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;
            }
            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['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);
        $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);
        }

        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);
    }
}
