<?php
/*
Plugin Name: MassPost – Post, Share, Send to Several Websites
Description: Publish posts seamlessly to multiple WordPress websites via a secure REST API. MassPost streamlines content distribution, ensuring your latest posts reach multiple target sites without manual copying or pasting.
Version: 1.1.1
Author: Unioney
Author URI: https://unioney.com
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Requires at least: 6.0
Requires PHP: 7.4
Text Domain: masspost
*/

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

// Plugin version
define('MASSPOST_VERSION', '1.1.1');

// Load license manager (authorization integration)
require_once __DIR__ . '/includes/class-masspost-license.php';

// Load admin activation interface
require_once __DIR__ . '/includes/class-masspost-admin-activation.php';

// Initialize admin activation handlers
add_action('init', function () {
    if (is_admin()) {
        masspost_admin_activation();
    }
});

// Load diagnostics tool (safe for production)
require_once __DIR__ . '/masspost-diagnostics.php';

/**
 * Register post meta for Gutenberg REST API access
 */
function masspost_register_post_meta()
{
    register_post_meta('post', '_masspost_selected_sites', [
        'single'            => true,
        'type'              => 'array',
        'default'           => [],
        'show_in_rest'      => [
            'schema' => [
                'type'  => 'array',
                'items' => [
                    'type' => 'string',
                ],
            ],
        ],
        'auth_callback'     => function () {
            return current_user_can('edit_posts');
        },
        'sanitize_callback' => function ($value) {
            if (!is_array($value)) {
                return [];
            }
            return array_map('sanitize_text_field', $value);
        },
    ]);
}
add_action('init', 'masspost_register_post_meta');

/**
 * Remove classic metabox in block editor (Gutenberg panel replaces it)
 */
function masspost_remove_classic_metabox_in_gutenberg()
{
    $screen = get_current_screen();
    if ($screen && $screen->is_block_editor()) {
        remove_meta_box('masspost_meta_box', 'post', 'side');
    }
}
add_action('add_meta_boxes', 'masspost_remove_classic_metabox_in_gutenberg', 100);

/** Allowed HTML fragments used in admin-rendered pieces */
function masspost_admin_allowed_html()
{
    return [
        "div" => ["class" => true, "style" => true],
        "span" => [
            "class" => true,
            "style" => true,
            "tabindex" => true,
            "aria-label" => true,
            "data-selected" => true,
        ],
        "button" => ["type" => true, "class" => true],
        "input" => [
            "type" => true,
            "id" => true,
            "name" => true,
            "value" => true,
            "placeholder" => true,
            "class" => true,
            "style" => true,
            "checked" => true,
        ],
        "b" => [],
        "strong" => [],
        "small" => [],
        "code" => [],
        "label" => ["for" => true],
        "br" => [],
        "h3" => ["style" => true],
        "p" => ["class" => true, "style" => true],
    ];
}

/** Pluggable logger (hook into `masspost_debug_log` to record) */
function masspost_log($message)
{
    do_action("masspost_debug_log", "MassPost: " . $message);
}

/** ---------------------------------------------------------
 * Helpers for diffing & hashing
 * --------------------------------------------------------- */

/** Build the outbound payload + content hash */
function masspost_build_payload($post_id)
{
    $category_names = wp_get_post_terms($post_id, "category", [
        "fields" => "names",
    ]);
    $tags = wp_get_post_terms($post_id, "post_tag", ["fields" => "names"]);

    $category_names = is_array($category_names)
        ? array_values(
            array_filter(array_map("sanitize_text_field", $category_names)),
        )
        : [];
    $tags = is_array($tags)
        ? array_values(array_filter(array_map("sanitize_text_field", $tags)))
        : [];

    $fmt = get_post_format($post_id);
    if (!$fmt) {
        $fmt = "standard";
    }

    $date_local = get_post_field("post_date", $post_id);
    $date_gmt = get_post_field("post_date_gmt", $post_id);

    // Get source post author info for ABC integration
    $post_author_id = (int) get_post_field("post_author", $post_id);
    $post_author = get_user_by("id", $post_author_id);
    $author_login = $post_author ? $post_author->user_login : "";
    $author_roles = $post_author && !empty($post_author->roles) ? implode(",", $post_author->roles) : "";

    $payload = [
        "title" => get_the_title($post_id),
        "content" => get_post_field("post_content", $post_id),
        "category_names" => $category_names,
        "tags" => $tags,
        "featured_image" => get_the_post_thumbnail_url($post_id) ?: "",
        "date" => $date_local,
        "date_gmt" => $date_gmt,
        "status" => get_post_status($post_id),
        "post_format" => $fmt,
        "id" => (int) $post_id,
        "footnotes" => get_post_meta($post_id, "footnotes", true),
        "slug" => get_post_field("post_name", $post_id),
        // For ABC integration: source author info
        "author_login" => $author_login,
        "author_role" => $author_roles,
    ];

    // Hash over the "meaningful" fields INCLUDING schedule fields
    $payload["content_hash"] = wp_hash(
        wp_json_encode(
            [
                "title" => $payload["title"],
                "content" => $payload["content"],
                "category_names" => $payload["category_names"],
                "tags" => $payload["tags"],
                "featured_image" => $payload["featured_image"],
                "status" => $payload["status"],
                "post_format" => $payload["post_format"],
                "footnotes" => $payload["footnotes"],
                "slug" => $payload["slug"],
                "date" => $payload["date"],
                "date_gmt" => $payload["date_gmt"],
            ],
            JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
        ),
    );

    return $payload;
}

/** True if two flat arrays (string/int) contain same items (order-insensitive) */
function masspost_arrays_equal($a, $b)
{
    if (!is_array($a) || !is_array($b)) {
        return false;
    }
    sort($a);
    sort($b);
    return $a === $b;
}

/** Admin menu */
// file: masspost.php (inside your plugin)
function masspost_add_admin_menu()
{
    $icon_svg = 'data:image/svg+xml;base64,' . base64_encode('
        <svg width="18" height="18" xmlns="http://www.w3.org/2000/svg" baseProfile="full"><path d="M3.6 18h10.8a3.6 3.6 0 0 0 3.6-3.6V3.6A3.6 3.6 0 0 0 14.4 0H3.6A3.6 3.6 0 0 0 0 3.6v10.8A3.6 3.6 0 0 0 3.6 18z"/><path d="M13.8 9l2.718-4.7h-2.819V1.483L9 4.201 4.3 1.482v2.819H1.482L4.201 9l-2.719 4.7H4.3v2.818L9 13.799l4.7 2.719V13.7h2.818zM9 10.914L4.46 13.54 7.085 9 4.46 4.46 9 7.086l4.54-2.626L10.914 9l2.626 4.54z" fill="#fff"/></svg>
    ');

    add_menu_page(
        "MassPost",
        "MassPost",
        "manage_options",
        "masspost",
        "masspost_settings_page",
        $icon_svg,
        6
    );
}
add_action("admin_menu", "masspost_add_admin_menu");


/** Settings link on Plugins screen */
function masspost_settings_link($links)
{
    $settings_link =
        '<a href="admin.php?page=masspost">' .
        esc_html__("Settings", "masspost") .
        "</a>";
    array_unshift($links, $settings_link);
    return $links;
}
add_filter(
    "plugin_action_links_" . plugin_basename(__FILE__),
    "masspost_settings_link",
);

/** Settings page assets */
function masspost_admin_assets($hook)
{
    // Get the current screen safely
    $screen = function_exists('get_current_screen') ? get_current_screen() : null;

    // Identify contexts
    $is_settings_page = ($hook === 'toplevel_page_masspost');
    $is_diagnostics   = ($hook === 'masspost_page_masspost-diagnostics');
    $is_posts_list    = ($hook === 'edit.php' && $screen && $screen->id === 'edit-post');
    $is_post_new      = ($hook === 'post-new.php' && $screen && $screen->post_type === 'post');
    $is_post_edit     = ($hook === 'post.php'     && $screen && $screen->post_type === 'post');


    // Enqueue CSS on settings page, diagnostics page, posts list, and post editor (for panel styling)
    if ($is_settings_page || $is_diagnostics || $is_posts_list || $is_post_new || $is_post_edit) {
        wp_enqueue_style(
            'masspost-admin-css',
            plugin_dir_url(__FILE__) . 'assets/css/masspost-admin.css',
            [],
            '1.0.5' // bump for cache-bust
        );
    }

    // Keep the admin JS only on the settings page
    if ($is_settings_page) {
        wp_register_script(
            'masspost-admin-js',
            plugin_dir_url(__FILE__) . 'assets/js/masspost-admin.js',
            ['jquery', 'jquery-ui-sortable'],
            '1.0.0',
            true
        );

        $template = masspost_website_fields_html('__INDEX__');
        wp_localize_script('masspost-admin-js', 'masspost_admin_vars', [
            'template' => $template,
            'ajax_url' => admin_url('admin-ajax.php'),
            'import_nonce' => wp_create_nonce('masspost_import_config'),
            'save_nonce' => wp_create_nonce('masspost_save_settings'),
            'strings' => [
                'saving' => __('Saving...', 'masspost'),
                'saved' => __('Settings saved successfully.', 'masspost'),
                'error' => __('Error saving settings.', 'masspost'),
            ],
        ]);

        wp_enqueue_script('masspost-admin-js');
    }

    // Enqueue Gutenberg notices script on post edit screens
    if ($is_post_new || $is_post_edit) {
        wp_enqueue_script(
            'masspost-notices-js',
            plugin_dir_url(__FILE__) . 'assets/js/masspost-notices.js',
            ['wp-data', 'wp-notices'],
            '1.0.0',
            true
        );
        wp_localize_script('masspost-notices-js', 'masspostNoticesVars', [
            'ajaxUrl' => admin_url('admin-ajax.php'),
            'nonce'   => wp_create_nonce('masspost_get_errors_nonce'),
        ]);

        // Enqueue Gutenberg panel for MassPost Target (replaces classic metabox)
        $websites = get_option('masspost_websites', []);

        // Get post ID safely from global $post (avoids nonce verification warning)
        global $post;
        $post_id = ($post instanceof WP_Post) ? $post->ID : 0;

        $selected_sites = $post_id ? get_post_meta($post_id, '_masspost_selected_sites', true) : [];
        if (!is_array($selected_sites)) {
            $selected_sites = [];
        }

        wp_enqueue_script(
            'masspost-gutenberg-panel',
            plugin_dir_url(__FILE__) . 'assets/js/masspost-gutenberg-panel.js',
            ['wp-plugins', 'wp-edit-post', 'wp-element', 'wp-components', 'wp-data', 'wp-i18n'],
            '1.0.4',
            true
        );
        wp_localize_script('masspost-gutenberg-panel', 'masspostPanelData', [
            'websites'      => array_values($websites),
            'selectedSites' => array_map('strval', $selected_sites),
            'nonce'         => wp_create_nonce('masspost_meta_box_action'),
        ]);
    }
}
add_action('admin_enqueue_scripts', 'masspost_admin_assets');

/** AJAX handler: Get MassPost errors for Gutenberg */
function masspost_ajax_get_errors()
{
    check_ajax_referer('masspost_get_errors_nonce', 'nonce');

    $post_id = isset($_POST['post_id']) ? (int) $_POST['post_id'] : 0;

    if (!$post_id) {
        wp_send_json_error(['message' => 'Invalid post ID']);
    }

    // Read from FILE (bypass database entirely)
    $upload_dir = wp_upload_dir();
    $error_file = $upload_dir['basedir'] . '/masspost-errors-' . $post_id . '.json';

    $errors = [];
    if (file_exists($error_file)) {
        $content = file_get_contents($error_file);
        $errors = json_decode($content, true);
    }

    // Debug info
    $selected_sites = get_post_meta($post_id, "_masspost_selected_sites", true);
    $websites = get_option("masspost_websites", []);
    $debug = [
        'post_id' => $post_id,
        'selected_sites' => $selected_sites,
        'websites_count' => count($websites),
        'errors_found' => !empty($errors),
        'file_exists' => file_exists($error_file),
    ];

    if (!empty($errors) && is_array($errors)) {
        // Delete file after reading (one-time display)
        wp_delete_file($error_file);
        wp_send_json_success(['errors' => $errors, 'debug' => $debug]);
    }

    wp_send_json_success(['errors' => [], 'debug' => $debug]);
}
add_action('wp_ajax_masspost_get_errors', 'masspost_ajax_get_errors');


/** Load Quick Edit helper only on Posts list */
function masspost_enqueue_inline_edit_script($hook)
{
    if ($hook !== "edit.php") {
        return;
    }
    $screen = function_exists("get_current_screen")
        ? get_current_screen()
        : null;
    if (!$screen || $screen->id !== "edit-post") {
        return;
    }

    wp_enqueue_script(
        "masspost-inline-edit",
        plugin_dir_url(__FILE__) . "assets/js/masspost-inline-edit.js",
        ["jquery", "inline-edit-post"],
        "1.0.0",
        true,
    );
}
add_action("admin_enqueue_scripts", "masspost_enqueue_inline_edit_script");

/** Settings page */
function masspost_settings_page()
{
    // Refresh authorization status from server using TTL-based caching (1 hour)
    // This does NOT block page load on every visit - only refreshes if cache is stale
    if (function_exists('masspost_license') && function_exists('masspost_is_activated') && masspost_is_activated()) {
        masspost_license()->maybe_refresh_authorization(false); // Use cache, don't force
    }

    // Check if plugin is activated - if not (or if just got revoked above), show activation screen
    if (function_exists('masspost_is_activated') && !masspost_is_activated()) {
        masspost_admin_activation()->render_activation_screen();
        return;
    }
?>
    <div class="masspost-admin-wrap">
        <header class="masspost-header">
            <img src="<?php echo esc_url(plugin_dir_url(__FILE__) . "assets/images/masspost-logo-light.svg"); ?>" alt="MassPost">
            <h1><?php esc_html_e('Publisher Dashboard', 'masspost'); ?></h1>
            <p class="masspost-subtitle"><?php esc_html_e('Command center for your publication network. Manage secure Pass Keys, configure target websites, and streamline your multi-site workflow.', 'masspost'); ?></p>
        </header>

        <form method="post" action="options.php">
            <?php
            settings_fields("masspost_settings_group");
            ?>

            <div class="masspost-premium-card" style="border: none; background: transparent; box-shadow: none; margin-bottom: 24px;">
                <p style="margin: 0; font-size: 15px; color: var(--mp-text-muted); font-weight: 500;">
                    <?php echo esc_html__("Current Endpoint:", "masspost"); ?>
                    <code style="background: #fff; padding: 4px 8px; border-radius: 4px; border: 1px solid var(--mp-border); color: var(--mp-accent);"><?php echo esc_url(home_url("/wp-json/masspost/v1/post/")); ?></code>
                </p>
            </div>

            <?php
            // Get license data for subscription panel
            $license_data = function_exists('masspost_license') ? masspost_license()->get_license() : [];
            $user_email = isset($license_data['email']) ? $license_data['email'] : '';
            $expires_at = isset($license_data['authorization']['expires_at']) ? $license_data['authorization']['expires_at'] : '';

            // Check the actual license status (revoked, disabled, authorized, pending)
            $license_status = isset($license_data['status']) ? $license_data['status'] : '';
            $subscription_status = isset($license_data['authorization']['subscription_status']) ? $license_data['authorization']['subscription_status'] : '';

            // Determine display status and pill class
            if (in_array($license_status, ['revoked', 'disabled'], true)) {
                $display_status = ucfirst($license_status);
                $pill_class = 'mp-pill-inactive';
            } elseif ($license_status === 'authorized' && !empty($subscription_status)) {
                $display_status = ucfirst($subscription_status);
                $pill_class = in_array($subscription_status, ['active', 'trialing'], true) ? 'mp-pill-active' : 'mp-pill-pending';
            } elseif ($license_status === 'pending') {
                $display_status = __('Pending', 'masspost');
                $pill_class = 'mp-pill-pending';
            } else {
                $display_status = '';
                $pill_class = '';
            }
            ?>

            <?php if (!empty($user_email)) : ?>
                <!-- Subscription Panel -->
                <div class="masspost-premium-card" style="margin-bottom: 24px;">
                    <div class="masspost-premium-card-header">
                        <h2 class="masspost-premium-card-title"><?php esc_html_e('Subscription', 'masspost'); ?></h2>
                        <?php if (!empty($display_status)) : ?>
                            <span class="mp-pill <?php echo esc_attr($pill_class); ?>"><?php echo esc_html($display_status); ?></span>
                        <?php endif; ?>
                    </div>
                    <div class="masspost-premium-card-content">
                        <p style="margin: 0 0 8px 0; font-size: 14px; color: var(--mp-text);">
                            <strong><?php esc_html_e('Account email:', 'masspost'); ?></strong> <?php echo esc_html($user_email); ?>
                        </p>
                        <?php if (!empty($expires_at) && !in_array($license_status, ['revoked', 'disabled'], true)) : ?>
                            <p style="margin: 0; font-size: 14px; color: var(--mp-text);">
                                <strong><?php esc_html_e('Subscription renewal:', 'masspost'); ?></strong>
                                <?php
                                if (is_numeric($expires_at)) {
                                    echo esc_html(date_i18n('j F H:i', $expires_at));
                                } else {
                                    echo esc_html($expires_at);
                                }
                                ?>
                            </p>
                        <?php endif; ?>
                        <?php if (in_array($license_status, ['revoked', 'disabled'], true) && !empty($license_data['last_error']['message'])) : ?>
                            <p style="margin: 8px 0 0 0; font-size: 13px; color: #dc2626;">
                                <?php echo esc_html($license_data['last_error']['message']); ?>
                            </p>
                        <?php endif; ?>
                    </div>
                </div>
            <?php endif; ?>

            <div class="masspost-form-grid">
                <!-- Connection Settings -->
                <div class="masspost-premium-card" style="grid-column: span 2;">
                    <div class="masspost-premium-card-header">
                        <h2 class="masspost-premium-card-title"><?php esc_html_e('Local Authentication', 'masspost'); ?></h2>
                    </div>
                    <div class="masspost-premium-card-content">
                        <p style="color: var(--mp-text-muted); margin-bottom: 32px; line-height: 1.6;">
                            <?php esc_html_e('Configure how this specific website receives incoming posts. A Pass Key is required to enable remote publishing capability.', 'masspost'); ?>
                        </p>

                        <div class="masspost-form-grid">
                            <div class="masspost-field-group">
                                <label class="masspost-label" for="masspost_api_token"><?php esc_html_e('Local Pass Key', 'masspost'); ?></label>
                                <?php $api_token = get_option("masspost_api_token", ""); ?>
                                <input type="text" id="masspost_api_token" name="masspost_api_token" value="<?php echo esc_attr($api_token); ?>" class="masspost-input" placeholder="<?php echo esc_attr__('e.g. secure_random_key_123', 'masspost'); ?>" />
                            </div>
                            <div class="masspost-field-group">
                                <label class="masspost-label" for="masspost_api_user"><?php esc_html_e('Authorized User (ID/Login)', 'masspost'); ?></label>
                                <?php $api_user = get_option("masspost_api_user", ""); ?>
                                <input type="text" id="masspost_api_user" name="masspost_api_user" value="<?php echo esc_attr($api_user); ?>" class="masspost-input" placeholder="<?php echo esc_attr__('Default user for incoming posts', 'masspost'); ?>" />
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Export/Import Actions -->
                <div class="masspost-premium-card">
                    <div class="masspost-premium-card-header">
                        <h2 class="masspost-premium-card-title"><?php esc_html_e('Data Operations', 'masspost'); ?></h2>
                    </div>
                    <div class="masspost-premium-card-content">
                        <div style="margin-bottom: 32px;">
                            <h4 style="margin: 0 0 8px 0; font-size: 14px;"><?php esc_html_e('Export Configuration', 'masspost'); ?></h4>
                            <p style="font-size: 13px; color: var(--mp-text-muted); margin-bottom: 16px;"><?php esc_html_e('Securely download your target website list.', 'masspost'); ?></p>
                            <button type="button" id="masspost-export-config" class="masspost-btn mp-btn-outline" style="width: 100%;">
                                <span class="dashicons dashicons-upload"></span> <?php esc_html_e('Export JSON', 'masspost'); ?>
                            </button>
                        </div>

                        <div style="padding-top: 24px; border-top: 1px solid #f1f5f9;">
                            <h4 style="margin: 0 0 8px 0; font-size: 14px;"><?php esc_html_e('Import Configuration', 'masspost'); ?></h4>
                            <input type="file" id="masspost-import-file" accept=".json" style="width: 100%; margin-bottom: 16px; font-size: 13px;" />
                            <button type="button" id="masspost-import-config" class="masspost-btn mp-btn-outline" style="width: 100%;">
                                <span class="dashicons dashicons-download"></span> <?php esc_html_e('Restore Data', 'masspost'); ?>
                            </button>
                            <div id="masspost-import-result" style="margin-top: 12px; font-size: 12px;"></div>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Target Websites -->
            <div class="masspost-premium-card">
                <div class="masspost-premium-card-header">
                    <h2 class="masspost-premium-card-title"><?php esc_html_e('Target Publication Network', 'masspost'); ?></h2>
                    <span class="mp-pill" style="background: #e0e7ff; color: #4338ca; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: 700;">
                        <?php echo count(get_option("masspost_websites", [])); ?> <?php esc_html_e('Nodes Connected', 'masspost'); ?>
                    </span>
                </div>
                <div class="masspost-premium-card-content">
                    <p style="color: var(--mp-text-muted); margin-bottom: 32px; font-size: 15px;">
                        <?php esc_html_e('Adding a target website allows you to instantly post content from this site to others. Ensure the Pass Key matches the Local Pass Key on the destination site.', 'masspost'); ?>
                    </p>

                    <div id="website-fields" class="masspost-repeater">
                        <?php
                        $websites = get_option("masspost_websites", []);
                        if (empty($websites)) {
                            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Function uses esc_attr/esc_html internally
                            echo wp_kses(masspost_website_fields_html(), masspost_admin_allowed_html());
                        } else {
                            foreach ($websites as $index => $website) {
                                $name = isset($website["name"]) ? $website["name"] : "";
                                $api_url = isset($website["api_url"]) ? $website["api_url"] : "";
                                $api_token = isset($website["api_token"]) ? $website["api_token"] : "";
                                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Function uses esc_attr/esc_html internally
                                echo wp_kses(masspost_website_fields_html($index, $name, $api_url, $api_token), masspost_admin_allowed_html());
                            }
                        }
                        ?>
                    </div>

                    <div class="masspost-action-bar">
                        <div class="masspost-action-bar-left">
                            <button type="button" class="masspost-btn mp-btn-accent" id="add-new-website">
                                <span class="dashicons dashicons-plus-alt2"></span> <?php esc_html_e('New Node', 'masspost'); ?>
                            </button>
                            <button type="button" id="masspost-save-settings" class="masspost-btn mp-btn-primary">
                                <span class="dashicons dashicons-saved"></span> <?php esc_html_e('Save', 'masspost'); ?>
                            </button>
                        </div>
                        <div class="masspost-action-bar-right">
                            <?php if (function_exists('masspost_is_activated') && masspost_is_activated()) : ?>
                                <a href="<?php echo esc_url(wp_nonce_url(admin_url('admin-post.php?action=masspost_revoke_license'), 'masspost_revoke_license', 'masspost_revoke_nonce')); ?>"
                                    class="masspost-btn mp-btn-revoke"
                                    id="masspost-revoke-btn"
                                    onclick="return confirm('<?php echo esc_js(__('Are you sure you want to revoke your authorization? You will need to re-authorize to continue using MassPost.', 'masspost')); ?>');">
                                    <span class="dashicons dashicons-dismiss"></span> <?php esc_html_e('Revoke', 'masspost'); ?>
                                </a>
                            <?php endif; ?>
                        </div>
                    </div>
                    <div id="masspost-save-result"></div>
                </div>
            </div>
        </form>
    </div>
<?php
}

/** Website fields template */
function masspost_website_fields_html(
    $index = "__INDEX__",
    $name = "",
    $api_url = "",
    $api_token = "",
) {
    return '
    <div class="website-field masspost-repeater-item">
        <div class="masspost-repeater-fields">
            <div class="masspost-field-group">
                <label class="masspost-label">' . esc_html__('Node Name', 'masspost') . '</label>
                <input class="masspost-input" type="text" name="masspost_websites[' . esc_attr($index) . '][name]" value="' . esc_attr($name) . '" placeholder="e.g. My Website" />
            </div>
            <div class="masspost-field-group">
                <label class="masspost-label">' . esc_html__('Pass Key', 'masspost') . '</label>
                <input class="masspost-input" type="text" name="masspost_websites[' . esc_attr($index) . '][api_token]" value="' . esc_attr($api_token) . '" placeholder="Matching Key" />
            </div>
            <div class="masspost-field-group" style="grid-column: span 2;">
                <label class="masspost-label">' . esc_html__('Target Endpoint URL', 'masspost') . '</label>
                <input class="masspost-input" type="text" name="masspost_websites[' . esc_attr($index) . '][api_url]" value="' . esc_attr($api_url) . '" placeholder="https://target.com/wp-json/masspost/v1/post/" />
            </div>
        </div>
        <div style="display: flex; justify-content: flex-end;">
            <button type="button" class="remove-website mp-btn-remove" title="' . esc_attr__('Remove Node', 'masspost') . '">
                <span class="dashicons dashicons-trash"></span>
            </button>
        </div>
    </div>';
}

/** Sanitize websites option */
function masspost_sanitize_websites($input)
{
    $sanitized = [];

    if (is_array($input)) {
        foreach ($input as $website) {
            $name = isset($website["name"])
                ? sanitize_text_field($website["name"])
                : "";
            $api_url = isset($website["api_url"])
                ? esc_url_raw($website["api_url"])
                : "";
            $api_token = isset($website["api_token"])
                ? sanitize_text_field($website["api_token"])
                : "";

            if ($name && $api_url && $api_token) {
                $sanitized[] = [
                    "name" => $name,
                    "api_url" => $api_url,
                    "api_token" => $api_token,
                ];
            }
        }
    }
    return $sanitized;
}

/** Register options */
function masspost_register_settings()
{
    register_setting("masspost_settings_group", "masspost_websites", [
        "sanitize_callback" => "masspost_sanitize_websites",
        "type" => "array",
        "default" => [],
    ]);
    register_setting("masspost_settings_group", "masspost_api_token", [
        "sanitize_callback" => "sanitize_text_field",
        "type" => "string",
        "default" => "",
    ]);
    register_setting("masspost_settings_group", "masspost_api_user", [
        "sanitize_callback" => "sanitize_text_field",
        "type" => "string",
        "default" => "",
    ]);
}
add_action("admin_init", "masspost_register_settings");

/** AJAX handler for importing configuration */
function masspost_ajax_import_config()
{
    // Security check
    if (!check_ajax_referer('masspost_import_config', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed', 'masspost')]);
        return;
    }

    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Insufficient permissions', 'masspost')]);
        return;
    }

    // Get the JSON data
    if (!isset($_POST['json_data'])) {
        wp_send_json_error(['message' => __('No data provided', 'masspost')]);
        return;
    }

    $json_data = wp_unslash($_POST['json_data']); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    $data = json_decode($json_data, true);

    if (json_last_error() !== JSON_ERROR_NONE) {
        wp_send_json_error(['message' => __('Invalid JSON format', 'masspost')]);
        return;
    }

    // Validate structure
    if (!isset($data['version'])) {
        wp_send_json_error(['message' => __('Invalid configuration format', 'masspost')]);
        return;
    }

    if ($data['version'] !== '1.0') {
        wp_send_json_error(['message' => __('Unsupported configuration version', 'masspost')]);
        return;
    }

    // Import Pass Key (if provided)
    if (isset($data['api_token'])) {
        $api_token = sanitize_text_field($data['api_token']);
        update_option('masspost_api_token', $api_token);
    }

    // Import User ID/Username (if provided)
    if (isset($data['api_user'])) {
        $api_user = sanitize_text_field($data['api_user']);
        update_option('masspost_api_user', $api_user);
    }

    // Import websites (if provided)
    $websites_count = 0;
    if (isset($data['websites']) && is_array($data['websites'])) {
        $sanitized = masspost_sanitize_websites($data['websites']);
        update_option('masspost_websites', $sanitized);
        $websites_count = count($sanitized);
    }

    // Build success message
    $messages = [];
    if (isset($data['api_token']) || isset($data['api_user'])) {
        $messages[] = __('Site settings imported', 'masspost');
    }
    if ($websites_count > 0) {
        $messages[] = sprintf(
            /* translators: %d: number of websites imported */
            _n(
                '%d website imported',
                '%d websites imported',
                $websites_count,
                'masspost'
            ),
            $websites_count
        );
    }

    if (empty($messages)) {
        wp_send_json_error(['message' => __('No valid data found in configuration file', 'masspost')]);
        return;
    }

    wp_send_json_success([
        'message' => implode(', ', $messages) . '.',
        'count' => $websites_count
    ]);
}
add_action('wp_ajax_masspost_import_config', 'masspost_ajax_import_config');

/**
 * AJAX: Save all settings (api_token, api_user, websites)
 */
function masspost_ajax_save_settings()
{
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Permission denied.', 'masspost')], 403);
        return;
    }

    if (!check_ajax_referer('masspost_save_settings', 'nonce', false)) {
        wp_send_json_error(['message' => __('Security check failed.', 'masspost')], 400);
        return;
    }

    // Save API Token
    if (isset($_POST['api_token'])) {
        update_option('masspost_api_token', sanitize_text_field(wp_unslash($_POST['api_token'])));
    }

    // Save API User
    if (isset($_POST['api_user'])) {
        update_option('masspost_api_user', sanitize_text_field(wp_unslash($_POST['api_user'])));
    }

    // Save Websites
    if (isset($_POST['websites'])) {
        // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Sanitized per-field in the loop below
        $websites_raw = wp_unslash($_POST['websites']);
        $websites = [];

        if (is_array($websites_raw)) {
            foreach ($websites_raw as $website) {
                $name = isset($website['name']) ? sanitize_text_field($website['name']) : '';
                $api_url = isset($website['api_url']) ? esc_url_raw($website['api_url']) : '';
                $api_token = isset($website['api_token']) ? sanitize_text_field($website['api_token']) : '';

                if ($name && $api_url && $api_token) {
                    $websites[] = [
                        'name' => $name,
                        'api_url' => $api_url,
                        'api_token' => $api_token,
                    ];
                }
            }
        }

        update_option('masspost_websites', $websites);
    }

    wp_send_json_success(['message' => __('Settings saved successfully.', 'masspost')]);
}
add_action('wp_ajax_masspost_save_settings', 'masspost_ajax_save_settings');

/** Metabox */
function masspost_add_meta_box()
{
    add_meta_box(
        "masspost_meta_box",
        __("MassPost Target", "masspost"),
        "masspost_meta_box_callback",
        "post",
        "side",
        "core",
    );
}
add_action("add_meta_boxes", "masspost_add_meta_box", 1);

function masspost_meta_box_callback($post)
{
    $websites = get_option("masspost_websites", []);
    wp_nonce_field("masspost_meta_box_action", "masspost_meta_box_nonce");

    if (!empty($websites)) {
        echo '<label for="share_to_sites">' .
            esc_html__(
                "Select the other website(s) to publish the post to:",
                "masspost",
            ) .
            "</label><br>";
        $selected_sites = get_post_meta(
            $post->ID,
            "_masspost_selected_sites",
            true,
        );
        if (!is_array($selected_sites)) {
            $selected_sites = [];
        }
        $selected_set = array_map("strval", $selected_sites);

        foreach ($websites as $index => $website) {
            $is_checked = in_array((string) $index, $selected_set, true);
            printf(
                '<input type="checkbox" name="share_to_site[]" value="%s" %s> %s<br>',
                esc_attr((string) $index),
                checked($is_checked, true, false),
                esc_html(trim(isset($website["name"]) ? $website["name"] : "")),
            );
        }
    } else {
        echo esc_html__("No websites have been added yet.", "masspost");
    }
}

/** Save from metabox & Quick Edit */
function masspost_save_postdata($post_id)
{
    if (defined("DOING_AUTOSAVE") && DOING_AUTOSAVE) {
        return;
    }
    if (wp_is_post_revision($post_id)) {
        return;
    }
    if (get_post_type($post_id) !== "post") {
        return;
    }
    if (!current_user_can("edit_post", $post_id)) {
        return;
    }

    $valid_nonce = false;
    if (isset($_POST["masspost_meta_box_nonce"])) {
        $valid_nonce = wp_verify_nonce(
            sanitize_text_field(wp_unslash($_POST["masspost_meta_box_nonce"])),
            "masspost_meta_box_action",
        );
    } elseif (isset($_POST["_inline_edit"])) {
        $valid_nonce = wp_verify_nonce(
            sanitize_text_field(wp_unslash($_POST["_inline_edit"])),
            "inlineeditnonce",
        );
    }
    if (!$valid_nonce) {
        return;
    }

    $posted_sites = [];
    if (isset($_POST["share_to_site"])) {
        $posted_sites = (array) wp_unslash($_POST["share_to_site"]); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
    }

    $clean = [];
    foreach ($posted_sites as $v) {
        $clean[] = sanitize_text_field((string) $v);
    }

    if (!empty($clean)) {
        update_post_meta($post_id, "_masspost_selected_sites", $clean);
    } else {
        delete_post_meta($post_id, "_masspost_selected_sites");
    }
}
add_action("save_post", "masspost_save_postdata", 10);

/**
 * Prevent publish when MassPost will fail validation
 * This runs BEFORE the post is saved, changing status from 'publish' to 'draft'
 * Works with both Classic Editor and Gutenberg (REST API)
 */
function masspost_prevent_publish_without_image($data, $postarr)
{
    // Only check for posts (not pages, etc.)
    if ($data['post_type'] !== 'post') {
        return $data;
    }

    // Only intercept when trying to publish
    if ($data['post_status'] !== 'publish') {
        return $data;
    }

    // Get post ID - might be new post or existing
    $post_id = isset($postarr['ID']) ? (int) $postarr['ID'] : 0;
    if (!$post_id) {
        return $data;
    }

    // Check if MassPost targets are selected (from existing meta only)
    $selected_sites = get_post_meta($post_id, '_masspost_selected_sites', true);

    if (empty($selected_sites) || !is_array($selected_sites)) {
        return $data; // No MassPost targets, allow publish
    }

    // Check if featured image exists
    $thumbnail_id = get_post_thumbnail_id($post_id);

    // If no featured image and MassPost targets selected, force draft
    if (empty($thumbnail_id) || $thumbnail_id <= 0) {
        $data['post_status'] = 'draft';

        // Store a flag to show error message
        $upload_dir = wp_upload_dir();
        $error_file = $upload_dir['basedir'] . '/masspost-errors-' . $post_id . '.json';
        $errors = [
            __('Cannot publish: Featured image is required when MassPost targets are selected. Please add a featured image and try again.', 'masspost')
        ];
        file_put_contents($error_file, wp_json_encode($errors)); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
    }

    return $data;
}
add_filter('wp_insert_post_data', 'masspost_prevent_publish_without_image', 10, 2);

/**
 * REST API filter to prevent publishing without featured image (Gutenberg)
 */
function masspost_rest_pre_insert_post($prepared_post, $request)
{
    // Only for posts being published
    if (!isset($prepared_post->post_status) || $prepared_post->post_status !== 'publish') {
        return $prepared_post;
    }

    // Only for post type 'post'
    if (!isset($prepared_post->post_type) || $prepared_post->post_type !== 'post') {
        return $prepared_post;
    }

    $post_id = isset($prepared_post->ID) ? (int) $prepared_post->ID : 0;
    if (!$post_id) {
        return $prepared_post;
    }

    // Check if MassPost targets are selected
    $selected_sites = get_post_meta($post_id, '_masspost_selected_sites', true);

    if (empty($selected_sites) || !is_array($selected_sites)) {
        return $prepared_post; // No MassPost targets, allow publish
    }

    // Check if featured image exists (check request or existing)
    $thumbnail_id = 0;
    if ($request->has_param('featured_media')) {
        $thumbnail_id = (int) $request->get_param('featured_media');
    }
    if (!$thumbnail_id) {
        $thumbnail_id = get_post_thumbnail_id($post_id);
    }

    // If no featured image and MassPost targets selected, force draft
    if (empty($thumbnail_id) || $thumbnail_id <= 0) {
        $prepared_post->post_status = 'draft';

        // Store error message
        $upload_dir = wp_upload_dir();
        $error_file = $upload_dir['basedir'] . '/masspost-errors-' . $post_id . '.json';
        $errors = [
            __('Cannot publish: Featured image is required when MassPost targets are selected. Please add a featured image and try again.', 'masspost')
        ];
        file_put_contents($error_file, wp_json_encode($errors)); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
    }

    return $prepared_post;
}
add_filter('rest_pre_insert_post', 'masspost_rest_pre_insert_post', 10, 2);


/** Send post to targets on save (diff-aware) */
function masspost_send_post($post_id)
{
    // Handle WP_Post object (passed by rest_after_insert_post)
    if ($post_id instanceof WP_Post) {
        $post_id = $post_id->ID;
    }

    if (defined("DOING_AUTOSAVE") && DOING_AUTOSAVE) {
        return;
    }
    if (wp_is_post_revision($post_id)) {
        return;
    }

    $selected_sites = get_post_meta(
        $post_id,
        "_masspost_selected_sites",
        true,
    );

    if (empty($selected_sites) || !is_array($selected_sites)) {
        return;
    }

    $websites = get_option("masspost_websites", []);
    if (empty($websites)) {
        return;
    }

    // Build once; includes content_hash
    $post_data = masspost_build_payload($post_id);

    $errors = [];

    foreach ($selected_sites as $site_index) {
        $site_index = (int) $site_index;
        if (!isset($websites[$site_index])) {
            continue;
        }

        $api_url = rtrim((string) $websites[$site_index]["api_url"]);
        $token = trim((string) $websites[$site_index]["api_token"]);
        $site_name = esc_html($websites[$site_index]["name"]);

        // 1) Quick GET to see if remote has same hash (fast update path)
        // Reduced timeout to 3s for faster failure detection
        $exists_url = preg_replace('#/post/?$#', "", $api_url);
        $exists_url = trailingslashit($exists_url) . "post/" . (int) $post_id;

        $should_post = true; // Default: always POST

        $pre = wp_safe_remote_get($exists_url, [
            "headers" => ["Authorization" => "Bearer " . $token],
            "timeout" => 3, // Reduced from 8s
            "decompress" => true,
        ]);

        // Only skip POST if we got a successful response with matching hash
        if (
            !is_wp_error($pre) &&
            (int) wp_remote_retrieve_response_code($pre) === 200
        ) {
            $json = json_decode(wp_remote_retrieve_body($pre), true);
            if (
                isset($json["content_hash"]) &&
                $json["content_hash"] === $post_data["content_hash"]
            ) {
                // Up to date — skip POST entirely
                masspost_log(
                    "Skip POST (unchanged) for site index {$site_index}",
                );
                $should_post = false;
            }
        }

        if (!$should_post) {
            continue;
        }

        // 2) POST when new or changed - with retry on failure
        $args = [
            "headers" => [
                "Authorization" => "Bearer " . $token,
                "Content-Type" => "application/json",
                "Accept-Encoding" => "gzip, deflate",
            ],
            "body" => wp_json_encode(
                $post_data,
                JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES,
            ),
            "timeout" => 10, // Reduced from 20s
            "decompress" => true,
        ];

        $args["blocking"] = (bool) apply_filters(
            "masspost_http_blocking",
            true,
        );

        // First attempt
        $response = wp_safe_remote_post($api_url, $args);

        // Retry once on connection failure (not on HTTP errors)
        if (is_wp_error($response)) {
            masspost_log("First attempt failed for {$site_name}, retrying...");
            usleep(500000); // 0.5 second delay before retry
            $response = wp_safe_remote_post($api_url, $args);
        }

        if (is_wp_error($response)) {
            $msg = $response->get_error_message();
            $errors[] = sprintf(
                /* translators: %1$s: target site name, %2$s: error message */
                __('Target "%1$s": %2$s', 'masspost'),
                $site_name,
                esc_html($msg)
            );
            masspost_log("POST failed for {$site_name}: {$msg}");
            continue;
        }

        $response_code = (int) wp_remote_retrieve_response_code($response);
        $body = wp_remote_retrieve_body($response);

        if ($response_code !== 200 && $response_code !== 204) {
            $data = json_decode($body, true);
            $msg = isset($data["message"]) ? $data["message"] : $body;
            $errors[] = sprintf(
                /* translators: %1$s: target site name, %2$d: HTTP response code, %3$s: error message */
                __('Target "%1$s" (HTTP %2$d): %3$s', 'masspost'),
                $site_name,
                $response_code,
                esc_html($msg)
            );
            masspost_log("POST HTTP error for {$site_name}: {$response_code} - {$msg}");
        } else {
            masspost_log("POST success for {$site_name}");
        }
    }

    // Store errors to FILE (bypass database entirely due to replication issues)
    $upload_dir = wp_upload_dir();
    $error_file = $upload_dir['basedir'] . '/masspost-errors-' . $post_id . '.json';

    if (!empty($errors)) {
        file_put_contents($error_file, wp_json_encode($errors));

        // Revert post to draft if target rejected it
        // This prevents source from publishing when target fails
        $current_status = get_post_status($post_id);
        if ($current_status === 'publish') {
            // Remove save_post hook temporarily to avoid infinite loop
            remove_action('save_post', 'masspost_send_post', 20);

            wp_update_post([
                'ID' => $post_id,
                'post_status' => 'draft',
            ]);

            // Clear cache IMMEDIATELY so REST API gets the new draft status
            clean_post_cache($post_id);
            wp_cache_delete($post_id, 'posts');

            // Re-add the hook
            add_action('save_post', 'masspost_send_post', 20);
        }
    } else {
        if (file_exists($error_file)) {
            wp_delete_file($error_file);
        }
    }
}
// Hook into save_post for classic editor and general updates
add_action("save_post", "masspost_send_post", 20);

// Hook into rest_after_insert_post for Gutenberg (initial publish)
add_action("rest_after_insert_post", "masspost_send_post", 20, 2);

/**
 * Modify REST API response after MassPost errors
 * Changes response status to 'draft' so Gutenberg shows "Draft saved" instead of "Post published"
 */
function masspost_modify_rest_response($response, $handler, $request)
{
    // Only modify responses for post updates
    if (!is_a($response, 'WP_REST_Response')) {
        return $response;
    }

    $route = $request->get_route();

    // Get post ID - either from route or from response data
    $post_id = 0;

    // Try to match route with ID (for updates: /wp/v2/posts/{id})
    if (preg_match('#^/wp/v2/posts/(\d+)#', $route, $matches)) {
        $post_id = (int) $matches[1];
    }
    // For new posts (POST /wp/v2/posts), get ID from response data
    elseif ($route === '/wp/v2/posts') {
        $data = $response->get_data();
        if (is_array($data) && isset($data['id'])) {
            $post_id = (int) $data['id'];
        }
    }

    if (!$post_id) {
        return $response;
    }

    // Check if MassPost errors exist for this post
    $upload_dir = wp_upload_dir();
    $error_file = $upload_dir['basedir'] . '/masspost-errors-' . $post_id . '.json';

    if (!file_exists($error_file)) {
        return $response;
    }

    // Regenerate the FULL response from the fresh post object
    // This ensures all fields (status, date, link, etc.) match the 'draft' state exactly
    // forcing Gutenberg to treat this as a "Draft Saved" event
    $current_post = get_post($post_id);
    if ($current_post && $current_post->post_status === 'draft') {
        $controller = new WP_REST_Posts_Controller($current_post->post_type);
        $new_response = $controller->prepare_item_for_response($current_post, $request);
        return $new_response;
    }

    // Fallback if something fails
    $data = $response->get_data();
    if (is_array($data) && isset($data['status'])) {
        $data['status'] = 'draft';
        $response->set_data($data);
    }

    return $response;
}
add_filter('rest_request_after_callbacks', 'masspost_modify_rest_response', 999, 3);



/** Display MassPost errors as admin notices */
function masspost_admin_notices()
{
    global $pagenow, $post;
    if (!in_array($pagenow, ['post.php', 'post-new.php'], true)) {
        return;
    }
    if (!isset($post->ID)) {
        return;
    }

    $errors = get_post_meta($post->ID, '_masspost_errors', true);
    if (empty($errors) || !is_array($errors)) {
        return;
    }

    echo '<div class="notice notice-error is-dismissible"><p><strong>' . esc_html__('MassPost Error:', 'masspost') . '</strong></p><ul>';
    foreach ($errors as $error) {
        echo '<li>' . esc_html($error) . '</li>';
    }
    echo '</ul></div>';

    // Clear after displaying
    delete_post_meta($post->ID, '_masspost_errors');
    delete_post_meta($post->ID, '_masspost_errors_time');
}
add_action('admin_notices', 'masspost_admin_notices');

/** Quick & Bulk Edit UI (single print per context) */
function masspost_inline_edit_custom_box($column_name, $post_type)
{
    if ($post_type !== "post") {
        return;
    }

    static $printed = [];
    $ctx = current_filter();
    if (isset($printed[$ctx])) {
        return;
    }
    $printed[$ctx] = true;

    $websites = get_option("masspost_websites", []);
    if (empty($websites)) {
        return;
    }
?>
    <div class="inline-edit-group masspost-inline-group inline-edit-col inline-edit-col-center inline-edit-categories">
        <?php wp_nonce_field(
            "masspost_meta_box_action",
            "masspost_meta_box_nonce",
            false,
        ); ?>

        <label class="alignleft" style="display:block;margin:6px 0 6px 0;">
            <span class="title"><?php echo esc_html__(
                                    "MassPost Target",
                                    "masspost",
                                ); ?></span>
        </label>
        <br>

        <!-- Only this inner wrapper will scroll -->
        <div class="masspost-sites-scroll">
            <?php foreach ($websites as $index => $website): ?>
                <label class="masspost-site-row alignleft">
                    <input type="checkbox" name="share_to_site[]" value="<?php echo esc_attr((string) $index); ?>" />
                    <span class="checkbox-title"><?php echo esc_html(trim(isset($website["name"]) ? $website["name"] : "")); ?></span>
                </label>
            <?php endforeach; ?>
        </div>

        <br class="clear" />
    </div>

<?php
}
add_action("quick_edit_custom_box", "masspost_inline_edit_custom_box", 10, 2);
add_action("bulk_edit_custom_box", "masspost_inline_edit_custom_box", 10, 2);

/** Inject hidden marker per row so JS can read saved selection */
add_filter(
    "post_row_actions",
    function ($actions, $post) {
        if ($post->post_type !== "post") {
            return $actions;
        }

        $selected = get_post_meta($post->ID, "_masspost_selected_sites", true);
        $selected = is_array($selected) ? array_map("strval", $selected) : [];

        $actions["masspost_marker"] = sprintf(
            '<span class="masspost-selected-sites" data-selected="%s" style="display:none"></span>',
            esc_attr(implode(",", $selected)),
        );
        return $actions;
    },
    10,
    2,
);

/** REST endpoints */
function masspost_create_custom_endpoints()
{
    register_rest_route("masspost/v1", "/post/", [
        "methods" => "POST",
        "callback" => "masspost_receive_post",
        "permission_callback" => "masspost_permission_callback",
    ]);

    register_rest_route("masspost/v1", "/post/(?P<source_post_id>\d+)", [
        "methods" => "GET",
        "callback" => "masspost_check_post_exists",
        "permission_callback" => "masspost_permission_callback",
    ]);
}
add_action("rest_api_init", "masspost_create_custom_endpoints");

/** Trash sync from source */
function masspost_send_trash($post_id)
{
    if (defined("DOING_AUTOSAVE") && DOING_AUTOSAVE) {
        return;
    }
    if (wp_is_post_revision($post_id)) {
        return;
    }
    if (get_post_type($post_id) !== "post") {
        return;
    }

    $selected_sites = get_post_meta(
        $post_id,
        "_masspost_selected_sites",
        true,
    );
    if (empty($selected_sites) || !is_array($selected_sites)) {
        return;
    }

    $websites = get_option("masspost_websites", []);

    $post_data = [
        "id" => (int) $post_id,
        "status" => "trash",
    ];

    foreach ($selected_sites as $site_index) {
        $site_index = (int) $site_index;
        if (!isset($websites[$site_index])) {
            continue;
        }

        $api_url = trim((string) $websites[$site_index]["api_url"]);
        $token = trim((string) $websites[$site_index]["api_token"]);

        $response = wp_safe_remote_post($api_url, [
            "headers" => [
                "Authorization" => "Bearer " . $token,
                "Content-Type" => "application/json",
            ],
            "body" => wp_json_encode($post_data),
            "timeout" => 20,
        ]);

        if (is_wp_error($response)) {
            masspost_log(
                "trash transport error -> " . $response->get_error_message(),
            );
            continue;
        }
        $code = (int) wp_remote_retrieve_response_code($response);
        if (200 !== $code) {
            masspost_log(
                "trash API error -> " . wp_remote_retrieve_body($response),
            );
        }
    }
}
add_action("trashed_post", "masspost_send_trash");

/** REST permission callback */
function masspost_permission_callback($request)
{
    $authorization_header = $request->get_header("authorization");

    if (!$authorization_header) {
        return new WP_Error(
            "rest_forbidden",
            "Authorization header not found",
            [
                "status" => 401,
            ],
        );
    }

    $token = preg_replace("/^Bearer\s+/i", "", (string) $authorization_header);
    $stored_token = (string) get_option("masspost_api_token", "");

    if ($stored_token === "" || !hash_equals($stored_token, $token)) {
        return new WP_Error("rest_forbidden", "Invalid API token", [
            "status" => 401,
        ]);
    }

    $api_user = get_option("masspost_api_user", "");
    $user = is_numeric($api_user)
        ? get_user_by("id", (int) $api_user)
        : get_user_by("login", (string) $api_user);

    if (!$user) {
        return new WP_Error("rest_forbidden", "API user not found", [
            "status" => 403,
        ]);
    }
    if (!user_can($user, "edit_posts")) {
        return new WP_Error(
            "rest_forbidden",
            "API user does not have permission to create or edit posts",
            [
                "status" => 403,
            ],
        );
    }

    wp_set_current_user($user->ID);
    return true;
}

/** REST receiver (diff-aware, minimal writes) */
function masspost_receive_post($request)
{
    $params = (array) $request->get_json_params();

    if (empty($params["id"])) {
        return new WP_Error("rest_invalid_data", "Missing id", [
            "status" => 400,
        ]);
    }
    $is_trash_request =
        isset($params["status"]) && $params["status"] === "trash";

    if (
        !$is_trash_request &&
        (empty($params["title"]) || !isset($params["content"]))
    ) {
        return new WP_Error("rest_invalid_data", "Invalid data received", [
            "status" => 400,
        ]);
    }

    // Require featured image for non-trash requests
    if (
        !$is_trash_request &&
        (empty($params["featured_image"]) || trim((string) $params["featured_image"]) === "")
    ) {
        return new WP_Error(
            "rest_missing_featured_image",
            __("Featured image is required. Please check a featured image and Save/Publish again.", "masspost"),
            ["status" => 400]
        );
    }

    $allowed_statuses = [
        "publish",
        "draft",
        "pending",
        "private",
        "future",
        "trash",
    ];
    $status = in_array($params["status"] ?? "draft", $allowed_statuses, true)
        ? $params["status"]
        : "draft";

    $date = isset($params["date"])
        ? sanitize_text_field($params["date"])
        : current_time("mysql");
    $date_gmt = isset($params["date_gmt"])
        ? sanitize_text_field($params["date_gmt"])
        : get_gmt_from_date($date);

    $source_post_id = (int) $params["id"];

    // Lookup existing mapping (cached)
    $cache_group = "masspost";
    $cache_key = "source_map_" . $source_post_id;

    $existing_post_id = wp_cache_get($cache_key, $cache_group);
    if (false === $existing_post_id) {
        global $wpdb;
        /* phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery */
        $existing_post_id = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT p.ID
                 FROM {$wpdb->postmeta} pm
                 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
                 WHERE pm.meta_key = %s AND pm.meta_value = %d
                 LIMIT 1",
                "_masspost_source_post_id",
                $source_post_id,
            ),
        );
        wp_cache_set(
            $cache_key,
            $existing_post_id,
            $cache_group,
            5 * MINUTE_IN_SECONDS,
        );
    }

    // Trash-only flow
    if ($is_trash_request) {
        if ($existing_post_id > 0) {
            wp_trash_post($existing_post_id);
            wp_cache_set(
                $cache_key,
                (int) $existing_post_id,
                $cache_group,
                5 * MINUTE_IN_SECONDS,
            );
            return new WP_REST_Response(
                [
                    "success" => true,
                    "post_id" => $existing_post_id,
                    "status" => "trash",
                ],
                200,
            );
        }
        return new WP_REST_Response(
            ["success" => false, "reason" => "not_found"],
            200,
        );
    }

    $incoming_hash = isset($params["content_hash"])
        ? (string) $params["content_hash"]
        : "";

    // Early exit only if hash matches AND schedule is unchanged.
    if ($existing_post_id > 0 && $incoming_hash !== "") {
        $last_hash = (string) get_post_meta(
            $existing_post_id,
            "_masspost_content_hash",
            true,
        );

        // Compute incoming dates (already sanitized above)
        $incoming_date = isset($date) ? $date : current_time("mysql");
        $incoming_date_gmt = isset($date_gmt)
            ? $date_gmt
            : get_gmt_from_date($incoming_date);

        // Read existing scheduled dates
        $existing_date = (string) get_post_field(
            "post_date",
            $existing_post_id,
        );
        $existing_date_gmt = (string) get_post_field(
            "post_date_gmt",
            $existing_post_id,
        );

        $hash_same =
            $last_hash !== "" && hash_equals($last_hash, $incoming_hash);
        $dates_same =
            $existing_date === $incoming_date &&
            $existing_date_gmt === $incoming_date_gmt;

        if ($hash_same && $dates_same) {
            return new WP_REST_Response(["success" => true], 204); // No Content
        }
        // If hash same but dates differ, fall through to update post (schedule changed).
    }

    // Determine desired slug
    $incoming_slug = "";
    if (isset($params["slug"]) && $params["slug"] !== "") {
        $incoming_slug = sanitize_title($params["slug"]);
    }
    if ($incoming_slug === "") {
        $incoming_slug = sanitize_title($params["title"]);
    }

    // If updating an existing post, only change slug when it actually differs.
    // Always ensure uniqueness using wp_unique_post_slug().
    if (!empty($existing_post_id)) {
        $current_slug = get_post_field("post_name", $existing_post_id);
        if ($current_slug !== $incoming_slug) {
            $incoming_slug = wp_unique_post_slug(
                $incoming_slug,
                $existing_post_id,
                $status,
                "post",
                0,
            );
        } else {
            // Keep current (avoid unnecessary writes)
            $incoming_slug = $current_slug;
        }
    } else {
        // New post case
        $incoming_slug = wp_unique_post_slug(
            $incoming_slug,
            0,
            $status,
            "post",
            0,
        );
    }

    $post_arr = [
        "post_title" => sanitize_text_field($params["title"]),
        "post_content" => (string) $params["content"],
        "post_status" => $status,
        "post_date" => $date,
        "post_date_gmt" => $date_gmt,
        "post_name" => $incoming_slug, // ← use the resolved slug
        "post_type" => "post",
        "post_author" => get_current_user_id(),
    ];

    if ($existing_post_id > 0) {
        $post_arr["ID"] = $existing_post_id;
        $updated_id = wp_update_post($post_arr, true);
        if (is_wp_error($updated_id)) {
            return new WP_Error(
                "rest_post_update_failed",
                "Failed to update post",
                [
                    "status" => 500,
                ],
            );
        }
        $post_id = $updated_id;
    } else {
        $post_id = wp_insert_post($post_arr, true);
        if (is_wp_error($post_id)) {
            return new WP_Error("rest_post_failed", "Failed to create post", [
                "status" => 500,
            ]);
        }
        add_post_meta($post_id, "_masspost_source_post_id", $source_post_id, true);
    }
    wp_cache_set(
        $cache_key,
        (int) $post_id,
        $cache_group,
        5 * MINUTE_IN_SECONDS,
    );

    // Post format (only touch when needed)
    if (!empty($params["post_format"])) {
        $allowed_formats = [
            "aside",
            "audio",
            "gallery",
            "image",
            "link",
            "quote",
            "standard",
            "status",
            "video",
        ];
        $fmt = sanitize_text_field($params["post_format"]);
        if (in_array($fmt, $allowed_formats, true) && $fmt !== "standard") {
            if (get_post_format($post_id) !== $fmt) {
                set_post_format($post_id, $fmt);
            }
        } else {
            if (get_post_format($post_id)) {
                set_post_format($post_id, "");
            }
        }
    } else {
        if (get_post_format($post_id)) {
            set_post_format($post_id, "");
        }
    }

    // Categories (only set when changed)
    if (
        !empty($params["category_names"]) &&
        is_array($params["category_names"])
    ) {
        $category_ids = [];
        foreach ($params["category_names"] as $category_name) {
            $category_name = sanitize_text_field($category_name);
            if ($category_name === "") {
                continue;
            }

            $term = get_term_by("name", $category_name, "category");
            if ($term && !is_wp_error($term)) {
                $category_ids[] = (int) $term->term_id;
            } else {
                $created = wp_insert_term($category_name, "category");
                if (!is_wp_error($created) && isset($created["term_id"])) {
                    $category_ids[] = (int) $created["term_id"];
                }
            }
        }
        $current = wp_get_post_categories($post_id);
        $target = array_unique($category_ids);
        sort($current);
        sort($target);
        if ($current !== $target) {
            wp_set_post_categories($post_id, $target);
        }

        /**
         * ABC (Author by Category) Integration
         * Signal that categories have been set so ABC can apply author rules.
         * Passes source author info for potential matching.
         */
        do_action("masspost_post_categories_set", $post_id, [
            "author_login" => $params["author_login"] ?? "",
            "author_role"  => $params["author_role"] ?? "",
        ]);
    }

    // Tags (only set when changed)
    if (!empty($params["tags"]) && is_array($params["tags"])) {
        $new_tags = array_values(
            array_filter(array_map("sanitize_text_field", $params["tags"])),
        );
        $current = wp_get_post_tags($post_id, ["fields" => "names"]);
        if (!masspost_arrays_equal($current, $new_tags)) {
            wp_set_post_tags($post_id, $new_tags, false);
        }
    }

    // Featured image: only re-upload when the URL actually changed
    if (!empty($params["featured_image"])) {
        $image_url = esc_url_raw((string) $params["featured_image"]);
        $last_src = (string) get_post_meta(
            $post_id,
            "_masspost_featured_src",
            true,
        );
        if ($image_url && $image_url !== $last_src) {
            $image_id = masspost_upload_image($image_url, $post_id);
            if ($image_id) {
                set_post_thumbnail($post_id, $image_id);
                update_post_meta(
                    $post_id,
                    "_masspost_featured_src",
                    $image_url,
                );
            }
        }
    }

    // Sync images from post content to local media library
    // This downloads external images and replaces URLs with local ones
    masspost_sync_content_images($post_id);

    // Footnotes meta passthrough (optional)
    if (isset($params["footnotes"]) && $params["footnotes"] !== "") {
        update_post_meta($post_id, "footnotes", (string) $params["footnotes"]);
    }

    // Ensure a footnotes block placeholder exists if you rely on it
    $content = (string) get_post_field("post_content", $post_id);
    // Only append a footnotes placeholder when the core block exists.
    if (function_exists("has_block")) {
        $footnotes_registered = class_exists("WP_Block_Type_Registry")
            ? WP_Block_Type_Registry::get_instance()->is_registered(
                "core/footnotes",
            )
            : false;

        if ($footnotes_registered && !has_block("core/footnotes", $content)) {
            $content .= "\n<!-- wp:footnotes /-->";
            wp_update_post(["ID" => $post_id, "post_content" => $content]);
        }
    }

    // Store the new content hash (for fast future compares)
    if ($incoming_hash !== "") {
        update_post_meta($post_id, "_masspost_content_hash", $incoming_hash);
    }

    return new WP_REST_Response(
        ["success" => true, "post_id" => $post_id],
        200,
    );
}

/** REST: check existing mapping + last hash */
function masspost_check_post_exists($request)
{
    $source_post_id = (int) $request["source_post_id"];

    $cache_group = "masspost";
    $cache_key = "source_map_" . $source_post_id;

    $existing_post_id = wp_cache_get($cache_key, $cache_group);
    if (false === $existing_post_id) {
        global $wpdb;
        /* phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery */
        $existing_post_id = (int) $wpdb->get_var(
            $wpdb->prepare(
                "SELECT p.ID
                 FROM {$wpdb->postmeta} pm
                 INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
                 WHERE pm.meta_key = %s AND pm.meta_value = %d
                 LIMIT 1",
                "_masspost_source_post_id",
                $source_post_id,
            ),
        );
        wp_cache_set(
            $cache_key,
            $existing_post_id,
            $cache_group,
            5 * MINUTE_IN_SECONDS,
        );
    }

    if ($existing_post_id > 0) {
        $last_hash = (string) get_post_meta(
            $existing_post_id,
            "_masspost_content_hash",
            true,
        );
        return new WP_REST_Response(
            [
                "exists" => true,
                "post_id" => $existing_post_id,
                "content_hash" => $last_hash,
            ],
            200,
        );
    }

    return new WP_REST_Response(["exists" => false], 200);
}

/**
 * Sync images from post content to local media library
 *
 * Extracts all image URLs from post_content, downloads external images,
 * uploads them to the local media library, and replaces URLs in content.
 *
 * @param int    $post_id The post ID to process.
 * @param string $source_domain Optional. Only sync images from this domain.
 * @return bool True if content was updated, false otherwise.
 */
function masspost_sync_content_images($post_id, $source_domain = '')
{
    $post = get_post($post_id);
    if (!$post || empty($post->post_content)) {
        return false;
    }

    $content = $post->post_content;
    $local_host = wp_parse_url(home_url(), PHP_URL_HOST);

    // Find all image URLs in content (both <img> tags and background images)
    $image_urls = [];

    // Match <img> src attributes
    if (preg_match_all('/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $content, $matches)) {
        $image_urls = array_merge($image_urls, $matches[1]);
    }

    // Match srcset attributes
    if (preg_match_all('/srcset=["\']([^"\']+)["\']/i', $content, $matches)) {
        foreach ($matches[1] as $srcset) {
            // Parse srcset format: "url1 1x, url2 2x" or "url1 100w, url2 200w"
            $srcset_parts = explode(',', $srcset);
            foreach ($srcset_parts as $part) {
                $part = trim($part);
                $url = preg_replace('/\s+\d+[wx]$/i', '', $part);
                if ($url) {
                    $image_urls[] = trim($url);
                }
            }
        }
    }

    // Match wp:image block URLs
    if (preg_match_all('/"url":"([^"]+)"/i', $content, $matches)) {
        $image_urls = array_merge($image_urls, $matches[1]);
    }

    if (empty($image_urls)) {
        return false;
    }

    // Remove duplicates and filter
    $image_urls = array_unique($image_urls);

    // Get already-synced URLs from meta to avoid re-downloading
    $synced_urls = get_post_meta($post_id, '_masspost_synced_images', true);
    if (!is_array($synced_urls)) {
        $synced_urls = [];
    }

    $url_replacements = [];
    $new_synced = [];

    foreach ($image_urls as $image_url) {
        // Skip data URIs
        if (strpos($image_url, 'data:') === 0) {
            continue;
        }

        // Skip relative URLs that are already local
        if (strpos($image_url, '/') === 0 && strpos($image_url, '//') !== 0) {
            continue;
        }

        // Parse the URL
        $parsed = wp_parse_url($image_url);
        if (empty($parsed['host'])) {
            continue;
        }

        // Skip if already local
        if ($parsed['host'] === $local_host) {
            continue;
        }

        // If source_domain specified, only sync from that domain
        if ($source_domain && $parsed['host'] !== $source_domain) {
            continue;
        }

        // Check if already synced
        if (isset($synced_urls[$image_url])) {
            $url_replacements[$image_url] = $synced_urls[$image_url];
            $new_synced[$image_url] = $synced_urls[$image_url];
            continue;
        }

        // Download and upload the image
        $attachment_id = masspost_upload_image($image_url, $post_id);
        if ($attachment_id) {
            $local_url = wp_get_attachment_url($attachment_id);
            if ($local_url) {
                $url_replacements[$image_url] = $local_url;
                $new_synced[$image_url] = $local_url;
                masspost_log("Content image synced: {$image_url} -> {$local_url}");
            }
        }
    }

    if (empty($url_replacements)) {
        return false;
    }

    // Replace all URLs in content
    $new_content = $content;
    foreach ($url_replacements as $old_url => $new_url) {
        // Replace in img src
        $new_content = str_replace($old_url, $new_url, $new_content);
    }

    // Update post if content changed
    if ($new_content !== $content) {
        wp_update_post([
            'ID' => $post_id,
            'post_content' => $new_content,
        ]);

        // Store synced URLs for future reference
        update_post_meta($post_id, '_masspost_synced_images', $new_synced);

        return true;
    }

    return false;
}

/** Image fetch & attach */
function masspost_upload_image($image_url, $post_id)
{
    if (!$image_url) {
        return false;
    }

    $response = wp_remote_get($image_url, ["timeout" => 15]);
    if (is_wp_error($response)) {
        masspost_log(
            "Failed to fetch image: " . $response->get_error_message(),
        );
        return false;
    }

    $code = (int) wp_remote_retrieve_response_code($response);
    if ($code < 200 || $code > 299) {
        masspost_log("Image fetch HTTP {$code} for URL: {$image_url}");
        return false;
    }

    $ctype = wp_remote_retrieve_header($response, "content-type");
    if (!$ctype || stripos($ctype, "image/") !== 0) {
        masspost_log(
            "Non-image content-type ({$ctype}) for URL: {$image_url}",
        );
        return false;
    }

    $image_data = wp_remote_retrieve_body($response);
    if (empty($image_data)) {
        masspost_log("Empty image body for URL: {$image_url}");
        return false;
    }

    $upload_dir = wp_upload_dir();
    if (!wp_mkdir_p($upload_dir["path"])) {
        masspost_log("Failed to create upload dir: " . $upload_dir["path"]);
        return false;
    }

    $parsed = wp_parse_url($image_url);
    $path = isset($parsed["path"]) ? $parsed["path"] : "";
    $raw_name = basename($path ?: "image");
    $filename = sanitize_file_name($raw_name);
    if ($filename === "" || $filename === "." || $filename === "..") {
        $filename = "image-" . wp_generate_uuid4() . ".jpg";
    }
    $filename = wp_unique_filename($upload_dir["path"], $filename);
    $file = trailingslashit($upload_dir["path"]) . $filename;

    if (file_put_contents($file, $image_data) === false) {
        masspost_log("Failed to save image to: {$file}");
        return false;
    }

    $wp_filetype = wp_check_filetype($filename, null);
    if (
        empty($wp_filetype["type"]) ||
        stripos($wp_filetype["type"], "image/") !== 0
    ) {
        wp_delete_file($file);
        masspost_log("Invalid image filetype for {$filename}");
        return false;
    }

    $attachment = [
        "post_mime_type" => $wp_filetype["type"],
        "post_title" => preg_replace('/\.[^.]+$/', "", $filename),
        "post_content" => "",
        "post_status" => "inherit",
    ];

    $attach_id = wp_insert_attachment($attachment, $file, $post_id);
    if (is_wp_error($attach_id)) {
        wp_delete_file($file);
        masspost_log("Failed to insert attachment for post {$post_id}");
        return false;
    }

    require_once ABSPATH . "wp-admin/includes/image.php";
    $attach_data = wp_generate_attachment_metadata($attach_id, $file);
    wp_update_attachment_metadata($attach_id, $attach_data);

    return $attach_id;
}

/** (Optional) Shortcode mirror of the checkboxes */
function masspost_share_to_shortcode()
{
    ob_start();
    $websites = get_option("masspost_websites", []);

    if (!empty($websites)) {
        echo '<div class="share-post-to">';
        echo '<br><br><label for="share_to_sites">' .
            esc_html__(
                "Select the other websites to publish the news to:",
                "masspost",
            ) .
            "</label><br>";
        foreach ($websites as $index => $website) {
            printf(
                '<input type="checkbox" name="share_to_site[]" value="%s"> %s<br>',
                esc_attr((string) $index),
                esc_html(trim(isset($website["name"]) ? $website["name"] : "")),
            );
        }
        echo "</div>";
    } else {
        echo esc_html__("No target websites have been added yet.", "masspost");
    }

    return ob_get_clean();
}
add_shortcode("share_post_to", "masspost_share_to_shortcode");
