<?php
/**
 * Module Name: VMO Premium Voice Client Bridge Hotfix
 * Description: Surgical client-side hotfix for VMO Premium Live Voice. Forces hosted clients to route Realtime WebRTC session/signaling through the VMO master broker and adds visible console diagnostics.
 * Version: 2026.06.20-client-bridge-1
 * Author: VMO
 *
 * Integrated in VMO full plugin package.
 * Auto-runs only on hosted QuickLaunch/client installs.
 */

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

if (!function_exists('vmo_premium_client_voice_bridge_config')) {
    function vmo_premium_client_voice_bridge_config() {
        $is_client_mode = (
            (defined('VMO_CLIENT_MODE') && VMO_CLIENT_MODE === true)
            || get_option('vmo_quicklaunch_hosted_client') === '1'
            || get_option('vmo_client_distribution_channel') === 'quicklaunch_hosted'
            || get_option('vmo_quicklaunch_domain', '') !== ''
        );

        $master_proxy_url = '';
        if (defined('VMO_MASTER_PROXY_URL') && VMO_MASTER_PROXY_URL) {
            $master_proxy_url = (string) VMO_MASTER_PROXY_URL;
        }
        if ($master_proxy_url === '') {
            $master_proxy_url = (string) get_option('vmo_master_proxy_url', 'https://getvmo.com/wp-json/vmo-builder/v1/proxy');
        }
        $master_proxy_url = rtrim(trim($master_proxy_url), '/');
        $master_api_url = preg_replace('#/proxy/?$#', '', $master_proxy_url);

        $license_key = trim((string) get_option('vmo_license_key', ''));
        if ($license_key === '' && is_user_logged_in()) {
            $license_key = trim((string) get_user_meta(get_current_user_id(), 'vmo_license_key', true));
        }

        $installation_id = trim((string) get_option('vmo_installation_id', ''));

        $enabled = $is_client_mode
            && $master_api_url !== ''
            && strpos($master_api_url, '__VMO_MASTER_PROXY_URL__') === false
            && $license_key !== ''
            && $installation_id !== '';

        return array(
            'enabled' => (bool) $enabled,
            'client_mode' => (bool) $is_client_mode,
            'hosted_client' => (bool) $is_client_mode,
            'direct_master_voice' => (bool) $enabled,
            'force_direct_master_voice' => true,
            'master_proxy_url' => esc_url_raw($master_proxy_url),
            'master_api_url' => esc_url_raw($master_api_url),
            'license_key' => $license_key,
            'installation_id' => $installation_id,
            'site_url' => esc_url_raw(home_url('/')),
            'patch_version' => '2026.06.20-client-bridge-1',
        );
    }
}

if (!function_exists('vmo_premium_client_voice_bridge_boot_script')) {
    function vmo_premium_client_voice_bridge_boot_script() {
        $cfg = vmo_premium_client_voice_bridge_config();
        if (empty($cfg['client_mode']) && empty($cfg['enabled']) && empty($cfg['hosted_client'])) { return; }
        $json = wp_json_encode($cfg);
        if (!$json) { $json = '{}'; }
        echo "\n<script id=\"vmo-premium-client-voice-bridge-config\">\n";
        echo "window.vmoClientProxy = Object.assign({}, window.vmoClientProxy || {}, {$json});\n";
        echo "window.__VMO_PREMIUM_CLIENT_BRIDGE_CONFIG__ = window.vmoClientProxy;\n";
        echo "try { console.info('[VMO Premium Bridge] config injected', {client_mode: !!window.vmoClientProxy.client_mode, enabled: !!window.vmoClientProxy.enabled, master_api_url: window.vmoClientProxy.master_api_url, installation_id: window.vmoClientProxy.installation_id ? String(window.vmoClientProxy.installation_id).slice(0, 8) + '…' : '', license_present: !!window.vmoClientProxy.license_key}); } catch(e) {}\n";
        echo "</script>\n";
    }
}

add_action('admin_head', 'vmo_premium_client_voice_bridge_boot_script', 0);
add_action('wp_head', 'vmo_premium_client_voice_bridge_boot_script', 0);

if (!function_exists('vmo_premium_client_voice_bridge_patch_script')) {
    function vmo_premium_client_voice_bridge_patch_script() {
        $cfg = vmo_premium_client_voice_bridge_config();
        if (empty($cfg['client_mode']) && empty($cfg['enabled']) && empty($cfg['hosted_client'])) { return; }
        ?>
<script id="vmo-premium-client-voice-bridge-patch">
(function () {
  'use strict';
  var PATCH = '2026.06.20-client-bridge-1';

  function log() {
    try { console.log.apply(console, ['[VMO Premium Bridge]'].concat([].slice.call(arguments))); } catch (_) {}
  }
  function warn() {
    try { console.warn.apply(console, ['[VMO Premium Bridge]'].concat([].slice.call(arguments))); } catch (_) {}
  }
  function errlog() {
    try { console.error.apply(console, ['[VMO Premium Bridge]'].concat([].slice.call(arguments))); } catch (_) {}
  }
  function cfg() {
    var c = window.vmoClientProxy || window.__VMO_PREMIUM_CLIENT_BRIDGE_CONFIG__ || {};
    var base = String(c.master_proxy_url || '').replace(/\/+$/, '');
    var apiBase = String(c.master_api_url || '').replace(/\/+$/, '');
    if (!apiBase && base) apiBase = base.replace(/\/proxy\/?$/i, '');
    var clientMode = !!(c.client_mode || c.hosted_client || c.enabled || c.direct_master_voice || c.force_direct_master_voice);
    var configured = !!(apiBase && c.license_key && c.installation_id);
    return {
      clientMode: clientMode,
      configured: configured,
      enabled: !!(clientMode && configured),
      apiBase: apiBase,
      base: base,
      license: String(c.license_key || ''),
      installation: String(c.installation_id || ''),
      siteUrl: String(c.site_url || window.location.origin || '')
    };
  }
  function requestId(prefix) {
    try { if (window.crypto && window.crypto.randomUUID) return prefix + '_' + window.crypto.randomUUID(); } catch (_) {}
    return prefix + '_' + Date.now() + '_' + Math.random().toString(16).slice(2);
  }
  function safetyId(installation) {
    return 'vmo_' + String(installation || 'client').replace(/[^A-Za-z0-9_-]/g, '').slice(0, 48);
  }
  function clone(obj) {
    try { return obj ? JSON.parse(JSON.stringify(obj)) : {}; } catch (_) { return {}; }
  }
  function sanitizeSessionConfig(config, voice, model) {
    var s = clone(config);
    if (!s || typeof s !== 'object') s = {};
    s.type = s.type || 'realtime';
    if (model && !s.model) s.model = model;
    if (!s.model) s.model = 'gpt-realtime-mini';
    if (s.max_response_output_tokens !== undefined && s.max_output_tokens === undefined) {
      s.max_output_tokens = s.max_response_output_tokens;
    }
    try { delete s.max_response_output_tokens; } catch (_) {}
    if (!s.max_output_tokens) s.max_output_tokens = 4096;
    s.audio = s.audio && typeof s.audio === 'object' ? s.audio : {};
    s.audio.input = s.audio.input && typeof s.audio.input === 'object' ? s.audio.input : {};
    s.audio.output = s.audio.output && typeof s.audio.output === 'object' ? s.audio.output : {};
    s.audio.output.voice = voice || s.audio.output.voice || 'ash';
    s.audio.input.turn_detection = s.audio.input.turn_detection && typeof s.audio.input.turn_detection === 'object'
      ? s.audio.input.turn_detection
      : { type: 'server_vad' };
    s.audio.input.turn_detection.type = s.audio.input.turn_detection.type || 'server_vad';
    s.audio.input.turn_detection.create_response = true;
    s.audio.input.turn_detection.interrupt_response = true;
    return s;
  }
  function parseJsonMaybe(text) {
    try { return text ? JSON.parse(text) : null; } catch (_) { return null; }
  }

  function installFetchDiagnostics() {
    if (window.__vmoPremiumBridgeFetchDiagnostics) return;
    window.__vmoPremiumBridgeFetchDiagnostics = true;
    var originalFetch = window.fetch;
    if (typeof originalFetch !== 'function') return;
    window.fetch = function (input, init) {
      var url = '';
      try { url = typeof input === 'string' ? input : (input && input.url) || ''; } catch (_) {}
      var bodyText = '';
      try { bodyText = init && init.body ? String(init.body).slice(0, 250) : ''; } catch (_) {}
      var interesting = /\/realtime\/(session|sdp)/i.test(url) || (/admin-ajax\.php/i.test(url) && /vmo_realtime/i.test(bodyText));
      if (interesting) log('fetch →', (init && init.method) || 'GET', url, bodyText ? { body_preview: bodyText } : '');
      return originalFetch.apply(this, arguments).then(function (response) {
        if (interesting) log('fetch ←', response.status, response.statusText, url);
        return response;
      }).catch(function (e) {
        if (interesting) errlog('fetch ✕', url, e && e.message ? e.message : e);
        throw e;
      });
    };
  }

  function installRealtimePatch() {
    if (!window.VMORealtimeSession || !window.VMORealtimeSession.prototype) return false;
    var proto = window.VMORealtimeSession.prototype;
    if (proto.__vmoPremiumClientBridgePatched) return true;

    var originalMint = proto._mintToken;
    var originalExchange = proto._exchangeSdpViaMaster;

    proto._mintToken = function () {
      var self = this;
      var c = cfg();
      log('Premium session requested', { clientMode: c.clientMode, enabled: c.enabled, apiBase: c.apiBase, installation: c.installation ? c.installation.slice(0, 8) + '…' : '' });

      if (c.clientMode && !c.enabled) {
        throw new Error('Premium Live Voice client bridge is not configured. Missing master API URL, license key, or installation ID. Client must not originate Realtime without master billing.');
      }
      if (!c.enabled) {
        return originalMint.apply(this, arguments);
      }

      return fetch(c.apiBase + '/realtime/session', {
        method: 'POST',
        mode: 'cors',
        credentials: 'omit',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
          'X-VMO-License': c.license,
          'X-VMO-Installation': c.installation,
          'X-VMO-Operation': 'realtime_audio_min',
          'X-VMO-Request-ID': requestId('rt_session'),
          'OpenAI-Safety-Identifier': safetyId(c.installation)
        },
        body: JSON.stringify({ context: self.context || 'jarvis', voice: self.voice || 'ash', site_url: c.siteUrl })
      }).then(function (r) {
        return r.text().then(function (t) {
          var j = parseJsonMaybe(t);
          if (!r.ok || !j || j.success === false) {
            var msg = (j && (j.message || (j.data && j.data.message))) || t || r.statusText || ('HTTP ' + r.status);
            throw new Error('Master Realtime session failed: ' + r.status + ' ' + String(msg).slice(0, 600));
          }
          var data = j.data || j;
          if (!data.session_id) throw new Error('Master Realtime session returned no session_id.');
          if (!data.model) data.model = 'gpt-realtime-mini';
          data.master_routed = true;
          data.direct_master_session = true;
          log('Master session ready', { session_id: String(data.session_id).slice(0, 18) + '…', model: data.model, voice: data.voice || self.voice });
          return data;
        });
      });
    };

    proto._exchangeSdpViaMaster = function (offerSdp) {
      var self = this;
      var c = cfg();
      log('SDP exchange requested', { clientMode: c.clientMode, enabled: c.enabled, offer_chars: offerSdp ? String(offerSdp).length : 0 });

      if (c.clientMode && !c.enabled) {
        throw new Error('Premium Live Voice client bridge cannot exchange SDP because master broker config is missing.');
      }
      if (!c.enabled) {
        return originalExchange.apply(this, arguments);
      }
      if (!offerSdp || String(offerSdp).trim().indexOf('v=0') !== 0) {
        throw new Error('Invalid browser SDP offer; cannot send to master.');
      }

      var sessionConfig = sanitizeSessionConfig((self.session && self.session.session_update) || {}, self.voice || 'ash', (self.session && self.session.model) || 'gpt-realtime-mini');

      log('POSTing browser SDP offer to VMO master broker', c.apiBase + '/realtime/sdp');
      return fetch(c.apiBase + '/realtime/sdp', {
        method: 'POST',
        mode: 'cors',
        credentials: 'omit',
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
          'X-VMO-License': c.license,
          'X-VMO-Installation': c.installation,
          'X-VMO-Operation': 'realtime_audio_min',
          'X-VMO-Request-ID': requestId('rt_sdp'),
          'OpenAI-Safety-Identifier': safetyId(c.installation)
        },
        body: JSON.stringify({
          offer_sdp: offerSdp,
          session: sessionConfig,
          session_id: self.session && self.session.session_id,
          model: sessionConfig.model || (self.session && self.session.model) || 'gpt-realtime-mini',
          voice: self.voice || 'ash',
          context: self.context || 'jarvis'
        })
      }).then(function (r) {
        return r.text().then(function (t) {
          var j = parseJsonMaybe(t);
          var data = j && (j.data || j);
          if (!r.ok || !data || !data.answer_sdp || String(data.answer_sdp).trim().indexOf('v=0') !== 0) {
            var msg = (j && (j.message || (j.data && j.data.message))) || t || r.statusText || ('HTTP ' + r.status);
            throw new Error('Master Realtime SDP failed: ' + r.status + ' ' + String(msg).slice(0, 800));
          }
          data.session_id = data.session_id || (self.session && self.session.session_id);
          data.model = data.model || (self.session && self.session.model) || 'gpt-realtime-mini';
          data.master_routed = true;
          data.direct_master_proxy = true;
          log('Received answer SDP from VMO master', { answer_chars: String(data.answer_sdp).length, model: data.model });
          return data;
        });
      });
    };

    proto.__vmoPremiumClientBridgePatched = PATCH;
    window.VMORealtimeSession.__vmoPremiumClientBridgePatch = {
      version: PATCH,
      status: function () {
        var c = cfg();
        console.table({
          patch: PATCH,
          clientMode: c.clientMode,
          enabled: c.enabled,
          configured: c.configured,
          apiBase: c.apiBase,
          licensePresent: !!c.license,
          installation: c.installation ? c.installation.slice(0, 8) + '…' : ''
        });
        return c;
      }
    };
    log('Realtime client patched. Run VMORealtimeSession.__vmoPremiumClientBridgePatch.status() to inspect config.');
    return true;
  }

  installFetchDiagnostics();
  if (!installRealtimePatch()) {
    var tries = 0;
    var timer = setInterval(function () {
      tries++;
      if (installRealtimePatch() || tries > 80) {
        clearInterval(timer);
        if (tries > 80) warn('VMORealtimeSession was not found. Premium script may not be loading on this page.');
      }
    }, 250);
  }
})();
</script>
        <?php
    }
}

add_action('admin_footer', 'vmo_premium_client_voice_bridge_patch_script', 999999);
add_action('wp_footer', 'vmo_premium_client_voice_bridge_patch_script', 999999);
