<?php
/**
 * Plugin Name: BrassGate Assistants Deluxe (v4)
 * Description: Next-gen Assistants (Chat + Report Studio) with sectioned composer, sources drawer, copy buttons, quotas, progressive concat preview, auto-save & saved list.
 * Version: 1.2.0
 * Author: BrassGate
 */

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

if ( file_exists(__DIR__ . '/includes/bg-headers.php') ) {
  require_once __DIR__ . '/includes/bg-headers.php';
}

if ( file_exists(__DIR__ . '/includes/bg-user-context.php') ) {
  require_once __DIR__ . '/includes/bg-user-context.php';
}

if ( file_exists(__DIR__ . '/includes/content-studio.php') ) {
  require_once __DIR__ . '/includes/content-studio.php';
}

if ( file_exists(__DIR__ . '/includes/rest.php') ) {
  require_once __DIR__ . '/includes/rest.php';
}

$lvs_storage_page = __DIR__ . '/pages/lvs-storage-v3.php';
if ( file_exists($lvs_storage_page) ) {
  require_once $lvs_storage_page;
}

$dashboard_file = __DIR__ . '/brassgate/brassgate-dashboard.php';
if ( file_exists($dashboard_file) ) {
  require_once $dashboard_file;
}

$lvu_settings = __DIR__ . '/inc/lvu-settings.php';
if ( file_exists($lvu_settings) ) {
  require_once $lvu_settings;
}
$lvu_ajax = __DIR__ . '/inc/lvu-ajax.php';
if ( file_exists($lvu_ajax) ) {
  require_once $lvu_ajax;
}
$lvu_proxy = __DIR__ . '/inc/lvu-proxy.php';
if ( file_exists($lvu_proxy) ) {
  require_once $lvu_proxy;
}
if ( file_exists(__DIR__ . '/inc/calendar-schema.php') ) {
  require_once __DIR__ . '/inc/calendar-schema.php';
}
if ( file_exists(__DIR__ . '/inc/calendar-rest.php') ) {
  require_once __DIR__ . '/inc/calendar-rest.php';
}
if ( file_exists(__DIR__ . '/inc/avery-rest.php') ) {
  require_once __DIR__ . '/inc/avery-rest.php';
}
if ( file_exists(__DIR__ . '/inc/social-tracking.php') ) {
  require_once __DIR__ . '/inc/social-tracking.php';
}

// Calendar: activation + deactivation hooks (phase 1: schema + cron)
register_activation_hook(__FILE__, function () {
    if (function_exists('bgad_calendar_install_tables')) {
        bgad_calendar_install_tables();
    }
    if (function_exists('bgad_calendar_schedule_cron')) {
        bgad_calendar_schedule_cron();
    }
    if (function_exists('bgad_social_install_tables')) {
        bgad_social_install_tables();
    }
    if (function_exists('bgad_social_schedule_cron')) {
        bgad_social_schedule_cron();
    }
});

register_deactivation_hook(__FILE__, function () {
    if (function_exists('bgad_calendar_clear_cron')) {
        bgad_calendar_clear_cron();
    }
    if (function_exists('bgad_social_clear_cron')) {
        bgad_social_clear_cron();
    }
});

// Ensure schema is present after upgrades
add_action('plugins_loaded', function () {
    if (function_exists('bgad_calendar_maybe_upgrade')) {
        bgad_calendar_maybe_upgrade();
    }
    if (function_exists('bgad_social_maybe_upgrade')) {
        bgad_social_maybe_upgrade();
    }
});

final class BrassGate_Assistants_Deluxe {
    const VERSION     = '1.2.2-cachebust';
    const SLUG        = 'brassgate-assistants-deluxe';
    const NONCE       = 'bga4_nonce';
    const MANAGER_SLUG = 'brassgate-manager';
    const MANAGER_SLUG_LEGACY = 'brassgate-plans';
  // Video Studio: circuit breaker state (static)
  private static $vs_cb_fails = 0;
  private static $vs_cb_last_fail = 0;
  private static $vs_cb_open_until = 0;
  private static $page_feature_map = null;
  private static $current_quota_context = null;
  private static $current_quota_features = [];

  // NEW: slug for the new Deluxe Chat page
  const SLUG_ADCHAT = 'brassgate-assistants-deluxe-deluxe-chat';
  const SLUG_IDEAS_BOARD_MANAGER = 'bgad-ideas-board-manager';
    // NEW: slug for embedded Documents repo (v3)
    const SLUG_DOCS   = 'brassgate-assistants-deluxe-docs';
  // NEW: slug for compact Doc Reader (narrow pane)
  const SLUG_READER = 'brassgate-assistants-deluxe-doc-reader';
  const SLUG_SOCIAL_TRACKING = 'bgad-social-tracking';
  const SLUG_SOCIAL_CONNECTIONS = 'bgad-social-connections';
  // Large Video Interrogator admin page slug
  const SLUG_LVI    = 'bgad-large-video-interrogator';
  const SLUG_LVS    = 'large-video-suite';
    const SLUG_LARGE_VIDEO_STORAGE = 'brassgate-assistants-deluxe-large-video-storage';
    const SLUG_VERTICAL_SOCIALS = 'bgad-vertical-socials-helper';
    const SLUG_HORIZONTAL_SOCIALS = 'bgad-horizontal-socials';
    const SLUG_AUDIO_TEST = 'bgad-audio-test';
    const SLUG_ARTICLES_SOCIALS_2 = 'brassgate-assistants-deluxe-articles-socials-2';
    const SLUG_ARTICLES_SOCIALS_3 = 'brassgate-assistants-deluxe-articles-socials-v3';
    const SLUG_CALENDAR = 'brassgate-calendar';
    const SLUG_MEETING_ROOM = 'bgad-meeting-room';
    const SLUG_MEETING_MINUTES = 'bgad-meeting-minutes';
    const SLUG_MEETING_SUMMARIES = 'bgad-meeting-summaries';
    const SLUG_SECRETARY = 'bgad-avery';
    const SLUG_SECRETARY_LEGACY = 'bgad-secretary';
    const SLUG_MEETING_ARCHIVE = 'bgad-meeting-archive';
    const SLUG_ACTION_BOARD = 'bgad-action-board';
    const SLUG_MEETING_TIMELINE = 'bgad-meeting-timeline';
    const SLUG_JIMMY = 'bgad-jimmy';
    const SLUG_SECRETARY_GMAIL_AUTH = 'bgad-avery-gmail-auth';
    const SLUG_SECRETARY_GMAIL_AUTH_LEGACY = 'bgad-secretary-gmail-auth';
    const SLUG_ADMIN_MANAGERS = 'bgad-admin-managers';
    const SLUG_ALLOWANCES = 'bgad-allowances';
  const SLUG_ADMIN_FILE_CULL = 'bgad-admin-file-cull';
  const SLUG_LARGE_VIDEO_UPLOADER = 'bgad-large-video-uploader';
  const SLUG_VIDEO_DIRECTOR = 'bgad-video-director';

  // Content Studio target page (existing module provided elsewhere)
  const CONTENT_STUDIO_PAGE = 'brassgate-content-studio-v3';
  // Helper slug for our submenu entry that forwards to Content Studio
  const SLUG_CONTENT_STUDIO = 'brassgate-assistants-deluxe-content-studio';

  // LaunchOS removed — slugs, meta, and SSO key options deleted

    // Existing keys
    const OPT_API_URL = 'brassgate_api_base_url';
    const USER_KEY    = 'bg_user_api_key';
    const MANAGER_KEY = 'bg_manager_api_key';

    // Pricing option keys (admin editable)
    const OPT_PRICE_BRASSO = 'bga4_price_brasso_base';
    const OPT_PRICE_AGENT  = 'bga4_price_agent_base';
    const OPT_MULT_ONEPAGER= 'bga4_price_mult_onepager';
    const OPT_MULT_SHORT   = 'bga4_price_mult_short';
    const OPT_MULT_EXT     = 'bga4_price_mult_extended';
    const OPT_SERVICE_API_KEY = 'bgad_service_api_key';

    // Quotas fallback integration — ONLY Admin Key now
    const OPT_ADMIN_KEY    = 'bga4_admin_key';

  // Report Studio removed: CPT and quick-start toggles deleted

  public static function init() {
        add_action('admin_menu',               [__CLASS__, 'menu']);
    add_action('admin_init',               [__CLASS__, 'redirect_legacy_large_video']);
    add_action('admin_init',               [__CLASS__, 'redirect_legacy_content_studio']);
    add_action('admin_init',               [__CLASS__, 'redirect_legacy_secretary']);
    // Late hook to hide the original Content Studio submenu under BrassGate root
        add_action('admin_menu',               [__CLASS__, 'reparent_content_studio'], 100);
        // Late hook to remove the default first submenu (duplicate of top-level)
        add_action('admin_menu',               [__CLASS__, 'fix_submenu_order'], 200);
    add_action('admin_enqueue_scripts',    [__CLASS__, 'assets']);
    add_action('admin_enqueue_scripts',    'bgad_enqueue_common_js');
        add_action('init',                     [__CLASS__, 'register_sharecard_rewrites']);
        add_filter('query_vars',               [__CLASS__, 'sharecard_query_vars']);
        add_action('template_redirect',        [__CLASS__, 'maybe_render_sharecard']);
    add_action('admin_notices',            [__CLASS__, 'admin_notices']);
        add_action('wp_enqueue_scripts',       [__CLASS__, 'frontend_assets']);

        // LaunchOS removed — SSO endpoints, workbench routes, and overlay markup

        // AJAX bridge → backend v3 endpoints
        add_action('wp_ajax_bga4_send',            [__CLASS__, 'ajax_send']);
        add_action('wp_ajax_bga4_history',         [__CLASS__, 'ajax_history']);
        add_action('wp_ajax_bga4_convos',          [__CLASS__, 'ajax_convos']);
        add_action('wp_ajax_bga4_sources',         [__CLASS__, 'ajax_sources']);
        add_action('wp_ajax_bga4_usage',           [__CLASS__, 'ajax_usage']);   // quotas

  // Report Studio removed: no report AJAX actions or local saved reports

  // Admin health checks (admin only)
  add_action('wp_ajax_bga4_health',          [__CLASS__, 'ajax_health']);

        // Doc Repo (embedded from Core Tools Documents v3)
        add_action('wp_ajax_bgad_doc_list',    [__CLASS__, 'ajax_doc_list']);
        add_action('wp_ajax_bgad_doc_upload',  [__CLASS__, 'ajax_doc_upload']);
        add_action('wp_ajax_bgad_doc_approve', [__CLASS__, 'ajax_doc_approve']);
        add_action('wp_ajax_bgad_doc_index',   [__CLASS__, 'ajax_doc_index']);
        add_action('wp_ajax_bgad_doc_delete',  [__CLASS__, 'ajax_doc_delete']);
        add_action('wp_ajax_bgad_quota_snapshot', [__CLASS__, 'ajax_quota_snapshot']);
        add_action('wp_ajax_bgad_quota_refresh', [__CLASS__, 'ajax_quota_refresh']);
        add_action('wp_ajax_bgad_allowances_refresh', [__CLASS__, 'ajax_allowances_refresh']);
        add_action('wp_ajax_bgad_whoami', [__CLASS__, 'ajax_whoami']);
        add_action('wp_ajax_bgad_create_sharecard', [__CLASS__, 'ajax_create_sharecard']);
        add_action('wp_ajax_bgad_sideload_backend_image', [__CLASS__, 'ajax_sideload_backend_image']);
        add_action('wp_ajax_bgad_upload_share_image', [__CLASS__, 'ajax_upload_share_image']);
        add_action('wp_ajax_bgad_image_manager_js', [__CLASS__, 'ajax_image_manager_js']);
        add_action('wp_ajax_bgad_as3v3_create_post', [__CLASS__, 'ajax_as3v3_create_post']);
        add_action('wp_ajax_bgad_social_state', [__CLASS__, 'ajax_social_state']);
        add_action('wp_ajax_bgad_social_connect', [__CLASS__, 'ajax_social_connect']);
        add_action('wp_ajax_bgad_social_refresh', [__CLASS__, 'ajax_social_refresh']);
        add_action('wp_ajax_bgad_social_fb_save_app', [__CLASS__, 'ajax_social_fb_save_app']);
        add_action('admin_post_bgad_fb_oauth_start', [__CLASS__, 'fb_oauth_start']);
        add_action('admin_post_bgad_fb_oauth_callback', [__CLASS__, 'fb_oauth_callback']);
        add_action('admin_post_bgad_fb_oauth_select', [__CLASS__, 'fb_oauth_select']);

  // Video Studio AJAX proxies (frontend shortcodes)
  add_action('wp_ajax_bgvs_preflight',        [__CLASS__, 'ajax_bgvs_preflight']);
  add_action('wp_ajax_bgvs_plan',             [__CLASS__, 'ajax_bgvs_plan']);
  add_action('wp_ajax_bgvs_preview_start',    [__CLASS__, 'ajax_bgvs_preview_start']);
  add_action('wp_ajax_bgvs_preview_status',   [__CLASS__, 'ajax_bgvs_preview_status']);
  add_action('wp_ajax_bgvs_preview_stream',   [__CLASS__, 'ajax_bgvs_preview_stream']);
  add_action('wp_ajax_bgvs_preview_delete',   [__CLASS__, 'ajax_bgvs_preview_delete']);
  add_action('wp_ajax_bgvs_audio_plan',       [__CLASS__, 'ajax_bgvs_audio_plan']);
  // Audio generation proxies
  add_action('wp_ajax_bgvs_music_start',      [__CLASS__, 'ajax_bgvs_music_start']);
  add_action('wp_ajax_bgvs_music_stream',     [__CLASS__, 'ajax_bgvs_music_stream']);
  add_action('wp_ajax_bgvs_narrate_start',    [__CLASS__, 'ajax_bgvs_narrate_start']);
  add_action('wp_ajax_bgvs_narrate_stream',   [__CLASS__, 'ajax_bgvs_narrate_stream']);
  add_action('wp_ajax_bgvs_voices',           [__CLASS__, 'ajax_bgvs_voices']);
  add_action('wp_ajax_bgvs_finalize_start',   [__CLASS__, 'ajax_bgvs_finalize_start']);
  add_action('wp_ajax_bgvs_library_list',     [__CLASS__, 'ajax_bgvs_library_list']);
  add_action('wp_ajax_bgvs_library_stream',   [__CLASS__, 'ajax_bgvs_library_stream']);
  add_action('wp_ajax_bgad_vertical_socials_status',   [__CLASS__, 'ajax_vertical_socials_status']);
  add_action('wp_ajax_bgad_vertical_socials_ping',     [__CLASS__, 'ajax_vertical_socials_ping']);
  add_action('wp_ajax_bgad_vertical_socials_download', [__CLASS__, 'ajax_vertical_socials_download']);
  add_action('wp_ajax_bgad_horizontal_socials_ping',   [__CLASS__, 'ajax_horizontal_socials_ping']);

        // Large Video Interrogator proxy (admin-only)
        add_action('wp_ajax_bgad_lvi_proxy', [__CLASS__, 'ajax_lvi_proxy']);

  // Shortcodes
  add_shortcode('bga4_assistant',      [__CLASS__, 'render_chat']);
  // Tombstone: keep shortcode to avoid breaking legacy content
  add_shortcode('bga4_report_studio',  [__CLASS__, 'render_report_studio']);
        // Settings (pricing + quotas admin key)
  // New: public shortcodes for Video Studio + Player
  add_shortcode('brassgate_video_studio', [__CLASS__, 'render_video_studio_embed']);
  add_shortcode('brassgate_video_player', [__CLASS__, 'render_video_player_embed']);
  add_shortcode('brassgate_avery', [__CLASS__, 'render_avery_shortcode']);
  add_shortcode('brassgate_jimmy', [__CLASS__, 'render_jimmy_shortcode']);
        add_action('admin_init', [__CLASS__, 'register_settings']);

        if (class_exists('BGAD_Content_Studio')) {
            BGAD_Content_Studio::init();
        }
    }

  // Remove the standalone Content Studio submenu from the BrassGate root menu,
  // since we mount it under Assistants Deluxe.
  public static function reparent_content_studio() {
    if (class_exists('BGAD_Content_Studio_Page')) {
      remove_submenu_page('brassgate-root', BGAD_Content_Studio_Page::SLUG);
    }
  }

  /** ---------- Sharecards (OG) ---------- */
  public static function register_sharecard_rewrites() {
    add_rewrite_rule('^share/i/([A-Za-z0-9_-]+)/?$', 'index.php?bgad_sharecard_slug=$matches[1]', 'top');
  }

  public static function sharecard_query_vars($vars) {
    $vars[] = 'bgad_sharecard_slug';
    return $vars;
  }

  public static function maybe_render_sharecard() {
    $slug = get_query_var('bgad_sharecard_slug');
    if (!$slug) return;
    $template = plugin_dir_path(__FILE__) . 'sharecard/sharecard.php';
    if (is_readable($template)) {
      include $template;
      exit;
    }
    status_header(404);
    echo 'Not found';
    exit;
  }

  public static function ajax_create_sharecard() {
    self::check_nonce();
    if (!current_user_can('manage_options')) {
      wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $title = sanitize_text_field($_POST['title'] ?? '');
    $description = sanitize_text_field($_POST['description'] ?? '');
    $image_url = esc_url_raw($_POST['image_url'] ?? '');
    $html_content = wp_kses_post($_POST['html_content'] ?? '');

    if ($title === '' || $image_url === '') {
      wp_send_json_error(['message' => 'Missing title or image'], 400);
    }

    $slug = sanitize_key(wp_generate_password(12, false, false));
    $upload = wp_upload_dir();
    $base_dir = trailingslashit($upload['basedir']) . 'brassgate-sharecards/' . $slug;
    if (!wp_mkdir_p($base_dir)) {
      wp_send_json_error(['message' => 'Unable to prepare sharecard storage'], 500);
    }

    $og_url = trailingslashit(home_url('/share/i/' . $slug . '/'));
    $html  = "<!doctype html><html><head>";
    $html .= '<meta charset="utf-8" />';
    $html .= '<meta property="og:title" content="' . esc_attr($title) . '">';
    $html .= '<meta property="og:description" content="' . esc_attr($description) . '">';
    $html .= '<meta property="og:image" content="' . esc_url($image_url) . '">';
    $html .= '<meta property="og:type" content="website">';
    $html .= '<meta name="twitter:card" content="summary_large_image">';
    $html .= '<meta name="robots" content="noindex">';
    $html .= '<title>' . esc_html($title) . '</title>';
    $html .= '</head><body>';
    $html .= '<img src="' . esc_url($image_url) . '" alt="">';
    $html .= '<article>' . $html_content . '</article>';
    $html .= '</body></html>';

    $file = trailingslashit($base_dir) . 'index.html';
    if (file_put_contents($file, $html) === false) {
      wp_send_json_error(['message' => 'Unable to write sharecard'], 500);
    }

    $card = [
      'slug' => $slug,
      'title' => $title,
      'description' => $description,
      'image_url' => $image_url,
      'created_at' => current_time('mysql'),
    ];
    $existing = get_option('bgad_sharecards', []);
    if (!is_array($existing)) $existing = [];
    $existing[$slug] = $card;
    update_option('bgad_sharecards', $existing, false);

    wp_send_json_success([
      'slug' => $slug,
      'share_url' => $og_url,
      'image_url' => $image_url,
      'title' => $title,
      'description' => $description,
    ]);
  }

  /**
   * Sideload an existing backend image into the WP media library for sharing.
   */
  public static function ajax_sideload_backend_image() {
    self::check_nonce();
    if (!current_user_can('manage_options')) {
      wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    $image_url = esc_url_raw($_POST['image_url'] ?? '');
    $platform  = sanitize_key($_POST['platform'] ?? 'social');
    if (!$image_url) {
      wp_send_json_error(['message' => 'Missing image URL'], 400);
    }

    $ext = strtolower(pathinfo(parse_url($image_url, PHP_URL_PATH), PATHINFO_EXTENSION));
    if (!$ext) {
      // Some signed URLs store the real filename in the query string (?path=/mnt/...jpg)
      $qs = [];
      parse_str((string) parse_url($image_url, PHP_URL_QUERY), $qs);
      if (!empty($qs['path']) && is_string($qs['path'])) {
        $ext = strtolower(pathinfo($qs['path'], PATHINFO_EXTENSION));
      }
    }

    $resp = wp_remote_get($image_url, ['timeout' => 20]);
    if (is_wp_error($resp)) {
      wp_send_json_error(['message' => $resp->get_error_message()], 500);
    }
    $code = (int) wp_remote_retrieve_response_code($resp);
    $body = wp_remote_retrieve_body($resp);
    $ctype = wp_remote_retrieve_header($resp, 'content-type');
    if ($code < 200 || $code >= 300 || !$body) {
      wp_send_json_error(['message' => 'Unable to fetch image'], 500);
    }
    $ctypeValid = $ctype && preg_match('#^image/(jpe?g|png)$#i', $ctype);
    $extValid = in_array($ext, ['jpg', 'jpeg', 'png'], true);
    if (!$ctypeValid && !$extValid) {
      wp_send_json_error(['message' => 'Only JPG and PNG are allowed'], 400);
    }
    // Normalize extension and mime type
    if (!$extValid) {
      $ext = stripos((string) $ctype, 'png') !== false ? 'png' : 'jpg';
    }
    $ext = $ext === 'jpeg' ? 'jpg' : $ext;
    if (!$ctypeValid) {
      // Fallback to extension-derived mime when remote server omits/uses octet-stream
      $ctype = $ext === 'png' ? 'image/png' : 'image/jpeg';
    } else {
      // Ensure jpeg mime normalizes to jpg extension if extension was empty
      if (!$ext) {
        $ext = stripos($ctype, 'png') !== false ? 'png' : 'jpg';
      }
    }

    $tmp = wp_tempnam($image_url);
    if (!$tmp) {
      wp_send_json_error(['message' => 'Unable to create temp file'], 500);
    }
    if (file_put_contents($tmp, $body) === false) {
      @unlink($tmp);
      wp_send_json_error(['message' => 'Unable to write temp file'], 500);
    }

    $filename = sprintf('brassgate-share-%s-%s.%s', $platform ?: 'social', time(), $ext ?: 'jpg');
    $file = [
      'name'     => sanitize_file_name($filename),
      'tmp_name' => $tmp,
      'type'     => $ctype ?: 'image/' . ($ext === 'png' ? 'png' : 'jpeg'),
      'size'     => filesize($tmp),
      'error'    => 0,
    ];

    require_once ABSPATH . 'wp-admin/includes/file.php';
    require_once ABSPATH . 'wp-admin/includes/media.php';
    require_once ABSPATH . 'wp-admin/includes/image.php';

    $sideload = media_handle_sideload($file, 0);
    @unlink($tmp);

    if (is_wp_error($sideload)) {
      wp_send_json_error(['message' => $sideload->get_error_message()], 500);
    }

    $url = wp_get_attachment_url($sideload);
    if (!$url) {
      wp_send_json_error(['message' => 'Upload did not return a URL'], 500);
    }

    wp_send_json_success([
      'public_url' => $url,
      'attachment_id' => $sideload,
    ]);
  }

  /** ---------- Social share uploads (sideload existing image) ---------- */
  public static function ajax_upload_share_image() {
    self::check_nonce();
    if (!self::user_has_plan()) {
      wp_send_json_error(['message' => 'Unauthorized'], 403);
    }
    if (empty($_FILES['share_image'])) {
      wp_send_json_error(['message' => 'No image provided'], 400);
    }
    $file = $_FILES['share_image'];
    $file['name'] = 'brassgate-share-' . uniqid('', true) . '-' . sanitize_file_name($file['name']);

    require_once ABSPATH . 'wp-admin/includes/file.php';
    $overrides = ['test_form' => false];
    $uploaded = wp_handle_sideload($file, $overrides);
    if (isset($uploaded['error'])) {
      wp_send_json_error(['message' => $uploaded['error']], 500);
    }
    $url = $uploaded['url'] ?? '';
    $type = $uploaded['type'] ?? '';
    $file_path = $uploaded['file'] ?? '';
    $attachment_id = 0;
    if ($url && $file_path) {
      $attachment = [
        'post_mime_type' => $type,
        'post_title' => preg_replace('/\.[^.]+$/', '', basename($file_path)),
        'post_content' => '',
        'post_status' => 'inherit'
      ];
      $attachment_id = wp_insert_attachment($attachment, $file_path);
      if (!is_wp_error($attachment_id)) {
        require_once ABSPATH . 'wp-admin/includes/image.php';
        $attach_data = wp_generate_attachment_metadata($attachment_id, $file_path);
        wp_update_attachment_metadata($attachment_id, $attach_data);
      }
    }
    if (empty($url)) {
      wp_send_json(['success' => false, 'message' => 'Upload did not return a URL.'], 500);
    }
    wp_send_json_success([
      'public_url' => $url,
      'url' => $url,
      'attachment_id' => $attachment_id,
    ]);
  }

  // Remove the default first submenu that mirrors the top-level page slug,
  // so our numbered items lead the list.
  public static function fix_submenu_order() {
    remove_submenu_page(self::SLUG, self::SLUG);
  }

  public static function redirect_legacy_content_studio() {
    if (!is_admin()) {
      return;
    }

    $page = isset($_GET['page']) ? sanitize_key($_GET['page']) : '';
    if ($page !== self::SLUG . '-home') {
      return;
    }

    $target = admin_url('admin.php?page=' . self::CONTENT_STUDIO_PAGE);
    if (!headers_sent()) {
      wp_safe_redirect($target);
      exit;
    }

    echo '<div class="wrap"><h1>Content Studio</h1><p>';
    printf(
      esc_html__('Content Studio has moved. Continue to the new location: %s', 'brassgate-assistants-deluxe'),
      '<a href="' . esc_url($target) . '">' . esc_html__('Open Content Studio', 'brassgate-assistants-deluxe') . '</a>'
    );
    echo '</p></div>';
    exit;
  }

  public static function redirect_legacy_large_video() {
    if (!is_admin()) {
      return;
    }

    $page = isset($_GET['page']) ? sanitize_key($_GET['page']) : '';
    $legacy_slugs = array(
      self::SLUG_LVI          => __('Large Video Interrogator has moved to Large Video Storage.', 'bgad'),
      self::SLUG_LVS          => __('Large Video Suite has moved to Large Video Storage.', 'bgad'),
      'lvs-director-v3'       => __('Large Video Suite Director has moved to Large Video Storage.', 'bgad'),
      'lvs-storage-v3'        => __('Large Video Storage has moved to the refreshed Large Video Storage page.', 'bgad'),
    );

    if (!isset($legacy_slugs[$page])) {
      return;
    }

    $target = admin_url('admin.php?page=' . self::SLUG_LARGE_VIDEO_STORAGE);
    if (!headers_sent()) {
      wp_safe_redirect($target);
      exit;
    }

    $notice = $legacy_slugs[$page];
    echo '<div class="wrap"><h1>' . esc_html__('Large Video Storage', 'bgad') . '</h1><p>';
    echo esc_html($notice) . ' ';
    echo '<a href="' . esc_url($target) . '">' . esc_html__('Open Large Video Storage', 'bgad') . '</a>';
    echo '</p></div>';
    exit;
  }

  public static function redirect_legacy_secretary() {
    if (!is_admin()) {
      return;
    }

    $page = isset($_GET['page']) ? sanitize_key($_GET['page']) : '';
    if ($page !== self::SLUG_SECRETARY_LEGACY && $page !== self::SLUG_SECRETARY_GMAIL_AUTH_LEGACY) {
      return;
    }

    $target_page = ($page === self::SLUG_SECRETARY_GMAIL_AUTH_LEGACY)
      ? self::SLUG_SECRETARY_GMAIL_AUTH
      : self::SLUG_SECRETARY;
    $target = admin_url('admin.php?page=' . $target_page);

    if (!headers_sent()) {
      wp_safe_redirect($target);
      exit;
    }

    echo '<div class="wrap"><h1>Avery</h1><p>';
    printf(
      esc_html__('The Avery page has moved. Continue to the new location: %s', 'brassgate-assistants-deluxe'),
      '<a href="' . esc_url($target) . '">' . esc_html__('Open Avery', 'brassgate-assistants-deluxe') . '</a>'
    );
    echo '</p></div>';
    exit;
  }

  /** LaunchOS SSO removed */

  /** LaunchOS Workbench routes removed */

  /** ---------------- Admin Notices (stability) ---------------- */
  public static function admin_notices() {
    if (!is_admin() || !current_user_can('manage_options')) return;
    $page = isset($_GET['page']) ? sanitize_key($_GET['page']) : '';
    if ($page !== self::SLUG_ADCHAT && $page !== self::SLUG) return;

    $key = self::guess_client_key();
    if (!$key) {
      echo '<div class="notice notice-warning"><p><strong>Assistants Deluxe:</strong> No client API key detected. Set <code>BRASSGATE_CLIENT_KEY</code> in env or add a User/Manager API key to your profile to enable chat and document lookups.</p></div>';
    }
  }

    /** CPT removed: Report Studio decommissioned */

  /** ---------------- Menus ---------------- */
    public static function menu() {
    // Top-level → container that forwards to Content Studio
    add_menu_page(
      'Assistants Deluxe',
      'Assistants Deluxe',
      'read',
      self::SLUG,
      [__CLASS__, 'render_content_studio'],
      'dashicons-format-chat',
      57
    );

    // Remove Content Studio submenu; keep access via top-level page redirect.

    // Create
    add_submenu_page(
      self::SLUG,
      'Articles & Social Media',
      'Articles & Social Media',
      'read',
      self::SLUG_ARTICLES_SOCIALS_3,
      [__CLASS__, 'render_articles_socials_3'],
      10
    );
    add_submenu_page(
      self::SLUG,
      'Image Manager',
      'Image Manager',
      'read',
      self::SLUG . '-image-manager',
      [__CLASS__, 'render_image_manager'],
      12
    );
    add_submenu_page(
      self::SLUG,
      'Video Studio',
      'Video Studio',
      'read',
      self::SLUG . '-video-studio',
      [__CLASS__, 'render_video_studio'],
      13
    );

    // Video & Media
    add_submenu_page(
      self::SLUG,
      'Video Socials (Vertical)',
      'Video Socials (Vertical)',
      'read',
      self::SLUG_VERTICAL_SOCIALS,
      [__CLASS__, 'render_vertical_socials_helper'],
      20
    );
    add_submenu_page(
      self::SLUG,
      'Video Socials (Horizontal)',
      'Video Socials (Horizontal)',
      'read',
      self::SLUG_HORIZONTAL_SOCIALS,
      [__CLASS__, 'render_horizontal_socials'],
      21
    );
    add_submenu_page(
      self::SLUG,
      'Portrait Video Storage',
      'Portrait Video Storage',
      'read',
      self::SLUG . '-videos-portrait',
      [__CLASS__, 'render_videos_portrait'],
      22
    );
    add_submenu_page(
      self::SLUG,
      'Video Storage',
      'Video Storage',
      'read',
      self::SLUG . '-videos',
      [__CLASS__, 'render_videos'],
      23
    );
    add_submenu_page(
      self::SLUG,
      'Splash Images',
      'Splash Images',
      'read',
      self::SLUG . '-splash',
      [__CLASS__, 'render_splash'],
      24
    );

    // Organisation
    add_submenu_page(
      self::SLUG,
      'Document Repo',
      'Document Repo',
      'read',
      self::SLUG_DOCS,
      [__CLASS__, 'render_doc_repo'],
      30
    );
    add_submenu_page(
      self::SLUG,
      'Doc Reader',
      'Doc Reader',
      'read',
      self::SLUG_READER,
      [__CLASS__, 'render_doc_reader'],
      31
    );
    add_submenu_page(
      self::SLUG,
      'Timeline Mode',
      'Timeline Mode',
      'read',
      self::SLUG_MEETING_TIMELINE,
      [__CLASS__, 'render_meeting_timeline'],
      32
    );
    add_submenu_page(
      self::SLUG,
      'Calendar',
      'Calendar',
      'read',
      self::SLUG_CALENDAR,
      [__CLASS__, 'render_calendar'],
      33
    );

    // Planning
    add_submenu_page(
      self::SLUG,
      'Ideas Board',
      'Ideas Board',
      'read',
      self::SLUG_ADCHAT,
      [__CLASS__, 'render_ideas_board'],
      40
    );
    add_submenu_page(
      self::SLUG,
      'Action Board',
      'Action Board',
      'read',
      self::SLUG_ACTION_BOARD,
      [__CLASS__, 'render_action_board'],
      41
    );

    // AI Workspaces
    add_submenu_page(
      self::SLUG,
      'Avery (AI)',
      'Avery (AI)',
      'read',
      self::SLUG_SECRETARY,
      [__CLASS__, 'render_secretary'],
      50
    );
    add_submenu_page(
      self::SLUG,
      'Meeting Room (AI)',
      'Meeting Room (AI)',
      'read',
      self::SLUG_MEETING_ROOM,
      [__CLASS__, 'render_meeting_room'],
      51
    );
    add_submenu_page(
      self::SLUG,
      'Meeting Minutes (AI)',
      'Meeting Minutes (AI)',
      'read',
      self::SLUG_MEETING_MINUTES,
      [__CLASS__, 'render_meeting_minutes'],
      52
    );
    add_submenu_page(
      self::SLUG,
      'Meeting Summaries (AI)',
      'Meeting Summaries (AI)',
      'read',
      self::SLUG_MEETING_SUMMARIES,
      [__CLASS__, 'render_meeting_summaries'],
      53
    );

    // Analytics
    add_submenu_page(
      self::SLUG,
      'Social Media Tracking',
      'Social Media Tracking',
      'read',
      self::SLUG_SOCIAL_TRACKING,
      [__CLASS__, 'render_social_tracking'],
      60
    );

    // Hidden/internal pages (keep routes but remove from menu)
    add_submenu_page(
      null,
      'Ideas Board (Manager)',
      'Ideas Board (Manager)',
      'read',
      self::SLUG_IDEAS_BOARD_MANAGER,
      [__CLASS__, 'render_ideas_board_manager']
    );
    add_submenu_page(
      null,
      'Audio Test',
      'Audio Test',
      'manage_options',
      self::SLUG_AUDIO_TEST,
      [__CLASS__, 'render_audio_test']
    );
    add_submenu_page(
      null,
      'Allowances',
      'Allowances',
      'read',
      self::SLUG_ALLOWANCES,
      [__CLASS__, 'render_allowances']
    );

      // Avery Gmail auth helper (hidden from menu)
      add_submenu_page(
        null,
        'Avery Gmail Auth',
        'Avery Gmail Auth',
        'read',
        self::SLUG_SECRETARY_GMAIL_AUTH,
        [__CLASS__, 'render_secretary_gmail_auth']
      );

    // Hidden connections plumbing page
    add_submenu_page(
      null,
      'Social Media Connections',
      'Social Media Connections',
      'read',
      self::SLUG_SOCIAL_CONNECTIONS,
      [__CLASS__, 'render_social_connections']
    );

    add_submenu_page(
      null,
      'Admin - File Cull',
      'Admin - File Cull',
      'manage_options',
      self::SLUG_ADMIN_FILE_CULL,
      [__CLASS__, 'render_admin_file_cull']
    );
    add_submenu_page(
      null,
      'Admin - Managers',
      'Admin - Managers',
      'manage_options',
      self::SLUG_ADMIN_MANAGERS,
      [__CLASS__, 'render_admin_managers']
    );

  // Admin - Pricing at end
  add_submenu_page(
    null,
    'Pricing & Quotas',
    'Admin - Pricing',
    'manage_options',
    self::SLUG . '-pricing',
    [__CLASS__, 'render_pricing']
  );

    // LaunchOS menus removed
    }

    /** ---------------- Settings ---------------- */
    public static function register_settings() {
        // Pricing
        register_setting(self::SLUG, self::OPT_PRICE_BRASSO, ['type'=>'number','default'=>20]);
        register_setting(self::SLUG, self::OPT_PRICE_AGENT,  ['type'=>'number','default'=>80]);
        register_setting(self::SLUG, self::OPT_MULT_ONEPAGER,['type'=>'number','default'=>1]);
        register_setting(self::SLUG, self::OPT_MULT_SHORT,   ['type'=>'number','default'=>5]);
        register_setting(self::SLUG, self::OPT_MULT_EXT,     ['type'=>'number','default'=>10]);

        // Quotas fallback admin key
        register_setting(self::SLUG, self::OPT_ADMIN_KEY, ['type'=>'string','default'=>'']);
        register_setting(self::SLUG, self::OPT_SERVICE_API_KEY, ['type' => 'string', 'default' => '']);
    }

    /** ---------------- Utilities ---------------- */
    public static function manager_slug() {
        if (defined('BG_MANAGER_MENU_SLUG') && BG_MANAGER_MENU_SLUG) {
            return sanitize_key(BG_MANAGER_MENU_SLUG);
        }

        return self::MANAGER_SLUG;
    }

  private static function manager_avatar_urls() {
    $ids = array('finance','marketing','business','social','creative','producer','sales','community','avery');
        $base_dir = plugin_dir_path(__FILE__) . 'images/';
        $base_url = plugins_url('images/', __FILE__);
        $default = $base_url . 'manager_default.png';
        $out = array();
        foreach ($ids as $id) {
            $found = glob($base_dir . "manager_{$id}.*");
            $out[$id] = $found ? $base_url . basename($found[0]) : $default;
        }
        if ($out['avery'] === $default) {
            $legacy = glob($base_dir . "manager_secretary.*");
            if ($legacy) {
                $out['avery'] = $base_url . basename($legacy[0]);
            }
        }
        $out['default'] = $default;
        return $out;
    }

    private static function env_flag($name, $default = true) {
        $raw = getenv($name);
        if ($raw === false || $raw === '') {
            return (bool) $default;
        }

        $filtered = filter_var($raw, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
        if ($filtered === null) {
            return (bool) $raw;
        }

        return (bool) $filtered;
    }

    public static function lvs_director_features() {
        $flags = array(
            'enabled'  => self::env_flag('BGAD_FEATURE_LVS_DIRECTOR', true),
            'analysis' => self::env_flag('BGAD_FEATURE_LVS_DIRECTOR_ANALYSIS', true),
            'clipGrid' => self::env_flag('BGAD_FEATURE_LVS_DIRECTOR_CLIPS', true),
            'controls' => self::env_flag('BGAD_FEATURE_LVS_DIRECTOR_CONTROLS', true),
            'plan'     => self::env_flag('BGAD_FEATURE_LVS_DIRECTOR_PLAN', true),
            'preview'  => self::env_flag('BGAD_FEATURE_LVS_DIRECTOR_PREVIEW', true),
            'export'   => self::env_flag('BGAD_FEATURE_LVS_DIRECTOR_EXPORT', true),
        );

        if (empty($flags['enabled'])) {
            foreach ($flags as $key => $value) {
                if ($key === 'enabled') {
                    continue;
                }
                $flags[$key] = false;
            }
        }

        return $flags;
    }

    public static function manager_url() {
        return admin_url('admin.php?page=' . self::manager_slug());
    }

    private static function page_feature_map() {
        if (self::$page_feature_map !== null) {
            return self::$page_feature_map;
        }

        $map = array();

        if (function_exists('bg_get_assistants_page_feature_map')) {
            $map = bg_get_assistants_page_feature_map();
        }

        if (!is_array($map) || empty($map)) {
            $map = self::default_page_feature_map();
        }

        self::$page_feature_map = $map;
        return self::$page_feature_map;
    }

    private static function default_page_feature_map() {
        return array(
            self::SLUG_DOCS => array('features' => array('docs_upload', 'docs_tag')),
            self::SLUG_READER => array('features' => array('docs_upload')),
            self::SLUG_ADCHAT => array('features' => array('chat_messages')),
            self::SLUG . '-videos' => array('features' => array('videos_upload', 'videos_tag')),
            self::SLUG . '-videos-portrait' => array('features' => array('videos_upload', 'videos_tag')),

            self::SLUG . '-video-studio' => array('features' => array('video_generate')),
            self::SLUG . '-image-manager' => array('features' => array('images_upload', 'images_tag')),
            self::SLUG . '-splash' => array('features' => array('images_upload')),
            self::SLUG . '-live-director' => array('features' => array('video_generate')),
            self::SLUG_LARGE_VIDEO_STORAGE => array('features' => array('videos_upload', 'videos_tag')),
            self::SLUG . '-lower-thirds' => array('features' => array('video_generate')),
              self::SLUG_VERTICAL_SOCIALS => array('features' => array('videos_upload', 'video_generate')),
              self::SLUG_LVI => array('features' => array('videos_upload')),
              self::SLUG_ARTICLES_SOCIALS_3 => array('features' => array('articles_from_text', 'articles_from_image')),
              self::SLUG_MEETING_ROOM => array('features' => array()),
              self::SLUG_SOCIAL_TRACKING => array('features' => array()),
              'brassgate-video-studio-debug' => array('features' => array('video_generate')),
              self::SLUG . '-pricing' => array('features' => array()),
              self::SLUG_JIMMY => array('features' => array('jimmy_enabled')),
          );
      }

    private static function features_for_page($page_slug) {
        $page_slug = sanitize_key($page_slug);
        if ($page_slug === '') {
            return array();
        }

        $map = self::page_feature_map();
        if (!isset($map[$page_slug])) {
            return array();
        }

        $features = isset($map[$page_slug]['features']) ? (array) $map[$page_slug]['features'] : array();
        $features = array_filter($features, static function ($feature) {
            return is_string($feature) && $feature !== '';
        });

        if (empty($features)) {
            return array();
        }

        return array_values(array_unique(array_map('sanitize_key', $features)));
    }

    private static function quota_features_for_scope($scope) {
        $scope = is_string($scope) ? trim($scope) : '';
        $scope_lower = strtolower($scope);
        if ($scope === '' || $scope_lower === 'auto') {
            $features = self::features_for_page(self::SLUG_LARGE_VIDEO_STORAGE);
            if (!empty($features)) {
                return $features;
            }
            return array('videos_upload', 'videos_tag');
        }

        $scope_key = sanitize_key($scope_lower !== '' ? $scope_lower : $scope);
        $aliases = array(
            'video' => self::SLUG_LARGE_VIDEO_STORAGE,
            'videos' => self::SLUG_LARGE_VIDEO_STORAGE,
            'director' => self::SLUG_LARGE_VIDEO_STORAGE,
            'advanced' => self::SLUG_LARGE_VIDEO_STORAGE,
            'studio' => self::SLUG_LARGE_VIDEO_STORAGE,
        );

        if (isset($aliases[$scope_key])) {
            $scope_key = $aliases[$scope_key];
        }

        if ($scope_key === self::SLUG_LARGE_VIDEO_STORAGE) {
            $features = self::features_for_page($scope_key);
            if (!empty($features)) {
                return $features;
            }
        }

        if (stripos($scope, 'feature:') === 0) {
            $feature = sanitize_key(substr($scope, strlen('feature:')));
            return $feature !== '' ? array($feature) : array();
        }

        if (strpos($scope, ',') !== false) {
            $parts = array_filter(array_map('sanitize_key', explode(',', $scope)));
            if (!empty($parts)) {
                return array_values(array_unique($parts));
            }
        }

        if ($scope_key !== '') {
            return array($scope_key);
        }

        return array('video_generate', 'videos_upload');
    }

    private static function resolve_plan_slug($user_id = 0) {
        $uid = $user_id ? intval($user_id) : get_current_user_id();
        if (!$uid) {
            return '';
        }

        if (function_exists('bg_get_user_plan')) {
            $plan = bg_get_user_plan($uid);
            if ($plan && isset($plan->slug)) {
                return sanitize_key($plan->slug);
            }
        }

        $plan_id = (int) get_user_meta($uid, 'bg_plan_id', true);
        if ($plan_id > 0 && function_exists('bg_get_plan')) {
            $plan = bg_get_plan($plan_id);
            if ($plan && isset($plan->slug)) {
                return sanitize_key($plan->slug);
            }
        }

        return '';
    }

    private static function has_plan_access_for_page($plan_slug, $page_slug) {
        $page_slug = sanitize_key($page_slug);
        if ($page_slug === '' || current_user_can('manage_options')) {
            return true;
        }

        $plan_slug = sanitize_key($plan_slug);
        if ($plan_slug === '') {
            return !self::user_has_plan() ? false : true;
        }

        if (function_exists('bg_plan_has_page_access')) {
            return bg_plan_has_page_access($plan_slug, $page_slug);
        }

        return true;
    }

    private static function backend_base() {
        $opt = trim((string)get_option(self::OPT_API_URL, ''));
        if ($opt === '') $opt = 'https://brassgate-backend.onrender.com';
        if (!preg_match('#^https?://#i', $opt)) $opt = 'https://' . $opt;
        return rtrim($opt, '/');
    }

    public static function render_videos() {
        $context = self::ensure_plan_access('Video Storage', array(), self::SLUG . '-videos');
        $GLOBALS['bgad_video_quota'] = $context;
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/videos.php';
        if (file_exists($tpl)) include $tpl; else echo '<h1>Videos</h1><p>Template missing.</p>';
        echo '</div>';
    }

    public static function render_large_video_storage() {
        echo '<div class="wrap"><h1>Large Video Storage</h1><p>This feature has been removed.</p></div>';
    }

    public static function render_large_video_uploader() {
        echo '<div class="wrap"><h1>Large Video Uploader</h1><p>This feature has been removed.</p></div>';
    }

    public static function render_video_director() {
        echo '<div class="wrap"><h1>Video Director</h1><p>This feature has been removed.</p></div>';
    }

    public static function render_videos_portrait() {
        $context = self::ensure_plan_access('Portrait Video Storage', array(), self::SLUG . '-videos-portrait');
        $GLOBALS['bgad_video_quota'] = $context;
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/videos-portrait.php';
        if (file_exists($tpl)) {
            include $tpl;
        } else {
            echo '<h1>Portrait Video Storage</h1><p>Template missing.</p>';
        }
        echo '</div>';
    }

    public static function render_audio() {
        $context = self::ensure_plan_access('Audio Generation', array('audio_generate'), self::SLUG . '-audio');
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/audio.php';
        if (file_exists($tpl)) include $tpl; else echo '<h1>Audio Generation</h1><p>Template missing.</p>';
        echo '</div>';
    }

    public static function render_music() {
        $context = self::ensure_plan_access('Music Generation', array('audio_generate'), self::SLUG . '-music');
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/music.php';
        if (file_exists($tpl)) include $tpl; else echo '<h1>Music Generation</h1><p>Template missing.</p>';
        echo '</div>';
    }
    public static function render_video_studio() {
        $context = self::ensure_plan_access('Video Studio (Landscape)', array(), self::SLUG . '-video-studio');
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/video-studio.php';
        if (file_exists($tpl)) include $tpl; else echo '<h1>Video Studio</h1><p>Template missing.</p>';
        echo '</div>';
    }


    public static function render_image_manager() {
        $context = self::ensure_plan_access('Image Manager', array(), self::SLUG . '-image-manager');
        if (empty($context['has_plan']) || !empty($context['needs_upgrade'])) {
            return;
        }

        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/image-manager.php';
        if (file_exists($tpl)) {
            include $tpl;
        } else {
            echo '<h1>Image Manager</h1><p>Template missing.</p>';
        }
        echo '</div>';
    }

    public static function render_large_video_interrogator() {
        if (current_user_can('manage_options')) {
            echo '<div class="notice notice-warning"><p>' . esc_html__('Large Video Interrogator has been replaced by the Large Video Suite. You were redirected to the new experience.', 'bgad') . '</p></div>';
        }
        self::render_large_video_suite();
    }

    public static function render_large_video_suite() {
        $context = self::quota_context(array('videos_upload'));
        $tpl = __DIR__ . '/pages/large-video-suite.php';
        if (file_exists($tpl)) {
            include $tpl;
        } else {
            echo '<div class="wrap"><h1>Large Video Suite</h1><p>Template missing.</p></div>';
        }
    }

    public static function render_vertical_socials_helper() {
        $context = self::ensure_plan_access('Vertical Socials Helper', array(), self::SLUG_VERTICAL_SOCIALS);
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/vertical-socials-helper.php';
        if (file_exists($tpl)) {
            include $tpl;
        } else {
            echo '<h1>Vertical Socials Helper</h1><p>Template missing.</p>';
        }
        echo '</div>';
    }

    public static function render_horizontal_socials() {
        $context = self::ensure_plan_access('Video Socials (Horizontal)', array(), self::SLUG_HORIZONTAL_SOCIALS);
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/horizontal-socials.php';
        if (file_exists($tpl)) {
            include $tpl;
        } else {
            echo '<h1>Video Socials (Horizontal)</h1><p>Template missing.</p>';
        }
        echo '</div>';
    }

    public static function render_audio_test() {
        if (!current_user_can('manage_options')) {
            wp_die('Unauthorized');
        }
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/audio-test-music.php';
        if (file_exists($tpl)) {
            include $tpl;
        } else {
            echo '<h1>Audio Test</h1><p>Template missing.</p>';
        }
        echo '</div>';
    }

    public static function render_splash() {
        $context = self::ensure_plan_access('Splash Images', array(), self::SLUG . '-splash');
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/splash.php';
        if (file_exists($tpl)) include $tpl; else echo '<h1>Splash Images</h1><p>Template missing.</p>';
        echo '</div>';
    }

    public static function render_live_director() {
        $context = self::ensure_plan_access('Live Director', array(), self::SLUG . '-live-director');
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/live-director.php';
        if (file_exists($tpl)) include $tpl; else echo '<h1>Live Director</h1><p>Template missing.</p>';
        echo '</div>';
    }

    public static function render_lower_thirds() {
        $context = self::ensure_plan_access('Lower Thirds', array(), self::SLUG . '-lower-thirds');
        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/overlays-lower-thirds.php';
        if (file_exists($tpl)) include $tpl; else echo '<h1>Lower Thirds</h1><p>Template missing.</p>';
        echo '</div>';
    }

    public static function render_video_studio_debug() {
        echo "<div class=\"wrap\"><h1>Video Studio Debug</h1><p>This feature has been removed.</p></div>";
    }

    public static function render_admin_file_cull() {
        if (!current_user_can('manage_options')) {
            echo '<div class="wrap"><div class="notice notice-error"><p>' . esc_html__('You do not have permission to access Admin File Cull.', 'bgad') . '</p></div></div>';
            return;
        }

        echo '<div class="wrap">';
        $tpl = __DIR__ . '/pages/admin-file-cull.php';
        if (file_exists($tpl)) {
            include $tpl;
        } else {
            echo '<h1>Admin File Cull</h1><p>Template missing.</p>';
        }
        echo '</div>';
    }

    // NEW: guess a usable client key for the JS page (env var → user key → manager key)
    private static function guess_client_key() {
        $env = getenv('BRASSGATE_CLIENT_KEY');
        if ($env && strlen($env) > 10) return $env;

        $u = wp_get_current_user();
        if ($u && $u->ID) {
            $user_k = trim((string)get_user_meta($u->ID, self::USER_KEY, true));
            if ($user_k) return $user_k;
            $mgr_k  = trim((string)get_user_meta($u->ID, self::MANAGER_KEY, true));
            if ($mgr_k) return $mgr_k;
        }
        return '';
    }

    private static function user_headers() {
        $u      = wp_get_current_user();
        $user_k = trim((string)get_user_meta($u->ID, self::USER_KEY, true));
        $mgr_k  = trim((string)get_user_meta($u->ID, self::MANAGER_KEY, true));

        $h = [
            'Accept'       => 'application/json',
            'Content-Type' => 'application/json',
            'X-BGA-Plugin' => 'assistants-deluxe-v4',
        ];
        if ($user_k) $h['X-User-API-Key'] = $user_k;
        if ($mgr_k)  $h['X-Manager-API-Key'] = $mgr_k;
        return $h;
    }

    public static function user_has_plan($user_id = 0) {
        $uid = $user_id ? intval($user_id) : get_current_user_id();
        if (!$uid) return false;
        if (user_can($uid, 'manage_options')) return true;

        $has_plan = false;
        if (function_exists('bg_get_user_plan')) {
            $plan = bg_get_user_plan($uid);
            if ($plan && (!isset($plan->is_active) || intval($plan->is_active) === 1)) {
                $has_plan = true;
            }
        } else {
            $plan_id = (int) get_user_meta($uid, 'bg_plan_id', true);
            if ($plan_id > 0) {
                $has_plan = true;
            }
        }

        return (bool) apply_filters('bgad_user_has_plan', $has_plan, $uid);
    }

    private static function fetch_remote_quota_snapshot($feature_slugs = array()) {
        $feature_slugs = array_values(array_unique(array_filter((array) $feature_slugs)));
        if (empty($feature_slugs)) {
            return array();
        }

        $joined = implode(',', $feature_slugs);
        $path = '/v3/dashboard/quota';
        if ($joined !== '') {
            $path .= '?resources=' . rawurlencode($joined);
        }

        $response = self::http('GET', $path, null, 20);
        if (empty($response['ok']) || !isset($response['data']) || !is_array($response['data'])) {
            return array();
        }

        $data = $response['data'];
        $resources = isset($data['resources']) && is_array($data['resources']) ? $data['resources'] : array();
        if (empty($resources)) {
            return array();
        }

        $user_id = get_current_user_id();
        $client_id = $user_id ? trim((string) get_user_meta($user_id, 'bg_client_id', true)) : '';
        if ($user_id && $client_id !== '' && isset($data['features']) && is_array($data['features'])) {
            $base = trim((string) getenv('BG_ORCHESTRATOR_BASE'));
            if ($base === '' && function_exists('bgad_backend_base')) {
                $base = (string) bgad_backend_base();
            }
            if ($base !== '') {
                $url = rtrim($base, '/') . '/quota-snapshot';
                $payload = array(
                    'client_id' => $client_id,
                    'user_id' => (string) $user_id,
                    'quota_snapshot' => $data,
                );
                wp_remote_post($url, array(
                    'timeout' => 1,
                    'headers' => array('Content-Type' => 'application/json'),
                    'body' => wp_json_encode($payload),
                ));
            }
        }

        $snapshot = array();
        foreach ($resources as $entry) {
            if (!is_array($entry)) {
                continue;
            }

            $slug = isset($entry['slug']) ? sanitize_key($entry['slug']) : '';
            if ($slug === '' || !in_array($slug, $feature_slugs, true)) {
                continue;
            }

            $status = isset($entry['status']) ? sanitize_key($entry['status']) : '';
            if ($status === 'unavailable') {
                continue;
            }

            $limit = null;
            if (isset($entry['limit']) && $entry['limit'] !== null) {
                $limit = (int) $entry['limit'];
            }

            $used = isset($entry['used']) ? max(0, (int) $entry['used']) : 0;

            $unlimited = ($status === 'unlimited');
            if (!$unlimited && $limit !== null && $limit < 0) {
                $unlimited = true;
            }

            if ($unlimited) {
                $limit = -1;
                $remaining = -1;
            } else {
                if ($limit === null) {
                    continue;
                }

                if (array_key_exists('remaining', $entry) && $entry['remaining'] !== null) {
                    $remaining = max(0, (int) $entry['remaining']);
                } else {
                    $remaining = max(0, $limit - $used);
                }
            }

            $snapshot[$slug] = array(
                'limit' => $limit,
                'used' => $used,
                'remaining' => $remaining,
                'unlimited' => $unlimited,
                'disabled' => false,
            );
        }

        return $snapshot;
    }

    // Non-blocking orchestrator quota snapshot reader (display-only).
    public static function fetch_orchestrator_quota_snapshot() {
        $user_id = get_current_user_id();
        if (!$user_id) {
            return null;
        }
        $client_id = trim((string) get_user_meta($user_id, 'bg_client_id', true));
        if ($client_id === '') {
            return null;
        }

        $base = trim((string) getenv('BG_ORCHESTRATOR_BASE'));
        if ($base === '') {
            $base = function_exists('bgad_backend_base') ? (string) bgad_backend_base() : '';
        }
        if ($base === '') {
            return null;
        }

        $url = rtrim($base, '/') . '/quota-snapshot/' . rawurlencode($client_id) . '/' . rawurlencode((string) $user_id);
        $response = wp_remote_get($url, array('timeout' => 1));
        if (is_wp_error($response)) {
            return null;
        }

        $code = (int) wp_remote_retrieve_response_code($response);
        if ($code !== 200) {
            return null;
        }

        $body = (string) wp_remote_retrieve_body($response);
        $data = json_decode($body, true);
        if (!is_array($data)) {
            return null;
        }

        return $data;
    }

    private static function quota_summary_for($slugs = array()) {
        $user_id = get_current_user_id();
        $slugs = array_values(array_unique(array_filter((array) $slugs)));

        $has_plan = self::user_has_plan($user_id);
        $is_admin = $user_id ? user_can($user_id, 'manage_options') : current_user_can('manage_options');
        $plan = null;
        $plan_limits = array();
        if (function_exists('bg_get_user_plan')) {
            $plan = bg_get_user_plan($user_id);
            if ($plan && isset($plan->limits) && is_array($plan->limits)) {
                $plan_limits = $plan->limits;
            }
        }

        $usage_summary = array();
        if (function_exists('bg_get_user_usage_summary')) {
            $usage_summary = bg_get_user_usage_summary($user_id);
        }

        $remote_snapshot = array();
        $orch_snapshot = self::fetch_orchestrator_quota_snapshot();
        if (is_array($orch_snapshot) && isset($orch_snapshot['features']) && is_array($orch_snapshot['features'])) {
            $remote_snapshot = $orch_snapshot['features'];
        }
        $snapshot = array();
        foreach ($slugs as $slug) {
            $row = isset($usage_summary[$slug]) ? $usage_summary[$slug] : null;
            if (isset($remote_snapshot[$slug]) && is_array($remote_snapshot[$slug])) {
                $remote = $remote_snapshot[$slug];
                $limit = isset($remote['limit']) ? (int) $remote['limit'] : 0;
                $used = isset($remote['used']) ? (int) $remote['used'] : 0;
                $remaining = isset($remote['remaining']) ? (int) $remote['remaining'] : ($limit === -1 ? -1 : max(0, $limit - $used));
                $unlimited = !empty($remote['unlimited']);
                $disabled = !empty($remote['disabled']);
            } elseif ($is_admin) {
                $limit = -1;
                $used = 0;
                $remaining = -1;
                $unlimited = true;
                $disabled = false;
            } elseif (is_array($row)) {
                $limit = isset($row['limit']) ? (int) $row['limit'] : 0;
                $used = isset($row['used']) ? (int) $row['used'] : 0;
                $remaining = isset($row['remaining']) ? (int) $row['remaining'] : ($limit === -1 ? -1 : max(0, $limit - $used));
                $unlimited = !empty($row['unlimited']);
                $disabled = !empty($row['disabled']);
            } else {
                $limit = isset($plan_limits[$slug]) ? (int) $plan_limits[$slug] : ($has_plan ? -1 : 0);
                $used = function_exists('bg_get_user_usage') ? bg_get_user_usage($user_id, $slug) : 0;
                if ($limit === -1) {
                    $remaining = -1;
                } elseif ($limit <= 0) {
                    $remaining = 0;
                } else {
                    $remaining = max(0, $limit - $used);
                }
                $unlimited = ($limit === -1);
                $disabled = ($limit === 0 && !$has_plan);
            }

            $snapshot[$slug] = array(
                'limit' => $limit,
                'used' => $used,
                'remaining' => $remaining,
                'unlimited' => $unlimited,
                'disabled' => $disabled,
            );
        }

        return array($snapshot, $plan, $has_plan);
    }

    public static function quota_context($feature_slugs = array()) {
        list($snapshot, $plan, $has_plan) = self::quota_summary_for($feature_slugs);

        $needs_upgrade = !$has_plan;
        $quota_exhausted = false;

        foreach ($snapshot as $row) {
            $disabled = !empty($row['disabled']);
            $unlimited = !empty($row['unlimited']);
            $remaining = isset($row['remaining']) ? (int) $row['remaining'] : 0;

            if (!$has_plan && $disabled) {
                $needs_upgrade = true;
            }

            if (!$disabled && !$unlimited && $remaining <= 0) {
                $quota_exhausted = true;
            }
        }

        $context = array(
            'has_plan' => $has_plan,
            'needs_upgrade' => $needs_upgrade,
            'quota_exhausted' => $quota_exhausted,
            'snapshot' => $snapshot,
            'cta_url' => self::manager_url(),
            'plan' => $plan ? array(
                'id' => isset($plan->id) ? (int) $plan->id : 0,
                'slug' => isset($plan->slug) ? sanitize_key($plan->slug) : '',
                'name' => isset($plan->name) ? (string) $plan->name : '',
            ) : null,
        );

        return $context;
    }

    private static function ensure_plan_access($page_title = '', $feature_slugs = array(), $page_slug = '') {
        $page_slug = $page_slug !== '' ? sanitize_key($page_slug) : '';
        if ($page_slug === '' && isset($_GET['page'])) {
            $page_slug = sanitize_key(wp_unslash($_GET['page']));
        }

        if (empty($feature_slugs)) {
            $feature_slugs = self::features_for_page($page_slug);
        }

        $context   = self::quota_context($feature_slugs);
        $plan_slug = '';

        if (isset($context['plan']['slug']) && $context['plan']['slug'] !== '') {
            $plan_slug = sanitize_key($context['plan']['slug']);
        } else {
            $plan_slug = self::resolve_plan_slug();
        }

        if (!self::has_plan_access_for_page($plan_slug, $page_slug)) {
            $context['has_plan']      = false;
            $context['needs_upgrade'] = true;
            $context['cta_url']       = self::manager_url();
        }

        return $context;
    }

    private static function ensure_plan_for_ajax() {
        if (self::user_has_plan()) return true;
        wp_send_json_error(['message' => 'plan_required'], 403);
    }
  // Video Studio headers: add feature tag and client bucket
  private static function vs_headers($feature = 'video-studio-v3') {
    if (function_exists('bgad_user_headers')) {
      $h = bgad_user_headers(true, $feature);
    } else {
      $h = self::user_headers();
      $h['X-BG-Feature'] = $feature;
    }
    $ck = self::guess_client_key();
    if ($ck) {
      $h['X-Brassgate-Client-Key'] = $ck;
    }
    if (!isset($h['X-BG-Feature'])) {
      $h['X-BG-Feature'] = $feature;
    }
    return $h;
  }

  private static function vertical_backend_base() {
    if (function_exists('bgad_backend_base')) {
      $base = bgad_backend_base();
    } else {
      $base = self::backend_base();
    }
    return rtrim($base, '/');
  }

  private static function vertical_headers($json = true) {
    if (function_exists('bgad_user_headers')) {
      $headers = bgad_user_headers($json, 'vertical-socials-helper');
    } else {
      $headers = self::user_headers();
      if (!$json && isset($headers['Content-Type'])) {
        unset($headers['Content-Type']);
      }
      $headers['X-BGA-Plugin'] = 'vertical-socials-helper';
    }

    if (!isset($headers['X-BG-Feature'])) {
      $headers['X-BG-Feature'] = 'vertical-socials-helper';
    }

    return $headers;
  }

  private static function horizontal_backend_base() {
    if (function_exists('bgad_backend_base')) {
      $base = bgad_backend_base();
    } else {
      $base = self::backend_base();
    }
    return rtrim($base, '/');
  }

  private static function horizontal_headers($json = true) {
    if (function_exists('bgad_user_headers')) {
      $headers = bgad_user_headers($json, 'horizontal-socials-helper');
    } else {
      $headers = self::user_headers();
      if (!$json && isset($headers['Content-Type'])) {
        unset($headers['Content-Type']);
      }
      $headers['X-BGA-Plugin'] = 'horizontal-socials-helper';
    }

    if (!isset($headers['X-BG-Feature'])) {
      $headers['X-BG-Feature'] = 'horizontal-socials-helper';
    }

    return $headers;
  }

  private static function vertical_activity_log($message, array $context = []) {
    $entry = [
      'ts'      => function_exists('current_time') ? current_time('mysql', true) : gmdate('Y-m-d H:i:s'),
      'message' => (string) $message,
    ];

    if (!empty($context)) {
      $entry['context'] = $context;
    }

    $option = 'bgad_vertical_activity_log';
    $log    = get_option($option, []);
    if (!is_array($log)) {
      $log = [];
    }

    $log[] = $entry;
    if (count($log) > 100) {
      $log = array_slice($log, -100);
    }

    update_option($option, $log, false);

    if (function_exists('do_action')) {
      do_action('bgad_vertical_activity_log', $entry);
    }
  }
    private static function http($method, $path, $body=null, $timeout=600, $extra_headers=[]) {
        $url = self::backend_base() . $path;
        $headers = array_merge(self::user_headers(), is_array($extra_headers)? $extra_headers : []);
        $args = [
            'method'  => strtoupper($method),
            'timeout' => $timeout,
            'headers' => $headers,
        ];
        if ($body !== null) $args['body'] = wp_json_encode($body);
        $res  = wp_remote_request($url, $args);
        if (is_wp_error($res)) return ['ok'=>false, 'status'=>500, 'error'=>$res->get_error_message()];
        $code = wp_remote_retrieve_response_code($res);
        $raw  = wp_remote_retrieve_body($res);
        $json = json_decode($raw, true);
        return ($code>=200 && $code<300) ? ['ok'=>true,'status'=>$code,'data'=>$json?:[]] : ['ok'=>false,'status'=>$code,'data'=>$json?:$raw];
    }

  // -------- Unified JSON proxy (with retries + correlation id) --------
  private static function bgvs_proxy_json($method, $path, $body = null, $query = []) {
    $base = function_exists('bgad_backend_base') ? rtrim(bgad_backend_base(), '/') : self::backend_base();
    $url  = rtrim($base, '/') . $path;
    if (is_array($query) && !empty($query)) {
      $q = [];
      foreach ($query as $k => $v) {
        if ($v === null) continue;
        if (is_array($v)) {
          $q[$k] = array_map('strval', array_filter($v, static function($vv){ return $vv !== null; }));
        } else {
          $q[$k] = (string) $v;
        }
      }
      if (!empty($q)) {
        $url = add_query_arg($q, $url);
      }
    }
    $headers = self::vs_headers('video-studio-v3');
    $headers['Content-Type'] = 'application/json; charset=utf-8';
    $corr = function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : uniqid('corr_', true);
    $headers['X-Correlation-Id'] = $corr;
    $args = [
      'method'      => strtoupper($method),
      'timeout'     => 45,
      'headers'     => $headers,
      'sslverify'   => true,
      'httpversion' => '1.1',
      'redirection' => 0,
    ];
    if ($body !== null) {
      $args['body'] = is_string($body) ? $body : wp_json_encode($body);
    }
    $attempts = 0;
    $max      = 3;
    $sleep    = 0.5;
    $lastErr  = '';
    while ($attempts < $max) {
      $attempts++;
      $res = wp_remote_request($url, $args);
      if (is_wp_error($res)) {
        $lastErr = $res->get_error_message();
        usleep((int)($sleep * 1000000));
        $sleep *= 2;
        continue;
      }
      $code = (int) wp_remote_retrieve_response_code($res);
      $raw  = (string) wp_remote_retrieve_body($res);
      $decoded = json_decode($raw, true);
      if ($code >= 200 && $code < 300) {
        return [
          'ok' => true,
          'status' => $code,
          'body' => ($decoded !== null ? $decoded : $raw),
          'attempts' => $attempts,
          'via' => 'wp-proxy',
          'corr' => $corr,
        ];
      }
      if ($code === 429 || $code >= 500) {
        $lastErr = $raw !== '' ? $raw : wp_remote_retrieve_response_message($res);
        usleep((int)($sleep * 1000000));
        $sleep *= 2;
        continue;
      }
      $msg = $raw !== '' ? $raw : wp_remote_retrieve_response_message($res);
      return [
        'ok' => false,
        'status' => $code ?: 0,
        'error' => is_string($msg) ? substr($msg, 0, 512) : 'upstream error',
        'body' => ($decoded !== null ? $decoded : null),
        'attempts' => $attempts,
        'via' => 'wp-proxy',
        'corr' => $corr,
      ];
    }
    return [
      'ok' => false,
      'status' => 504,
      'error' => $lastErr ? substr($lastErr, 0, 512) : 'proxy timeout',
      'body' => null,
      'attempts' => $attempts,
      'via' => 'wp-proxy',
      'corr' => $corr,
    ];
  }

  // -------- Streaming proxy (with retries + JSON error on failure) --------
  private static function bgvs_proxy_stream($path, $query = []) {
    $base = function_exists('bgad_backend_base') ? rtrim(bgad_backend_base(), '/') : self::backend_base();
    $url  = rtrim($base, '/') . $path;
    if (is_array($query) && !empty($query)) {
      $q = [];
      foreach ($query as $k => $v) {
        if ($v === null) continue;
        if (is_array($v)) {
          $q[$k] = array_map('strval', array_filter($v, static function($vv){ return $vv !== null; }));
        } else {
          $q[$k] = (string) $v;
        }
      }
      if (!empty($q)) {
        $url = add_query_arg($q, $url);
      }
    }
    $headers = self::vs_headers('video-studio-v3');
    $corr = function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : uniqid('corr_', true);
    $headers['X-Correlation-Id'] = $corr;
    $args = [
      'method'      => 'GET',
      'timeout'     => 45,
      'headers'     => $headers,
      'sslverify'   => true,
      'httpversion' => '1.1',
      'redirection' => 0,
    ];
    $attempts = 0;
    $max      = 3;
    $sleep    = 0.5;
    $lastErr  = '';
    $res = null;
    while ($attempts < $max) {
      $attempts++;
      $res = wp_remote_request($url, $args);
      if (is_wp_error($res)) {
        $lastErr = $res->get_error_message();
        usleep((int)($sleep * 1000000));
        $sleep *= 2;
        continue;
      }
      $code = (int) wp_remote_retrieve_response_code($res);
      if ($code === 429 || $code >= 500) {
        $lastErr = (string) wp_remote_retrieve_body($res) ?: wp_remote_retrieve_response_message($res);
        usleep((int)($sleep * 1000000));
        $sleep *= 2;
        continue;
      }
      break;
    }
    if (is_wp_error($res)) {
      status_header(502);
      nocache_headers();
      header('Content-Type: application/json; charset=utf-8');
      echo wp_json_encode(['ok'=>false,'status'=>0,'error'=>$lastErr ?: 'proxy failure','via'=>'wp-proxy','corr'=>$corr,'attempts'=>$attempts]);
      wp_die();
    }
    $code = (int) wp_remote_retrieve_response_code($res);
    $body = (string) wp_remote_retrieve_body($res);
    if ($code < 200 || $code >= 300) {
      $msg = $body !== '' ? $body : wp_remote_retrieve_response_message($res);
      status_header($code ?: 500);
      nocache_headers();
      header('Content-Type: application/json; charset=utf-8');
      echo wp_json_encode(['ok'=>false,'status'=>$code ?: 0,'error'=>is_string($msg)? substr($msg,0,512):'upstream error','via'=>'wp-proxy','corr'=>$corr,'attempts'=>$attempts]);
      wp_die();
    }
    $ctype = (string) wp_remote_retrieve_header($res, 'content-type');
    $disp  = (string) wp_remote_retrieve_header($res, 'content-disposition');
    nocache_headers();
    if (!$ctype) {
      $ctype = 'application/octet-stream';
    }
    header('Content-Type: ' . $ctype);
    if ($disp) {
      header('Content-Disposition: ' . $disp);
    } else {
      header('Content-Disposition: inline');
    }
    header('X-Correlation-Id: ' . $corr);
    echo $body;
    wp_die();
  }

  /** ---------------- Assets ---------------- */
  public static function assets($hook) {
        $page = isset($_GET['page']) ? sanitize_key($_GET['page']) : '';
  $is_main  = ($hook === 'toplevel_page_' . self::SLUG);                 // top-level container (now redirect)
  $is_adnew = ($page === self::SLUG_ADCHAT);                             // Chat (New UI)
  $is_docs  = ($page === self::SLUG_DOCS);                               // Doc Repo page
  $is_articles2 = ($page === self::SLUG_ARTICLES_SOCIALS_2) || ($hook === self::SLUG . '_page_' . self::SLUG_ARTICLES_SOCIALS_2);
  $is_articles3 = ($page === self::SLUG_ARTICLES_SOCIALS_3) || ($hook === self::SLUG . '_page_' . self::SLUG_ARTICLES_SOCIALS_3);
  // Legacy Link Socials removed; keep false to avoid undefined constant.
  $is_link_socials = false;
  $is_vs    = ($page === self::SLUG . '-video-studio') || ($hook === self::SLUG . '_page_' . self::SLUG . '-video-studio');
  $is_lvi   = ($hook === self::SLUG . '_page_' . self::SLUG_LVI);
  $is_lvs   = false;
    $is_large_video_storage = false;
    $is_video_director = false;
    $is_image_manager = ($page === self::SLUG . '-image-manager');
  $is_lvu = false;
    $is_meeting = ($page === self::SLUG_MEETING_ROOM) || ($hook === self::SLUG . '_page_' . self::SLUG_MEETING_ROOM);
    $is_meeting_minutes = ($page === self::SLUG_MEETING_MINUTES) || ($hook === self::SLUG . '_page_' . self::SLUG_MEETING_MINUTES);
    $is_meeting_summaries = ($page === self::SLUG_MEETING_SUMMARIES) || ($hook === self::SLUG . '_page_' . self::SLUG_MEETING_SUMMARIES);
    $is_meeting_archive = ($page === self::SLUG_MEETING_ARCHIVE) || ($hook === self::SLUG . '_page_' . self::SLUG_MEETING_ARCHIVE);
    $is_meeting_timeline = ($page === self::SLUG_MEETING_TIMELINE) || ($hook === self::SLUG . '_page_' . self::SLUG_MEETING_TIMELINE);
    $is_action_board = ($page === self::SLUG_ACTION_BOARD) || ($hook === self::SLUG . '_page_' . self::SLUG_ACTION_BOARD);
    $is_jimmy = ($page === self::SLUG_JIMMY) || ($hook === self::SLUG . '_page_' . self::SLUG_JIMMY);
    $is_social_tracking = ($page === self::SLUG_SOCIAL_TRACKING) || ($hook === self::SLUG . '_page_' . self::SLUG_SOCIAL_TRACKING);
    $is_social_connections = ($page === self::SLUG_SOCIAL_CONNECTIONS) || ($hook === self::SLUG . '_page_' . self::SLUG_SOCIAL_CONNECTIONS);

    if (class_exists('BGAD_Content_Studio')) {
      BGAD_Content_Studio::maybe_enqueue_assets($hook);
    }

        $ver = self::VERSION;

        // Global skin + common
        wp_enqueue_style(self::SLUG, plugins_url('assets/css/app.css', __FILE__), [], $ver);
        wp_enqueue_style(self::SLUG . '-menu', plugins_url('assets/css/menu-groups.css', __FILE__), [], $ver);
        wp_enqueue_script(self::SLUG . '-menu', plugins_url('assets/js/menu-groups.js', __FILE__), [], $ver, true);
        wp_register_script(self::SLUG.'-common', plugins_url('assets/js/common.js', __FILE__), [], $ver, true);

  // Old report assets are no longer enqueued (module removed)

  // Deluxe Chat page assets removed from menu; keep code path disabled
    if ($is_adnew) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/bga_ad_chat.css';
      $js_path   = $base_path . 'assets/js/bga_ad_chat.js';
      $css_url   = plugins_url('assets/css/bga_ad_chat.css', __FILE__);
      $js_url    = plugins_url('assets/js/bga_ad_chat.js',  __FILE__);

      // Fallback to minified files if primary not present
      if (!is_readable($css_path) && is_readable($base_path . 'assets/css/bga_ad_chat.min.css')) {
        $css_path = $base_path . 'assets/css/bga_ad_chat.min.css';
        $css_url  = plugins_url('assets/css/bga_ad_chat.min.css', __FILE__);
      }
      if (!is_readable($js_path) && is_readable($base_path . 'assets/js/bga_ad_chat.min.js')) {
        $js_path = $base_path . 'assets/js/bga_ad_chat.min.js';
        $js_url  = plugins_url('assets/js/bga_ad_chat.min.js', __FILE__);
      }

      $css_ver = @filemtime($css_path) ?: $ver;
      $js_ver  = @filemtime($js_path)  ?: $ver;

      // Enqueue only if files exist; otherwise surface a clear admin notice
      if (is_readable($css_path) && !wp_style_is('bga-chat-css','enqueued')) {
        wp_enqueue_style('bga-chat-css', $css_url, [], $css_ver);
      }
      if (is_readable($js_path) && !wp_script_is('bga-chat-js','enqueued')) {
        wp_enqueue_script('bga-chat-js', $js_url, [], $js_ver, true);
                // Enhanced config for Web 3.0 features
        $cfg = [
                    'apiBase'         => self::backend_base(),
                    'clientKey'       => self::guess_client_key(),
                    'nonce'           => wp_create_nonce(self::NONCE),
                    'ajax'            => admin_url('admin-ajax.php'),
                    'storageKey'      => 'bga.chat.v3',
          // Default scope for /v3/documents/list; JS can override
          'docScope'        => 'self',
                    'features'        => [
                        'documentVerification' => true,
                        'factChecking'        => true,
                        'contextAwareness'    => true,
                        'enhancedArticles'    => true,
                        'accessibility'       => true,
                    ],
                    'endpoints'       => [
                        'conversations'   => '/v3/assistant/conversations',
                        'send'           => '/v3/assistant/send', 
                        'history'        => '/v3/assistant/history',
                        'sources'        => '/v3/assistant/sources',
                        'documents'      => '/v3/documents/list',
                        'factCheck'      => '/v3/assistant/fact-check',
                    ],
                    'accessibility'   => [
                        'announceMessages'    => true,
                        'keyboardShortcuts'   => true,
                        'screenReaderSupport' => true,
                    ],
                    'plansUrl'        => self::manager_url(),
                ];
        $cfg['quota'] = self::quota_context(array('chat_messages'));
        wp_add_inline_script('bga-chat-js', 'window.BGA_CHAT=' . wp_json_encode($cfg) . ';', 'before');
      } else if (!is_readable($js_path)) {
        // Flag missing assets for an admin notice and inline message in the UI container
        $rel_js = $js_path ? str_replace($base_path, '', $js_path) : 'assets/js/bga_ad_chat.js';
        $msg    = 'Assistants Deluxe: missing chat asset ' . esc_html($rel_js) . ". Please upload the file to the plugin's assets directory.";

        // Admin notice for site admins viewing the page
        add_action('admin_notices', function() use ($msg) {
          if (!current_user_can('manage_options')) return;
          echo '<div class="notice notice-error"><p>' . esc_html($msg) . '</p></div>';
        });

        // Ensure jQuery is enqueued to attach an inline banner script
        wp_enqueue_script('jquery');
        wp_add_inline_script('jquery', 'document.addEventListener("DOMContentLoaded",function(){var t=document.getElementById("bga-chat-app");if(!t){return;}var b=document.createElement("div");b.setAttribute("role","alert");b.style.background="#7f1d1d";b.style.color="#fff";b.style.padding="12px";b.style.borderRadius="8px";b.style.margin="12px";b.style.border="1px solid #991b1b";b.textContent=' . wp_json_encode('Assistants Deluxe: Chat JS file missing on server. Please deploy assets/js/bga_ad_chat.js (or .min.js).') . ';t.prepend(b);});', 'after');
            }
        }

    // Doc Repo assets (embedded Documents v3)
  if ($is_docs) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/bgad_doc_repo.css';
      $js_path   = $base_path . 'assets/js/bgad_doc_repo.js';
      $css_url   = plugins_url('assets/css/bgad_doc_repo.css', __FILE__);
      $js_url    = plugins_url('assets/js/bgad_doc_repo.js', __FILE__);

      // Fallback to minified files if primary not present
      if (!is_readable($css_path) && is_readable($base_path . 'assets/css/bgad_doc_repo.min.css')) {
        $css_path = $base_path . 'assets/css/bgad_doc_repo.min.css';
        $css_url  = plugins_url('assets/css/bgad_doc_repo.min.css', __FILE__);
      }
      if (!is_readable($js_path) && is_readable($base_path . 'assets/js/bgad_doc_repo.min.js')) {
        $js_path = $base_path . 'assets/js/bgad_doc_repo.min.js';
        $js_url  = plugins_url('assets/js/bgad_doc_repo.min.js', __FILE__);
      }

      $css_ver = @filemtime($css_path) ?: '1.0.0';
      $js_ver  = @filemtime($js_path)  ?: '1.0.0';

      if (is_readable($css_path)) wp_enqueue_style('bgad-doc-css', $css_url, [], $css_ver);
      if (is_readable($js_path))  wp_enqueue_script('bgad-doc-js', $js_url, [], $js_ver, true);

    $doc_quota = self::quota_context(array('docs_upload', 'docs_tag'));
    $local = [
                'ajax' => admin_url('admin-ajax.php'),
                'nonce'=> wp_create_nonce(self::NONCE),
        'chatPage' => admin_url('admin.php?page=' . self::SLUG_ADCHAT),
    'readerPage' => admin_url('admin.php?page=' . self::SLUG_READER),
                'actions' => [
                    'list'    => 'bgad_doc_list',
                    'upload'  => 'bgad_doc_upload',
                    'approve' => 'bgad_doc_approve',
                    'index'   => 'bgad_doc_index',
                    'delete'  => 'bgad_doc_delete',
                    'quota'   => 'bgad_quota_snapshot',
                    'whoami'  => 'bgad_whoami',
                ],
        'quota' => $doc_quota,
        'plansUrl' => self::manager_url(),
            ];
            wp_add_inline_script('bgad-doc-js', 'window.BGAD_DOC=' . wp_json_encode($local) . ';', 'before');

      $doc_css_new = $base_path . 'assets/css/doc-repo.css';
      $doc_js_new  = $base_path . 'assets/js/doc-repo.js';
      if (is_readable($doc_css_new)) {
        $doc_css_ver = @filemtime($doc_css_new) ?: $ver;
        wp_enqueue_style('bgad-doc-repo-css', plugins_url('assets/css/doc-repo.css', __FILE__), [], $doc_css_ver);
      }
      if (is_readable($doc_js_new)) {
        $doc_js_ver = @filemtime($doc_js_new) ?: $ver;
        wp_enqueue_script('bgad-doc-repo-js', plugins_url('assets/js/doc-repo.js', __FILE__), array('bgad-doc-js','jquery'), $doc_js_ver, true);
      }
        }

    // Doc Reader assets
    if ($page === self::SLUG_READER) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/bgad_doc_reader.css';
      $js_path   = $base_path . 'assets/js/bgad_doc_reader.js';
      $css_url   = plugins_url('assets/css/bgad_doc_reader.css', __FILE__);
      $js_url    = plugins_url('assets/js/bgad_doc_reader.js', __FILE__);

      if (!is_readable($css_path) && is_readable($base_path . 'assets/css/bgad_doc_reader.min.css')) {
        $css_path = $base_path . 'assets/css/bgad_doc_reader.min.css';
        $css_url  = plugins_url('assets/css/bgad_doc_reader.min.css', __FILE__);
      }
      if (!is_readable($js_path) && is_readable($base_path . 'assets/js/bgad_doc_reader.min.js')) {
        $js_path = $base_path . 'assets/js/bgad_doc_reader.min.js';
        $js_url  = plugins_url('assets/js/bgad_doc_reader.min.js', __FILE__);
      }

      $css_ver = @filemtime($css_path) ?: '1.0.0';
      $js_ver  = @filemtime($js_path)  ?: '1.0.0';

      if (is_readable($css_path)) wp_enqueue_style('bgad-reader-css', $css_url, [], $css_ver);
      if (is_readable($js_path))  wp_enqueue_script('bgad-reader-js', $js_url, [], $js_ver, true);

      // Provide minimal config
      $doc_id = isset($_GET['doc_id']) ? sanitize_text_field($_GET['doc_id']) : '';
      $localR = [
        'docId' => $doc_id,
        'nonce' => wp_create_nonce(self::NONCE),
      ];
      wp_add_inline_script('bgad-reader-js', 'window.BGAD_READER=' . wp_json_encode($localR) . ';', 'before');
    }

    // Meeting Room assets
    if ($is_meeting) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/meeting-room.css';
      $js_path   = $base_path . 'assets/js/meeting-room.js';
      $css_url   = plugins_url('assets/css/meeting-room.css', __FILE__);
      $js_url    = plugins_url('assets/js/meeting-room.js', __FILE__);

      if (!is_readable($css_path) && is_readable($base_path . 'assets/css/meeting-room.min.css')) {
        $css_path = $base_path . 'assets/css/meeting-room.min.css';
        $css_url  = plugins_url('assets/css/meeting-room.min.css', __FILE__);
      }
      if (!is_readable($js_path) && is_readable($base_path . 'assets/js/meeting-room.min.js')) {
        $js_path = $base_path . 'assets/js/meeting-room.min.js';
        $js_url  = plugins_url('assets/js/meeting-room.min.js', __FILE__);
      }

      $css_ver = @filemtime($css_path) ?: $ver;
      $js_ver  = time(); // force fresh load to avoid stale cache

      if (is_readable($css_path)) wp_enqueue_style('bgad-meeting-room', $css_url, [], $css_ver);
      if (is_readable($js_path))  wp_enqueue_script('bgad-meeting-room', $js_url, [], $js_ver, true);

      if (is_readable($js_path)) {
        $cfg = [
          'backend'  => rtrim(self::backend_base(), '/'),
          'apiBase'  => rtrim(self::backend_base(), '/') . '/v3/meeting-room',
          'clientId' => function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'clientKey'=> function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'userKey'  => function_exists('bgad_get_user_api_key') ? bgad_get_user_api_key() : '',
          'nonce'    => wp_create_nonce(self::NONCE),
          'today'    => gmdate('Y-m-d'),
          'avatars'  => self::manager_avatar_urls(),
          'routes'   => [
            'status'      => '/status',
            'enable'      => '/enable',
            'profile'     => '/profile',
            'day'         => '/day',
            'post'        => '/post',
            'tasksUpdate' => '/tasks/update',
            'pin'         => '/pin',
            'summary'     => '/summary',
          ],
        ];
        wp_add_inline_script('bgad-meeting-room', 'window.BG_MEETING_ROOM=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    // Meeting memory pages (minutes, summaries, archive, timeline)
    if ($is_meeting_minutes || $is_meeting_summaries || $is_meeting_archive || $is_meeting_timeline || $is_action_board) {
      $base_path = plugin_dir_path(__FILE__);
      $css_mem_path = $base_path . 'assets/css/meeting-memory.css';
      $css_mem_url  = plugins_url('assets/css/meeting-memory.css', __FILE__);
      $css_mem_ver  = @filemtime($css_mem_path) ?: $ver;
      if (is_readable($css_mem_path)) {
        wp_enqueue_style('bgad-meeting-memory', $css_mem_url, [], $css_mem_ver);
      }
    }

    if ($is_meeting_minutes) {
      $base_path = plugin_dir_path(__FILE__);
      $js_path   = $base_path . 'assets/js/meeting-minutes.js';
      $js_url    = plugins_url('assets/js/meeting-minutes.js', __FILE__);
      $js_ver    = @filemtime($js_path) ?: time();
      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-meeting-minutes', $js_url, [], $js_ver, true);
        $cfg = [
          'backend'  => rtrim(self::backend_base(), '/'),
          'apiBase'  => rtrim(self::backend_base(), '/') . '/v3/meetingroom/minutes',
          'summariesBase' => rtrim(self::backend_base(), '/') . '/v3/meetingroom/summaries',
          'clientId' => function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'clientKey'=> function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'userKey'  => function_exists('bgad_get_user_api_key') ? bgad_get_user_api_key() : '',
          'nonce'    => wp_create_nonce(self::NONCE),
          'avatars'  => self::manager_avatar_urls(),
        ];
        wp_add_inline_script('bgad-meeting-minutes', 'window.BG_MEETING_MINUTES=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    if ($is_meeting_summaries) {
      $base_path = plugin_dir_path(__FILE__);
      $js_path   = $base_path . 'assets/js/meeting-summaries.js';
      $js_url    = plugins_url('assets/js/meeting-summaries.js', __FILE__);
      $js_ver    = @filemtime($js_path) ?: time();
      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-meeting-summaries', $js_url, [], $js_ver, true);
        $cfg = [
          'backend'  => rtrim(self::backend_base(), '/'),
          'apiBase'  => rtrim(self::backend_base(), '/') . '/v3/meetingroom/summaries',
          'reportsBase' => rtrim(self::backend_base(), '/') . '/v3/meetingroom/reports',
          'clientId' => function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'clientKey'=> function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'userKey'  => function_exists('bgad_get_user_api_key') ? bgad_get_user_api_key() : '',
          'nonce'    => wp_create_nonce(self::NONCE),
          'avatars'  => self::manager_avatar_urls(),
        ];
        wp_add_inline_script('bgad-meeting-summaries', 'window.BG_MEETING_SUMMARIES=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    if ($is_meeting_archive) {
      $base_path = plugin_dir_path(__FILE__);
      $js_path   = $base_path . 'assets/js/meeting-archive.js';
      $js_url    = plugins_url('assets/js/meeting-archive.js', __FILE__);
      $js_ver    = @filemtime($js_path) ?: time();
      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-meeting-archive', $js_url, [], $js_ver, true);
        $cfg = [
          'backend'  => rtrim(self::backend_base(), '/'),
          'apiBase'  => rtrim(self::backend_base(), '/') . '/v3/meetingroom/minutes',
          'summariesBase' => rtrim(self::backend_base(), '/') . '/v3/meetingroom/summaries',
          'clientId' => function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'clientKey'=> function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'userKey'  => function_exists('bgad_get_user_api_key') ? bgad_get_user_api_key() : '',
          'nonce'    => wp_create_nonce(self::NONCE),
          'avatars'  => self::manager_avatar_urls(),
        ];
        wp_add_inline_script('bgad-meeting-archive', 'window.BG_MEETING_ARCHIVE=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    if ($is_meeting_timeline) {
      $base_path = plugin_dir_path(__FILE__);
      $js_path   = $base_path . 'assets/js/meeting-timeline.js';
      $js_url    = plugins_url('assets/js/meeting-timeline.js', __FILE__);
      $js_ver    = @filemtime($js_path) ?: time();
      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-meeting-timeline', $js_url, [], $js_ver, true);
        $cfg = [
          'backend'  => rtrim(self::backend_base(), '/'),
          'apiBase'  => rtrim(self::backend_base(), '/') . '/v3/meetingroom/timeline',
          'reportsBase' => rtrim(self::backend_base(), '/') . '/v3/meetingroom/reports',
          'clientId' => function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'clientKey'=> function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'userKey'  => function_exists('bgad_get_user_api_key') ? bgad_get_user_api_key() : '',
          'nonce'    => wp_create_nonce(self::NONCE),
          'avatars'  => self::manager_avatar_urls(),
        ];
        wp_add_inline_script('bgad-meeting-timeline', 'window.BG_MEETING_TIMELINE=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    if ($is_action_board) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/action-board.css';
      $js_path   = $base_path . 'assets/js/meeting-action-board.js';
      $css_url   = plugins_url('assets/css/action-board.css', __FILE__);
      $js_url    = plugins_url('assets/js/meeting-action-board.js', __FILE__);
      $css_ver   = @filemtime($css_path) ?: $ver;
      $js_ver    = @filemtime($js_path) ?: time();
      if (is_readable($css_path)) wp_enqueue_style('bgad-action-board', $css_url, [], $css_ver);
      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-action-board', $js_url, [], $js_ver, true);
        $cfg = [
          'backend'  => rtrim(self::backend_base(), '/'),
          'apiBase'  => rtrim(self::backend_base(), '/') . '/v3/meeting-room/tasks',
          'clientId' => function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'clientKey'=> function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : self::guess_client_key(),
          'userKey'  => function_exists('bgad_get_user_api_key') ? bgad_get_user_api_key() : '',
          'nonce'    => wp_create_nonce(self::NONCE),
          'avatars'  => self::manager_avatar_urls(),
        ];
        wp_add_inline_script('bgad-action-board', 'window.BG_ACTION_BOARD=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    $needs_lvu_assets = $is_lvu || $is_video_director;
    if ($needs_lvu_assets) {
      $base_path      = plugin_dir_path(__FILE__);
      $css_path       = $base_path . 'assets/css/lvu.css';
      $utils_path     = $base_path . 'assets/js/lvu-utils.js';
      $uploader_path  = $base_path . 'assets/js/lvu-uploader.js';
      $css_url        = plugins_url('assets/css/lvu.css', __FILE__);
      $utils_url      = plugins_url('assets/js/lvu-utils.js', __FILE__);
      $uploader_url   = plugins_url('assets/js/lvu-uploader.js', __FILE__);

      $css_ver      = @filemtime($css_path) ?: $ver;
      $utils_ver    = @filemtime($utils_path) ?: $ver;
      $uploader_ver = @filemtime($uploader_path) ?: $ver;

      if (is_readable($css_path)) {
        wp_enqueue_style('bgad-lvu-css', $css_url, array(), $css_ver);
      }
      $utils_available = is_readable($utils_path);
      if ($utils_available) {
        wp_enqueue_script('bgad-lvu-utils', $utils_url, array(), $utils_ver, true);
      }

      $opts = function_exists('bgad_lvu_get_opts') ? bgad_lvu_get_opts() : array();
      $backend_url = isset($opts['backend_url']) ? rtrim((string) $opts['backend_url'], '/') : '';
      if ($backend_url === '') {
        $backend_url = 'https://brassgate-backend.onrender.com';
      }
      $cfg  = array(
        'ajax_url'    => admin_url('admin-ajax.php'),
        'nonce'       => wp_create_nonce('bgad_lvu'),
        'backend_url' => $backend_url,
        'client_id'   => isset($opts['client_id']) ? (string) $opts['client_id'] : '',
        'api_key'     => isset($opts['api_key']) ? (string) $opts['api_key'] : '',
        'chunk_size'  => 16 * 1024 * 1024,
      );
      $cfg_json = wp_json_encode($cfg);

      $uploader_exists = is_readable($uploader_path);

      if ($uploader_exists) {
        $deps = $utils_available ? array('bgad-lvu-utils') : array();
        wp_register_script('bgad-lvu-uploader', $uploader_url, $deps, 'v1', true);
      }

      if ($uploader_exists) {
        wp_add_inline_script('bgad-lvu-uploader', 'window.BGLVUCFG=' . $cfg_json . ';', 'before');
      }

      if ($uploader_exists) {
        wp_enqueue_script('bgad-lvu-uploader');
      }
    }

    if ($is_articles2) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/articles-socials.css';
      $js_path   = $base_path . 'assets/js/articles-socials.js';
      $dom_path  = $base_path . 'assets/vendor/dompurify.min.js';
      $pdf_path  = $base_path . 'assets/vendor/html2pdf.bundle.min.js';

      $css_url = plugins_url('assets/css/articles-socials.css', __FILE__);
      $js_url  = plugins_url('assets/js/articles-socials.js', __FILE__);
      $dom_url = plugins_url('assets/vendor/dompurify.min.js', __FILE__);
      $pdf_url = plugins_url('assets/vendor/html2pdf.bundle.min.js', __FILE__);

      $missing = array();
      if (!is_readable($css_path)) { $missing[] = 'assets/css/articles-socials.css'; }
      if (!is_readable($js_path)) { $missing[] = 'assets/js/articles-socials.js'; }
      if (!is_readable($dom_path)) { $missing[] = 'assets/vendor/dompurify.min.js'; }
      if (!is_readable($pdf_path)) { $missing[] = 'assets/vendor/html2pdf.bundle.min.js'; }

      if ($missing) {
        add_action('admin_notices', function () use ($missing) {
          if (!current_user_can('manage_options')) {
            return;
          }
          echo '<div class="notice notice-error"><p>' . esc_html__('Articles & Socials (2) assets missing:', 'brassgate-assistants-deluxe') . ' ' . esc_html(implode(', ', $missing)) . '</p></div>';
        });
      }

      if (is_readable($css_path)) {
        $css_ver = @filemtime($css_path) ?: $ver;
        wp_enqueue_style('bgad-articles-socials', $css_url, array(), $css_ver);
      }

      $deps = array();
      if (is_readable($dom_path)) {
        $dom_ver = @filemtime($dom_path) ?: $ver;
        wp_register_script('bgad-articles-dompurify', $dom_url, array(), $dom_ver, true);
        $deps[] = 'bgad-articles-dompurify';
      }
      if (is_readable($pdf_path)) {
        $pdf_ver = @filemtime($pdf_path) ?: $ver;
        wp_register_script('bgad-articles-html2pdf', $pdf_url, array(), $pdf_ver, true);
        $deps[] = 'bgad-articles-html2pdf';
      }

      if (is_readable($js_path)) {
        if (!function_exists('bgad_backend_base') || !function_exists('bgad_user_headers')) {
          $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
          if (file_exists($inc)) {
            require_once $inc;
          }
        }

        $js_ver = @filemtime($js_path) ?: $ver;
        wp_register_script('bgad-articles-socials', $js_url, $deps, $js_ver, true);

        $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();
        $headers_raw = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'articles-socials-v2') : array('Accept' => 'application/json');
        $auth_headers = array();
        foreach ($headers_raw as $hk => $hv) {
          if (!is_scalar($hv) || $hv === '') {
            continue;
          }
          $auth_headers[$hk] = sanitize_text_field((string) $hv);
        }
        if (isset($auth_headers['x-brassgate-client-key']) && !isset($auth_headers['X-BrassGate-Client-Key'])) {
          $auth_headers['X-BrassGate-Client-Key'] = $auth_headers['x-brassgate-client-key'];
        }
        if (isset($auth_headers['X-BrassGate-Client-Key']) && !isset($auth_headers['X-BG-Client'])) {
          $auth_headers['X-BG-Client'] = $auth_headers['X-BrassGate-Client-Key'];
        } elseif (isset($auth_headers['x-brassgate-client-key']) && !isset($auth_headers['X-BG-Client'])) {
          $auth_headers['X-BG-Client'] = $auth_headers['x-brassgate-client-key'];
        }
        unset($auth_headers['x-brassgate-client-key']);
        if (isset($auth_headers['X-User-API-Key']) && !isset($auth_headers['X-BG-User'])) {
          $auth_headers['X-BG-User'] = $auth_headers['X-User-API-Key'];
        }
        if (isset($auth_headers['x-user-api-key']) && !isset($auth_headers['X-User-API-Key'])) {
          $auth_headers['X-User-API-Key'] = $auth_headers['x-user-api-key'];
        }
        unset($auth_headers['x-user-api-key']);
        if (isset($auth_headers['X-Manager-API-Key']) && !isset($auth_headers['X-BG-Key'])) {
          $auth_headers['X-BG-Key'] = $auth_headers['X-Manager-API-Key'];
        }
        if (isset($auth_headers['x-manager-api-key']) && !isset($auth_headers['X-Manager-API-Key'])) {
          $auth_headers['X-Manager-API-Key'] = $auth_headers['x-manager-api-key'];
        }
        unset($auth_headers['x-manager-api-key']);
        if (!isset($auth_headers['X-BGA-Plugin'])) {
          $auth_headers['X-BGA-Plugin'] = 'articles-socials-v2';
        }
        if (!isset($auth_headers['X-BG-Feature'])) {
          $auth_headers['X-BG-Feature'] = 'articles-socials-v2';
        }
        $user_obj = function_exists('wp_get_current_user') ? wp_get_current_user() : null;
        $user_data = array(
          'id' => $user_obj ? intval($user_obj->ID) : 0,
          'display' => $user_obj ? sanitize_text_field($user_obj->display_name) : '',
          'login' => $user_obj ? sanitize_text_field($user_obj->user_login) : '',
          'email' => $user_obj ? sanitize_email($user_obj->user_email) : ''
        );

        $config = array(
          'backendBase' => esc_url_raw($backend),
          'authHeaders' => $auth_headers,
          'nonce' => wp_create_nonce(self::NONCE),
          'user' => $user_data,
          'version' => self::VERSION
        );

        wp_add_inline_script('bgad-articles-socials', 'window.bgadArticles2=' . wp_json_encode($config) . ';', 'before');
        wp_enqueue_script('bgad-articles-socials');
      }
    }

    if ($is_articles3) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/articles-socials-v3.css';
      $js_path   = $base_path . 'assets/js/articles-socials-v3.js';
      $css_url   = plugins_url('assets/css/articles-socials-v3.css', __FILE__);
      $js_url    = plugins_url('assets/js/articles-socials-v3.js', __FILE__);

      if (!is_readable($css_path) || !is_readable($js_path)) {
        add_action('admin_notices', function () {
          if (!current_user_can('manage_options')) {
            return;
          }
          echo '<div class="notice notice-error"><p>' . esc_html__('Articles and Social Media assets are missing. Ensure assets/css/articles-socials-v3.css and assets/js/articles-socials-v3.js are deployed.', 'brassgate-assistants-deluxe') . '</p></div>';
        });
      }

      if (is_readable($css_path)) {
        $css_ver = @filemtime($css_path) ?: $ver;
        wp_enqueue_style('bgad-articles-socials-v3', $css_url, array(), $css_ver);
      }

      if (is_readable($js_path)) {
        if (!function_exists('bgad_backend_base') || !function_exists('bgad_user_headers')) {
          $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
          if (file_exists($inc)) {
            require_once $inc;
          }
        }

        $js_ver = @filemtime($js_path) ?: $ver;
        $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();
        $headers_raw = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'articles-socials-v3') : array('Accept' => 'application/json');
        $auth_headers = array();
        foreach ($headers_raw as $hk => $hv) {
          if (!is_scalar($hv) || $hv === '') {
            continue;
          }
          $auth_headers[$hk] = sanitize_text_field((string) $hv);
        }
        $client_key = '';
        if (isset($auth_headers['x-brassgate-client-key']) && $auth_headers['x-brassgate-client-key'] !== '') {
          $client_key = $auth_headers['x-brassgate-client-key'];
        } elseif (isset($auth_headers['X-BrassGate-Client-Key']) && $auth_headers['X-BrassGate-Client-Key'] !== '') {
          $client_key = $auth_headers['X-BrassGate-Client-Key'];
        }
        unset($auth_headers['x-brassgate-client-key'], $auth_headers['X-BrassGate-Client-Key']);
        if ($client_key !== '') {
          $auth_headers['x-brassgate-client-key'] = $client_key;
          if (!isset($auth_headers['X-BG-Client'])) {
            $auth_headers['X-BG-Client'] = $client_key;
          }
        }
        if (isset($auth_headers['X-User-API-Key']) && !isset($auth_headers['X-BG-User'])) {
          $auth_headers['X-BG-User'] = $auth_headers['X-User-API-Key'];
        }
        if (isset($auth_headers['X-Manager-API-Key']) && !isset($auth_headers['X-BG-Key'])) {
          $auth_headers['X-BG-Key'] = $auth_headers['X-Manager-API-Key'];
        }
        if (!isset($auth_headers['Accept'])) {
          $auth_headers['Accept'] = 'application/json';
        }
        if (!isset($auth_headers['Content-Type'])) {
          $auth_headers['Content-Type'] = 'application/json';
        }
        if (!isset($auth_headers['X-BGA-Plugin'])) {
          $auth_headers['X-BGA-Plugin'] = 'articles-socials-v3';
        }
        if (!isset($auth_headers['X-BG-Feature'])) {
          $auth_headers['X-BG-Feature'] = 'articles-socials-v3';
        }

        wp_enqueue_script('wp-util');
        wp_register_script('bgad-articles-socials-v3', $js_url, array('wp-util'), $js_ver, true);
        $rest_root = esc_url_raw(get_rest_url());
        $rest_nonce = wp_create_nonce('wp_rest');
        $user_id   = get_current_user_id();
        $timezone = $user_id ? get_user_meta($user_id, 'bg_user_timezone', true) : '';
        if (!$timezone) {
          $timezone = get_option('timezone_string', 'UTC');
        }
        $is_manager = current_user_can('manage_options') || current_user_can('edit_others_posts');
        $config = array(
          'apiBase' => esc_url_raw($backend),
          'headers' => $auth_headers,
          'assetVer' => $js_ver,
          'ajax' => admin_url('admin-ajax.php'),
          'nonce' => wp_create_nonce(self::NONCE),
          'strings' => array(
            'working' => esc_html__('Generating…', 'brassgate-assistants-deluxe'),
          ),
          'calendar' => array(
            'restRoot'  => $rest_root,
            'restNonce' => $rest_nonce,
            'timezone'  => $timezone ?: 'UTC',
            'isManager' => (bool) $is_manager,
          ),
        );
        wp_add_inline_script('bgad-articles-socials-v3', 'window.BGAD_AS3V3=' . wp_json_encode($config) . ';', 'before');
        wp_enqueue_script('bgad-articles-socials-v3');
      }
    }

    if ($is_link_socials) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/content-studio.css';
      $css_url   = plugins_url('assets/css/content-studio.css', __FILE__);
      $js_path   = $base_path . 'assets/js/bgad_link_socials.js';
      $js_url    = plugins_url('assets/js/bgad_link_socials.js', __FILE__);

      if (!is_readable($js_path)) {
        add_action('admin_notices', function () {
          if (!current_user_can('manage_options')) {
            return;
          }
          echo '<div class="notice notice-error"><p>' . esc_html__('Link Socials assets are missing. Ensure assets/js/bgad_link_socials.js is deployed.', 'brassgate-assistants-deluxe') . '</p></div>';
        });
      } else {
        if (is_readable($css_path)) {
          $css_ver = @filemtime($css_path) ?: $ver;
          wp_enqueue_style('bgad-link-socials-css', $css_url, array(), $css_ver);
        }
        if (!function_exists('bgad_backend_base') || !function_exists('bgad_user_headers')) {
          $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
          if (file_exists($inc)) {
            require_once $inc;
          }
        }
        $js_ver   = @filemtime($js_path) ?: $ver;
        $backend  = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();
        $headers_raw = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'link-socials') : array('Accept' => 'application/json');
        $auth_headers = array();
        foreach ($headers_raw as $hk => $hv) {
          if (!is_scalar($hv) || $hv === '') {
            continue;
          }
          $auth_headers[$hk] = sanitize_text_field((string) $hv);
        }
        if (!isset($auth_headers['Accept'])) {
          $auth_headers['Accept'] = 'application/json';
        }
        if (!isset($auth_headers['Content-Type'])) {
          $auth_headers['Content-Type'] = 'application/json';
        }
        if (!isset($auth_headers['X-BGA-Plugin'])) {
          $auth_headers['X-BGA-Plugin'] = 'link-socials';
        }
        if (!isset($auth_headers['X-BG-Feature'])) {
          $auth_headers['X-BG-Feature'] = 'link-socials';
        }

        $user_id = get_current_user_id();
        $start_url = rtrim($backend, '/') . '/v3/social/facebook/start';
        if ($user_id) {
          $start_url = add_query_arg('user_id', $user_id, $start_url);
        }

        wp_register_script('bgad-link-socials', $js_url, array(self::SLUG . '-common'), $js_ver, true);
        $config = array(
          'apiBase' => esc_url_raw($backend),
          'headers' => $auth_headers,
          'nonce' => wp_create_nonce(self::NONCE),
          'ajax' => admin_url('admin-ajax.php'),
          'startUrl' => esc_url_raw($start_url),
          'siteUrl' => home_url(),
          'userId' => $user_id,
        );
        wp_add_inline_script('bgad-link-socials', 'window.BGAD_LINK=' . wp_json_encode($config) . ';', 'before');
        wp_enqueue_script('bgad-link-socials');
      }
    }

    if ($is_image_manager) {
      if (!function_exists('bgad_backend_base') || !function_exists('bgad_user_headers')) {
        $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
        if (file_exists($inc)) {
          require_once $inc;
        }
      }
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/image-manager.css';
      $css_url   = plugins_url('assets/css/image-manager.css', __FILE__);

      if (is_readable($css_path)) {
        wp_enqueue_style('bgad-image-manager', $css_url, [], $ver);
      }

      $src = add_query_arg(
        ['action' => 'bgad_image_manager_js', 'ver' => $ver],
        admin_url('admin-ajax.php')
      );

      wp_register_script('bgad-image-manager', $src, [], $ver, true);

      $backend     = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();
      $headers_raw = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'image-manager') : array('Accept' => 'application/json');
      $auth_headers = array();
      foreach ($headers_raw as $hk => $hv) {
        if (!is_scalar($hv) || $hv === '') {
          continue;
        }
        $auth_headers[$hk] = sanitize_text_field((string) $hv);
      }

      $cfg = [
        'backend'     => $backend,
        'clientKey'   => function_exists('bgad_user_client_key') ? bgad_user_client_key() : self::guess_client_key(),
        'userKey'     => isset($auth_headers['X-User-API-Key']) ? $auth_headers['X-User-API-Key'] : (isset($auth_headers['x-user-api-key']) ? $auth_headers['x-user-api-key'] : ''),
        'headers'     => $auth_headers,
        'nonce'       => wp_create_nonce(self::NONCE),
        'captionBand' => get_option('brassgate_default_caption_band', 'bottom'),
      ];

      wp_localize_script('bgad-image-manager', 'BGV3', $cfg);
      wp_enqueue_script('bgad-image-manager');
    }
    if ($is_lvi || $is_lvs) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/large-video-suite.css';
      $js_path   = $base_path . 'assets/js/large-video-suite.js';
      $css_url   = plugins_url('assets/css/large-video-suite.css', __FILE__);
      $js_url    = plugins_url('assets/js/large-video-suite.js', __FILE__);

      $css_ver = is_readable($css_path) ? @filemtime($css_path) : $ver;
      $js_ver  = is_readable($js_path) ? @filemtime($js_path) : $ver;

      if (is_readable($css_path)) {
        wp_enqueue_style('bgad-large-video-suite', $css_url, [], $css_ver);
      }
      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-large-video-suite', $js_url, [], $js_ver, true);
      }

      if (!function_exists('bgad_user_headers')) {
        $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
        if (file_exists($inc)) {
          require_once $inc;
        }
      }

      $headers = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'large-video-suite') : ['Accept' => 'application/json'];
      $client_key = '';
      if (!empty($headers['X-Client-Id'])) {
        $client_key = (string) $headers['X-Client-Id'];
      } elseif (!empty($headers['x-brassgate-client-key'])) {
        $client_key = (string) $headers['x-brassgate-client-key'];
      } elseif (function_exists('bgad_user_client_key')) {
        $client_key = (string) bgad_user_client_key();
      }
      $service_key = '';
      if (!empty($headers['X-API-Key'])) {
        $service_key = (string) $headers['X-API-Key'];
      } elseif (function_exists('bgad_service_api_key')) {
        $service_key = (string) bgad_service_api_key();
      }
      if ($service_key !== '') {
        $headers['X-API-Key'] = $service_key;
      }

      $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();
      $ajax_url = admin_url('admin-ajax.php');
      $nonce    = wp_create_nonce('bgad_lvs3_nonce');

      $local = [
        'backend' => $backend,
        'headers' => $headers,
        'clientId' => $client_key,
        'nonce' => $nonce,
        'serviceKey' => $service_key,
        'hasServiceKey' => ($service_key !== ''),
        'ajax' => $ajax_url,
        'upgradeUrl' => 'https://thebrassgate.com/upgrade/',
        'statusPollMs' => 4000,
        'strings' => [
          'cancel' => __('Cancel', 'bgad'),
          'proceed' => __('Proceed', 'bgad'),
          'missingServiceKey' => __('Missing X-API-Key. Update site settings.', 'bgad'),
        ],
      ];
      wp_localize_script('bgad-large-video-suite', 'BGAD_LVS3', $local);
    }

    if ($is_social_tracking || $is_social_connections) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/social-tracking.css';
      $js_path   = $base_path . 'assets/js/social-tracking.js';
      $css_url   = plugins_url('assets/css/social-tracking.css', __FILE__);
      $js_url    = plugins_url('assets/js/social-tracking.js', __FILE__);
      $css_ver   = @filemtime($css_path) ?: $ver;
      $js_ver    = @filemtime($js_path) ?: $ver;
      if (is_readable($css_path)) {
        wp_enqueue_style('bgad-social-tracking', $css_url, [], $css_ver);
      }
      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-social-tracking', $js_url, [], $js_ver, true);
        $platforms = function_exists('bgad_social_platforms') ? bgad_social_platforms() : array();
        $cfg = array(
          'ajax' => admin_url('admin-ajax.php'),
          'nonce' => wp_create_nonce(self::NONCE),
          'connectionsUrl' => admin_url('admin.php?page=' . self::SLUG_SOCIAL_CONNECTIONS),
          'trackingUrl' => admin_url('admin.php?page=' . self::SLUG_SOCIAL_TRACKING),
          'platforms' => $platforms,
          'defaultWindow' => 7,
          'oauth' => array(
            'facebook' => admin_url('admin-post.php?action=bgad_fb_oauth_start'),
          ),
          'fbConfigured' => false,
        );
        if (method_exists(__CLASS__, 'fb_creds')) {
          $fb_creds = self::fb_creds();
          $cfg['fbConfigured'] = !empty($fb_creds[0]) && !empty($fb_creds[1]);
        }
        wp_add_inline_script('bgad-social-tracking', 'window.BGAD_SOCIAL=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    // Common localized data (still useful for remaining pages)
        $nonce = wp_create_nonce(self::NONCE);
        wp_localize_script(self::SLUG.'-common', 'BGA4', [
            'ajax'  => admin_url('admin-ajax.php'),
            'nonce' => $nonce,
            'base'  => self::backend_base(),
            'pricing' => [
                'brasso' => (int)get_option(self::OPT_PRICE_BRASSO, 20),
                'agent'  => (int)get_option(self::OPT_PRICE_AGENT, 80),
                'mult'   => [
                    'onepager' => (int)get_option(self::OPT_MULT_ONEPAGER, 1),
                    'short'    => (int)get_option(self::OPT_MULT_SHORT, 5),
                    'extended' => (int)get_option(self::OPT_MULT_EXT, 10),
                ],
            ],
      'msgCosts' => ['brasso'=>1, 'agent'=>5],
        ]);

        // LaunchOS overlay removed
    }

  public static function frontend_assets() {
        if (!is_user_logged_in()) return;
        if (!is_singular()) return;
        global $post;
        if (!$post) return;
        $ver = self::VERSION;
  if (has_shortcode($post->post_content, 'bga4_assistant')) {
            self::assets('');
        }

    if (has_shortcode($post->post_content, 'brassgate_avery')) {
      wp_enqueue_style('bgad-avery', plugins_url('assets/css/avery.css', __FILE__), [], $ver);
      wp_enqueue_style('bgad-avery-embed', plugins_url('assets/css/avery-embed.css', __FILE__), [], $ver);
      wp_enqueue_script('bgad-avery', plugins_url('assets/js/avery.js', __FILE__), [], $ver, true);
    }

    if (has_shortcode($post->post_content, 'brassgate_jimmy')) {
      $css_path = plugin_dir_path(__FILE__) . 'assets/css/jimmy.css';
      $js_path  = plugin_dir_path(__FILE__) . 'assets/js/jimmy.js';
      $css_url  = plugins_url('assets/css/jimmy.css', __FILE__);
      $js_url   = plugins_url('assets/js/jimmy.js', __FILE__);

      if (is_readable($css_path)) {
        wp_enqueue_style('bgad-jimmy', $css_url, [], @filemtime($css_path) ?: $ver);
      }

      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-jimmy', $js_url, [], @filemtime($js_path) ?: $ver, true);

        if (!function_exists('bgad_user_headers')) {
          $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
          if (file_exists($inc)) {
            require_once $inc;
          }
        }

        $headers = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'jimmy') : ['Accept' => 'application/json'];
        $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();
        $cfg = [
          'apiBase' => rtrim($backend, '/'),
          'headers' => $headers,
          'mode' => 'shortcode',
        ];
        wp_add_inline_script('bgad-jimmy', 'window.BGAD_JIMMY=' . wp_json_encode($cfg) . ';', 'before');
      }
    }
    if ($is_jimmy) {
      $base_path = plugin_dir_path(__FILE__);
      $css_path  = $base_path . 'assets/css/jimmy.css';
      $js_path   = $base_path . 'assets/js/jimmy.js';
      $css_url   = plugins_url('assets/css/jimmy.css', __FILE__);
      $js_url    = plugins_url('assets/js/jimmy.js', __FILE__);

      if (is_readable($css_path)) {
        wp_enqueue_style('bgad-jimmy', $css_url, [], @filemtime($css_path) ?: $ver);
      }

      if (is_readable($js_path)) {
        wp_enqueue_script('bgad-jimmy', $js_url, [], @filemtime($js_path) ?: $ver, true);

        if (!function_exists('bgad_user_headers')) {
          $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
          if (file_exists($inc)) {
            require_once $inc;
          }
        }

        $headers = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'jimmy') : ['Accept' => 'application/json'];
        $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();

        $cfg = [
          'apiBase' => rtrim($backend, '/'),
          'headers' => $headers,
          'mode' => 'admin',
        ];

        wp_add_inline_script('bgad-jimmy', 'window.BGAD_JIMMY=' . wp_json_encode($cfg) . ';', 'before');
      }
    }

    // Video Studio / Player embeds
    $has_vs = (has_shortcode($post->post_content, 'brassgate_video_studio') || has_shortcode($post->post_content, 'brassgate_video_player'));
    if ($has_vs) {
      // CSS
      wp_enqueue_style('bgvs-embed-css', plugins_url('assets/css/bgvs_embed.css', __FILE__), [], $ver);
      // JS
      wp_enqueue_script('bgvs-embed-js', plugins_url('assets/js/bgvs_embed.js', __FILE__), ['jquery'], $ver, true);
      $nonce = wp_create_nonce('bgad_sc');
      $cfg = [
        'ajax'   => admin_url('admin-ajax.php'),
        'nonce'  => $nonce,
        'base'   => self::backend_base(),
        'actions'=> [
          'preflight'       => 'bgvs_preflight',
          'plan'            => 'bgvs_plan',
          'preview_start'   => 'bgvs_preview_start',
          'preview_status'  => 'bgvs_preview_status',
          'preview_stream'  => 'bgvs_preview_stream',
          'preview_delete'  => 'bgvs_preview_delete',
          'audio_plan'      => 'bgvs_audio_plan',
          'music_start'     => 'bgvs_music_start',
          'music_stream'    => 'bgvs_music_stream',
          'narrate_start'   => 'bgvs_narrate_start',
          'narrate_stream'  => 'bgvs_narrate_stream',
          'voices'          => 'bgvs_voices',
          'finalize_start'  => 'bgvs_finalize_start',
          'library_list'    => 'bgvs_library_list',
          'library_stream'  => 'bgvs_library_stream',
          'library_download'=> 'bgvs_library_stream',
          'library_delete'  => 'bgvs_library_delete',
          'clips_search'    => 'bgvs_clips_search',
          'thumb'           => 'bgvs_thumb',
        ],
      ];
      wp_add_inline_script('bgvs-embed-js', 'window.BGVS=' . wp_json_encode($cfg) . ';', 'before');
    }

        // LaunchOS overlay removed
    }

    /** ---------------- Overlay markup (admin) ---------------- */
    // LaunchOS overlay dialog removed

    /** ---------------- LaunchOS Workbench pages ---------------- */
    // LaunchOS workbench removed

    // LaunchOS app renderer removed

    /** ---------------- Shortcode (frontend) ---------------- */
    // LaunchOS shortcode removed

    /** ---------------- Renderers ---------------- */

  // Redirect helper: forward to Content Studio page
  public static function render_redirect_to_content_studio() {
    self::render_content_studio();
  }

  public static function render_content_studio() {
    if (!current_user_can('read')) {
      wp_die(__('Insufficient permissions.', 'brassgate-assistants-deluxe'));
    }

    $context = self::quota_context(['chat_messages', 'articles_from_text']);
    if (class_exists('BGAD_Content_Studio')) {
      BGAD_Content_Studio::render_page();
      return;
    }

    echo '<div class="wrap"><h1>Content Studio</h1><p>';
    esc_html_e('Content Studio helper missing. Please reinstall the plugin assets.', 'brassgate-assistants-deluxe');
    echo '</p></div>';
  }

  public static function render_articles_socials_2() {
    $context = self::ensure_plan_access(__('Articles & Socials', 'brassgate-assistants-deluxe'), array('articles_from_text', 'articles_from_image'), self::SLUG_ARTICLES_SOCIALS_2);
    $template = plugin_dir_path(__FILE__) . 'pages/articles-socials.php';
    if (is_readable($template)) {
      include $template;
      return;
    }


    echo '<div class="wrap"><h1>' . esc_html__('Articles & Socials (2)', 'brassgate-assistants-deluxe') . '</h1><p>' . esc_html__('Template missing. Please deploy pages/articles-socials.php.', 'brassgate-assistants-deluxe') . '</p></div>';
  }

  public static function render_calendar() {
    if (!current_user_can('read')) {
      wp_die(__('Insufficient permissions.', 'brassgate-assistants-deluxe'));
    }

    $version = class_exists('BrassGate_Assistants_Deluxe') ? BrassGate_Assistants_Deluxe::VERSION : '1.0.0';
    $style_url   = plugins_url('assets/css/calendar.css', __FILE__);
    $script_url  = plugins_url('assets/js/calendar.js', __FILE__);
    wp_enqueue_style('bgad-calendar', $style_url, [], $version);
    wp_enqueue_script('bgad-calendar', $script_url, [], $version, true);

    $user_id   = get_current_user_id();
    $rest_root = esc_url_raw(get_rest_url());
    $rest_nonce = wp_create_nonce('wp_rest');
    $timezone = get_user_meta($user_id, 'bg_user_timezone', true);
    if (!$timezone) $timezone = get_option('timezone_string', 'UTC');
    $is_manager = current_user_can('manage_options') || current_user_can('edit_others_posts');

    wp_localize_script('bgad-calendar', 'BGAD_CALENDAR_DATA', array(
      'restRoot'  => $rest_root,
      'restNonce' => $rest_nonce,
      'userId'    => $user_id,
      'isManager' => (bool) $is_manager,
      'timezone'  => $timezone ?: 'UTC',
    ));

    ?>
    <div class="wrap bgad-calendar-wrap">
      <h1><?php esc_html_e('BrassGate Calendar', 'brassgate-assistants-deluxe'); ?></h1>
      <p class="description"><?php esc_html_e('Stage and plan your bundles, tasks, and reminders. Drag to reschedule.', 'brassgate-assistants-deluxe'); ?></p>
      <div id="bgad-calendar-root" class="bgad-calendar-root" aria-live="polite"></div>
    </div>
    <?php
  }

  public static function render_articles_socials_3() {
    $feature_slugs = array('articles_from_text', 'articles_from_image');
    $context = null;
    $orch_snapshot = self::fetch_orchestrator_quota_snapshot();
    if (is_array($orch_snapshot) && !empty($orch_snapshot)) {
        $snapshot = array();
        if (isset($orch_snapshot['resources']) && is_array($orch_snapshot['resources'])) {
            foreach ($orch_snapshot['resources'] as $entry) {
                if (!is_array($entry)) {
                    continue;
                }
                $slug = isset($entry['slug']) ? sanitize_key($entry['slug']) : '';
                if ($slug === '' || !in_array($slug, $feature_slugs, true)) {
                    continue;
                }
                $limit = isset($entry['limit']) ? (int) $entry['limit'] : 0;
                $used = isset($entry['used']) ? (int) $entry['used'] : 0;
                $remaining = isset($entry['remaining']) ? (int) $entry['remaining'] : ($limit === -1 ? -1 : max(0, $limit - $used));
                $unlimited = !empty($entry['unlimited']) || $limit === -1;
                $disabled = !empty($entry['disabled']);
                $snapshot[$slug] = array(
                    'limit' => $limit,
                    'used' => $used,
                    'remaining' => $remaining,
                    'unlimited' => $unlimited,
                    'disabled' => $disabled,
                );
            }
        } else {
            foreach ($feature_slugs as $slug) {
                if (!isset($orch_snapshot[$slug]) || !is_array($orch_snapshot[$slug])) {
                    continue;
                }
                $row = $orch_snapshot[$slug];
                $snapshot[$slug] = array(
                    'limit' => isset($row['limit']) ? (int) $row['limit'] : 0,
                    'used' => isset($row['used']) ? (int) $row['used'] : 0,
                    'remaining' => isset($row['remaining']) ? (int) $row['remaining'] : 0,
                    'unlimited' => !empty($row['unlimited']),
                    'disabled' => !empty($row['disabled']),
                );
            }
        }

        if (!empty($snapshot)) {
            $user_id = get_current_user_id();
            $has_plan = self::user_has_plan($user_id);
            $needs_upgrade = !$has_plan;
            $quota_exhausted = false;
            foreach ($snapshot as $row) {
                $disabled = !empty($row['disabled']);
                $unlimited = !empty($row['unlimited']);
                $remaining = isset($row['remaining']) ? (int) $row['remaining'] : 0;
                if (!$has_plan && $disabled) {
                    $needs_upgrade = true;
                }
                if (!$disabled && !$unlimited && $remaining <= 0) {
                    $quota_exhausted = true;
                }
            }

            $plan = null;
            if (function_exists('bg_get_user_plan')) {
                $plan = bg_get_user_plan($user_id);
            }

            $context = array(
                'has_plan' => $has_plan,
                'needs_upgrade' => $needs_upgrade,
                'quota_exhausted' => $quota_exhausted,
                'snapshot' => $snapshot,
                'cta_url' => self::manager_url(),
                'plan' => $plan ? array(
                    'id' => isset($plan->id) ? (int) $plan->id : 0,
                    'slug' => isset($plan->slug) ? sanitize_key($plan->slug) : '',
                    'name' => isset($plan->name) ? (string) $plan->name : '',
                ) : null,
            );

            $plan_slug = '';
            if (isset($context['plan']['slug']) && $context['plan']['slug'] !== '') {
                $plan_slug = sanitize_key($context['plan']['slug']);
            } else {
                $plan_slug = self::resolve_plan_slug();
            }
            if (!self::has_plan_access_for_page($plan_slug, self::SLUG_ARTICLES_SOCIALS_3)) {
                $context['has_plan'] = false;
                $context['needs_upgrade'] = true;
                $context['cta_url'] = self::manager_url();
            }

        }
    }

    if ($context === null) {
        $context = self::ensure_plan_access(__('Articles and Social Media', 'brassgate-assistants-deluxe'), $feature_slugs, self::SLUG_ARTICLES_SOCIALS_3);
    }
    $template = plugin_dir_path(__FILE__) . 'pages/articles-socials-v3.php';
    if (is_readable($template)) {
      include $template;
      return;
    }

    echo '<div class="wrap"><h1>' . esc_html__('Articles and Social Media', 'brassgate-assistants-deluxe') . '</h1><p>' . esc_html__('Template missing. Please deploy pages/articles-socials-v3.php.', 'brassgate-assistants-deluxe') . '</p></div>';
  }

    public static function render_ideas_board() {
        if (!current_user_can('read')) {
            wp_die('Insufficient permissions.');
        }

        $template = plugin_dir_path(__FILE__) . 'pages/ideas_board.php';
        if (is_readable($template)) {
            include $template;
            return;
        }

        echo '<div class="wrap"><h1>' . esc_html__('Ideas Board', 'brassgate-assistants-deluxe') . '</h1><p>' . esc_html__('Template missing. Please deploy pages/ideas_board.php.', 'brassgate-assistants-deluxe') . '</p></div>';
    }

    public static function render_ideas_board_manager() {
        if (!current_user_can('read')) {
            wp_die('Insufficient permissions.');
        }

        $template = plugin_dir_path(__FILE__) . 'pages/ideas_board_manager.php';
        if (is_readable($template)) {
            include $template;
            return;
        }

        echo '<div class="wrap"><h1>' . esc_html__('Ideas Board (Manager)', 'brassgate-assistants-deluxe') . '</h1><p>' . esc_html__('Template missing. Please deploy pages/ideas_board_manager.php.', 'brassgate-assistants-deluxe') . '</p></div>';
    }

    // Legacy hook retained for old menu slug (now forwards to Ideas Board).
    public static function render_ad_chat() {
        self::render_ideas_board();
    }

  // Inline fallback template compatible with bga_ad_chat.js
  private static function render_ad_chat_inline() {
    $nonce = wp_create_nonce(self::NONCE);
    echo '<article class="bga-chat-page" id="bga-chat-app" data-nonce="'.esc_attr($nonce).'" role="main">';
    echo '  <header class="bga-chat-header" role="banner">';
    echo '    <h1 id="bga-main-title">BrassGate Assistants Deluxe — Chat</h1>';
  // Report Studio link removed
    echo '  </header>';

    echo '  <div class="bga-chat-grid" role="main">';
    // Left: Conversations
    echo '    <aside class="bga-col bga-col-left" role="complementary" aria-labelledby="bga-conversations-title">';
    echo '      <section class="bga-panel bga-conversations-panel">';
    echo '        <header class="bga-panel-head">';
    echo '          <h2 id="bga-conversations-title" class="bga-title">Conversations</h2>';
    echo '          <div class="bga-actions">';
    echo '            <button class="button button-secondary" id="bga-new-conv" title="Create new conversation">+</button>';
    echo '          </div>';
    echo '        </header>';
    echo '        <div class="bga-search" role="search">';
    echo '          <input type="search" id="bga-search" placeholder="Search conversations..." />';
    echo '        </div>';
    echo '        <ul class="bga-conv-list" id="bga-conv-list" role="listbox" aria-label="Conversation list"></ul>';
    echo '      </section>';
    echo '    </aside>';

    // Center: Thread
    echo '    <main class="bga-col bga-col-center" role="main" aria-labelledby="bga-thread-title">';
    echo '      <section class="bga-panel bga-thread">';
    echo '        <header class="bga-panel-head">';
    echo '          <nav class="bga-tabs" role="tablist" aria-label="View options">';
    echo '            <button role="tab" aria-selected="true" class="bga-tab is-active" data-tab="formatted" id="bga-tab-formatted" aria-controls="bga-thread-formatted">Formatted View</button>';
    echo '            <button role="tab" aria-selected="false" class="bga-tab" data-tab="raw" id="bga-tab-raw" aria-controls="bga-thread-raw">Raw View</button>';
    echo '          </nav>';
    echo '        </header>';
    echo '        <div class="bga-thread-scroll" role="log" aria-live="polite" aria-label="Chat messages">';
    echo '          <div class="bga-thread-content" id="bga-thread-formatted" data-tab="formatted" role="tabpanel" aria-labelledby="bga-tab-formatted"></div>';
    echo '          <pre class="bga-thread-raw" id="bga-thread-raw" data-tab="raw" role="tabpanel" aria-labelledby="bga-tab-raw" hidden></pre>';
    echo '        </div>';
    echo '        <section class="bga-composer" role="region" aria-labelledby="bga-composer-title">';
    echo '          <h3 id="bga-composer-title" class="sr-only">Message composer</h3>';
    echo '          <textarea id="bga-composer-textarea" rows="3" placeholder="Compose your message..."></textarea>';
    echo '          <div class="bga-composer-actions">';
    echo '            <span class="bga-metrics" id="bga-metrics">0 characters</span>';
    echo '            <button id="bga-send" class="button button-primary" type="button">Send</button>';
    echo '          </div>';
    echo '        </section>';
    echo '      </section>';
    echo '    </main>';

    // Right: Tools
    echo '    <aside class="bga-col bga-col-right" role="complementary" aria-labelledby="bga-tools-title">';
    echo '      <section class="bga-panel bga-tools-panel">';
    echo '        <header class="bga-panel-head"><h2 id="bga-tools-title" class="bga-title">Details & Tools</h2></header>';
    echo '        <section class="bga-section" aria-labelledby="bga-outline-title">';
    echo '          <h3 id="bga-outline-title">Document Outline</h3>';
    echo '          <ul class="bga-outline" id="bga-outline"></ul>';
    echo '        </section>';
    echo '        <section class="bga-section" aria-labelledby="bga-entities-title">';
    echo '          <h3 id="bga-entities-title">Entities & Tags</h3>';
    echo '          <div class="bga-chips" id="bga-chips" role="list"></div>';
    echo '        </section>';
    echo '        <section class="bga-section" aria-labelledby="bga-sources-title">';
    echo '          <h3 id="bga-sources-title">Document Sources</h3>';
    echo '          <div class="bga-sources" id="bga-sources" role="list"></div>';
    echo '        </section>';
    echo '        <section class="bga-section" aria-labelledby="bga-actions-title">';
    echo '          <h3 id="bga-actions-title">Actions</h3>';
    echo '          <div class="bga-actions-vert" role="group">';
    echo '            <button class="button" id="bga-copy-all" type="button">Copy All Content</button>';
    echo '            <button class="button" id="bga-export-md" type="button">Export as Markdown</button>';
    echo '            <button class="button" id="bga-export-html" type="button">Export as HTML</button>';
    echo '            <button class="button" id="bga-generate-article" type="button">Generate Enhanced Article</button>';
    echo '            <button class="button" id="bga-fact-check" type="button">Verify Facts</button>';
    echo '            <button class="button" id="bga-print" type="button">Print Conversation</button>';
    echo '          </div>';
    echo '        </section>';
    echo '      </section>';
    echo '    </aside>';
    echo '  </div>';

    // Overlay + SR announcements
    echo '  <div class="bga-overlay" id="bga-overlay" role="dialog" aria-modal="true" hidden>'; 
    echo '    <div class="bga-ov-card">';
    echo '      <ul class="bga-ov-steps">';
    echo '        <li data-step="1">Analyzing...</li><li data-step="2">Checking sources...</li><li data-step="3">Verifying facts...</li><li data-step="4">Generating...</li><li data-step="5">Finalizing...</li>';
    echo '      </ul>';
    echo '      <div class="bga-ov-bar"><span id="bga-ov-bar-fill"></span></div>';
    echo '      <button class="button button-secondary" id="bga-cancel-request" type="button">Cancel</button>';
    echo '    </div>';
    echo '  </div>';
    echo '  <div aria-live="assertive" aria-atomic="true" class="sr-only" id="bga-announcements"></div>';
    echo '</article>';

    // Admin-only notice to signal fallback rendering
    if (current_user_can('manage_options')) {
      echo '<div class="notice notice-warning"><p>Assistants Deluxe: page template missing (pages/bga_ad_chat.php or pages/ad-chat.php). Rendering inline fallback. Deploy the template file to remove this notice.</p></div>';
    }

  }

    // Existing (legacy) chat renderer
    public static function render_chat() {
        if (!current_user_can('read')) wp_die('Insufficient permissions.');
        // Redirect legacy chat page to Content Studio
        self::render_redirect_to_content_studio();
    }

  // Tombstone renderer for legacy [bga4_report_studio] shortcode
  public static function render_report_studio() {
    // Frontend-safe notice; avoid admin redirects for shortcode contexts
    $target = admin_url('admin.php?page=' . self::CONTENT_STUDIO_PAGE);
    $msg = '<div class="wrap"><div class="notice notice-info"><p><strong>Report Studio was retired.</strong> Please use <a href="' . esc_url($target) . '">Content Studio</a> to generate articles and reports.</p></div></div>';
    if (is_admin()) {
      // If rendered in admin, forward cleanly
      if (!current_user_can('read')) wp_die('Insufficient permissions.');
      self::render_redirect_to_content_studio();
      return '';
    }
    return $msg;
  }

    // ----------- Shortcodes: Video Studio & Player (frontend) -----------
    public static function render_video_studio_embed($atts = [], $content = '') {
        if (!is_user_logged_in() || !current_user_can('read')) {
            return '<div class="bgvs-msg">Please sign in to use the Video Studio.</div>';
        }
        if (!self::user_has_plan()) {
            $plans_url = esc_url(self::manager_url());
            return '<div class="bgvs-msg">An active BrassGate plan is required to use the Video Studio. <a href="' . $plans_url . '">View plans</a>.</div>';
        }
        $atts = shortcode_atts([
            'orientation' => 'landscape',
            'length' => '30',
            'title' => 'Video Studio',
        ], $atts, 'brassgate_video_studio');
        $nonce = wp_create_nonce(self::NONCE);
        ob_start();
        ?>
        <section class="bgvs" data-nonce="<?php echo esc_attr($nonce); ?>">
          <header class="bgvs-head"><h2><?php echo esc_html($atts['title']); ?></h2></header>
          <div class="bgvs-form">
            <label>Prompt<br><textarea id="bgvs-prompt" rows="3" placeholder="Describe your trailer idea..."></textarea></label>
            <div class="bgvs-row">
              <label>Length (sec)<br><input id="bgvs-length" type="number" min="15" max="300" value="<?php echo esc_attr($atts['length']); ?>"/></label>
              <label>Orientation<br>
                <select id="bgvs-orient">
                  <option value="landscape" <?php selected($atts['orientation'],'landscape'); ?>>Landscape</option>
                  <option value="portrait" <?php selected($atts['orientation'],'portrait'); ?>>Portrait</option>
                </select>
              </label>
            </div>
            <div class="bgvs-actions">
              <button class="button button-primary" id="bgvs-btn-preview" type="button">Generate Preview</button>
              <button class="button" id="bgvs-btn-audio-plan" type="button">Audio Plan</button>
              <button class="button" id="bgvs-btn-finalize" type="button">Finalize</button>
            </div>
          </div>
          <div class="bgvs-status" id="bgvs-status" aria-live="polite"></div>
          <div class="bgvs-player-wrap"><video id="bgvs-video" controls playsinline preload="metadata"></video></div>
          <div class="bgvs-note" id="bgvs-audio-plan"></div>
          <div class="bgvs-library">
            <h3>Your Library</h3>
            <ul id="bgvs-lib"></ul>
          </div>
        </section>
        <?php
        return ob_get_clean();
    }

    public static function render_video_player_embed($atts = [], $content = '') {
        if (!is_user_logged_in() || !current_user_can('read')) {
            return '<div class="bgvs-msg">Please sign in to view videos.</div>';
        }
        if (!self::user_has_plan()) {
            $plans_url = esc_url(self::manager_url());
            return '<div class="bgvs-msg">An active BrassGate plan is required to view the video library. <a href="' . $plans_url . '">View plans</a>.</div>';
        }
        $atts = shortcode_atts([
            'filename' => '', // optional preset file
            'title' => 'Video Player',
        ], $atts, 'brassgate_video_player');
        $nonce = wp_create_nonce(self::NONCE);
        ob_start();
        ?>
        <section class="bgvs-player" data-nonce="<?php echo esc_attr($nonce); ?>" data-filename="<?php echo esc_attr($atts['filename']); ?>">
          <header class="bgvs-head"><h2><?php echo esc_html($atts['title']); ?></h2></header>
          <div class="bgvs-player-wrap"><video id="bgvs-player-video" controls playsinline preload="metadata"></video></div>
          <div class="bgvs-actions">
            <button class="button" id="bgvs-refresh-lib" type="button">Refresh Library</button>
          </div>
          <ul id="bgvs-player-lib"></ul>
        </section>
        <?php
        return ob_get_clean();
    }

    public static function render_avery_shortcode($atts = [], $content = '') {
        if (!is_user_logged_in() || !current_user_can('read')) {
            return '<div class="bgad-avery-embed">Please log in to view Avery.</div>';
        }
        ob_start();
        $bgad_avery_embed = true;
        $page = plugin_dir_path(__FILE__) . 'pages/14-avery.php';
        if (is_readable($page)) {
            include $page;
        } else {
            echo '<div class="bgad-avery-embed">Avery is unavailable at the moment.</div>';
        }
        return ob_get_clean();
    }

    public static function render_jimmy_shortcode($atts = [], $content = '') {
        if (!is_user_logged_in() || !current_user_can('read')) {
            return '<div class="bgad-jimmy-embed">Please log in to view Jimmy.</div>';
        }
        ob_start();
        $page = plugin_dir_path(__FILE__) . 'pages/jimmy.php';
        if (is_readable($page)) {
            include $page;
        } else {
            echo '<div class="bgad-jimmy-embed">Jimmy is unavailable at the moment.</div>';
        }
        return ob_get_clean();
    }

    
    // Doc Repo renderer (embedded Documents v3)
    public static function render_doc_repo() {
        if (!current_user_can('read')) wp_die('Insufficient permissions.');
        $context = self::ensure_plan_access('Document Repo', array(), self::SLUG_DOCS);
        $GLOBALS['bgad_doc_quota'] = $context;
        $page = plugin_dir_path(__FILE__) . 'pages/doc-repo.php';
        if (is_readable($page)) { include $page; return; }
        echo '<div class="wrap"><h1>C1 Doc Repo</h1><p>Template missing.</p></div>';
  }

  public static function render_doc_reader() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Doc Reader', array(), self::SLUG_READER);
    $GLOBALS['bgad_doc_quota'] = $context;
    $page = plugin_dir_path(__FILE__) . 'pages/doc-reader.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Doc Reader</h1><p>Template missing.</p></div>';
  }

  public static function render_allowances() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Allowances', array(), self::SLUG_ALLOWANCES);
    $page = plugin_dir_path(__FILE__) . 'pages/allowances.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Allowances</h1><p>Template missing.</p></div>';
  }

  public static function render_social_tracking() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Social Media Tracking', array(), self::SLUG_SOCIAL_TRACKING);
    $GLOBALS['bgad_social_quota'] = $context;
    $page = plugin_dir_path(__FILE__) . 'pages/social-tracking.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Social Media Tracking</h1><p>Template missing.</p></div>';
  }

  public static function render_social_connections() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Social Media Connections', array(), self::SLUG_SOCIAL_TRACKING);
    $GLOBALS['bgad_social_quota'] = $context;
    $page = plugin_dir_path(__FILE__) . 'pages/social-connections.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Social Media Connections</h1><p>Template missing.</p></div>';
  }

  public static function render_meeting_room() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Meeting Room', array(), self::SLUG_MEETING_ROOM);
    // Quota HUD suppressed on Meeting Room pages
    $page = plugin_dir_path(__FILE__) . 'brassgate/pages/meeting-room.php';
    if (is_readable($page)) { include $page; return; }
    $fallback = plugin_dir_path(__FILE__) . 'pages/meeting-room.php';
    if (is_readable($fallback)) { include $fallback; return; }
    echo '<div class="wrap"><h1>Meeting Room</h1><p>Template missing.</p></div>';
  }

  public static function render_meeting_minutes() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Meeting Minutes', array(), self::SLUG_MEETING_MINUTES);
    // Quota HUD suppressed on Meeting Minutes
    $page = plugin_dir_path(__FILE__) . 'brassgate/pages/meeting-minutes.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Meeting Minutes</h1><p>Template missing.</p></div>';
  }

  public static function render_meeting_summaries() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Meeting Summaries', array(), self::SLUG_MEETING_SUMMARIES);
    // Quota HUD suppressed on Meeting Summaries
    $page = plugin_dir_path(__FILE__) . 'brassgate/pages/meeting-summaries.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Meeting Summaries</h1><p>Template missing.</p></div>';
  }

  public static function render_meeting_archive() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Meeting Archive', array(), self::SLUG_MEETING_ARCHIVE);
    // Quota HUD suppressed on Meeting Archive / Action Board Archive
    $page = plugin_dir_path(__FILE__) . 'brassgate/pages/meeting-archive.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Meeting Archive</h1><p>Template missing.</p></div>';
  }

  public static function render_action_board() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Action Board', array(), self::SLUG_ACTION_BOARD);
    // Quota HUD suppressed on Action Board
    $page = plugin_dir_path(__FILE__) . 'brassgate/pages/meeting-action-board.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Action Board</h1><p>Template missing.</p></div>';
  }

  public static function render_meeting_timeline() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Timeline Mode', array(), self::SLUG_MEETING_TIMELINE);
    $page = plugin_dir_path(__FILE__) . 'brassgate/pages/meeting-timeline.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Timeline Mode</h1><p>Template missing.</p></div>';
  }

  public static function render_secretary() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $context = self::ensure_plan_access('Avery', array(), self::SLUG_SECRETARY);
    // Quota HUD optional; enable if desired
    $page = plugin_dir_path(__FILE__) . 'pages/14-avery.php';
    if (is_readable($page)) { include $page; return; }
    echo '<div class="wrap"><h1>Avery</h1><p>Template missing.</p></div>';
  }

  public static function render_secretary_gmail_auth() {
    if (!current_user_can('read')) wp_die('Insufficient permissions.');
    $base = BrassGate_Assistants_Deluxe::backend_base();
    $headers = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'bgad-avery') : array();
    $auth_url = '';
    try {
      $resp = wp_remote_get(
        rtrim($base, '/') . '/v3/gmail/auth-url',
        array(
          'headers' => $headers,
          'timeout' => 10,
          'sslverify' => false,
        )
      );
      if (!is_wp_error($resp)) {
        $body = json_decode(wp_remote_retrieve_body($resp), true);
        if (!empty($body['auth_url'])) {
          $auth_url = $body['auth_url'];
        }
      }
    } catch (Exception $e) {
      $auth_url = '';
    }

    echo '<div class="wrap"><h1>Avery Gmail Auth</h1>';
    if ($auth_url) {
      printf('<p><a class="button button-primary" href="%s" target="_blank" rel="noopener noreferrer">Open Google OAuth</a></p>', esc_url($auth_url));
      echo '<p>After granting access, close the Google window and return to the Avery page.</p>';
    } else {
      echo '<p>Unable to start Gmail OAuth. Please check configuration.</p>';
    }
    echo '</div>';
  }

  public static function render_admin_managers() {
    if (!current_user_can('manage_options')) wp_die('Insufficient permissions.');
    $managers = array(
      'finance'   => 'Finance (Harper)',
      'marketing' => 'Marketing (Ellis)',
      'business'  => 'Business (Rowan)',
      'social'    => 'Social (Mira)',
      'creative'  => 'Creative (Juno)',
      'producer'  => 'Producer (Sage)',
      'sales'     => 'Sales (Vance)',
      'community' => 'Community (Lena)',
      'avery'     => 'Avery (Parker)',
    );
    $base_dir = plugin_dir_path(__FILE__) . 'images/';
    $base_url = plugins_url('images/', __FILE__);
    if (!is_dir($base_dir)) {
      wp_mkdir_p($base_dir);
    }
    $updated = false;
    if ($_SERVER['REQUEST_METHOD'] === 'POST' && check_admin_referer('bgad_manager_icons')) {
      foreach ($managers as $id => $label) {
        if (!isset($_FILES["manager_$id"])) continue;
        $file = $_FILES["manager_$id"];
        if (!empty($file['tmp_name']) && $file['error'] === UPLOAD_ERR_OK) {
          $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
          if (!in_array($ext, array('png','jpg','jpeg'))) continue;
          $dest = $base_dir . "manager_{$id}.{$ext}";
          @array_map('unlink', glob($base_dir . "manager_{$id}.*"));
          move_uploaded_file($file['tmp_name'], $dest);
          $updated = true;
        }
      }
      if ($updated) {
        echo '<div class="notice notice-success"><p>Manager icons updated.</p></div>';
      }
    }
    ?>
    <div class="wrap">
      <h1>Admin - Managers</h1>
      <p>Upload 1024x1024 PNG/JPG icons per manager. These apply to all users. Default is manager_default.png.</p>
      <form method="post" enctype="multipart/form-data">
        <?php wp_nonce_field('bgad_manager_icons'); ?>
        <table class="form-table">
          <tbody>
            <?php foreach ($managers as $id => $label):
              $current = glob($base_dir . "manager_{$id}.*");
              if ($id === 'avery' && empty($current)) {
                $current = glob($base_dir . "manager_secretary.*");
              }
              $current_url = $current ? $base_url . basename($current[0]) : $base_url . 'manager_default.png';
            ?>
            <tr>
              <th scope="row"><?php echo esc_html($label); ?></th>
              <td>
                <img src="<?php echo esc_url($current_url); ?>" alt="" style="width:64px;height:64px;border-radius:50%;object-fit:cover;margin-right:10px;">
                <input type="file" name="<?php echo esc_attr("manager_{$id}"); ?>" accept="image/png,image/jpeg">
              </td>
            </tr>
            <?php endforeach; ?>
          </tbody>
        </table>
        <p><button type="submit" class="button button-primary">Save Icons</button></p>
      </form>
    </div>
    <?php
  }

  // LaunchOS SPA launcher removed

  public static function render_pricing() {
        if (!current_user_can('manage_options')) wp_die('Insufficient permissions.');
        ?>
        <div class="wrap">
          <h1>Assistants (Deluxe) — Pricing & Quotas</h1>
          <form method="post" action="options.php">
            <?php settings_fields(self::SLUG); ?>
            <h2 class="title">Report Pricing</h2>
            <table class="form-table" role="presentation">
              <tr><th>Brasso base</th><td><input type="number" name="<?php echo esc_attr(self::OPT_PRICE_BRASSO) ?>" value="<?php echo esc_attr(get_option(self::OPT_PRICE_BRASSO,20)) ?>"></td></tr>
              <tr><th>Agent base</th><td><input type="number" name="<?php echo esc_attr(self::OPT_PRICE_AGENT) ?>" value="<?php echo esc_attr(get_option(self::OPT_PRICE_AGENT,80)) ?>"></td></tr>
              <tr><th>One-pager multiplier</th><td><input type="number" name="<?php echo esc_attr(self::OPT_MULT_ONEPAGER) ?>" value="<?php echo esc_attr(get_option(self::OPT_MULT_ONEPAGER,1)) ?>"></td></tr>
              <tr><th>Short multiplier</th><td><input type="number" name="<?php echo esc_attr(self::OPT_MULT_SHORT) ?>" value="<?php echo esc_attr(get_option(self::OPT_MULT_SHORT,5)) ?>"></td></tr>
              <tr><th>Extended multiplier</th><td><input type="number" name="<?php echo esc_attr(self::OPT_MULT_EXT) ?>" value="<?php echo esc_attr(get_option(self::OPT_MULT_EXT,10)) ?>"></td></tr>
            </table>

            <h2 class="title" style="margin-top:24px;">Quotas Fallback (optional)</h2>
            <p>If your backend exposes <code>/v3/assistant/quota</code> this is not needed. If not, set the Admin Key so we can call <code>/admin/usage</code>. Client ID is auto-derived.</p>
            <table class="form-table" role="presentation">
              <tr>
                <th scope="row">Admin Key</th>
                <td><input type="password" name="<?php echo esc_attr(self::OPT_ADMIN_KEY) ?>" value="<?php echo esc_attr(get_option(self::OPT_ADMIN_KEY,'')) ?>" class="regular-text" autocomplete="off">
                  <p class="description">Use the same value as your backend <code>BRASSGATE_ADMIN_KEY</code>; it is sent as <code>X-Admin-Key</code> only to your backend when needed.</p>
                </td>
              </tr>
              <tr>
                <th scope="row">Service X-API-Key</th>
                <td>
                  <input type="password" name="<?php echo esc_attr(self::OPT_SERVICE_API_KEY); ?>" value="<?php echo esc_attr(get_option(self::OPT_SERVICE_API_KEY, '')); ?>" class="regular-text" autocomplete="off">
                  <p class="description">Stored once per site and forwarded as <code>X-API-Key</code> to every Large Video Suite request. Leave blank to inherit the <code>BGAD_SERVICE_API_KEY</code> environment variable.</p>
                </td>
              </tr>
            </table>

            <?php submit_button(); ?>
          </form>

          <hr style="margin:24px 0;">
          <h2 class="title">Health & Diagnostics</h2>
          <p>Run quick checks to verify template availability, API connectivity, and key detection. Results are admin-only.</p>
          <p><button id="bga4-health-run" class="button">Run Health Check</button></p>
          <pre id="bga4-health-out" style="max-height:320px;overflow:auto;background:#111;color:#eee;padding:12px;" aria-live="polite"></pre>
          <script>
          (function(){
            const btn = document.getElementById('bga4-health-run');
            const out = document.getElementById('bga4-health-out');
            if (!btn || !out) return;
            function qs(obj){ const u=new URLSearchParams(); for (const k in obj){u.append(k, obj[k]);} return u.toString(); }
            btn.addEventListener('click', function(){
              out.textContent = 'Running...';
              fetch('<?php echo esc_js(admin_url('admin-ajax.php')); ?>', {
                method: 'POST',
                headers: {'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8'},
                credentials: 'same-origin',
                body: qs({action: 'bga4_health', nonce: '<?php echo esc_js(wp_create_nonce(self::NONCE)); ?>'})
              }).then(r => r.json()).then(j => {
                if (!j || !j.success) throw (j && j.data) || 'Unknown error';
                out.textContent = JSON.stringify(j.data, null, 2);
              }).catch(e => {
                out.textContent = 'Health check failed: ' + (e && (e.message||e));
              });
            });
          })();
          </script>
        </div>
        <?php
    }

    /** ---------------- AJAX bridge (server → server) ---------------- */
    private static function check_nonce() {
        if (!current_user_can('read')) wp_send_json_error(['message'=>'forbidden'], 403);
        self::ensure_plan_for_ajax();
        $valid = check_ajax_referer('bgad_sc', 'nonce', false);
        if (!$valid) {
            $valid = check_ajax_referer(self::NONCE, 'nonce', false);
        }
        if (!$valid) {
            wp_send_json_error(['message' => 'invalid nonce'], 403);
        }
    }

  // Capability guard for Video Studio creator routes (filterable)
  private static function check_studio_cap() {
    $default_cap = has_filter('bgvs_required_capability') ? 'edit_posts' : 'read';
    $cap = apply_filters('bgvs_required_capability', $default_cap);
    if (!$cap) {
      $cap = $default_cap;
    }
    if (!current_user_can($cap)) wp_send_json_error(['message'=>'forbidden'], 403);
  }

  // Helper: proxy a streaming GET to backend while preserving identity headers
  private static function proxy_stream($path, $filename_hint = '', $default_type = 'application/octet-stream', $timeout = 600) {
    $url = self::backend_base() . $path;
    $headers = self::user_headers();
    $res = wp_remote_request($url, [
      'method'  => 'GET',
      'timeout' => $timeout,
      'headers' => $headers,
    ]);
    if (is_wp_error($res)) {
      status_header(502);
      echo 'Upstream error: ' . esc_html($res->get_error_message());
      wp_die();
    }
    $code = wp_remote_retrieve_response_code($res);
    $body = wp_remote_retrieve_body($res);
    $ctype = wp_remote_retrieve_header($res, 'content-type');
    if ($code < 200 || $code >= 300) {
      status_header($code ?: 500);
      echo esc_html(is_string($body) ? $body : 'Upstream error');
      wp_die();
    }
    if (!$ctype) $ctype = $default_type;
    nocache_headers();
    header('Content-Type: ' . $ctype);
    if ($filename_hint) {
      header('Content-Disposition: inline; filename="' . basename($filename_hint) . '"');
    }
    echo $body;
    wp_die();
  }

    public static function ajax_send() {
        self::check_nonce();
    $cid    = sanitize_text_field($_POST['conversation_id'] ?? '');
    $payload = [
      'message' => (string)($_POST['message'] ?? ''),
      'scope'   => sanitize_text_field($_POST['scope'] ?? 'self'),
    ];
    // Only include conversation_id if it's a real server ID (not empty and not local_)
    if (!empty($cid) && stripos($cid, 'local_') !== 0) {
      $payload['conversation_id'] = $cid;
    }
        $headers = self::user_headers();
        $headers['x-assistant-style'] = sanitize_text_field($_POST['style'] ?? 'friendly');
        $headers['x-assistant-strict'] = !empty($_POST['strict']) ? '1' : '0';
        $headers['x-assistant-model'] = sanitize_text_field($_POST['model'] ?? 'brasso');

        $r = self::http('POST', '/v3/assistant/send', $payload, 120);
        if (empty($r['ok'])) wp_send_json_error($r, $r['status'] ?? 500);
        wp_send_json_success($r['data']);
    }

    public static function ajax_history() {
        self::check_nonce();
    $cid = sanitize_text_field($_POST['conversation_id'] ?? $_GET['conversation_id'] ?? '');
    // For local / unsynced conversations, return empty history to avoid 404
    if (empty($cid) || stripos($cid, 'local_') === 0) {
      wp_send_json_success(['messages' => []]);
    }
        $r = self::http('GET', '/v3/assistant/history?conversation_id='.rawurlencode($cid), null, 60);
        if (empty($r['ok'])) wp_send_json_error($r, $r['status'] ?? 500);
        wp_send_json_success($r['data']);
    }

    public static function ajax_convos() {
        self::check_nonce();
        $r = self::http('GET', '/v3/assistant/conversations?page=1&page_size=50', null, 60);
        if (empty($r['ok'])) wp_send_json_error($r, $r['status'] ?? 500);
        wp_send_json_success($r['data']);
    }

    public static function ajax_sources() {
        self::check_nonce();
    $cid = sanitize_text_field($_POST['conversation_id'] ?? '');
    // For local / unsynced conversations, return empty sources to avoid 404
    if (empty($cid) || stripos($cid, 'local_') === 0) {
      wp_send_json_success(['sources' => []]);
    }
        $r = self::http('GET', '/v3/assistant/sources?conversation_id='.rawurlencode($cid), null, 60);
        if (empty($r['ok'])) wp_send_json_error($r, $r['status'] ?? 500);
        wp_send_json_success($r['data']);
    }

  // -------- Video Studio AJAX (v3/video) --------
  public static function ajax_bgvs_preflight() {
    self::check_nonce();
    $now = time();
    if (self::$vs_cb_open_until && $now < self::$vs_cb_open_until) {
      $corr = function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : uniqid('corr_', true);
      wp_send_json([
        'ok'=>false,
        'status'=>503,
        'error'=>'backend unreachable (circuit open)',
        'via'=>'wp-proxy',
        'corr'=>$corr,
        'ts'=>time(),
      ], 200);
    }
    $r = self::bgvs_proxy_json('GET', '/v3/video/preflight');
    // Single 503 retry (explicit) in addition to base proxy retries
    if (empty($r['ok']) && (int)($r['status'] ?? 0) === 503) {
      usleep(300000); // 300ms
      $r = self::bgvs_proxy_json('GET', '/v3/video/preflight');
    }
    if (!empty($r['ok'])) {
      // reset CB on success
      self::$vs_cb_fails = 0; self::$vs_cb_last_fail = 0; self::$vs_cb_open_until = 0;
      wp_send_json($r);
    }
    // failure
    $last = self::$vs_cb_last_fail ?: 0;
    $within = ($now - $last) <= 60;
    if ($within) {
      self::$vs_cb_fails += 1;
    } else {
      self::$vs_cb_fails = 1;
    }
    self::$vs_cb_last_fail = $now;
    if (self::$vs_cb_fails >= 2 && $within) {
      self::$vs_cb_open_until = $now + 15; // open for 15s
    }
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_plan() {
    self::check_nonce();
    self::check_studio_cap();
    // Support unified payload body or individual fields
    $payload = null;
    if (isset($_POST['payload'])) {
      $pl = json_decode(stripslashes((string)$_POST['payload']), true);
      if (is_array($pl)) $payload = $pl;
    }
    if (!is_array($payload)) {
      $payload = [
        'prompt'      => (string)($_POST['prompt'] ?? ''),
        'total_sec'   => max(15, min(300, intval($_POST['total_sec'] ?? 30))),
        'orientation' => sanitize_text_field($_POST['orientation'] ?? 'landscape'),
        'variety'     => isset($_POST['variety']) ? floatval($_POST['variety']) : 0.35,
        'use_lore'    => isset($_POST['use_lore']) ? (bool)$_POST['use_lore'] : true,
      ];
    }
    $r = self::bgvs_proxy_json('POST', '/v3/video/plan', $payload);
    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      $context = array(
        'source' => 'ajax',
        'page' => 'shortform',
        'action' => 'plan',
        'endpoint' => '/v3/video/plan',
        'method' => 'POST',
      );
      bg_increment_user_usage(get_current_user_id(), 'shortform_from_user_videos', 1, null, $context);
    }
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_preview_start() {
    self::check_nonce();
    self::check_studio_cap();
    // Debug: log receipt of POST for diagnostics
    if (defined('WP_DEBUG') && WP_DEBUG) {
      error_log('[bgvs_preview_start] received: ' . print_r($_POST, true));
    }
    // Accept JSON payload or individual form fields
    $payload = null;
    if (isset($_POST['payload'])) {
      $pl = json_decode(stripslashes((string)$_POST['payload']), true);
      if (is_array($pl)) $payload = $pl;
    }
    if (!is_array($payload)) {
      // Accept JSON-like form fields and forward as-is
      $payload = [
        'prompt'        => (string)($_POST['prompt'] ?? ''),
        'length_sec'    => max(5, min(3600, intval($_POST['length_sec'] ?? 30))),
        'orientation'   => sanitize_text_field($_POST['orientation'] ?? 'landscape'),
        // Legacy flat fields (kept for backward compatibility)
        'intro_splash'  => sanitize_text_field($_POST['intro_splash'] ?? ''),
        'outro_splash'  => sanitize_text_field($_POST['outro_splash'] ?? ''),
        'intro_dur'     => floatval($_POST['intro_dur'] ?? 0),
        'outro_dur'     => floatval($_POST['outro_dur'] ?? 0),
        'variety'       => isset($_POST['variety']) ? floatval($_POST['variety']) : 0.35,
        'strict_orientation' => isset($_POST['strict_orientation']) ? (bool)$_POST['strict_orientation'] : true,
        'max_candidates_per_beat' => intval($_POST['max_candidates_per_beat'] ?? 10),
        'healer'        => isset($_POST['healer']) ? (bool)$_POST['healer'] : true,
        'seed'          => isset($_POST['seed']) ? intval($_POST['seed']) : null,
      ];
    }
    // Optional object-shaped splashes
    if (!empty($_POST['intro_splash']) && is_string($_POST['intro_splash'])) {
      $js = json_decode(stripslashes((string)$_POST['intro_splash']), true);
      if (is_array($js) && isset($js['filename'])) $payload['intro_splash'] = $js;
    }
    if (!empty($_POST['outro_splash']) && is_string($_POST['outro_splash'])) {
      $js = json_decode(stripslashes((string)$_POST['outro_splash']), true);
      if (is_array($js) && isset($js['filename'])) $payload['outro_splash'] = $js;
    }
    // Optional interstitials JSON
    if (!empty($_POST['interstitials'])) {
      $ints = json_decode(stripslashes((string)$_POST['interstitials']), true);
      if (is_array($ints)) $payload['interstitials'] = $ints;
    }
    // Optional JSON plan
    if (!empty($_POST['plan'])) {
      $plan = json_decode(stripslashes((string)$_POST['plan']), true);
      if (is_array($plan)) $payload['plan'] = $plan;
    }
    $r = self::bgvs_proxy_json('POST', '/v3/video/preview/start', $payload);
    if (defined('WP_DEBUG') && WP_DEBUG) {
      $code = isset($r['status']) ? (int)$r['status'] : 0;
      error_log('[bgvs_preview_start] backend status: ' . $code);
    }
    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      $context = array(
        'source' => 'ajax',
        'page' => 'shortform',
        'action' => 'preview',
        'endpoint' => '/v3/video/preview/start',
        'method' => 'POST',
      );
      bg_increment_user_usage(get_current_user_id(), 'shortform_from_user_videos', 1, null, $context);
    }
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_splash_list() {
    self::check_nonce();
    $role = sanitize_text_field($_POST['role'] ?? $_GET['role'] ?? '');
    $orientation = sanitize_text_field($_POST['orientation'] ?? $_GET['orientation'] ?? '');
    // Translate role -> type (API expects `type`), and forward orientation if provided
    $qs = [];
    if ($role !== '') { $qs['type'] = $role; }
    if ($orientation !== '') { $qs['orientation'] = $orientation; }
    $r = self::bgvs_proxy_json('GET', '/v3/splash/list', null, $qs);
    // Single 503 retry for transient catalog hiccups
    if (empty($r['ok']) && (int)($r['status'] ?? 0) === 503) {
      usleep(300000); // 300ms
      $r = self::bgvs_proxy_json('GET', '/v3/splash/list', null, $qs);
    }
    if (!empty($r['ok']) && is_array($r['body'])) {
      $body = $r['body'];
      $items = [];
      if (isset($body['items']) && is_array($body['items'])) {
        $items = $body['items'];
      } elseif (isset($body['list']) && is_array($body['list'])) {
        $items = $body['list'];
      }
      $r['body'] = ['items' => $items];
    }
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_preview_status() {
    self::check_nonce();
    $fn = sanitize_text_field($_POST['filename'] ?? $_GET['filename'] ?? '');
    $r = self::bgvs_proxy_json('GET', '/v3/video/preview/status', null, ['filename'=>$fn]);
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_preview_stream() {
    self::check_nonce();
    $fn = sanitize_text_field($_GET['filename'] ?? $_POST['filename'] ?? '');
    if (!$fn) wp_send_json_error(['message'=>'missing filename'], 400);
    self::bgvs_proxy_stream('/v3/video/preview/stream', ['filename'=>$fn]);
  }

  public static function ajax_bgvs_preview_delete() {
    self::check_nonce();
    self::check_studio_cap();
    $fn = sanitize_text_field($_POST['filename'] ?? '');
    $r = self::bgvs_proxy_json('DELETE', '/v3/video/preview', null, ['filename' => $fn]);
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_audio_plan() {
    self::check_nonce();
    self::check_studio_cap();
    // Support unified payload body or individual fields
    $payload = null;
    if (isset($_POST['payload'])) {
      $pl = json_decode(stripslashes((string)$_POST['payload']), true);
      if (is_array($pl)) $payload = $pl;
    }
    if (!is_array($payload)) {
      $payload = [
        'preview_filename' => sanitize_text_field($_POST['preview_filename'] ?? ''),
        'use_music'        => isset($_POST['use_music']) ? (bool)$_POST['use_music'] : true,
        'music_style'      => sanitize_text_field($_POST['music_style'] ?? ''),
        'instrumental'     => isset($_POST['instrumental']) ? (bool)$_POST['instrumental'] : true,
        'use_narration'    => isset($_POST['use_narration']) ? (bool)$_POST['use_narration'] : false,
        'voice_id'         => sanitize_text_field($_POST['voice_id'] ?? ''),
        'tone'             => sanitize_text_field($_POST['tone'] ?? ''),
      ];
    }
    // Optional plan JSON
    if (!empty($_POST['plan'])) {
      $pl = json_decode(stripslashes((string)$_POST['plan']), true);
      if (is_array($pl)) $payload['plan'] = $pl;
    }
    $r = self::bgvs_proxy_json('POST', '/v3/video/audio/plan', $payload);
    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      $context = array(
        'source' => 'ajax',
        'page' => 'shortform',
        'action' => 'audio_plan',
        'endpoint' => '/v3/video/audio/plan',
        'method' => 'POST',
      );
      bg_increment_user_usage(get_current_user_id(), 'shortform_from_user_videos', 1, null, $context);
    }
    wp_send_json($r, 200);
  }

  // ---- Music generation (server→server) ----
  public static function ajax_bgvs_music_start() {
    self::check_nonce();
    self::check_studio_cap();
    // Support unified payload body or individual fields
    $payload = null;
    if (isset($_POST['payload'])) {
      $pl = json_decode(stripslashes((string)$_POST['payload']), true);
      if (is_array($pl)) $payload = $pl;
    }
    if (!is_array($payload)) {
      $payload = [
        'prompt_music' => (string)($_POST['prompt'] ?? ''),
        'length_sec'   => max(1, min(600, intval($_POST['duration'] ?? 30))),
        'instrumental' => isset($_POST['instrumental']) ? (bool)$_POST['instrumental'] : true,
        'orientation'  => sanitize_text_field($_POST['orientation'] ?? 'landscape'),
        'style'        => sanitize_text_field($_POST['style'] ?? ''),
      ];
    }
    $r = self::bgvs_proxy_json('POST', '/v3/audio/music/start', $payload);
    if (!empty($r['ok']) && is_array($r['body'])) {
      $data = $r['body'];
    } else {
      $data = is_array($r['body'] ?? null) ? $r['body'] : [];
    }
    // Normalize to vendor_filename for UI
    if (is_array($data) && isset($data['filename']) && !isset($data['vendor_filename'])) {
      $data['vendor_filename'] = $data['filename'];
    }
    $r['body'] = $data;
    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      $context = array(
        'source' => 'ajax',
        'page' => 'shortform',
        'action' => 'music_start',
        'endpoint' => '/v3/audio/music/start',
        'method' => 'POST',
      );
      bg_increment_user_usage(get_current_user_id(), 'shortform_from_user_videos', 1, null, $context);
    }
    wp_send_json($r, 200);
  }
  public static function ajax_bgvs_music_stream() {
    self::check_nonce();
    $fn = sanitize_text_field($_GET['vendor_filename'] ?? $_POST['vendor_filename'] ?? '');
    if (!$fn) wp_send_json_error(['message'=>'missing vendor_filename'], 400);
    self::bgvs_proxy_stream('/v3/audio/music/stream', ['filename'=>$fn]);
  }

  // ---- Narration generation (server→server) ----
  public static function ajax_bgvs_narrate_start() {
    self::check_nonce();
    self::check_studio_cap();
    // Support unified payload body or individual fields
    $payload = null;
    if (isset($_POST['payload'])) {
      $pl = json_decode(stripslashes((string)$_POST['payload']), true);
      if (is_array($pl)) $payload = $pl;
    }
    if (!is_array($payload)) {
      $payload = [
        'script'  => (string)($_POST['script'] ?? ''),
        'voice_id'=> sanitize_text_field($_POST['voice_id'] ?? ''),
        'tone'    => sanitize_text_field($_POST['tone'] ?? ''),
      ];
    }
    // Backend v3/audio may use a different key naming; adapt if needed
    $r = self::bgvs_proxy_json('POST', '/v3/audio/narrate/start', $payload);
    $data = is_array($r['body'] ?? null) ? $r['body'] : [];
    if (is_array($data) && isset($data['filename']) && !isset($data['vendor_filename'])) {
      $data['vendor_filename'] = $data['filename'];
    }
    $r['body'] = $data;
    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      $context = array(
        'source' => 'ajax',
        'page' => 'shortform',
        'action' => 'narrate_start',
        'endpoint' => '/v3/audio/narrate/start',
        'method' => 'POST',
      );
      bg_increment_user_usage(get_current_user_id(), 'shortform_from_user_videos', 1, null, $context);
    }
    wp_send_json($r, 200);
  }
  public static function ajax_bgvs_narrate_stream() {
    self::check_nonce();
    $fn = sanitize_text_field($_GET['vendor_filename'] ?? $_POST['vendor_filename'] ?? '');
    if (!$fn) wp_send_json_error(['message'=>'missing vendor_filename'], 400);
    // Some backends use filename param; mirror music for consistency
    self::bgvs_proxy_stream('/v3/audio/narrate/stream', ['filename'=>$fn]);
  }

  // ---- Voices (optional) ----
  public static function ajax_bgvs_voices() {
    self::check_nonce();
    $r = self::bgvs_proxy_json('GET', '/v3/audio/voices');
    if (!empty($r['ok']) && is_array($r['body'])) {
      wp_send_json($r['body'], 200);
    }
    // Fallback small list
    wp_send_json(['items' => [ ['id'=>'edward','name'=>'Edward'], ['id'=>'luna','name'=>'Luna'] ]], 200);
  }

  public static function ajax_bgvs_finalize_start() {
    self::check_nonce();
    self::check_studio_cap();
    // Build finalize payload with minimal defaults (supports unified payload)
    $payload = null;
    if (isset($_POST['payload'])) {
      $pl = json_decode(stripslashes((string)$_POST['payload']), true);
      if (is_array($pl)) $payload = $pl;
    }
    if (!is_array($payload)) {
      $payload = [
        'preview_filename' => sanitize_text_field($_POST['preview_filename'] ?? ''),
        'music' => [
          'source' => sanitize_text_field($_POST['music_source'] ?? 'none'),
          'vendor_filename' => sanitize_text_field($_POST['music_vendor_filename'] ?? ''),
          'style'  => sanitize_text_field($_POST['music_style'] ?? ''),
          'instrumental' => isset($_POST['music_instrumental']) ? (bool)$_POST['music_instrumental'] : true,
        ],
        'narration' => [
          'source' => sanitize_text_field($_POST['nar_source'] ?? 'none'),
          'vendor_filename' => sanitize_text_field($_POST['nar_vendor_filename'] ?? ''),
          'voice_id' => sanitize_text_field($_POST['nar_voice_id'] ?? ''),
          'tone' => sanitize_text_field($_POST['nar_tone'] ?? ''),
        ],
        'overlays' => [],
        'title' => sanitize_text_field($_POST['title'] ?? ''),
        'description' => sanitize_textarea_field($_POST['description'] ?? ''),
        'strict_length' => isset($_POST['strict_length']) ? (bool)$_POST['strict_length'] : true,
      ];
      if (!empty($_POST['overlays'])) {
        $ov = json_decode(stripslashes((string)$_POST['overlays']), true);
        if (is_array($ov)) $payload['overlays'] = $ov;
      }
    }
    $r = self::bgvs_proxy_json('POST', '/v3/video/finalize/start', $payload);
    if (empty($r['ok']) || !is_array($r['body'])) {
      $status = isset($r['status']) ? intval($r['status']) : 500;
      $err = is_array($r['body'] ?? null) ? ($r['body']['error'] ?? $r['body']['detail'] ?? 'upstream error') : (is_string($r['body'] ?? '') ? $r['body'] : 'upstream error');
      wp_send_json_error(['message'=>$err], $status ?: 500);
    }
    // Attach a proxy URL for download convenience
    $data = $r['body'];
    if (is_array($data) && !empty($data['filename'])) {
      $data['proxy_url'] = add_query_arg([
        'action' => 'bgvs_library_stream',
        'nonce'  => wp_create_nonce(self::NONCE),
        'filename' => $data['filename'],
      ], admin_url('admin-ajax.php'));
    }
    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      $context = array(
        'source' => 'ajax',
        'page' => 'shortform',
        'action' => 'finalize',
        'endpoint' => '/v3/video/finalize/start',
        'method' => 'POST',
      );
      bg_increment_user_usage(get_current_user_id(), 'shortform_from_user_videos', 1, null, $context);
    }
    wp_send_json_success($data);
  }

  public static function ajax_bgvs_library_list() {
    self::check_nonce();
    $r = self::bgvs_proxy_json('GET', '/v3/video/library/list');
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_library_stream() {
    self::check_nonce();
    $fn = sanitize_text_field($_GET['filename'] ?? $_POST['filename'] ?? '');
    if (!$fn) wp_send_json_error(['message'=>'missing filename'], 400);
    self::bgvs_proxy_stream('/v3/video/library/download', ['filename'=>$fn]);
  }

  public static function ajax_bgvs_library_delete() {
    self::check_nonce();
    self::check_studio_cap();
    $fn = sanitize_text_field($_POST['filename'] ?? '');
    $r = self::bgvs_proxy_json('DELETE', '/v3/video/library', null, ['filename'=>$fn]);
    if (!empty($r['ok']) && function_exists('bg_decrement_user_usage')) {
      $context = array(
        'source' => 'ajax',
        'page' => 'videos',
        'action' => 'delete',
        'endpoint' => '/v3/video/library',
        'method' => 'DELETE',
      );
      bg_decrement_user_usage(get_current_user_id(), 'videos_upload', 1, null, $context);
    }
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_clips_search() {
    self::check_nonce();
    $q = isset($_GET['q']) ? sanitize_text_field($_GET['q']) : sanitize_text_field($_POST['q'] ?? '');
    $r = self::bgvs_proxy_json('GET', '/v3/video/clips/search', null, $q ? ['query'=>$q] : []);
    wp_send_json($r, 200);
  }

  public static function ajax_bgvs_thumb() {
    self::check_nonce();
    $fn = sanitize_text_field($_GET['filename'] ?? $_POST['filename'] ?? '');
    $t  = isset($_GET['t']) ? floatval($_GET['t']) : floatval($_POST['t'] ?? 0.5);
    if (!$fn) wp_send_json_error(['message'=>'missing filename'], 400);
    self::bgvs_proxy_stream('/v3/video/thumb', ['filename'=>$fn, 't'=>$t]);
  }

  public static function ajax_vertical_socials_status() {
    $nonce = '';
    if (isset($_POST['nonce'])) {
        $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
    } elseif (isset($_REQUEST['nonce'])) {
        $nonce = sanitize_text_field(wp_unslash($_REQUEST['nonce']));
    }

    if ($nonce === '' || !wp_verify_nonce($nonce, self::NONCE)) {
        wp_send_json(['ok' => false, 'error' => 'Invalid nonce'], 403);
    }

    if (!self::user_has_plan()) {
        wp_send_json(['ok' => false, 'error' => 'forbidden'], 403);
    }

    $client_key = function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : '';
    if ($client_key === '') {
        wp_send_json(['ok' => false, 'error' => 'Missing BrassGate client key in profile']);
    }

    $job_id = isset($_POST['job_id']) ? sanitize_text_field(wp_unslash($_POST['job_id'])) : '';
    if ($job_id === '') {
        wp_send_json(['ok' => false, 'error' => 'Missing job id']);
    }

    $base = self::vertical_backend_base();
    $url = rtrim($base, '/') . '/v3/vertical/status/' . rawurlencode($job_id);

    $headers = self::vertical_headers();
    if ($client_key !== '') {
        $headers['x-brassgate-client-key'] = $client_key;
        $headers['X-Brassgate-Client-Key'] = $client_key;
    }
    if (!isset($headers['X-Correlation-Id']) && function_exists('wp_generate_uuid4')) {
        $headers['X-Correlation-Id'] = wp_generate_uuid4();
    }

    $response = wp_remote_get($url, [
        'timeout'   => 60,
        'headers'   => $headers,
        'sslverify' => true,
    ]);

    if (is_wp_error($response)) {
        wp_send_json(['ok' => false, 'error' => $response->get_error_message()], 502);
    }

    $status = (int) wp_remote_retrieve_response_code($response);
    $raw_body = (string) wp_remote_retrieve_body($response);
    $decoded = json_decode($raw_body, true);
    $corr = wp_remote_retrieve_header($response, 'x-correlation-id');

    if ($status >= 200 && $status < 300 && is_array($decoded)) {
        wp_send_json(['ok' => true, 'corr' => $corr, 'body' => $decoded]);
        return;
    }

    $error_detail = '';
    if (is_array($decoded)) {
        $error_detail = isset($decoded['error']) ? (string) $decoded['error'] : '';
        if ($error_detail === '' && isset($decoded['detail'])) {
            $error_detail = is_string($decoded['detail']) ? $decoded['detail'] : wp_json_encode($decoded['detail']);
        }
        if ($error_detail === '' && isset($decoded['message'])) {
            $error_detail = (string) $decoded['message'];
        }
        if ($error_detail === '') {
            $error_detail = wp_json_encode($decoded);
        }
    } else {
        $error_detail = $raw_body;
    }

    wp_send_json(['ok' => false, 'error' => $error_detail ?: 'Backend error', 'status' => $status], $status >= 400 ? $status : 502);
}

  public static function ajax_vertical_socials_ping() {
    $nonce = '';
    if (isset($_POST['nonce'])) {
      $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
    } elseif (isset($_REQUEST['nonce'])) {
      $nonce = sanitize_text_field(wp_unslash($_REQUEST['nonce']));
    }

    if ($nonce === '' || !wp_verify_nonce($nonce, self::NONCE)) {
      wp_send_json(['ok' => false, 'error' => 'Invalid nonce'], 403);
    }

    if (!current_user_can('manage_options')) {
      wp_send_json(['ok' => false, 'error' => 'forbidden'], 403);
    }

    $client_key = function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : '';
    if ($client_key === '') {
      wp_send_json(['ok' => false, 'error' => 'Missing BrassGate client key in profile']);
    }

    $base = self::vertical_backend_base();
    $headers = self::vertical_headers();
    if ($client_key !== '') {
      $headers['x-brassgate-client-key'] = $client_key;
      $headers['X-Brassgate-Client-Key'] = $client_key;
    }
    if (!isset($headers['X-Correlation-Id']) && function_exists('wp_generate_uuid4')) {
      $headers['X-Correlation-Id'] = wp_generate_uuid4();
    }

    $response = wp_remote_get($base . '/v3/vertical/ping', [
      'timeout'   => 60,
      'headers'   => $headers,
      'sslverify' => true,
    ]);

    if (is_wp_error($response)) {
      wp_send_json(['ok' => false, 'error' => $response->get_error_message()], 502);
    }

    $status = (int) wp_remote_retrieve_response_code($response);
    $raw_body = (string) wp_remote_retrieve_body($response);
    $decoded = json_decode($raw_body, true);
    $corr = wp_remote_retrieve_header($response, 'x-correlation-id');

    if ($status >= 200 && $status < 300) {
      wp_send_json([
        'ok' => true,
        'corr' => $corr,
        'body' => is_array($decoded) ? $decoded : null,
        'raw' => $raw_body,
      ]);
    }

    $error_detail = '';
    if (is_array($decoded)) {
      $error_detail = isset($decoded['error']) ? (string) $decoded['error'] : '';
      if ($error_detail === '' && isset($decoded['detail'])) {
        $error_detail = is_string($decoded['detail']) ? $decoded['detail'] : wp_json_encode($decoded['detail']);
      }
      if ($error_detail === '' && isset($decoded['message'])) {
        $error_detail = (string) $decoded['message'];
      }
      if ($error_detail === '') {
        $error_detail = wp_json_encode($decoded);
      }
    } else {
      $error_detail = $raw_body;
    }

    wp_send_json(['ok' => false, 'error' => $error_detail ?: 'Backend error', 'status' => $status], 502);
  }

  public static function ajax_horizontal_socials_ping() {
    $nonce = '';
    if (isset($_POST['nonce'])) {
      $nonce = sanitize_text_field(wp_unslash($_POST['nonce']));
    } elseif (isset($_REQUEST['nonce'])) {
      $nonce = sanitize_text_field(wp_unslash($_REQUEST['nonce']));
    }

    if ($nonce === '' || !wp_verify_nonce($nonce, self::NONCE)) {
      wp_send_json(['ok' => false, 'error' => 'Invalid nonce'], 403);
    }

    if (!current_user_can('manage_options')) {
      wp_send_json(['ok' => false, 'error' => 'forbidden'], 403);
    }

    $client_key = function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : '';
    if ($client_key === '') {
      wp_send_json(['ok' => false, 'error' => 'Missing BrassGate client key in profile']);
    }

    $base = self::horizontal_backend_base();
    $headers = self::horizontal_headers();
    if ($client_key !== '') {
      $headers['x-brassgate-client-key'] = $client_key;
      $headers['X-Brassgate-Client-Key'] = $client_key;
    }
    if (!isset($headers['X-Correlation-Id']) && function_exists('wp_generate_uuid4')) {
      $headers['X-Correlation-Id'] = wp_generate_uuid4();
    }

    $response = wp_remote_get($base . '/v3/horizontal_socials/ping', [
      'timeout'   => 60,
      'headers'   => $headers,
      'sslverify' => true,
    ]);

    if (is_wp_error($response)) {
      wp_send_json(['ok' => false, 'error' => $response->get_error_message()], 502);
    }

    $status = (int) wp_remote_retrieve_response_code($response);
    $raw_body = (string) wp_remote_retrieve_body($response);
    $decoded = json_decode($raw_body, true);
    $corr = wp_remote_retrieve_header($response, 'x-correlation-id');

    if ($status >= 200 && $status < 300) {
      wp_send_json([
        'ok' => true,
        'corr' => $corr,
        'body' => is_array($decoded) ? $decoded : null,
        'raw' => $raw_body,
      ]);
    }

    $error_detail = '';
    if (is_array($decoded)) {
      $error_detail = isset($decoded['error']) ? (string) $decoded['error'] : '';
      if ($error_detail === '' && isset($decoded['detail'])) {
        $error_detail = is_string($decoded['detail']) ? $decoded['detail'] : wp_json_encode($decoded['detail']);
      }
      if ($error_detail === '' && isset($decoded['message'])) {
        $error_detail = (string) $decoded['message'];
      }
      if ($error_detail === '') {
        $error_detail = wp_json_encode($decoded);
      }
    } else {
      $error_detail = $raw_body;
    }

    wp_send_json(['ok' => false, 'error' => $error_detail ?: 'Backend error', 'status' => $status], $status >= 400 ? $status : 502);
  }

  public static function ajax_vertical_socials_download() {
    $nonce = '';
    if (isset($_REQUEST['nonce'])) {
      $nonce = sanitize_text_field(wp_unslash($_REQUEST['nonce']));
    }

    if ($nonce === '' || !wp_verify_nonce($nonce, self::NONCE)) {
      status_header(403);
      echo 'Invalid nonce';
      wp_die();
    }

    if (!current_user_can('manage_options')) {
      status_header(403);
      echo 'forbidden';
      wp_die();
    }

    $user_id = get_current_user_id();
    $client_key = $user_id ? trim((string) get_user_meta($user_id, 'brassgate_client_key', true)) : '';
    if ($client_key === '') {
      wp_send_json(['ok' => false, 'error' => 'Missing BrassGate client key in profile']);
    }

    $job_id = '';
    if (isset($_REQUEST['job_id'])) {
      $job_id = sanitize_text_field(wp_unslash($_REQUEST['job_id']));
    }

    if ($job_id === '') {
      status_header(400);
      echo 'Missing job id';
      wp_die();
    }

    $inline = isset($_REQUEST['inline']) && (string) wp_unslash($_REQUEST['inline']) === '1';

    $query = [
      'job_id' => $job_id,
    ];
    if ($inline) {
      $query['inline'] = '1';
    }

    $base = self::vertical_backend_base();
    $url = add_query_arg($query, $base . '/v3/vertical/download');

    $headers = self::vertical_headers(false);
    if ($client_key !== '') {
      $headers['x-brassgate-client-key'] = $client_key;
      $headers['X-Brassgate-Client-Key'] = $client_key;
    }
    if ($inline) {
      $headers['Accept'] = '*/*';
    } elseif (!isset($headers['Accept'])) {
      $headers['Accept'] = 'application/json';
    }
    if (!isset($headers['X-Correlation-Id']) && function_exists('wp_generate_uuid4')) {
      $headers['X-Correlation-Id'] = wp_generate_uuid4();
    }

    $response = wp_remote_get($url, [
      'timeout'   => $inline ? 120 : 60,
      'headers'   => $headers,
      'sslverify' => true,
    ]);

    if (is_wp_error($response)) {
      if ($inline) {
        status_header(502);
        echo esc_html($response->get_error_message());
        wp_die();
      }

      wp_send_json(['ok' => false, 'error' => $response->get_error_message()], 502);
    }

    $status = (int) wp_remote_retrieve_response_code($response);
    $corr = wp_remote_retrieve_header($response, 'x-correlation-id');

    if ($inline) {
      $body = wp_remote_retrieve_body($response);
      if ($status < 200 || $status >= 300) {
        status_header($status);
        echo esc_html($body);
        wp_die();
      }

      $content_type = wp_remote_retrieve_header($response, 'content-type');
      $content_length = wp_remote_retrieve_header($response, 'content-length');
      $filename = sanitize_file_name($job_id . '.mp4');

      status_header(200);
      if ($content_type) {
        header('Content-Type: ' . $content_type);
      } else {
        header('Content-Type: video/mp4');
      }
      if ($content_length) {
        header('Content-Length: ' . $content_length);
      }
      header('Content-Disposition: inline; filename="' . $filename . '"');
      echo $body;
      wp_die();
    }

    $raw_body = (string) wp_remote_retrieve_body($response);
    $decoded = json_decode($raw_body, true);

    if ($status >= 200 && $status < 300 && is_array($decoded) && isset($decoded['url'])) {
      wp_send_json([
        'ok' => true,
        'corr' => $corr,
        'body' => [
          'url' => esc_url_raw($decoded['url']),
        ],
      ]);
    }

    $error_detail = '';
    if (is_array($decoded)) {
      $error_detail = isset($decoded['error']) ? (string) $decoded['error'] : '';
      if ($error_detail === '' && isset($decoded['detail'])) {
        $error_detail = is_string($decoded['detail']) ? $decoded['detail'] : wp_json_encode($decoded['detail']);
      }
      if ($error_detail === '' && isset($decoded['message'])) {
        $error_detail = (string) $decoded['message'];
      }
      if ($error_detail === '') {
        $error_detail = wp_json_encode($decoded);
      }
    } else {
      $error_detail = $raw_body;
    }

    wp_send_json(['ok' => false, 'error' => $error_detail ?: 'Backend error'], 502);
  }

  public static function ajax_lvi_proxy() {
    if (!current_user_can('manage_options')) {
      wp_send_json(['ok' => false, 'error' => 'forbidden'], 403);
    }

    check_ajax_referer('bgad_lvi_nonce', 'nonce');

    $mode = sanitize_key($_POST['mode'] ?? '');
    $override_api = trim((string)wp_unslash($_POST['api_key_override'] ?? ''));
    $override_client = trim((string)wp_unslash($_POST['client_key_override'] ?? ''));

    if (!function_exists('bgad_user_headers')) {
      $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
      if (file_exists($inc)) {
        require_once $inc;
      }
    }

    $headers = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'large-video-interrogator') : [];
    $api_key = $override_api !== '' ? $override_api : trim((string)($headers['X-User-API-Key'] ?? $headers['x-user-api-key'] ?? ''));
    $client_key = $override_client !== '' ? $override_client : trim((string)($headers['X-BrassGate-Client'] ?? $headers['x-brassgate-client-key'] ?? ''));
    if ($client_key === '' && function_exists('bgad_user_client_key')) {
      $client_key = trim((string)bgad_user_client_key());
    }

    $manager_key = '';
    if (isset($headers['X-Manager-API-Key']) && $headers['X-Manager-API-Key']) {
      $manager_key = trim((string)$headers['X-Manager-API-Key']);
    } elseif (isset($headers['x-manager-api-key']) && $headers['x-manager-api-key']) {
      $manager_key = trim((string)$headers['x-manager-api-key']);
    }

    if ($api_key === '' || $client_key === '') {
      wp_send_json(['ok' => false, 'error' => 'missing-keys'], 400);
    }

    $base = function_exists('bgad_backend_base') ? bgad_backend_base() : self::backend_base();
    $base = rtrim($base, '/');

    $headers = [
      'X-User-API-Key' => $api_key,
      'X-BrassGate-Client' => $client_key,
      'X-BrassGate-Client-Key' => $client_key,
      'x-brassgate-client-key' => $client_key,
      'Accept' => 'application/json',
    ];
    if ($manager_key !== '') {
      $headers['X-Manager-API-Key'] = $manager_key;
    }

    $meta = [
      'nonce_ok' => true,
      'mode' => $mode,
    ];

    $request_args = [
      'timeout' => 300,
      'headers' => $headers,
    ];

    $endpoint = '';
    $method = 'GET';

    switch ($mode) {
      case 'index':
        $method = 'POST';
        $endpoint = '/v3/video/index';
        if (empty($_FILES['file']) || !isset($_FILES['file']['tmp_name'])) {
          wp_send_json(['ok' => false, 'error' => 'missing-file'], 400);
        }
        $file = $_FILES['file'];
        if (!empty($file['error'])) {
          wp_send_json([
            'ok'    => false,
            'error' => 'upload-error',
            'code'  => (int) $file['error'],
          ], 400);
        }
        $allowed = ['mp4', 'mov', 'mkv', 'webm'];
        $ext = strtolower(pathinfo($file['name'] ?? '', PATHINFO_EXTENSION));
        if (!in_array($ext, $allowed, true)) {
          wp_send_json(['ok' => false, 'error' => 'unsupported-type'], 400);
        }
        $max_bytes = 2048 * 1024 * 1024; // 2 GiB
        if (!empty($file['size']) && (int)$file['size'] > $max_bytes) {
          wp_send_json(['ok' => false, 'error' => 'file-too-large'], 400);
        }
        $sample = isset($_POST['sample_rate_sec']) ? floatval($_POST['sample_rate_sec']) : 1.0;
        if (!is_finite($sample) || $sample < 1.0) {
          $sample = 1.0;
        }
        $meta['sample_rate_sec'] = $sample;
        $body = [
          'sample_rate_sec' => (string)$sample,
        ];
        $ctype = $file['type'] ?? 'application/octet-stream';
        $fname = $file['name'] ?? 'upload';
        if (function_exists('curl_file_create')) {
          $body['file'] = curl_file_create($file['tmp_name'], $ctype ?: 'application/octet-stream', $fname);
        } else {
          $body['file'] = '@' . $file['tmp_name'];
        }
        $request_args['body'] = $body;
        break;

      case 'status':
      case 'result':
        $video_id = sanitize_text_field($_POST['video_id'] ?? $_GET['video_id'] ?? '');
        if ($video_id === '') {
          wp_send_json(['ok' => false, 'error' => 'missing-video-id'], 400);
        }
        $meta['video_id'] = $video_id;
        if ($mode === 'status') {
          $endpoint = '/v3/video/index/status?video_id=' . rawurlencode($video_id);
          $request_args['timeout'] = 120;
        } else {
          $endpoint = '/v3/video/index/result?video_id=' . rawurlencode($video_id);
          $request_args['timeout'] = 300;
        }
        break;

      case 'reindex':
        $method = 'POST';
        $video_id = sanitize_text_field($_POST['video_id'] ?? '');
        if ($video_id === '') {
          wp_send_json(['ok' => false, 'error' => 'missing-video-id'], 400);
        }
        $sample = isset($_POST['sample_rate_sec']) ? floatval($_POST['sample_rate_sec']) : 1.0;
        if (!is_finite($sample) || $sample < 1.0) {
          $sample = 1.0;
        }
        $meta['video_id'] = $video_id;
        $meta['sample_rate_sec'] = $sample;
        $endpoint = '/v3/video/reindex?video_id=' . rawurlencode($video_id) . '&sample_rate_sec=' . rawurlencode($sample);
        $request_args['body'] = '';
        break;

      case 'delete':
        $method = 'DELETE';
        $video_id = sanitize_text_field($_POST['video_id'] ?? '');
        if ($video_id === '') {
          wp_send_json(['ok' => false, 'error' => 'missing-video-id'], 400);
        }
        $meta['video_id'] = $video_id;
        $endpoint = '/v3/video/index?video_id=' . rawurlencode($video_id);
        $request_args['timeout'] = 120;
        break;

      default:
        wp_send_json(['ok' => false, 'error' => 'unsupported-mode'], 400);
    }

    $url = $base . $endpoint;
    $meta['url'] = $url;
    $meta['method'] = $method;

    if ($method === 'GET') {
      $response = wp_remote_get($url, $request_args);
    } elseif ($method === 'POST') {
      $request_args['method'] = 'POST';
      $response = wp_remote_post($url, $request_args);
    } else {
      $request_args['method'] = $method;
      $response = wp_remote_request($url, $request_args);
    }

    if (is_wp_error($response)) {
      wp_send_json([
        'ok' => false,
        'error' => 'request-failed',
        'message' => $response->get_error_message(),
        'meta' => $meta,
      ], 502);
    }

    $status = (int) wp_remote_retrieve_response_code($response);
    $body = (string) wp_remote_retrieve_body($response);
    $parsed = null;
    if ($body !== '') {
      $json = json_decode($body, true);
      if (json_last_error() === JSON_ERROR_NONE) {
        $parsed = $json;
      }
    }

    $ok = ($status >= 200 && $status < 300);
    $payload = [
      'ok' => $ok,
      'status' => $status,
      'body' => $parsed !== null ? $parsed : $body,
      'is_json' => ($parsed !== null),
      'meta' => $meta + [
        'headers_forwarded' => array_keys($headers),
      ],
    ];
    if ($parsed !== null) {
      $payload['json'] = $parsed;
    }

    wp_send_json($payload, $ok ? 200 : ($status ?: 502));
  }

    public static function ajax_report_start() {
        self::check_nonce();
        $payload = [
            'conversation_id' => sanitize_text_field($_POST['conversation_id'] ?? ''),
            'mode'      => sanitize_text_field($_POST['mode'] ?? 'brasso'),
            'depth'     => sanitize_text_field($_POST['depth'] ?? 'onepager'),
            'guidance'  => (string)($_POST['guidance'] ?? ''),
        ];
    $path = '/v3/assistant/report';
        if ($path==='/v3/assistant/report') $payload['long'] = true;

        $r = self::http('POST', $path, $payload, 30);
        if (empty($r['ok'])) wp_send_json_error($r, $r['status'] ?? 500);

        // Flatten {ok,data:{...}} → {...}
        $out = $r['data'];
        if (is_array($out) && array_key_exists('data', $out)) $out = $out['data'];
        wp_send_json_success($out);
    }

    public static function ajax_report_status() {
        self::check_nonce();
        $job_id = sanitize_text_field($_POST['job_id'] ?? '');
        $r = self::http('POST', '/v3/assistant/report/status', ['job_id'=>$job_id], 30);
        if (empty($r['ok'])) wp_send_json_error($r, $r['status'] ?? 500);

        // Flatten
        $out = $r['data'];
        if (is_array($out) && array_key_exists('data', $out)) $out = $out['data'];
        wp_send_json_success($out);
    }

    /**
     * Quotas (messages left)
     * Priority:
     *  1) MU-plugin bridge (bg_get_user_usage_summary + local delta)
     *  2) /v3/assistant/quota (user-scoped, if present)
     *  3) /admin/usage (X-Admin-Key only; client_id auto-derived)
     */
    public static function ajax_usage() {
        self::check_nonce();

        // 0) Preferred: local MU-plugin bridge
        if (function_exists('bg_get_user_usage_summary')) {
            $u       = wp_get_current_user();
            $user_id = (int)$u->ID;

            $summary = bg_get_user_usage_summary($user_id);
            $base_remaining = null;
            if (is_array($summary) && !empty($summary['chat_messages'])) {
                $cm = $summary['chat_messages'];
                if (isset($cm['remaining'])) {
                    $base_remaining = (int)$cm['remaining'];
                } else {
                    $limit = isset($cm['limit']) ? (int)$cm['limit'] : 0;
                    $used  = isset($cm['used'])  ? (int)$cm['used']  : 0;
                    $base_remaining = max(0, $limit - $used);
                }
            }

            $delta = 0;
            $m = get_user_meta($user_id, 'bg_quota_delta_chat_messages', true);
            if (is_numeric($m)) $delta = (int)$m;
            if ($delta === 0) {
                $opt = get_option('bg_quota_bridge_delta');
                if (is_array($opt)) {
                    if (isset($opt[$user_id]) && is_array($opt[$user_id])) {
                        $v = $opt[$user_id]['chat_messages'] ?? 0;
                        if (is_numeric($v)) $delta = (int)$v;
                    }
                    if ($delta === 0 && isset($opt['chat_messages']) && is_array($opt['chat_messages'])) {
                        $v = $opt['chat_messages'][$user_id] ?? 0;
                        if (is_numeric($v)) $delta = (int)$v;
                    }
                }
            }
            if ($delta === 0) {
                $opt2 = get_option('bg_quota_deltas');
                if (is_array($opt2)) {
                    $v = $opt2['chat_messages'][$user_id] ?? ($opt2[$user_id]['chat_messages'] ?? 0);
                    if (is_numeric($v)) $delta = (int)$v;
                }
            }
            if ($delta === 0) {
                $arr = get_user_meta($user_id, 'bg_quota_bridge_user_'.$user_id, true);
                if (is_array($arr)) {
                    $v = $arr['chat_messages'] ?? 0;
                    if (is_numeric($v)) $delta = (int)$v;
                }
            }

            if ($base_remaining !== null) {
                $final_remaining = max(0, $base_remaining - $delta);
                wp_send_json_success([
                    'messages_left'  => $final_remaining,
                    'base_remaining' => $base_remaining,
                    'local_delta'    => $delta,
                    'source'         => 'mu-plugin',
                ]);
            }
        }

        // 1) Fallback: user-scoped backend endpoint
        $res = self::http('GET', '/v3/assistant/quota', null, 20);
        if (!empty($res['ok'])) wp_send_json_success($res['data'] + ['source'=>'backend-quota']);

        // 2) Last resort: /admin/usage
        $admin_key = trim((string)get_option(self::OPT_ADMIN_KEY, ''));
        if ($admin_key === '') {
            wp_send_json_success(['messages_left' => null, 'note' => 'quota endpoint unavailable; admin key not set', 'source'=>'none']);
        }

        $u      = wp_get_current_user();
        $user_k = trim((string)get_user_meta($u->ID, self::USER_KEY, true));
        $client_id = $user_k ?: '';

        $call_usage = function($cid) use ($admin_key) {
            $path = '/admin/usage?client_id=' . rawurlencode($cid);
            return BrassGate_Assistants_Deluxe::http('GET', $path, null, 20, ['X-Admin-Key' => $admin_key]);
        };

        if ($client_id) {
            $res2 = $call_usage($client_id);
            if (!empty($res2['ok'])) wp_send_json_success($res2['data'] + ['source'=>'admin-usage:key']);
        }

        $lookup = self::http('GET', '/v1/clients/', null, 20, ['X-Admin-Key' => $admin_key]);
        if (!empty($lookup['ok']) && !empty($lookup['data'])) {
            $list = $lookup['data']['items'] ?? $lookup['data'];
            if (is_array($list)) {
                foreach ($list as $row) {
                    $ak = $row['api_key'] ?? $row['key'] ?? '';
                    if ($ak && $ak === $user_k) {
                        $real_id = $row['id'] ?? $row['client_id'] ?? '';
                        if ($real_id) {
                            $res3 = $call_usage($real_id);
                            if (!empty($res3['ok'])) wp_send_json_success($res3['data'] + ['source'=>'admin-usage:id']);
                        }
                    }
                }
            }
        }

        wp_send_json_success(['messages_left' => null, 'note' => 'quota unavailable', 'source'=>'fallback']);
    }

  /** Saved reports removed */

  /** ---------------- Admin-only Health Check ---------------- */
  public static function ajax_health() {
    // Restrict to admins
    self::check_nonce();
    if (!current_user_can('manage_options')) wp_send_json_error(['message' => 'forbidden'], 403);

    // Template availability
    $base    = plugin_dir_path(__FILE__) . 'pages/';
    $legacy  = $base . 'bga_ad_chat.php';
    $renamed = $base . 'ad-chat.php';
    $tmpl = [
      'bga_ad_chat_php' => is_readable($legacy),
      'ad_chat_php'     => is_readable($renamed),
      'inline_fallback' => true,
    ];

    // Key detection (masked/no key value exposure)
    $env = getenv('BRASSGATE_CLIENT_KEY');
    $u   = wp_get_current_user();
    $user_k = $u && $u->ID ? trim((string)get_user_meta($u->ID, self::USER_KEY, true)) : '';
    $mgr_k  = $u && $u->ID ? trim((string)get_user_meta($u->ID, self::MANAGER_KEY, true)) : '';
    $key_source = $env ? 'env' : ($user_k ? 'user' : ($mgr_k ? 'manager' : 'none'));

    // Backend pings (safe, short timeouts)
    $apiBase = self::backend_base();
    $results = [
      'api_base' => $apiBase,
    ];

    // Conversations ping
    $ping1 = self::http('GET', '/v3/assistant/conversations?page=1&page_size=1', null, 15);
    $results['conversations'] = [
      'ok' => !empty($ping1['ok']),
      'status' => $ping1['status'] ?? null,
    ];

    // Documents list ping (self scope)
    $ping2 = self::http('GET', '/v3/documents/list?page=1&page_size=1&scope=self', null, 15);
    $results['documents'] = [
      'ok' => !empty($ping2['ok']),
      'status' => $ping2['status'] ?? null,
    ];

    // Doc Repo health: team/org scopes and admin key presence
    $u = wp_get_current_user();
    $has_mgr  = $u && $u->ID ? (bool) trim((string)get_user_meta($u->ID, self::MANAGER_KEY, true)) : false;
    $admin_key = trim((string)get_option(self::OPT_ADMIN_KEY, ''));
    $results['doc_repo'] = [
      'admin_key_configured' => ($admin_key !== ''),
    ];

    // Team scope (requires manager key)
    $ping_team = self::http('GET', '/v3/documents/list?page=1&page_size=1&scope=team', null, 15);
    $results['doc_repo']['documents_team'] = [
      'ok' => !empty($ping_team['ok']),
      'status' => $ping_team['status'] ?? null,
      'note' => $has_mgr ? 'manager key present' : 'no manager key on user',
    ];

    // Org scope (requires admin key)
    $ping_org = self::http('GET', '/v3/documents/list?page=1&page_size=1&scope=org', null, 15, self::backend_admin_headers());
    $results['doc_repo']['documents_org'] = [
      'ok' => !empty($ping_org['ok']),
      'status' => $ping_org['status'] ?? null,
      'note' => $admin_key !== '' ? 'admin key configured' : 'admin key missing',
    ];

    // Quota ping (user-scoped if available)
    $ping3 = self::http('GET', '/v3/assistant/quota', null, 15);
    $results['quota'] = [
      'ok' => !empty($ping3['ok']),
      'status' => $ping3['status'] ?? null,
    ];

    wp_send_json_success([
      'template' => $tmpl,
      'keys'     => [
        'has_key' => ($key_source !== 'none'),
        'source'  => $key_source,
      ],
      'api'      => $results,
    ]);
  }

  public static function ajax_as3v3_create_post() {
    self::check_nonce();
    if (!current_user_can('edit_posts')) {
      wp_send_json_error(['message' => __('You do not have permission to create posts.', 'brassgate-assistants-deluxe')], 403);
    }

    $title   = isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '';
    $content = isset($_POST['content']) ? wp_kses_post(wp_unslash($_POST['content'])) : '';

    if ($content === '') {
      wp_send_json_error(['message' => __('Content is required to create a draft post.', 'brassgate-assistants-deluxe')], 400);
    }

    if ($title === '') {
      $title = wp_trim_words(wp_strip_all_tags($content), 12, '…');
    }
    if ($title === '') {
      $title = __('Draft article', 'brassgate-assistants-deluxe');
    }

    $postarr = [
      'post_title'   => $title,
      'post_content' => $content,
      'post_status'  => 'draft',
      'post_type'    => 'post',
    ];

    $post_id = wp_insert_post($postarr, true);
    if (is_wp_error($post_id)) {
      wp_send_json_error(['message' => $post_id->get_error_message()], 500);
    }

    $edit_link = get_edit_post_link($post_id, 'raw');

    wp_send_json([
      'ok' => true,
      'post_id' => intval($post_id),
      'edit_link' => $edit_link ? esc_url_raw($edit_link) : '',
    ]);
  }

  /** ---------------- Social Tracking AJAX ---------------- */
  public static function ajax_social_state() {
    self::check_nonce();
    $user_id = get_current_user_id();
    $window = isset($_POST['window']) ? intval($_POST['window']) : 7;
    if (!$user_id) {
      wp_send_json_error(['message' => 'unauthorized'], 403);
    }
    if (!function_exists('bgad_social_get_state')) {
      wp_send_json_error(['message' => 'missing_social_module'], 500);
    }
    $state = bgad_social_get_state($user_id, $window);
    wp_send_json_success($state);
  }

  public static function ajax_social_connect() {
    self::check_nonce();
    $user_id = get_current_user_id();
    if (!$user_id) {
      wp_send_json_error(['message' => 'unauthorized'], 403);
    }
    $platform = isset($_POST['platform']) ? sanitize_key(wp_unslash($_POST['platform'])) : '';
    $connect  = isset($_POST['connect']) ? (intval($_POST['connect']) === 1) : true;
    $token    = isset($_POST['token']) ? sanitize_text_field(wp_unslash($_POST['token'])) : '';
    if ($platform === '' || !function_exists('bgad_social_set_connection')) {
      wp_send_json_error(['message' => 'invalid_platform'], 400);
    }
    if ($platform === 'facebook' && $connect) {
      wp_send_json_error(['message' => 'oauth_required'], 400);
    }
    $pull_at = $connect ? current_time('mysql') : null;
    if ($connect) {
      bgad_social_set_connection($user_id, $platform, $connect, $token, $pull_at);
      if (function_exists('bgad_social_collect_for_user')) {
        bgad_social_collect_for_user($user_id, array($platform));
      }
    } else {
      if (function_exists('bgad_social_disconnect')) {
        bgad_social_disconnect($user_id, $platform);
      } else {
        bgad_social_set_connection($user_id, $platform, false, '', null);
      }
    }
    $state = function_exists('bgad_social_get_state') ? bgad_social_get_state($user_id, 7) : array();
    wp_send_json_success(array('state' => $state));
  }

  public static function ajax_social_refresh() {
    self::check_nonce();
    $user_id = get_current_user_id();
    if (!$user_id) {
      wp_send_json_error(['message' => 'unauthorized'], 403);
    }
    $platform = isset($_POST['platform']) ? sanitize_key(wp_unslash($_POST['platform'])) : '';
    $window   = isset($_POST['window']) ? intval($_POST['window']) : 7;
    if (function_exists('bgad_social_collect_for_user')) {
      $connections = function_exists('bgad_social_connections_for_user') ? bgad_social_connections_for_user($user_id) : array();
      $targets = array();
      if ($platform) {
        $status = $connections[$platform]['connection_status'] ?? 'not_connected';
        if (in_array($status, array('connected_verified','connected_no_data_yet'), true)) {
          $targets[] = $platform;
        }
      } else {
        foreach ($connections as $slug => $conn) {
          $st = $conn['connection_status'] ?? 'not_connected';
          if (in_array($st, array('connected_verified','connected_no_data_yet'), true)) {
            $targets[] = $slug;
          }
        }
      }
      if (!empty($targets)) {
        bgad_social_collect_for_user($user_id, $targets);
      }
    }
    $state = function_exists('bgad_social_get_state') ? bgad_social_get_state($user_id, $window) : array();
    wp_send_json_success($state);
  }

  public static function ajax_social_fb_save_app() {
    $valid = check_ajax_referer(self::NONCE, 'nonce', false);
    if (!$valid) {
      wp_send_json_error(['message' => 'invalid_nonce'], 403);
    }
    if (!current_user_can('read')) {
      wp_send_json_error(['message' => 'unauthorized'], 403);
    }
    $user_id = get_current_user_id();
    if (!$user_id) {
      wp_send_json_error(['message' => 'unauthorized'], 403);
    }
    $app_id = isset($_POST['app_id']) ? sanitize_text_field(wp_unslash($_POST['app_id'])) : '';
    $app_secret = isset($_POST['app_secret']) ? sanitize_text_field(wp_unslash($_POST['app_secret'])) : '';
    if ($app_id === '' || $app_secret === '') {
      wp_send_json_error(['message' => 'missing_creds'], 400);
    }
    update_user_meta($user_id, 'bgad_fb_app_id', $app_id);
    update_user_meta($user_id, 'bgad_fb_app_secret', $app_secret);
    // Server-stored per-user map for admins who prefer central storage.
    $map = get_option('bgad_fb_user_creds', array());
    if (!is_array($map)) {
      $map = array();
    }
    $map[$user_id] = array('app_id' => $app_id, 'app_secret' => $app_secret);
    update_option('bgad_fb_user_creds', $map, false);
    wp_send_json_success(array('fbConfigured' => true));
  }

  private static function fb_creds() {
    $user_id = get_current_user_id();
    $app_id = $user_id ? trim((string) get_user_meta($user_id, 'bgad_fb_app_id', true)) : '';
    $app_secret = $user_id ? trim((string) get_user_meta($user_id, 'bgad_fb_app_secret', true)) : '';

    // Server-stored per-user map (array option keyed by user ID).
    if ($user_id && ($app_id === '' || $app_secret === '')) {
      $map = get_option('bgad_fb_user_creds', array());
      if (is_array($map) && isset($map[$user_id]) && is_array($map[$user_id])) {
        $app_id = $app_id ?: (string) ($map[$user_id]['app_id'] ?? '');
        $app_secret = $app_secret ?: (string) ($map[$user_id]['app_secret'] ?? '');
      }
    }

    // Fallbacks (deprecated site-wide)
    if ($app_id === '' && defined('BGAD_FB_APP_ID')) {
      $app_id = (string) BGAD_FB_APP_ID;
    }
    if ($app_secret === '' && defined('BGAD_FB_APP_SECRET')) {
      $app_secret = (string) BGAD_FB_APP_SECRET;
    }
    if ($app_id === '') {
      $app_id = trim((string) get_option('bgad_fb_app_id', ''));
    }
    if ($app_secret === '') {
      $app_secret = trim((string) get_option('bgad_fb_app_secret', ''));
    }
    if ($app_id === '' && getenv('BGAD_FB_APP_ID')) {
      $app_id = (string) getenv('BGAD_FB_APP_ID');
    }
    if ($app_secret === '' && getenv('BGAD_FB_APP_SECRET')) {
      $app_secret = (string) getenv('BGAD_FB_APP_SECRET');
    }

    $creds = array($app_id, $app_secret);
    $creds = apply_filters('bgad_fb_app_creds', $creds);
    return $creds;
  }

  private static function fb_render_popup($title, $body, $ok_script = '') {
    $safe_title = esc_html($title);
    echo '<!doctype html><html><head><meta charset="utf-8"><title>' . $safe_title . '</title>';
    echo '<style>body{font-family:Arial,sans-serif;padding:20px;background:#f8fafc;color:#111;} .card{background:#fff;border:1px solid #e5e7eb;border-radius:10px;padding:16px;box-shadow:0 10px 30px rgba(0,0,0,0.08);} h1{margin:0 0 8px;font-size:18px;} p{margin:0 0 10px;color:#374151;} button{padding:8px 12px;border-radius:8px;border:1px solid #d1d5db;background:#111827;color:#fff;cursor:pointer;} ul{padding-left:18px;}</style></head><body>';
    echo '<div class="card"><h1>' . $safe_title . '</h1>' . $body . '</div>';
    if ($ok_script) {
      echo '<script>' . $ok_script . '</script>';
    }
    echo '</body></html>';
    exit;
  }

  private static function fb_store_connection($user_id, $page_id, $page_name, $page_token, $expires_in = 0) {
    $meta = array(
      'page_id' => $page_id,
      'page_name' => $page_name,
      'page_token' => $page_token,
      'connected_at' => current_time('mysql'),
    );
    if ($expires_in && is_numeric($expires_in)) {
      $meta['token_expires_at'] = gmdate('Y-m-d H:i:s', time() + (int) $expires_in);
    }
    bgad_social_set_connection($user_id, 'facebook', true, $page_token, null, $meta, 'connected_no_data_yet');
  }

  public static function fb_oauth_start() {
    if (!is_user_logged_in() || !current_user_can('read')) {
      wp_die('Unauthorized', 403);
    }
    list($app_id, $app_secret) = self::fb_creds();
    if ($app_id === '' || $app_secret === '') {
      self::fb_render_popup('Facebook Connect', '<p>Missing Facebook App ID/Secret for this user. A server admin must store per-user credentials (user meta <code>bgad_fb_app_id</code>/<code>bgad_fb_app_secret</code> or option <code>bgad_fb_user_creds[user_id]</code>) or supply them via the <code>bgad_fb_app_creds</code> filter. Shared site-wide defaults are deprecated.</p>');
    }
    $state = wp_generate_password(24, false, false);
    $payload = array(
      'user_id' => get_current_user_id(),
      'nonce' => wp_generate_password(10, false, false),
      'created' => time(),
    );
    set_transient('bgad_fb_state_' . $state, $payload, 10 * MINUTE_IN_SECONDS);
    $redirect = admin_url('admin-post.php?action=bgad_fb_oauth_callback');
    $auth_url = add_query_arg(array(
      'client_id' => $app_id,
      'redirect_uri' => $redirect,
      'scope' => 'pages_show_list,pages_read_engagement,read_insights',
      'response_type' => 'code',
      'state' => $state,
    ), 'https://www.facebook.com/v18.0/dialog/oauth');
    wp_safe_redirect($auth_url);
    exit;
  }

  public static function fb_oauth_callback() {
    $state = isset($_GET['state']) ? sanitize_text_field(wp_unslash($_GET['state'])) : '';
    $error = isset($_GET['error']) ? sanitize_text_field(wp_unslash($_GET['error'])) : '';
    $error_desc = isset($_GET['error_description']) ? sanitize_text_field(wp_unslash($_GET['error_description'])) : '';
    $code = isset($_GET['code']) ? sanitize_text_field(wp_unslash($_GET['code'])) : '';

    $payload = $state ? get_transient('bgad_fb_state_' . $state) : null;
    if (!$payload || empty($payload['user_id'])) {
      self::fb_render_popup('Facebook Connect', '<p>Invalid or expired state.</p>');
    }
    $user_id = (int) $payload['user_id'];
    $user = get_user_by('id', $user_id);
    if (!$user) {
      self::fb_render_popup('Facebook Connect', '<p>User not found.</p>');
    }
    wp_set_current_user($user_id);
    if (!is_user_logged_in() || get_current_user_id() !== $user_id || !current_user_can('read')) {
      self::fb_render_popup('Facebook Connect', '<p>Unauthorized.</p>');
    }
    if ($error) {
      self::fb_render_popup('Facebook Connect', '<p>Authorization failed: ' . esc_html($error_desc ?: $error) . '</p><button onclick="window.close()">Close</button>');
    }
    if ($code === '') {
      self::fb_render_popup('Facebook Connect', '<p>No authorization code returned.</p>');
    }
    list($app_id, $app_secret) = self::fb_creds();
    if ($app_id === '' || $app_secret === '') {
      self::fb_render_popup('Facebook Connect', '<p>Missing Facebook App credentials for this user. A server admin must store per-user credentials (user meta <code>bgad_fb_app_id</code>/<code>bgad_fb_app_secret</code> or option <code>bgad_fb_user_creds[user_id]</code>) or provide them via the <code>bgad_fb_app_creds</code> filter.</p>');
    }

    $redirect = admin_url('admin-post.php?action=bgad_fb_oauth_callback');
    $token_url = add_query_arg(array(
      'client_id' => $app_id,
      'redirect_uri' => $redirect,
      'client_secret' => $app_secret,
      'code' => $code,
    ), 'https://graph.facebook.com/v18.0/oauth/access_token');
    $resp = wp_remote_get($token_url, array('timeout' => 15));
    if (is_wp_error($resp)) {
      self::fb_render_popup('Facebook Connect', '<p>Token exchange failed: ' . esc_html($resp->get_error_message()) . '</p>');
    }
    $body = json_decode((string) wp_remote_retrieve_body($resp), true);
    $user_token = is_array($body) && !empty($body['access_token']) ? $body['access_token'] : '';
    $expires_in = is_array($body) && isset($body['expires_in']) ? (int) $body['expires_in'] : 0;
    if (!$user_token) {
      self::fb_render_popup('Facebook Connect', '<p>Token exchange did not return an access token.</p>');
    }

    $pages_resp = bgad_social_fb_graph('me/accounts', array('fields' => 'id,name,access_token', 'limit' => 200), $user_token);
    if (empty($pages_resp['ok']) || empty($pages_resp['body']['data'])) {
      self::fb_render_popup('Facebook Connect', '<p>No Facebook Pages found for this account.</p>');
    }
    $pages = $pages_resp['body']['data'];
    set_transient('bgad_fb_pages_' . $state, array('pages' => $pages, 'user_id' => $user_id, 'expires_in' => $expires_in), 10 * MINUTE_IN_SECONDS);

    if (count($pages) === 1) {
      $page = $pages[0];
      self::fb_store_connection($user_id, $page['id'], $page['name'] ?? '', $page['access_token'] ?? '', $expires_in);
      if (function_exists('bgad_social_collect_for_user')) {
        bgad_social_collect_for_user($user_id, array('facebook'));
      }
      delete_transient('bgad_fb_state_' . $state);
      delete_transient('bgad_fb_pages_' . $state);
      $script = "if(window.opener){try{window.opener.postMessage({type:'bgad_social_fb_connected'}, '*');}catch(e){} window.close();}";
      self::fb_render_popup('Facebook Connected', '<p>Facebook connected. You can close this window.</p>', $script);
    }

    // Multiple pages: show selector.
    $html = '<p>Select which Facebook Page to track:</p><ul>';
    foreach ($pages as $page) {
      $html .= '<li style="margin:6px 0;"><form method="post" action="' . esc_url(admin_url('admin-post.php')) . '">';
      $html .= '<input type="hidden" name="action" value="bgad_fb_oauth_select" />';
      $html .= '<input type="hidden" name="state" value="' . esc_attr($state) . '" />';
      $html .= '<button type="submit" name="page_id" value="' . esc_attr($page['id']) . '">Use ' . esc_html($page['name'] ?? $page['id']) . '</button>';
      $html .= '</form></li>';
    }
    $html .= '</ul><p style="color:#6b7280;">Only read-only permissions are requested. This popup will close after selection.</p>';
    self::fb_render_popup('Select Facebook Page', $html);
  }

  public static function fb_oauth_select() {
    $state = isset($_POST['state']) ? sanitize_text_field(wp_unslash($_POST['state'])) : '';
    $page_id = isset($_POST['page_id']) ? sanitize_text_field(wp_unslash($_POST['page_id'])) : '';
    $stored = $state ? get_transient('bgad_fb_pages_' . $state) : null;
    if (!$stored || empty($stored['pages']) || empty($page_id)) {
      self::fb_render_popup('Facebook Connect', '<p>Selection expired. Please restart the connection.</p>');
    }
    $user_id = isset($stored['user_id']) ? (int) $stored['user_id'] : 0;
    $user = $user_id ? get_user_by('id', $user_id) : null;
    if (!$user) {
      self::fb_render_popup('Facebook Connect', '<p>User not found.</p>');
    }
    wp_set_current_user($user_id);
    if (!is_user_logged_in() || get_current_user_id() !== $user_id || !current_user_can('read')) {
      self::fb_render_popup('Facebook Connect', '<p>Unauthorized.</p>');
    }
    $page = null;
    foreach ($stored['pages'] as $entry) {
      if ((string) ($entry['id'] ?? '') === $page_id) {
        $page = $entry;
        break;
      }
    }
    if (!$page) {
      self::fb_render_popup('Facebook Connect', '<p>Selected page not found.</p>');
    }
    self::fb_store_connection($user_id, $page['id'], $page['name'] ?? '', $page['access_token'] ?? '', $stored['expires_in'] ?? 0);
    if (function_exists('bgad_social_collect_for_user')) {
      bgad_social_collect_for_user($user_id, array('facebook'));
    }
    delete_transient('bgad_fb_state_' . $state);
    delete_transient('bgad_fb_pages_' . $state);
    $script = "if(window.opener){try{window.opener.postMessage({type:'bgad_social_fb_connected'}, '*');}catch(e){} window.close();}";
    self::fb_render_popup('Facebook Connected', '<p>Facebook connected. You can close this window.</p>', $script);
  }

  /** ---------------- Embedded Doc Repo AJAX ---------------- */
  private static function backend_admin_headers() {
    $admin = trim((string)get_option(self::OPT_ADMIN_KEY, ''));
    return $admin ? ['X-Admin-Key' => $admin] : [];
  }

  public static function ajax_doc_list() {
    self::check_nonce();
    $status = sanitize_text_field($_GET['status'] ?? '');
    $scope  = sanitize_text_field($_GET['scope'] ?? 'self');
    $page   = max(1, intval($_GET['page'] ?? 1));
    $size   = max(1, intval($_GET['page_size'] ?? 25));
    $qs     = ['status'=>$status?:null, 'page'=>$page, 'page_size'=>$size];

    $parts = [];
    if ($scope === 'auto') {
      $self = self::http('GET', '/v3/documents/list?' . http_build_query(array_filter(array_merge($qs,['scope'=>'self']))));
      $team = self::http('GET', '/v3/documents/list?' . http_build_query(array_filter(array_merge($qs,['scope'=>'team']))));
      $org  = self::http('GET', '/v3/documents/list?' . http_build_query(array_filter(array_merge($qs,['scope'=>'org']))), null, 30, self::backend_admin_headers());
      $parts = [ 'self'=>$self, 'team'=>$team, 'org'=>$org ];

      $errors = [];
      $lists  = [];
      foreach ($parts as $sc=>$r) {
        if (empty($r['ok'])) {
          $errors[] = ['scope'=>$sc, 'status'=>$r['status'] ?? 0, 'error'=> is_array($r['data']??null) ? (($r['data']['detail'] ?? $r['data']['error'] ?? '')) : (is_string($r['data']??'') ? ($r['data']) : '') ];
          continue;
        }
        $lists[] = ['body' => $r['data'] ?? []];
      }
      // merge similar to bgd_merge_doc_lists
      $seen = [];$items=[];
      foreach ($lists as $entry){ $body = $entry['body'] ?? [];$arr = $body['items'] ?? ($body['documents'] ?? []); if (!is_array($arr)) $arr=[]; foreach ($arr as $doc){ $id = $doc['document_id'] ?? md5(wp_json_encode($doc)); if (isset($seen[$id])) continue; $seen[$id]=true; $items[]=$doc; }}
      usort($items, function($a,$b){ $ca=$a['created_at']??''; $cb=$b['created_at']??''; return strcmp($cb,$ca); });
      wp_send_json(['list'=>[
        'ok'=>true,
        'code'=>200,
        'body'=>['items'=>$items,'total'=>count($items),'page'=>1,'page_size'=>max(1,count($items))],
        'errors'=>$errors,
      ]]);
    } else {
      $priv = ($scope === 'org');
      $r = self::http('GET', '/v3/documents/list?' . http_build_query(array_filter(array_merge($qs,['scope'=>$scope]))), null, 30, $priv? self::backend_admin_headers(): []);
      $body = $r['data'] ?? [];
      $items = $body['items'] ?? ($body['documents'] ?? []);
      if (empty($r['ok'])) {
        wp_send_json(['list'=>[
          'ok'=>false,
          'code'=>$r['status'] ?? 0,
          'error'=> is_array($body) ? ($body['detail'] ?? $body['error'] ?? '') : (is_string($body) ? $body : ''),
          'body'=>['items'=>[], 'total'=>0, 'page'=>1, 'page_size'=>0],
        ]]);
      }
      $out = [
        'ok'=>true,
        'code'=>$r['status'] ?? 200,
        'body'=>[
          'items'=> is_array($items)? $items: [],
          'total'=> isset($body['total'])? intval($body['total']) : (is_array($items)? count($items):0),
          'page' => $body['page'] ?? 1,
          'page_size' => $body['page_size'] ?? ($body['limit'] ?? (is_array($items)? count($items):0)),
        ],
      ];
      wp_send_json(['list'=>$out]);
    }
  }

  public static function ajax_doc_upload() {
    self::check_nonce();
    if (!current_user_can('read')) {
      wp_send_json([
        'upload' => [
          'code'  => 403,
          'error' => 'forbidden',
        ],
      ], 403);
    }
    if (!isset($_FILES['file']) || empty($_FILES['file']['tmp_name'])) {
      wp_send_json([
        'upload' => [
          'code'  => 0,
          'error' => 'no_file',
        ],
      ], 400);
    }
    $base = self::backend_base();
    $url  = $base . '/v3/documents/upload';
    $headers = self::user_headers();
    $boundary = wp_generate_password(24, false, false);
    $file = $_FILES['file'];
    $body = [];
    $body[] = "--$boundary";
    $body[] = 'Content-Disposition: form-data; name="file"; filename="'.esc_attr($file['name']).'"';
    $body[] = 'Content-Type: '.($file['type'] ?: 'application/octet-stream');
    $body[] = '';
    $body[] = file_get_contents($file['tmp_name']);
    if (isset($_POST['metadata'])) {
      $body[] = "--$boundary";
      $body[] = 'Content-Disposition: form-data; name="metadata"';
      $body[] = '';
      $body[] = (string) wp_unslash($_POST['metadata']);
    }
    $body[] = "--$boundary--";
    $args = [
      'method'  => 'POST',
      'timeout' => 60,
      'headers' => array_merge($headers, ['Content-Type' => 'multipart/form-data; boundary='.$boundary]),
      'body'    => implode("\r\n", $body),
    ];
    $res = wp_remote_request($url, $args);
    if (is_wp_error($res)) wp_send_json(['upload'=>['ok'=>false,'code'=>0,'error'=>$res->get_error_message(),'body'=>null]]);
    $code = wp_remote_retrieve_response_code($res);
    $raw  = wp_remote_retrieve_body($res);
    $json = json_decode($raw, true);
    $err  = is_array($json) ? ($json['detail'] ?? $json['error'] ?? '') : '';

    if ($code >= 200 && $code < 300 && function_exists('bg_increment_user_usage')) {
      bg_increment_user_usage(get_current_user_id(), 'docs_upload', 1);
    }

    wp_send_json(['upload'=>['ok'=>($code>=200 && $code<300), 'code'=>$code, 'error'=>$err, 'body'=>$json]]);
  }

  public static function ajax_doc_approve() {
    self::check_nonce();
    if (!current_user_can('read')) {
      wp_send_json(['approve' => ['code' => 403]], 403);
    }

    $doc = sanitize_text_field($_POST['document_id'] ?? '');
    if ($doc === '') {
      wp_send_json([
        'approve' => [
          'code'  => 400,
          'error' => 'missing_document_id',
        ],
      ], 400);
    }

    $r = self::http('POST', '/v3/documents/approve', ['document_id' => $doc], 30, self::backend_admin_headers());
    $body = $r['data'] ?? null;
    $err  = is_array($body) ? ($body['detail'] ?? $body['error'] ?? '') : (is_string($body) ? $body : '');

    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      bg_increment_user_usage(get_current_user_id(), 'docs_tag', 1);
    }

    wp_send_json([
      'approve' => [
        'ok'    => !empty($r['ok']),
        'code'  => $r['status'] ?? 0,
        'error' => $err,
        'body'  => $body,
      ],
    ]);
  }

  public static function ajax_doc_index() {
    self::check_nonce();
    if (!current_user_can('read')) {
      wp_send_json(['index' => ['code' => 403]], 403);
    }

    $doc = sanitize_text_field($_POST['document_id'] ?? '');
    if ($doc === '') {
      wp_send_json([
        'index' => [
          'code'  => 400,
          'error' => 'missing_document_id',
        ],
      ], 400);
    }

    $r = self::http('POST', '/v3/documents/index', ['document_id' => $doc], 60, self::backend_admin_headers());
    $body = $r['data'] ?? null;
    $err  = is_array($body) ? ($body['detail'] ?? $body['error'] ?? '') : (is_string($body) ? $body : '');

    if (!empty($r['ok']) && function_exists('bg_increment_user_usage')) {
      bg_increment_user_usage(get_current_user_id(), 'docs_tag', 1);
    }

    wp_send_json([
      'index' => [
        'ok'    => !empty($r['ok']),
        'code'  => $r['status'] ?? 0,
        'error' => $err,
        'body'  => $body,
      ],
    ]);
  }

  public static function ajax_doc_delete() {
    self::check_nonce();
    if (!current_user_can('read')) {
      wp_send_json(['delete' => ['code' => 403]], 403);
    }

    $doc = sanitize_text_field($_POST['document_id'] ?? '');
    if ($doc === '') {
      wp_send_json([
        'delete' => [
          'code'  => 400,
          'error' => 'missing_document_id',
        ],
      ], 400);
    }

    $r = self::http('DELETE', '/v3/documents/delete', ['document_id' => $doc], 30, self::backend_admin_headers());
    $body = $r['data'] ?? null;
    $err  = is_array($body) ? ($body['detail'] ?? $body['error'] ?? '') : (is_string($body) ? $body : '');

    if (!empty($r['ok']) && function_exists('bg_decrement_user_usage')) {
      bg_decrement_user_usage(get_current_user_id(), 'docs_upload', 1);
    }

    wp_send_json([
      'delete' => [
        'ok'    => !empty($r['ok']),
        'code'  => $r['status'] ?? 0,
        'error' => $err,
        'body'  => $body,
      ],
    ]);
  }

  public static function ajax_quota_refresh() {
    if (!current_user_can('read')) {
      wp_send_json_error(array('message' => 'forbidden'), 403);
    }

    $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
    if ($nonce === '' || (!wp_verify_nonce($nonce, 'bgad_quota_refresh') && !wp_verify_nonce($nonce, 'bgad_quota'))) {
      wp_send_json_error(array('message' => 'invalid nonce'), 403);
    }

    $scope_raw = isset($_POST['scope']) ? wp_unslash($_POST['scope']) : '';
    $scope = is_string($scope_raw) ? trim($scope_raw) : '';
    if ($scope === '') {
      $scope = 'auto';
    }

    $page_raw = isset($_POST['page']) ? wp_unslash($_POST['page']) : '';
    $page = is_string($page_raw) ? sanitize_key($page_raw) : '';

    $features = array();
    if ($page !== '') {
      $features = self::features_for_page($page);
    }

    if (empty($features)) {
      $features = self::quota_features_for_scope($scope);
    }

    $features = array_values(array_unique(array_filter(array_map('sanitize_key', (array) $features))));
    if (empty($features)) {
      $features = array('video_generate', 'videos_upload');
    }

    $context = self::quota_context($features);
    $snapshot = isset($context['snapshot']) && is_array($context['snapshot']) ? $context['snapshot'] : array();

    $limits = array();
    foreach ($snapshot as $slug => $row) {
      if (!is_array($row)) {
        continue;
      }
      $limits[$slug] = array(
        'used' => isset($row['used']) ? (int) $row['used'] : 0,
        'limit' => isset($row['limit']) ? (int) $row['limit'] : 0,
        'remaining' => isset($row['remaining']) ? (int) $row['remaining'] : 0,
        'unlimited' => !empty($row['unlimited']),
        'disabled' => !empty($row['disabled']),
      );
    }

    $response = array(
      'ok' => true,
      'scope' => $scope,
      'page' => $page,
      'features' => $features,
      'limits' => $limits,
      'context' => $context,
      'updated' => time(),
    );

    wp_send_json_success($response, 200);
  }

  public static function ajax_quota_snapshot() {
    self::check_nonce(); if (!current_user_can('read')) wp_send_json(['code'=>403]);
    $slugs = array(
      'images_upload','images_tag','docs_upload','docs_tag','videos_upload','videos_tag',
      'chat_messages','articles_from_text','articles_from_image','audio_generate','music_generate',
      'shortform_from_user_videos','longform_summaries_trailers','longform_upload_tag'
    );
    $context = self::quota_context($slugs);
    wp_send_json(['code'=>200, 'snapshot'=>$context['snapshot'], 'context'=>$context]);
  }

  public static function ajax_allowances_refresh() {
    if (!current_user_can('read')) {
      wp_send_json_error(['message' => 'forbidden'], 403);
    }

    $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
    if ($nonce === '' || !wp_verify_nonce($nonce, 'bgad_allowances_refresh')) {
      wp_send_json_error(['message' => 'invalid nonce'], 403);
    }

    $current_user_id = get_current_user_id();
    $target_user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
    if ($target_user_id <= 0) {
      $target_user_id = $current_user_id;
    }

    $is_manager = current_user_can('manage_options') || current_user_can('edit_others_posts');
    if (!$is_manager) {
      $user = get_userdata($current_user_id);
      if ($user && !empty($user->roles)) {
        $roles = (array) $user->roles;
        $is_manager = in_array('bg_client_owner', $roles, true) || in_array('bg_client_admin', $roles, true);
      }
    }

    $current_client = trim((string) get_user_meta($current_user_id, 'bg_client_id', true));
    $target_client = trim((string) get_user_meta($target_user_id, 'bg_client_id', true));
    if (!$is_manager || $current_client === '' || $target_client === '' || $current_client !== $target_client) {
      $target_user_id = $current_user_id;
      $target_client = $current_client;
    }

    if ($target_client === '') {
      wp_send_json_error(['message' => 'missing client'], 400);
    }

    $data = null;
    if (function_exists('brassgate_get_quota_summary_for_user')) {
      $data = brassgate_get_quota_summary_for_user($target_user_id);
    }
    if (!is_array($data)) {
      $slugs = array(
        'images_upload','images_tag','docs_upload','docs_tag','videos_upload','videos_tag',
        'chat_messages','articles_from_text','articles_from_image','audio_generate','music_generate',
        'shortform_from_user_videos','longform_summaries_trailers','longform_upload_tag',
        'video_generate','assistant_messages','images_generate'
      );
      $slugs = array_values(array_unique(array_filter(array_map('sanitize_key', $slugs))));
      $path = '/v3/dashboard/quota';
      if (!empty($slugs)) {
        $path .= '?resources=' . rawurlencode(implode(',', $slugs));
      }
      $response = self::http('GET', $path, null, 20);
      if (empty($response['ok']) || !isset($response['data']) || !is_array($response['data'])) {
        wp_send_json_error(['message' => 'quota fetch failed'], 502);
      }
      $data = $response['data'];
    }
    $base = trim((string) getenv('BG_ORCHESTRATOR_BASE'));
    if ($base === '' && function_exists('bgad_backend_base')) {
      $base = (string) bgad_backend_base();
    }
    if ($base !== '') {
      $url = rtrim($base, '/') . '/quota-snapshot';
      $payload = array(
        'client_id' => $target_client,
        'user_id' => (string) $target_user_id,
        'quota_snapshot' => $data,
      );
      wp_remote_post($url, array(
        'timeout' => 1,
        'headers' => array('Content-Type' => 'application/json'),
        'body' => wp_json_encode($payload),
      ));
    }

    wp_send_json_success([
      'ok' => true,
      'snapshot' => $data,
    ], 200);
  }
  public static function ajax_whoami() {
    self::check_nonce(); if (!current_user_can('read')) wp_send_json(['code'=>403]);
    $u = wp_get_current_user();
    $has_user = !!get_user_meta($u->ID, self::USER_KEY, true);
    $has_mgr  = !!get_user_meta($u->ID, self::MANAGER_KEY, true);
    $has_admin= !!get_option(self::OPT_ADMIN_KEY, '');
    wp_send_json(['code'=>200,'uid'=>$u->ID,'has_user_key'=>$has_user,'has_manager_key'=>$has_mgr,'has_admin_key'=>$has_admin]);
  }

  public static function ajax_image_manager_js() {
    if (!current_user_can('read')) {
      status_header(403);
      wp_die(__('Insufficient permissions.', 'brassgate-assistants-deluxe'));
    }

    if (!self::user_has_plan()) {
      status_header(403);
      header('Content-Type: application/javascript; charset=UTF-8');
      echo "(() => { console.warn('[BGAD] image-manager.js blocked: plan required'); const el=document.getElementById('bgv3-log'); if(el){el.textContent='BrassGate: upgrade required to load Image Manager.';} })();";
      wp_die();
    }

    $path = __DIR__ . '/assets/js/image-manager.js';
    header('Content-Type: application/javascript; charset=UTF-8');

    if (is_readable($path)) {
      readfile($path);
    } else {
      echo "(() => { console.warn('[BGAD] image-manager.js missing'); const el=document.getElementById('bgv3-log'); if(el){el.textContent='BrassGate: image-manager.js missing from assets/js.';} })();";
    }

    wp_die();
  }
}

if (!function_exists('bgad_enqueue_common_js')) {
    function bgad_enqueue_common_js() {
        wp_enqueue_script(
            'bgad-common-js',
            plugins_url('assets/js/common.js', __FILE__),
            array('jquery'),
            filemtime(plugin_dir_path(__FILE__) . 'assets/js/common.js'),
            true
        );
    }
}
BrassGate_Assistants_Deluxe::init();

if (!function_exists('bgad_vertical_log_event')) {
    function bgad_vertical_log_event($message, array $context = []) {
        $prefix = '[BGAD vertical] ' . $message;
        if (!empty($context)) {
            $json = wp_json_encode($context);
            if (is_string($json) && $json !== '') {
                $prefix .= ' ' . $json;
            }
        }
        error_log($prefix);
        if (function_exists('do_action')) {
            do_action('bgad_vertical_activity_log_event', $message, $context);
        }
    }
}

// ---- Vertical Socials Helper: storage-first submit pipeline ----
add_action('wp_ajax_bgad_vertical_socials_submit', function () {
    $started = microtime(true);

    try {
        if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), BrassGate_Assistants_Deluxe::NONCE)) {
            return bgad_json_send(['ok' => false, 'error' => 'Bad nonce'], 403);
        }

        if (!BrassGate_Assistants_Deluxe::user_has_plan()) {
            return bgad_json_send(['ok' => false, 'error' => 'Insufficient permissions'], 403);
        }

        $client_key = function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : '';
        if (!$client_key) {
            return bgad_json_send(['ok' => false, 'error' => 'Missing BrassGate client key in profile'], 400);
        }
        $client_hash = substr(sha1($client_key), 0, 12);

        if (!function_exists('bgad_user_headers')) {
            return bgad_json_send(['ok' => false, 'error' => 'Header helpers missing'], 500);
        }

        $header_map = bgad_user_headers(false, 'vertical-socials-helper');
        $has_user_key = array_reduce(array_keys($header_map), function ($carry, $key) use ($header_map) {
            return $carry || (strtolower($key) === 'x-user-api-key' && !empty($header_map[$key]));
        }, false);

        if (!$has_user_key) {
            bgad_vertical_log_event('submit:missing-user-key', ['client' => $client_hash]);
            return bgad_json_send(['ok' => false, 'error' => 'Missing BrassGate API key in profile'], 400);
        }

        $header_map['x-brassgate-client-key'] = $client_key;
        $header_map['X-Correlation-Id'] = function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : uniqid('corr_', true);

        $text_fields = [
            'music_style'         => isset($_POST['music_style']) ? sanitize_text_field(wp_unslash($_POST['music_style'])) : 'cinematic',
            'overlay_url'         => isset($_POST['overlay_url']) ? esc_url_raw(wp_unslash($_POST['overlay_url'])) : '',
            'use_blur_background' => (!empty($_POST['use_blur_background']) && $_POST['use_blur_background'] !== 'false') ? 'true' : 'false',
        ];

        $storage_refs_json = isset($_POST['storage_refs']) ? wp_unslash($_POST['storage_refs']) : '';
        $storage_refs = [];
        if ($storage_refs_json) {
            $decoded = json_decode($storage_refs_json, true);
            if (is_array($decoded)) {
                $storage_refs = $decoded;
            }
        }

        if (empty($storage_refs)) {
            return bgad_json_send(['ok' => false, 'error' => 'No storage references provided. The direct upload process may have failed.'], 400);
        }

        bgad_vertical_log_event('submit:start-with-refs', [
            'client' => $client_hash,
            'corr' => $header_map['X-Correlation-Id'],
            'refs' => array_keys($storage_refs),
        ]);

        $base = function_exists('bgad_backend_base') ? rtrim(bgad_backend_base(), '/') : 'https://brassgate-backend.onrender.com';
        $submit_with_refs = bgad_vertical_submit_refs($header_map, $text_fields, $storage_refs, $base);

        if ($submit_with_refs['ok']) {
            $job_id = '';
            $body = $submit_with_refs['body'];
            if (is_array($body)) {
                $job_id = (string)($body['body']['job_id'] ?? $body['job_id'] ?? '');
            }
            $corr = $submit_with_refs['corr'] ?? $header_map['X-Correlation-Id'];
            bgad_vertical_log_event('submit:success', [
                'client'  => $client_hash,
                'path'    => 'storage-first-js',
                'job'     => $job_id,
                'corr'    => $corr,
                'elapsed' => round((microtime(true) - $started) * 1000),
            ]);
            return bgad_json_send([
                'ok'   => true,
                'corr' => $corr,
                'body' => ['job_id' => $job_id],
            ], 200);
        }

        $preview = isset($submit_with_refs['raw']) ? substr((string)$submit_with_refs['raw'], 0, 200) : ($submit_with_refs['error'] ?? '');
        bgad_vertical_log_event('submit:ref-submit-error', [
            'client' => $client_hash,
            'status' => $submit_with_refs['status'] ?? 0,
            'error'  => $submit_with_refs['error'] ?? '',
            'corr'   => $submit_with_refs['corr'] ?? '',
            'preview'=> $preview,
        ]);

        $status = $submit_with_refs['status'] ?? 502;
        $message = $submit_with_refs['error'] ?? 'Submission failed';
        return bgad_json_send(['ok' => false, 'error' => $message], ($status >= 400 && $status < 600) ? $status : 502);

    } catch (Throwable $th) {
        $context = [
            'error' => $th->getMessage(),
            'type'  => get_class($th),
            'file'  => basename($th->getFile()),
            'line'  => $th->getLine(),
        ];
        bgad_vertical_log_event('submit:exception', $context);
        return bgad_json_send(['ok' => false, 'error' => 'Unexpected error during submission. Check server logs.'], 500);
    }
});

if (!function_exists('bgad_vertical_ini_bytes')) {
    function bgad_vertical_ini_bytes($value) {
        if ($value === '' || $value === false || $value === null) {
            return 0;
        }
        if (is_numeric($value)) {
            return (int) $value;
        }
        if (!is_string($value)) {
            return 0;
        }
        $value = trim($value);
        if ($value === '') {
            return 0;
        }
        $unit = strtolower(substr($value, -1));
        $number = (float) $value;
        switch ($unit) {
            case 'g':
                return (int) ($number * 1024 * 1024 * 1024);
            case 'm':
                return (int) ($number * 1024 * 1024);
            case 'k':
                return (int) ($number * 1024);
            default:
                return (int) $number;
        }
    }
}

if (!function_exists('bgad_vertical_check_upload_limits')) {
    function bgad_vertical_check_upload_limits(array $files) {
        $upload_max = bgad_vertical_ini_bytes(ini_get('upload_max_filesize'));
        $post_max = bgad_vertical_ini_bytes(ini_get('post_max_size'));
        $max_files = (int) ini_get('max_file_uploads');
        $total_size = 0;
        $count = 0;

        foreach ($files as $info) {
            if (!is_array($info) || empty($info['file'])) {
                continue;
            }
            $count++;
            $size = isset($info['file']['size']) ? (int) $info['file']['size'] : 0;
            $total_size += max(0, $size);
            if ($upload_max > 0 && $size > $upload_max) {
                return sprintf(
                    'PHP upload limits too low (upload_max_filesize=%s, post_max_size=%s)',
                    ini_get('upload_max_filesize'),
                    ini_get('post_max_size')
                );
            }
        }

        if ($post_max > 0 && $total_size > $post_max) {
            return sprintf(
                'PHP upload limits too low (upload_max_filesize=%s, post_max_size=%s)',
                ini_get('upload_max_filesize'),
                ini_get('post_max_size')
            );
        }

        if ($max_files > 0 && $count > $max_files) {
            return sprintf(
                'Too many files for current PHP max_file_uploads (%d).',
                $max_files
            );
        }

        return '';
    }
}

if (!function_exists('bgad_vertical_post_multipart_stream')) {
    function bgad_vertical_post_multipart_stream($url, array $headers_map, array $fields, array $files, $timeout = 120) {
        $boundary = '----BGAD' . (function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : uniqid('bgad', true));
        $stream = fopen('php://temp', 'w+');
        if (!$stream) {
            return [
                'transport_ok' => false,
                'status'       => 0,
                'raw'          => '',
                'error'        => 'Unable to allocate stream buffer',
            ];
        }

        foreach ($fields as $name => $value) {
            fwrite($stream, "--{$boundary}\r\n");
            fwrite($stream, sprintf("Content-Disposition: form-data; name=\"%s\"\r\n\r\n", $name));
            fwrite($stream, (string) $value . "\r\n");
        }

        foreach ($files as $name => $meta) {
            if (empty($meta['tmp_name']) || !is_readable($meta['tmp_name'])) {
                continue;
            }
            $filename = sanitize_file_name(isset($meta['name']) ? (string) $meta['name'] : $name);
            $type = isset($meta['type']) && $meta['type'] ? (string) $meta['type'] : 'application/octet-stream';

            $handle = fopen($meta['tmp_name'], 'rb');
            if (!$handle) {
                fclose($stream);
                return [
                    'transport_ok' => false,
                    'status'       => 0,
                    'raw'          => '',
                    'error'        => sprintf('Unable to open file stream for %s', $filename),
                ];
            }

            fwrite($stream, "--{$boundary}\r\n");
            fwrite($stream, sprintf("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n", $name, $filename));
            fwrite($stream, "Content-Type: {$type}\r\n\r\n");
            while (!feof($handle)) {
                $chunk = fread($handle, 8192);
                if ($chunk === false) {
                    break;
                }
                fwrite($stream, $chunk);
            }
            fclose($handle);
            fwrite($stream, "\r\n");
        }

        fwrite($stream, "--{$boundary}--\r\n");

        $body = stream_get_contents($stream, -1, 0);
        if ($body === false) {
            fclose($stream);
            return [
                'transport_ok' => false,
                'status'       => 0,
                'raw'          => '',
                'error'        => 'Unable to read multipart payload',
            ];
        }
        $length = strlen($body);
        fclose($stream);

        $headers = $headers_map;
        $headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
        $headers['Content-Length'] = (string) $length;

        $args = [
            'method'      => 'POST',
            'timeout'     => $timeout,
            'headers'     => $headers,
            'body'        => $body,
            'redirection' => 0,
        ];

        $response = wp_remote_post($url, $args);
        if (is_wp_error($response)) {
            return [
                'transport_ok' => false,
                'status'       => 0,
                'raw'          => '',
                'error'        => $response->get_error_message(),
            ];
        }

        $status = (int) wp_remote_retrieve_response_code($response);
        $raw = (string) wp_remote_retrieve_body($response);

        return [
            'transport_ok' => true,
            'status'       => $status,
            'raw'          => $raw,
            'error'        => '',
        ];
    }
}

if (!function_exists('bgad_vertical_send_multipart')) {
    function bgad_vertical_send_multipart($url, array $headers_map, array $fields, array $files, $timeout = 120) {
        $normalized = [];
        foreach ($fields as $key => $value) {
            if (is_scalar($value) || $value === null) {
                $normalized[$key] = (string) $value;
            } else {
                $normalized[$key] = wp_json_encode($value);
            }
        }

        if (function_exists('curl_init')) {
            $post_fields = $normalized;
            foreach ($files as $name => $meta) {
                if (empty($meta['tmp_name']) || !is_readable($meta['tmp_name'])) {
                    continue;
                }
                $filename = sanitize_file_name(isset($meta['name']) ? (string) $meta['name'] : $name);
                $type = isset($meta['type']) && $meta['type'] ? (string) $meta['type'] : 'application/octet-stream';
                $post_fields[$name] = new CURLFile($meta['tmp_name'], $type ?: 'application/octet-stream', $filename);
            }

            $header_list = [];
            foreach ($headers_map as $header => $value) {
                if (!is_string($header) || $header === '') {
                    continue;
                }
                if (!is_string($value) || $value === '') {
                    continue;
                }
                $header_list[] = $header . ': ' . $value;
            }

            $ch = curl_init($url);
            curl_setopt_array($ch, [
                CURLOPT_POST           => true,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_HTTPHEADER     => $header_list,
                CURLOPT_POSTFIELDS     => $post_fields,
                CURLOPT_TIMEOUT        => $timeout,
            ]);
            $raw = curl_exec($ch);
            $error = curl_error($ch);
            $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
            curl_close($ch);

            if ($error) {
                return [
                    'transport_ok' => false,
                    'status'       => 0,
                    'raw'          => '',
                    'error'        => $error,
                ];
            }

            return [
                'transport_ok' => true,
                'status'       => $status,
                'raw'          => (string) $raw,
                'error'        => '',
            ];
        }

        return bgad_vertical_post_multipart_stream($url, $headers_map, $normalized, $files, $timeout);
    }
}

if (!function_exists('bgad_vertical_storage_upload')) {
    function bgad_vertical_storage_upload($client_key, array $file, $kind, $base) {
        if (empty($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
            return ['ok' => false, 'error' => 'Invalid upload handle', 'status' => 400];
        }

        $url = rtrim($base, '/') . '/v3/videos/upload';
        $headers = function_exists('bgad_user_headers') ? bgad_user_headers(false, 'videos-v3') : [];
        unset($headers['Content-Type']);
        $headers['x-brassgate-client-key'] = $client_key;
        $headers['X-Brassgate-Client-Key'] = $client_key;
        if (!isset($headers['X-BG-Feature'])) {
            $headers['X-BG-Feature'] = 'videos-v3';
        }

        $delays = [0.0, 0.5, 1.5];
        $max_delay = end($delays);
        reset($delays);

        $filename = sanitize_file_name(isset($file['name']) ? (string) $file['name'] : ($kind . '.mp4'));
        $mime = isset($file['type']) && $file['type'] ? (string) $file['type'] : 'application/octet-stream';
        $fields = [
            'role'        => $kind,
            'orientation' => ($kind === 'portrait') ? 'portrait' : (($kind === 'landscape') ? 'landscape' : 'auto'),
        ];

        $last_error = 'Upload failed';
        $last_status = 0;

        foreach ($delays as $delay) {
            if ($delay > 0) {
                usleep((int) ($delay * 1000000));
            }

            $headers['X-Correlation-Id'] = function_exists('wp_generate_uuid4') ? wp_generate_uuid4() : uniqid('corr_', true);

            $response = bgad_vertical_send_multipart($url, $headers, $fields, [
                'file' => [
                    'tmp_name' => $file['tmp_name'],
                    'type'     => $mime,
                    'name'     => $filename,
                ],
            ]);

            if (!$response['transport_ok']) {
                $last_error = $response['error'] ?: 'Transport error';
                $last_status = 0;
                continue;
            }

            $status = (int) ($response['status'] ?? 0);
            $raw = $response['raw'] ?? '';
            $json = json_decode((string) $raw, true);

            if ($status >= 200 && $status < 300 && is_array($json) && !empty($json['ok'])) {
                return [
                    'ok'        => true,
                    'status'    => $status,
                    'reference' => [
                        'filename' => isset($json['filename']) ? (string) $json['filename'] : '',
                        'url'      => isset($json['url']) ? (string) $json['url'] : '',
                        'duration' => $json['duration'] ?? null,
                        'kind'     => $kind,
                        'corr'     => $headers['X-Correlation-Id'],
                    ],
                    'response'  => $json,
                    'raw'       => $raw,
                ];
            }

            if (in_array($status, [429, 502, 503, 504], true) && $delay < $max_delay) {
                $last_error = is_string($raw) && $raw !== '' ? substr($raw, 0, 400) : 'Transient error';
                $last_status = $status;
                continue;
            }

            $last_status = $status;
            if (is_array($json) && isset($json['error'])) {
                $last_error = is_string($json['error']) ? $json['error'] : wp_json_encode($json['error']);
            } elseif (is_array($json) && isset($json['message'])) {
                $last_error = is_string($json['message']) ? $json['message'] : wp_json_encode($json['message']);
            } elseif ($raw !== '') {
                $last_error = substr((string) $raw, 0, 400);
            } else {
                $last_error = 'Upload failed';
            }
            break;
        }

        return [
            'ok'     => false,
            'error'  => $last_error,
            'status' => $last_status,
        ];
    }
}

if (!function_exists('bgad_vertical_submit_refs')) {
    function bgad_vertical_submit_refs(array $header_map, array $fields, array $storage_refs, $base) {
        $url = rtrim($base, '/') . '/v3/vertical/submit';
        $headers = $header_map;
        unset($headers['Content-Type']);
        if (!isset($headers['Accept'])) {
            $headers['Accept'] = 'application/json';
        }

        $payload = $fields;
        if (!empty($storage_refs)) {
            $payload['storage_refs'] = wp_json_encode($storage_refs);
        }

        // Use bgad_vertical_send_multipart to ensure correct multipart/form-data encoding
        $response = bgad_vertical_send_multipart($url, $headers, $payload, [], 120);

        if (!$response['transport_ok']) {
            return [
                'ok'     => false,
                'status' => 0,
                'error'  => $response['error'] ?: 'Transport error',
                'raw'    => '',
                'body'   => null,
                'corr'   => $headers['X-Correlation-Id'] ?? '',
            ];
        }

        $status = (int) ($response['status'] ?? 0);
        $raw = (string) ($response['raw'] ?? '');
        $json = json_decode($raw, true);
        $corr = '';
        if (is_array($json)) {
            $corr = $json['corr'] ?? $json['correlation'] ?? '';
        }
        if (!$corr && isset($headers['X-Correlation-Id'])) {
            $corr = $headers['X-Correlation-Id'];
        }

        if ($status >= 200 && $status < 300 && is_array($json) && (!empty($json['body']['job_id']) || !empty($json['job_id']))) {
            return [
                'ok'    => true,
                'status'=> $status,
                'body'  => $json,
                'raw'   => $raw,
                'corr'  => $corr,
            ];
        }

        $error = '';
        if (is_array($json) && isset($json['error'])) {
            $error = is_string($json['error']) ? $json['error'] : wp_json_encode($json['error']);
        } elseif (is_array($json) && isset($json['message'])) {
            $error = is_string($json['message']) ? $json['message'] : wp_json_encode($json['message']);
        } elseif ($raw !== '') {
            $error = substr((string) $raw, 0, 400);
        } else {
            $error = 'Upstream error';
        }

        return [
            'ok'     => false,
            'status' => $status,
            'error'  => $error,
            'raw'    => $raw,
            'body'   => $json,
            'corr'   => $corr,
        ];
    }
}

if (!function_exists('bgad_vertical_submit_curl')) {
    function bgad_vertical_submit_curl(array $header_map, array $payload, $base) {
        $url = rtrim($base, '/') . '/v3/vertical/submit';
        $headers = $header_map;
        unset($headers['Content-Type']);

        $fields = [];
        $files = [];
        foreach ($payload as $key => $value) {
            if (is_array($value) && isset($value['tmp_name']) && $value['tmp_name']) {
                $files[$key] = [
                    'tmp_name' => $value['tmp_name'],
                    'type'     => isset($value['type']) && $value['type'] ? (string) $value['type'] : 'application/octet-stream',
                    'name'     => sanitize_file_name(isset($value['name']) ? (string) $value['name'] : $key),
                ];
            } else {
                $fields[$key] = is_scalar($value) || $value === null ? (string) $value : wp_json_encode($value);
            }
        }

        $response = bgad_vertical_send_multipart($url, $headers, $fields, $files, 120);
        if (!$response['transport_ok']) {
            return [
                'ok'     => false,
                'status' => 0,
                'error'  => $response['error'] ?: 'Transport error',
                'raw'    => '',
                'body'   => null,
                'corr'   => $headers['X-Correlation-Id'] ?? '',
            ];
        }

        $status = (int) ($response['status'] ?? 0);
        $raw = (string) ($response['raw'] ?? '');
        $json = json_decode($raw, true);
        $corr = '';
        if (is_array($json)) {
            $corr = $json['corr'] ?? $json['correlation'] ?? '';
        }
        if (!$corr && isset($headers['X-Correlation-Id'])) {
            $corr = $headers['X-Correlation-Id'];
        }

        if ($status >= 200 && $status < 300 && is_array($json) && (!empty($json['body']['job_id']) || !empty($json['job_id']))) {
            return [
                'ok'     => true,
                'status' => $status,
                'body'   => $json,
                'raw'    => $raw,
                'corr'   => $corr,
            ];
        }

        $error = '';
        if (is_array($json) && isset($json['error'])) {
            $error = is_string($json['error']) ? $json['error'] : wp_json_encode($json['error']);
        } elseif (is_array($json) && isset($json['message'])) {
            $error = is_string($json['message']) ? $json['message'] : wp_json_encode($json['message']);
        } elseif ($raw !== '') {
            $error = substr((string) $raw, 0, 400);
        } else {
            $error = 'Upstream error';
        }

        return [
            'ok'     => false,
            'status' => $status,
            'error'  => $error,
            'raw'    => $raw,
            'body'   => $json,
            'corr'   => $corr,
        ];
    }
}

if (!function_exists('bgad_vertical_submit_requests')) {
    function bgad_vertical_submit_requests(array $header_map, array $payload, $base) {
        return bgad_vertical_submit_curl($header_map, $payload, $base);
    }
}

if (!function_exists('bgad_get_client_key_for_current_user')) {
    function bgad_get_client_key_for_current_user() {
        $env = getenv('BRASSGATE_CLIENT_KEY');
        if (is_string($env) && $env !== '') {
            return $env;
        }

        $user_id = get_current_user_id();
        if ($user_id) {
            $meta_keys = [
                BrassGate_Assistants_Deluxe::USER_KEY,
                'brassgate_client_key',
                'bg_api_key',
                BrassGate_Assistants_Deluxe::MANAGER_KEY,
            ];

            foreach ($meta_keys as $meta_key) {
                $value = trim((string) get_user_meta($user_id, $meta_key, true));
                if ($value !== '') {
                    return $value;
                }
            }
        }

        $option = trim((string) get_option('brassgate_client_key', ''));
        if ($option !== '') {
            return $option;
        }

        return '';
    }
}

if (!function_exists('bgad_json_send')) {
    function bgad_json_send($data, $status = 200) {
        wp_send_json($data, $status);
    }
}


add_action('plugins_loaded', function () {
  $dbg = __DIR__ . '/includes/bgvs-debug-proxy.php';
  if (file_exists($dbg)) {
    require_once $dbg;
  }
});

// Ensure calendar assets are available where needed (calendar + Avery pages).
add_action('admin_enqueue_scripts', function ($hook) {
    // Calendar page
    if ($hook === 'brassgate_page_bgad-calendar') {
        wp_enqueue_script('bgad-calendar');
        wp_enqueue_style('bgad-calendar');
        return;
    }
    // Avery page
    if ($hook === 'brassgate_page_bgad-avery') {
        wp_enqueue_script('bgad-calendar');
        wp_enqueue_style('bgad-calendar');
        return;
    }
});

// Audio test page assets
add_action('admin_enqueue_scripts', function ($hook) {
    $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
    if ($page !== BrassGate_Assistants_Deluxe::SLUG_AUDIO_TEST) {
        return;
    }
    $ver = defined('BGV3_VER') ? BGV3_VER : time();
    $base_url = plugin_dir_url(__FILE__);
    wp_enqueue_style('bgad-audio-test-css', $base_url . 'assets/css/vertical-socials-v2.css', [], $ver);
    wp_enqueue_script('bgad-audio-test-js', $base_url . 'assets/js/audio-test-music.js', [], $ver, true);
    $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : 'https://brassgate-backend.onrender.com';
    $client_key = function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : '';
    $user_key = function_exists('bgad_get_user_api_key') ? bgad_get_user_api_key() : '';
    wp_localize_script('bgad-audio-test-js', 'BGAD_AUDIO_TEST', [
        'backend' => rtrim($backend, '/'),
        'nonce' => wp_create_nonce(BrassGate_Assistants_Deluxe::NONCE),
        'clientKey' => $client_key,
        'userKey' => $user_key,
    ]);
});

add_action('admin_enqueue_scripts', function ($hook) {
    // Calendar + Avery pages should both load calendar assets + localization
    if (
        $hook === 'brassgate_page_bgad-calendar' ||
        $hook === 'brassgate_page_bgad-avery'
    ) {
        wp_enqueue_style('bgad-calendar', plugins_url('assets/css/calendar.css', __FILE__), [], BrassGate_Assistants_Deluxe::VERSION);
        wp_enqueue_script('bgad-calendar', plugins_url('assets/js/calendar.js', __FILE__), [], BrassGate_Assistants_Deluxe::VERSION, true);

        wp_localize_script('bgad-calendar', 'BGAD_CALENDAR', [
            'rest_url' => rest_url('brassgate/v1/calendar'),
            'nonce'    => wp_create_nonce('wp_rest'),
            'scope'    => 'user',
            'user_id'  => get_current_user_id(),
            'tz'       => wp_timezone_string(),
        ]);
        return;
    }

    // Vertical/horizontal socials helper pages
    $page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '';
    if (!in_array($page, array('bgad-vertical-socials-helper', 'bgad-horizontal-socials'), true)) {
        return;
    }

    $ver = defined('BGV3_VER') ? BGV3_VER : time();
    $base_url = plugin_dir_url(__FILE__);

    wp_enqueue_style(
        'bgad-vertical-socials-v2-css',
        $base_url . 'assets/css/vertical-socials-v2.css',
        [],
        $ver
    );

    if ($page === 'bgad-vertical-socials-helper') {
        wp_enqueue_script(
            'bgad-vertical-socials-v2-js',
            $base_url . 'assets/js/vertical-socials-v2.js',
            [],
            $ver,
            true
        );
    } else {
        wp_enqueue_script(
            'bgad-horizontal-socials-js',
            $base_url . 'assets/js/horizontal-socials.js',
            [],
            $ver,
            true
        );
    }

    $user_id = get_current_user_id();
    $user_key = $user_id ? trim((string) get_user_meta($user_id, BrassGate_Assistants_Deluxe::USER_KEY, true)) : '';
    if ($user_key === '' && $user_id) {
        $fallback = trim((string) get_user_meta($user_id, 'bg_api_key', true));
        if ($fallback !== '') {
            $user_key = $fallback;
        }
    }

    $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : 'https://brassgate-backend.onrender.com';
    $client_key = function_exists('bgad_get_client_key_for_current_user') ? bgad_get_client_key_for_current_user() : '';

    $headers_cfg = [
        'Accept' => 'application/json',
    ];
    if ($user_key) { // $user_key is already defined in this scope
        $headers_cfg['X-User-API-Key'] = $user_key;
    }
    if ($client_key) {
        $headers_cfg['x-brassgate-client-key'] = $client_key;
    }

    if ($page === 'bgad-vertical-socials-helper') {
        wp_localize_script('bgad-vertical-socials-v2-js', 'BGAD_VERTICAL_SOCIALS_V2', [
            'ajax'       => admin_url('admin-ajax.php'),
            'nonce'      => wp_create_nonce(BrassGate_Assistants_Deluxe::NONCE),
            'backend'    => rtrim($backend, '/'),
            'headers'    => $headers_cfg,
            'pollMs'     => 5000,
            'ttlSeconds' => 3600,
            'missingKey' => ($user_key === ''),
            'quotas'     => [
                'max_uploads'        => 50,
                'remaining_uploads'  => 50,
                'max_templates'      => 20,
                'remaining_templates'=> 20,
                'max_outputs'        => 100,
                'remaining_outputs'  => 100,
                'max_generations'    => 30,
                'remaining_generations' => 30,
            ],
            'actions'    => [
                'submit'   => 'bgad_vertical_socials_submit',
                'status'   => 'bgad_vertical_socials_status',
                'download' => 'bgad_vertical_socials_download',
                'ping'     => 'bgad_vertical_socials_ping',
            ],
        ]);
    } else {
        $bucket = function_exists('bgad_user_bucket') ? bgad_user_bucket() : '';
        wp_localize_script('bgad-horizontal-socials-js', 'BGAD_HORIZONTAL_SOCIALS', [
            'ajax'       => admin_url('admin-ajax.php'),
            'nonce'      => wp_create_nonce(BrassGate_Assistants_Deluxe::NONCE),
            'backend'    => rtrim($backend, '/'),
            'headers'    => $headers_cfg,
            'pollMs'     => 5000,
            'ttlSeconds' => 3600,
            'missingKey' => ($user_key === ''),
            'quotas'     => [
                'max_uploads'        => 50,
                'remaining_uploads'  => 50,
                'max_templates'      => 20,
                'remaining_templates'=> 20,
                'max_outputs'        => 100,
                'remaining_outputs'  => 100,
                'max_generations'    => 30,
                'remaining_generations' => 30,
            ],
            'actions'    => [
                'ping' => 'bgad_horizontal_socials_ping',
            ],
            'bucket'     => $bucket,
        ]);
    }
});
  // Video Studio admin assets (legacy)
  if (!empty($is_vs)) {
    $base_path = plugin_dir_path(__FILE__);
    $css_path  = $base_path . "assets/css/video-studio-v3.css";
    $js_path   = $base_path . "assets/js/video-studio-v3.js";
    $css_url   = plugins_url("assets/css/video-studio-v3.css", __FILE__);
    $js_url    = plugins_url("assets/js/video-studio-v3.js", __FILE__);

    // Rolling back to legacy assets so the flagship UI stays consistent.
    if (is_readable($css_path)) {
      wp_enqueue_style("bgad-video-studio", $css_url, [], @filemtime($css_path) ?: $ver);
    }
    if (is_readable($js_path)) {
      wp_enqueue_script("bgad-video-studio", $js_url, [], @filemtime($js_path) ?: $ver, true);
    }
  }

if (!function_exists('bgad_get_user_api_key')) {
    function bgad_get_user_api_key() {
        $user_id = get_current_user_id();
        $user_key = '';
        if ($user_id) {
            $user_key = (string) get_user_meta($user_id, 'bg_user_api_key', true);
        }
        if ($user_key === '') {
            $user_key = (string) get_option('bgad_user_api_key', '');
        } else {
            $stored = (string) get_option('bgad_user_api_key', '');
            if ($stored !== $user_key) {
                update_option('bgad_user_api_key', $user_key);
            }
        }
        return $user_key;
    }
}

if (!function_exists('bgad_get_user_fastapi_client')) {
    function bgad_get_user_fastapi_client() {
        $user_id = get_current_user_id();
        if (!$user_id) {
            return '';
        }
        return trim((string) get_user_meta($user_id, 'fastapi_client', true));
    }
}

// Video director helpers are kept near class to access static methods.

if (!function_exists('bgad_video_director_config_array')) {
    function bgad_video_director_config_array() {

        // --- Backend URL -----------------------------------------------------
        $backend = '';
        if (function_exists('bgad_backend_base')) {
            $backend = bgad_backend_base();
        }
        $backend = trim($backend ?: get_option(BrassGate_Assistants_Deluxe::OPT_API_URL, ''));
        if ($backend === '') {
            $backend = 'https://brassgate-backend.onrender.com';
        }
        $backend = rtrim($backend, '/');

        // --- User API Key (v3 requirement) ----------------------------------
        $user_key = '';
        if (function_exists('bgad_get_user_api_key')) {
            $user_key = trim((string) bgad_get_user_api_key());
        }

        // --- Service / Manager API Key ---------------------------------------
        $fastapi_client = '';
        if (function_exists('bgad_get_user_fastapi_client')) {
            $fastapi_client = trim((string) bgad_get_user_fastapi_client());
        }
        $service_key = $fastapi_client;

        if ($service_key === '' && function_exists('bgad_get_user_service_api_key')) {
            $service_key = trim((string) bgad_get_user_service_api_key());
        }
        if ($service_key === '' && function_exists('bgad_service_api_key')) {
            $service_key = trim((string) bgad_service_api_key());
        }

        if ($fastapi_client !== '' && get_option('bgad_service_api_key') !== $fastapi_client) {
            update_option('bgad_service_api_key', $fastapi_client);
        }

        $api_key = $service_key ?: '';

        // --- Client Key / Bucket --------------------------------------------
        $client_key = '';
        if (function_exists('bgad_get_client_key_for_current_user')) {
            $client_key = trim((string) bgad_get_client_key_for_current_user());
        }
        if ($client_key === '') {
            $client_key = trim((string) get_option('brassgate_client_key', ''));
        }

        $bucket = '';
        if (function_exists('bgad_user_bucket')) {
            $bucket = trim((string) bgad_user_bucket());
        }
        if ($bucket === '') {
            $bucket = trim((string) get_option('brassgate_default_bucket', ''));
        }

        // Normalized client ID for backend routing
        $client_id = $bucket !== '' ? $bucket : $client_key;

        // --- V3 AUTH HEADERS -------------------------------------------------
        // These MUST be passed verbatim or backend rejects the request
        $auth_headers = array(
            'X-User-API-Key'        => $user_key,
            'X-API-Key'             => $api_key,
            'X-BrassGate-Client-Key'=> $client_id,
            'X-BG-Client'           => $client_id,
            'X-BG-Feature'          => 'video-director',
            'X-BGA-Plugin'          => 'video-director',
        );

        // Filter empty values
        $auth_headers = array_filter($auth_headers, function($v) {
            return $v !== null && $v !== '';
        });

        return array(
            'backend' => $backend,
            'apiBase' => $backend . '/v3/',
            'clientId' => $client_id,
            'bucket'   => $client_id,

            'auth' => array(
                'apiKey'  => $api_key,
                'headers' => $auth_headers,
            ),

            'featureFlags' => array(
                'samplerCooldownS' => 1.5,
                'defaultFrameStep' => 30,
            ),

            'ui' => array(
                'locale' => get_locale() ?: 'en_US',
                'theme'  => 'auto',
            ),

            'missingKey' => ($api_key === ''),
        );
    }
}

add_action('wp_ajax_bgad_video_director_config', function () {
    if (!current_user_can('read')) {
        wp_send_json_error(['message' => 'Unauthorized'], 403);
    }

    $cfg = bgad_video_director_config_array();
    wp_send_json_success($cfg);
});

if (!function_exists('bgad_get_client_key')) {
    function bgad_get_client_key() {
        return (string) get_option('bgad_client_key', '');
    }
}

if (!function_exists('bgad_render_jimmy_bones')) {
    function bgad_render_jimmy_bones() {
        $context = class_exists('BrassGate_Assistants_Deluxe')
            ? BrassGate_Assistants_Deluxe::quota_context(array('jimmy_enabled', 'jimmy_standard_research', 'jimmy_deep_research'))
            : array();
        if (class_exists('BrassGate_Assistants_Deluxe')) {
        }
        if (!empty($context['needs_upgrade'])) {
            $cta = class_exists('BrassGate_Assistants_Deluxe') ? BrassGate_Assistants_Deluxe::manager_url() : admin_url('admin.php?page=brassgate-plan-access');
            echo '<div class="notice notice-warning"><p>';
            echo esc_html__('Jimmy is not available on your current plan.', 'brassgate-assistants-deluxe') . ' ';
            echo '<a class="button button-primary" href="' . esc_url($cta) . '">' . esc_html__('View Plans', 'brassgate-assistants-deluxe') . '</a>';
            echo '</p></div>';
            return;
        }
        $base_path = plugin_dir_path(__FILE__);
        $css_path  = $base_path . 'assets/css/jimmy.css';
        $js_path   = $base_path . 'assets/js/jimmy.js';
        $css_url   = plugins_url('assets/css/jimmy.css', __FILE__);
        $js_url    = plugins_url('assets/js/jimmy.js', __FILE__);

        if (is_readable($css_path) && !wp_style_is('bgad-jimmy', 'enqueued')) {
            wp_enqueue_style('bgad-jimmy', $css_url, [], @filemtime($css_path) ?: BrassGate_Assistants_Deluxe::VERSION);
        }

        if (is_readable($js_path) && !wp_script_is('bgad-jimmy', 'enqueued')) {
            wp_enqueue_script('bgad-jimmy', $js_url, [], @filemtime($js_path) ?: BrassGate_Assistants_Deluxe::VERSION, true);

            if (!function_exists('bgad_user_headers')) {
                $inc = plugin_dir_path(__FILE__) . 'includes/bg-headers.php';
                if (file_exists($inc)) {
                    require_once $inc;
                }
            }

            $headers = function_exists('bgad_user_headers') ? bgad_user_headers(true, 'jimmy') : ['Accept' => 'application/json'];
            $backend = function_exists('bgad_backend_base') ? bgad_backend_base() : BrassGate_Assistants_Deluxe::backend_base();
            $cfg = [
                'apiBase' => rtrim($backend, '/'),
                'headers' => $headers,
                'mode' => 'admin',
            ];
            wp_add_inline_script('bgad-jimmy', 'window.BGAD_JIMMY=' . wp_json_encode($cfg) . ';', 'before');
        }
        require_once __DIR__ . '/pages/jimmy.php';
    }
}

add_action('admin_menu', function () {
    add_submenu_page(
        BrassGate_Assistants_Deluxe::SLUG,
        'Jimmy (Sales AI)',
        'Jimmy (Sales AI)',
        'manage_options',
        BrassGate_Assistants_Deluxe::SLUG_JIMMY,
        'bgad_render_jimmy_bones',
        54
    );
}, 220);

