<?php
/**
 * Generic extension and commercial-state bridge for Social Publisher.
 */

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

final class Calliope_SP_Extensions {
    private const PRO_BASENAME = 'calliope-social-publisher-pro/calliope-social-publisher-pro.php';
    private const DESTINATION_FEATURE_ALIASES = [
        'wordpress_remote' => 'remote_wordpress',
    ];

    public static function init(): void {
        add_action('calliope_sp_render_external_destinations', [__CLASS__, 'render_external_destinations']);
        add_action('calliope_sp_render_pro_connection_cards', [__CLASS__, 'render_pro_connection_cards']);
        add_action('calliope_sp_render_pro_media_options', [__CLASS__, 'render_pro_media_options']);
        add_action('calliope_sp_render_publication_type_selector', [__CLASS__, 'render_publication_type_selector'], 5);
    }

    /**
     * Public, secret-free Pro state consumed by the Free UI.
     *
     * @return array{installed:bool,active:bool,license_active:bool,free_compatible:bool,state:string,admin_url:string,connections:array<string,array<string,mixed>>}
     */
    public static function get_pro_status(): array {
        $installed = false;
        $active = defined('CALLIOPE_SPP_VERSION');

        if (is_admin()) {
            if (!function_exists('get_plugins')) {
                $plugin_file = ABSPATH . 'wp-admin/includes/plugin.php';
                if (is_readable($plugin_file)) {
                    require_once $plugin_file;
                }
            }

            if (function_exists('get_plugins')) {
                $installed_plugins = get_plugins();
                $installed = isset($installed_plugins[self::PRO_BASENAME]);
            }
        }

        if ($active) {
            $installed = true;
        }

        $status = [
            'installed'      => $installed,
            'active'         => $active,
            'license_active'  => false,
            'free_compatible' => true,
            'state'           => $active ? 'active_unlicensed' : ($installed ? 'installed_inactive' : 'not_installed'),
            'admin_url'      => $active ? admin_url('admin.php?page=calliope-social-publisher&tab=connections') : '',
            'connections'    => [],
        ];

        $filtered = apply_filters('calliope_sp_pro_public_status', $status);
        if (!is_array($filtered)) {
            return $status;
        }

        return [
            'installed'      => !empty($filtered['installed']),
            'active'         => !empty($filtered['active']),
            'license_active'  => !empty($filtered['license_active']),
            'free_compatible' => !isset($filtered['free_compatible']) || !empty($filtered['free_compatible']),
            'state'           => self::sanitize_commercial_state($filtered['state'] ?? $status['state']),
            'admin_url'      => isset($filtered['admin_url']) ? esc_url_raw((string) $filtered['admin_url']) : $status['admin_url'],
            'connections'    => self::sanitize_public_connections($filtered['connections'] ?? []),
        ];
    }

    /** @return array<string,array<string,mixed>> */
    private static function sanitize_public_connections($connections): array {
        $clean = [];
        foreach (is_array($connections) ? $connections : [] as $slug => $definition) {
            $slug = sanitize_key((string) $slug);
            if ($slug === '' || !is_array($definition)) { continue; }
            $organizations = [];
            foreach (array_slice(is_array($definition['organizations'] ?? null) ? $definition['organizations'] : [], 0, 100) as $organization) {
                if (!is_array($organization)) { continue; }
                $id = trim((string) ($organization['organization_id'] ?? ''));
                if (!preg_match('/^[0-9]+$/D', $id)) { continue; }
                $organizations[] = [
                    'organization_id' => $id,
                    'label' => sanitize_text_field((string) ($organization['label'] ?? '')),
                    'vanity_name' => sanitize_title((string) ($organization['vanity_name'] ?? '')),
                ];
            }
            $locations = [];
            foreach (array_slice(is_array($definition['locations'] ?? null) ? $definition['locations'] : [], 0, 500) as $location) {
                if (!is_array($location)) { continue; }
                $key = sanitize_key((string) ($location['location_key'] ?? ''));
                if (!preg_match('/^[a-f0-9]{32}$/D', $key)) { continue; }
                $locations[] = [
                    'location_key' => $key,
                    'label' => sanitize_text_field((string) ($location['label'] ?? '')),
                    'store_code' => sanitize_text_field((string) ($location['store_code'] ?? '')),
                    'open_status' => sanitize_key((string) ($location['open_status'] ?? '')),
                ];
            }
            $default_organization = trim((string) ($definition['default_organization'] ?? ''));
            $default_location = sanitize_key((string) ($definition['default_location'] ?? ''));
            $clean[$slug] = [
                'configured' => !empty($definition['configured']),
                'configured_count' => absint($definition['configured_count'] ?? 0),
                'admin_url' => esc_url_raw((string) ($definition['admin_url'] ?? '')),
                'username' => sanitize_user((string) ($definition['username'] ?? ''), true),
                'name' => sanitize_text_field((string) ($definition['name'] ?? '')),
                'organizations' => $organizations,
                'default_organization' => preg_match('/^[0-9]+$/D', $default_organization) ? $default_organization : '',
                'locations' => $locations,
                'default_location' => preg_match('/^[a-f0-9]{32}$/D', $default_location) ? $default_location : '',
            ];
        }
        return $clean;
    }

    private static function feature_slug_for_destination(string $destination): string {
        $destination = sanitize_key($destination);
        return self::DESTINATION_FEATURE_ALIASES[$destination] ?? $destination;
    }

    private static function connection_slug_for_destination(string $destination): string {
        return self::feature_slug_for_destination($destination);
    }

    private static function pro_connection_card_icon(string $network): string {
        $network = sanitize_key($network);
        $icons = [
            'discord' => '<svg viewBox="0 0 256 199" width="22" height="22" focusable="false" aria-hidden="true"><path d="M216.9 16.6C200.3 8.8 182.6 3.2 164 0c-2.3 4.1-4.9 9.6-6.8 14-19.7-3-39.1-3-58.5 0-1.8-4.4-4.5-9.9-6.8-14-18.5 3.2-36.3 8.9-52.9 16.6C5.6 67.1-3.4 116.4 1.1 165c22.2 16.5 43.7 26.6 64.8 33.2 5.2-7.2 9.9-14.8 13.9-22.9-7.6-2.9-14.9-6.5-21.8-10.6 1.8-1.4 3.6-2.8 5.4-4.2 42.1 19.7 87.9 19.7 129.5 0 1.8 1.4 3.6 2.8 5.4 4.2-6.9 4.2-14.3 7.8-21.9 10.7 4 8 8.7 15.7 13.9 22.8 21.1-6.6 42.6-16.6 64.8-33.2 5.3-56.3-9.1-105.1-38.1-148.4ZM85.5 135.1c-12.6 0-23-11.8-23-26.2s10.1-26.2 23-26.2c12.9 0 23.2 11.8 23 26.2 0 14.4-10.2 26.2-23 26.2Zm85.1 0c-12.6 0-23-11.8-23-26.2s10.1-26.2 23-26.2c12.9 0 23.2 11.8 23 26.2 0 14.4-10.2 26.2-23 26.2Z" fill="currentColor"/></svg>',
            'remote_wordpress' => '<svg viewBox="44 3.5 32 32" width="22" height="22" focusable="false" aria-hidden="true"><path d="M46.803 19.485c0 5.234 3.007 9.744 7.406 11.86L47.973 14.14c-.724 1.67-1.17 3.452-1.17 5.346zm22.107-.67c0-1.615-.613-2.784-1.114-3.62-.668-1.114-1.28-2.005-1.28-3.118 0-1.225.947-2.34 2.227-2.34h.167c-2.34-2.168-5.458-3.45-8.9-3.45-4.622.056-8.63 2.394-11.025 5.958h.835c1.392 0 3.508-.167 3.508-.167.724-.056.78 1.002.1 1.114 0 0-.724.1-1.503.1l4.8 14.255 2.895-8.63-2.06-5.624-1.392-.1c-.724-.056-.613-1.114.056-1.114 0 0 2.172.167 3.452.167 1.392 0 3.508-.167 3.508-.167.724-.056.78 1.002.1 1.114 0 0-.724.1-1.503.1l4.8 14.2 1.336-4.4c.557-1.838 1.002-3.118 1.002-4.288zm-8.687 1.84l-3.953 11.47c1.17.334 2.45.557 3.73.557 1.56 0 3.007-.278 4.4-.724-.056-.056-.056-.1-.1-.167zm11.36-7.46l.1 1.336c0 1.336-.223 2.84-1.002 4.733l-4 11.637c3.898-2.283 6.57-6.515 6.57-11.415-.056-2.283-.668-4.4-1.67-6.292zM60 4.116c-8.464 0-15.368 6.905-15.368 15.368S51.536 34.853 60 34.853s15.368-6.905 15.368-15.368S68.464 4.116 60 4.116zm0 30.068c-8.074 0-14.644-6.57-14.644-14.7C45.356 11.4 51.926 4.84 60 4.84s14.644 6.57 14.644 14.644c0 8.13-6.57 14.7-14.644 14.7z" fill="currentColor"/></svg>',
            'wordpress_remote' => '<svg viewBox="44 3.5 32 32" width="22" height="22" focusable="false" aria-hidden="true"><path d="M46.803 19.485c0 5.234 3.007 9.744 7.406 11.86L47.973 14.14c-.724 1.67-1.17 3.452-1.17 5.346zm22.107-.67c0-1.615-.613-2.784-1.114-3.62-.668-1.114-1.28-2.005-1.28-3.118 0-1.225.947-2.34 2.227-2.34h.167c-2.34-2.168-5.458-3.45-8.9-3.45-4.622.056-8.63 2.394-11.025 5.958h.835c1.392 0 3.508-.167 3.508-.167.724-.056.78 1.002.1 1.114 0 0-.724.1-1.503.1l4.8 14.255 2.895-8.63-2.06-5.624-1.392-.1c-.724-.056-.613-1.114.056-1.114 0 0 2.172.167 3.452.167 1.392 0 3.508-.167 3.508-.167.724-.056.78 1.002.1 1.114 0 0-.724.1-1.503.1l4.8 14.2 1.336-4.4c.557-1.838 1.002-3.118 1.002-4.288zm-8.687 1.84l-3.953 11.47c1.17.334 2.45.557 3.73.557 1.56 0 3.007-.278 4.4-.724-.056-.056-.056-.1-.1-.167zm11.36-7.46l.1 1.336c0 1.336-.223 2.84-1.002 4.733l-4 11.637c3.898-2.283 6.57-6.515 6.57-11.415-.056-2.283-.668-4.4-1.67-6.292zM60 4.116c-8.464 0-15.368 6.905-15.368 15.368S51.536 34.853 60 34.853s15.368-6.905 15.368-15.368S68.464 4.116 60 4.116zm0 30.068c-8.074 0-14.644-6.57-14.644-14.7C45.356 11.4 51.926 4.84 60 4.84s14.644 6.57 14.644 14.644c0 8.13-6.57 14.7-14.644 14.7z" fill="currentColor"/></svg>',
            'youtube' => '<svg viewBox="0 0 28.57 20" width="22" height="22" focusable="false" aria-hidden="true"><path d="M28 3.1A3.6 3.6 0 0 0 25.4.6C23.2 0 14.3 0 14.3 0S5.4 0 3.1.6A3.6 3.6 0 0 0 .6 3.1C0 5.4 0 10 0 10s0 4.6.6 6.9a3.6 3.6 0 0 0 2.5 2.5c2.3.6 11.2.6 11.2.6s8.9 0 11.1-.6a3.6 3.6 0 0 0 2.6-2.5c.6-2.3.6-6.9.6-6.9s0-4.6-.6-6.9Z" fill="currentColor"/><path d="M11.4 14.3 18.8 10l-7.4-4.3v8.6Z" fill="#fff"/></svg>',
            'custom_webhook' => '<svg viewBox="0 0 48 48" width="22" height="22" focusable="false" aria-hidden="true"><path d="M35,37c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S37.2,37,35,37z" fill="#37474f"/><path d="M35,43c-3,0-5.9-1.4-7.8-3.7l3.1-2.5c1.1,1.4,2.9,2.3,4.7,2.3c3.3,0,6-2.7,6-6s-2.7-6-6-6 c-1,0-2,0.3-2.9,0.7l-1.7,1L23.3,16l3.5-1.9l5.3,9.4c1-0.3,2-0.5,3-0.5c5.5,0,10,4.5,10,10S40.5,43,35,43z" fill="#37474f"/><path d="M14,43C8.5,43,4,38.5,4,33c0-4.6,3.1-8.5,7.5-9.7l1,3.9C9.9,27.9,8,30.3,8,33c0,3.3,2.7,6,6,6 s6-2.7,6-6v-2h15v4H23.8C22.9,39.6,18.8,43,14,43z" fill="#37474f"/><path d="M14,37c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S16.2,37,14,37z" fill="#e91e63"/><path d="M25,19c-2.2,0-4-1.8-4-4s1.8-4,4-4s4,1.8,4,4S27.2,19,25,19z" fill="#37474f"/><path d="M15.7,34L12.3,32l5.9-9.7c-2-1.9-3.2-4.5-3.2-7.3c0-5.5,4.5-10,10-10c5.5,0,10,4.5,10,10 c0,0.9-0.1,1.7-0.3,2.5l-3.9-1c0.1-0.5,0.2-1,0.2-1.5c0-3.3-2.7-6-6-6s-6,2.7-6,6c0,2.1,1.1,4,2.9,5.1l1.7,1L15.7,34z" fill="#e91e63"/></svg>',
        ];

        return $icons[$network] ?? '<svg viewBox="0 0 24 24" width="22" height="22" focusable="false" aria-hidden="true"><circle cx="12" cy="12" r="9" fill="currentColor"/></svg>';
    }

    /**
     * Central feature registry used by Free and extended by Pro.
     *
     * @return array<string,array<string,string>>
     */
    public static function get_feature_registry(): array {
        $features = [
            'discord'                   => ['label' => 'Discord', 'type' => 'network', 'state' => 'locked'],
            'multiple_images'           => ['label' => __('Multiple images / Carousel', 'calliope-social-publisher'), 'type' => 'media', 'state' => 'locked'],
            'carousel'                  => ['label' => __('Carousel publishing', 'calliope-social-publisher'), 'type' => 'media', 'state' => 'locked'],
            'video'                     => ['label' => __('Video', 'calliope-social-publisher'), 'type' => 'media', 'state' => 'locked'],
            'remote_wordpress'          => ['label' => __('Remote WordPress', 'calliope-social-publisher'), 'type' => 'network', 'state' => 'locked'],
            'youtube'                   => ['label' => 'YouTube', 'type' => 'network', 'state' => 'locked'],
            'youtube_shorts'            => ['label' => 'YouTube Shorts', 'type' => 'network', 'state' => 'locked'],
            'custom_webhook'            => ['label' => __('Custom webhook', 'calliope-social-publisher'), 'type' => 'network', 'state' => 'locked'],
        ];

        $filtered = apply_filters('calliope_sp_feature_registry', $features);
        if (!is_array($filtered)) {
            $filtered = $features;
        }

        $normalized = [];
        foreach ($filtered as $slug => $definition) {
            $slug = sanitize_key((string) $slug);
            if ($slug === '' || !is_array($definition)) {
                continue;
            }

            $state = sanitize_key((string) ($definition['state'] ?? 'locked'));
            if (!in_array($state, ['available', 'locked', 'coming_soon'], true)) {
                $state = 'locked';
            }

            $type = sanitize_key((string) ($definition['type'] ?? 'feature'));
            $normalized[$slug] = [
                'label' => sanitize_text_field((string) ($definition['label'] ?? $slug)),
                'type'  => $type !== '' ? $type : 'feature',
                'state' => $state,
            ];
        }

        return $normalized;
    }

    /**
     * Public, secret-free media capabilities. Pro may extend this registry.
     *
     * @return array<string,array{single_image:bool,multiple_images:bool,video:bool}>
     */
    public static function get_media_network_capabilities(): array {
        $single_image_networks = [
            'bluesky', 'mastodon', 'tumblr', 'pinterest', 'threads', 'instagram',
            'linkedin', 'blogger', 'telegram', 'facebook',
        ];
        $capabilities = [];
        foreach ($single_image_networks as $network) {
            $capabilities[$network] = [
                'single_image'    => true,
                'multiple_images' => false,
                'video'           => false,
            ];
        }
        $capabilities['discord'] = ['single_image' => false, 'multiple_images' => false, 'video' => false];
        $capabilities['youtube'] = ['single_image' => false, 'multiple_images' => false, 'video' => false];
        $capabilities['youtube_shorts'] = ['single_image' => false, 'multiple_images' => false, 'video' => false];
        $capabilities['wordpress_remote'] = ['single_image' => false, 'multiple_images' => false, 'video' => false];
        $capabilities['custom_webhook'] = ['single_image' => false, 'multiple_images' => false, 'video' => false];

        $filtered = apply_filters('calliope_sp_media_network_capabilities', $capabilities);
        if (!is_array($filtered)) {
            $filtered = $capabilities;
        }

        $clean = [];
        foreach ($filtered as $network => $definition) {
            $network = sanitize_key((string) $network);
            if ($network === '' || !is_array($definition)) {
                continue;
            }
            $clean[$network] = [
                'single_image'    => !empty($definition['single_image']),
                'multiple_images' => !empty($definition['multiple_images']),
                'video'           => !empty($definition['video']),
            ];
        }
        return $clean;
    }

    /** @return string[] */
    private static function get_media_capability_labels(string $mode): array {
        $mode = sanitize_key($mode);
        if (!in_array($mode, ['multiple_images', 'video'], true)) {
            return [];
        }
        $labels = [
            'discord' => 'Discord',
            'facebook' => __('Facebook Page', 'calliope-social-publisher'),
            'instagram' => 'Instagram',
            'threads' => 'Threads',
            'youtube' => 'YouTube',
            'youtube_shorts' => 'YouTube Shorts',
            'wordpress_remote' => __('Remote WordPress', 'calliope-social-publisher'),
            'custom_webhook' => __('Custom webhook', 'calliope-social-publisher'),
        ];
        $supported = [];
        foreach (self::get_media_network_capabilities() as $network => $definition) {
            if (!empty($definition[$mode]) && isset($labels[$network])) {
                $supported[] = $labels[$network];
            }
        }
        return $supported;
    }

    public static function get_purchase_url(): string {
        $url = apply_filters(
            'calliope_sp_pro_purchase_url',
            'https://pluginswordpress.calliope.com.ar/calliope-social-publisher/'
        );

        return esc_url_raw((string) $url);
    }

    /**
     * Sanitize extension-owned metadata without knowing provider semantics.
     *
     * @param mixed $value Raw value.
     * @return mixed
     */
    public static function sanitize_metadata_value($value) {
        if (is_bool($value)) {
            return $value;
        }

        if (is_int($value) || is_float($value)) {
            return $value;
        }

        if (is_string($value)) {
            return sanitize_text_field($value);
        }

        if (!is_array($value)) {
            return null;
        }

        $clean = [];
        foreach ($value as $key => $item) {
            $clean_key = is_int($key) ? $key : sanitize_key((string) $key);
            if ($clean_key === '') {
                continue;
            }
            $clean[$clean_key] = self::sanitize_metadata_value($item);
        }

        return $clean;
    }

    /**
     * Keep only safe extension metadata added by generic filters.
     */
    public static function sanitize_publish_payload(array $payload): array {
        if (isset($payload['metadata'])) {
            $metadata = self::sanitize_metadata_value($payload['metadata']);
            $payload['metadata'] = is_array($metadata) ? $metadata : [];
        }

        if (array_key_exists('media_mode', $payload)) {
            $mode = sanitize_key((string) $payload['media_mode']);
            $payload['media_mode'] = in_array($mode, ['none', 'single_image', 'multiple_images', 'video', 'invalid'], true)
                ? $mode
                : 'invalid';
        }

        if (isset($payload['media'])) {
            $raw_media = is_array($payload['media']) ? $payload['media'] : [];
            $clean_media = [];
            $seen = [];
            foreach ($raw_media as $position => $item) {
                if (!is_array($item)) {
                    continue;
                }
                $id = absint($item['id'] ?? 0);
                $type = sanitize_key((string) ($item['type'] ?? ''));
                if ($id <= 0 || isset($seen[$id]) || !in_array($type, ['image', 'video'], true)) {
                    continue;
                }
                $seen[$id] = true;
                $clean_media[] = [
                    'id'        => $id,
                    'url'       => '',
                    'type'      => $type,
                    'mime_type' => sanitize_mime_type((string) ($item['mime_type'] ?? '')),
                    'alt'       => sanitize_text_field((string) ($item['alt'] ?? '')),
                    'caption'   => sanitize_textarea_field((string) ($item['caption'] ?? '')),
                    'position'  => absint($item['position'] ?? $position),
                ];
            }
            $payload['media'] = array_values($clean_media);
        }

        unset($payload['media_ids'], $payload['single_image_attachment_id']);
        return $payload;
    }

    public static function render_external_destinations(): void {
        $features = self::get_feature_registry();
        $status = self::get_pro_status();
        $purchase_url = self::get_purchase_url();
        $network_slugs = ['discord', 'youtube', 'wordpress_remote', 'custom_webhook'];

        // Video-first networks belong to the dedicated Video post composer.
        $video_composer_available = !empty($status['active']) && !empty($status['free_compatible']);
        if ($video_composer_available) {
            $network_slugs = array_values(array_diff($network_slugs, ['youtube', 'youtube_shorts']));
        }

        $pro_runtime_active = !empty($status['active']) && !empty($status['free_compatible']);

        foreach ($network_slugs as $slug) {
            $feature_slug = self::feature_slug_for_destination($slug);
            $definition = $features[$feature_slug] ?? ['label' => ucfirst(str_replace('_', ' ', $slug)), 'state' => 'locked'];
            $label = (string) $definition['label'];
            $state = (string) $definition['state'];
            $field = 'post_to_' . $slug;
            $connection_key = self::connection_slug_for_destination($slug);
            $connection_exists = array_key_exists($connection_key, is_array($status['connections'] ?? null) ? $status['connections'] : []);

            // Once Pro is actually active, never show a Free upsell card for a Pro
            // destination. A stale feature-state alias must not make an installed
            // destination look as though Pro still needs to be purchased.
            $runtime_available = $pro_runtime_active
                && !empty($status['license_active'])
                && ($state === 'available' || $connection_exists);

            if ($runtime_available) {
                $connection = is_array($status['connections'][$connection_key] ?? null) ? $status['connections'][$connection_key] : [];
                $configured = !empty($connection['configured']);
                $classes = 'calliope-sp-pro-destination calliope-sp-pro-destination--available';
                if (!$configured) {
                    $classes .= ' calliope-sp-pro-destination--not-configured';
                }
                echo '<label class="' . esc_attr($classes) . '"' . (!$configured ? ' title="' . esc_attr__('Connect this network from the Connections tab before publishing.', 'calliope-social-publisher') . '"' : '') . '>';
                echo '<input type="checkbox" name="' . esc_attr($field) . '" value="1" data-calliope-preview-network="' . esc_attr($slug) . '" ' . checked($configured, true, false) . disabled(!$configured, true, false) . ' />';
                echo '<span class="calliope-sp-pro-destination__label">';
                echo '<span>' . esc_html($label) . '</span></span>';
                echo '</label>';
                continue;
            }

            if ($pro_runtime_active) {
                // Pro is present but this destination is not currently usable
                // (for example, license unavailable). Do not render a purchase
                // upsell inside Publisher for a product that is already installed.
                continue;
            }

            $locked_label = sprintf(
                /* translators: %s: premium destination name. */
                __('Get Social Publisher Pro to unlock %s', 'calliope-social-publisher'),
                $label
            );

            if ($purchase_url !== '') {
                echo '<a class="calliope-sp-pro-destination calliope-sp-pro-destination--locked" href="' . esc_url($purchase_url) . '" target="_blank" rel="noopener noreferrer" aria-label="' . esc_attr($locked_label) . '" title="' . esc_attr__('Get Social Publisher Pro', 'calliope-social-publisher') . '">';
            } else {
                echo '<span class="calliope-sp-pro-destination calliope-sp-pro-destination--locked" aria-disabled="true">';
            }
            echo '<span class="calliope-sp-pro-destination__label">' . esc_html($label) . ' <span class="calliope-sp-pro-badge">PRO</span></span>';
            echo $purchase_url !== '' ? '</a>' : '</span>';
        }

    }

    public static function render_pro_connection_cards(): void {
        $features = self::get_feature_registry();
        $status = self::get_pro_status();
        $network_slugs = [
            'discord',
            'remote_wordpress', 'youtube', 'custom_webhook',
        ];

        if (!empty($status['active']) && !empty($status['free_compatible'])) {
            return;
        }

        echo '<section class="calliope-sp-pro-panel" aria-labelledby="calliope-sp-pro-connections-title">';
        echo '<div class="calliope-sp-pro-panel__head"><h3 id="calliope-sp-pro-connections-title">' . esc_html__('Pro networks', 'calliope-social-publisher') . '</h3>';
        echo '</div><div class="calliope-sp-pro-grid">';
        $purchase_url = self::get_purchase_url();

        foreach ($network_slugs as $slug) {
            if (empty($features[$slug])) {
                continue;
            }

            $feature = $features[$slug];
            $card_label = (string) $feature['label'];
            if ($purchase_url !== '') {
                echo '<a class="calliope-sp-pro-card calliope-sp-pro-card--link" href="' . esc_url($purchase_url) . '" target="_blank" rel="noopener noreferrer" title="' . esc_attr__('Get Social Publisher Pro', 'calliope-social-publisher') . '">';
            } else {
                echo '<span class="calliope-sp-pro-card">';
            }
            echo '<div class="calliope-sp-pro-card__title"><span class="calliope-sp-pro-card__icon" aria-hidden="true">' . self::pro_connection_card_icon($slug) . '</span><strong>' . esc_html($card_label) . '</strong><span class="calliope-sp-pro-badge">PRO</span></div>';
            echo $purchase_url !== '' ? '</a>' : '</span>';
        }

        echo '</div></section>';
    }

    public static function render_publication_type_selector(): void {
        $status = self::get_pro_status();

        if (!empty($status['active']) && !empty($status['license_active']) && !empty($status['free_compatible'])) {
            return;
        }

        $upgrade_url = '';
        if (!empty($status['admin_url'])) {
            $upgrade_url = $status['state'] === 'active_unlicensed'
                ? $status['admin_url'] . '#license'
                : $status['admin_url'];
        } else {
            $upgrade_url = self::get_purchase_url();
        }

        $locked_help = __('Available with Social Publisher Pro.', 'calliope-social-publisher');
        if ($status['state'] === 'active_unlicensed') {
            $locked_help = __('Activate the Pro license to unlock video posts.', 'calliope-social-publisher');
        } elseif ($status['state'] === 'installed_inactive') {
            $locked_help = __('Activate Social Publisher Pro to unlock video posts.', 'calliope-social-publisher');
        }

        echo '<fieldset class="calliope-spp-publication-type calliope-spp-publication-type--locked" aria-labelledby="calliope-spp-publication-type-title">';
        echo '<legend id="calliope-spp-publication-type-title">' . esc_html__('Publication type', 'calliope-social-publisher') . '</legend>';
        echo '<label class="is-selected"><input type="radio" name="calliope_sp_publication_type" value="standard" checked> <span><strong>' . esc_html__('Standard post', 'calliope-social-publisher') . '</strong><small>' . esc_html__('Text, link or image', 'calliope-social-publisher') . '</small></span></label>';
        echo '<div class="calliope-spp-publication-type__locked-option" aria-disabled="true" title="' . esc_attr($locked_help) . '"><div class="calliope-spp-publication-type__locked-content"><input type="radio" name="calliope_sp_publication_type" value="video" disabled> <span><strong>' . esc_html__('Video post', 'calliope-social-publisher') . ' <span class="calliope-sp-pro-badge">PRO</span></strong><small>' . esc_html($locked_help) . '</small></span></div></div>';
        echo '</fieldset>';
        if ($upgrade_url !== '') {
            $label = $status['state'] === 'active_unlicensed'
                ? __('Activate Pro license', 'calliope-social-publisher')
                : __('Get Social Publisher Pro', 'calliope-social-publisher');
            echo '<div class="calliope-spp-publication-type__actions">';
            echo '<a class="button calliope-spp-publication-type__cta-button" href="' . esc_url($upgrade_url) . '"' . (empty($status['admin_url']) ? ' target="_blank" rel="noopener noreferrer"' : '') . '>' . esc_html($label) . '</a>';
            echo '</div>';
        }
    }

    public static function render_pro_media_options(): void {
        $status = self::get_pro_status();
        $features = self::get_feature_registry();
        $purchase_url = self::get_purchase_url();
        $multiple_available = !empty($status['license_active']) && (($features['multiple_images']['state'] ?? '') === 'available');

        echo '<fieldset class="calliope-sp-media-modes" aria-describedby="calliope-sp-media-mode-help">';
        echo '<legend class="screen-reader-text">' . esc_html__('Media modes', 'calliope-social-publisher') . '</legend>';
        echo '<div class="calliope-sp-media-mode calliope-sp-media-mode--available">';
        echo '<label><input type="radio" name="calliope_spp_media_mode" value="single_image" form="calliope_sp_publish_form" checked> <strong>' . esc_html__('One image', 'calliope-social-publisher') . '</strong><span>' . esc_html__('Use one image.', 'calliope-social-publisher') . '</span></label></div>';

        $carousel_help = __('Use 2 to 10 images.', 'calliope-social-publisher');

        if ($multiple_available) {
            echo '<div class="calliope-sp-media-mode calliope-sp-media-mode--available">';
            echo '<label><input type="radio" name="calliope_spp_media_mode" value="multiple_images" form="calliope_sp_publish_form"> <strong>' . esc_html__('Multiple images / Carousel', 'calliope-social-publisher') . ' <span class="calliope-sp-pro-badge">PRO</span></strong><span>' . esc_html($carousel_help) . '</span></label></div>';
        } else {
            echo '<div class="calliope-sp-media-mode calliope-sp-media-mode--locked" aria-disabled="true"><strong>' . esc_html__('Multiple images / Carousel', 'calliope-social-publisher') . ' <span class="calliope-sp-pro-badge">PRO</span></strong><span>' . esc_html__('Available with Social Publisher Pro', 'calliope-social-publisher') . '</span></div>';
        }

        echo '</fieldset>';
        echo '<p id="calliope-sp-media-mode-help" class="calliope-sp-hint">' . esc_html__('Only one media mode is used for each publication.', 'calliope-social-publisher') . '</p>';

        if ($multiple_available) {
            echo '<section id="calliope-spp-multiple-images-panel" class="calliope-spp-media-panel" hidden aria-labelledby="calliope-spp-images-title">';
            echo '<div class="calliope-spp-media-panel__head"><div><strong id="calliope-spp-images-title">' . esc_html__('Multiple images / Carousel', 'calliope-social-publisher') . '</strong><p>' . esc_html__('Choose between 2 and 10 images. Each network receives up to its single-post maximum; extra images are omitted without creating another post.', 'calliope-social-publisher') . '</p></div><span id="calliope-spp-image-count" role="status" aria-live="polite">0 / 10</span></div>';
            echo '<button type="button" class="button" id="calliope-spp-select-images">' . esc_html__('Select images', 'calliope-social-publisher') . '</button>';
            echo '<ul id="calliope-spp-image-list" class="calliope-spp-media-list"></ul></section>';
        }

        if ($multiple_available) {
            echo '<div id="calliope-spp-media-hidden-fields"></div>';
        }

        if (!$multiple_available) {
            if ($status['state'] === 'not_installed' && $purchase_url !== '') {
                echo '<p class="calliope-sp-pro-media-cta"><a href="' . esc_url($purchase_url) . '" target="_blank" rel="noopener noreferrer">' . esc_html__('Get Pro', 'calliope-social-publisher') . '</a></p>';
            } elseif (empty($status['active']) && $status['admin_url'] !== '') {
                echo '<p class="calliope-sp-pro-media-cta"><a href="' . esc_url($status['admin_url']) . '">' . esc_html__('Open Social Publisher settings', 'calliope-social-publisher') . '</a></p>';
            }
        }
    }

    private static function sanitize_commercial_state($state): string {
        $state = sanitize_key((string) $state);
        return in_array($state, ['not_installed', 'installed_inactive', 'active_incompatible', 'active_unlicensed', 'active_licensed', 'active_licensed_grace'], true)
            ? $state
            : 'not_installed';
    }
}
