/**
 * yt_adblock.c - YouTube-specific ad blocking implementation
 *
 * This module injects JavaScript and CSS into YouTube pages to block ads
 * by intercepting API calls and hiding ad containers.
 */

#include "yt_adblock.h"
#include <stdio.h>

/* Private structure definition */
struct _YTAdblockManager {
  WebKitUserScript *interceptor_script;
  WebKitUserStyleSheet *cosmetic_stylesheet;
  gboolean enabled;
  guint ref_count;
};

/* YouTube URL patterns for script/style injection */
static const char *YT_URL_PATTERNS[] = {
    "https://www.youtube.com/*", "https://m.youtube.com/*",
    "https://www.youtube-nocookie.com/*", NULL /* Null-terminated array */
};

/**
 * YouTube Ad Interceptor Script - Phase 2
 *
 * This script intercepts YouTube's API calls and removes ad-related data
 * from JSON responses before the page's JavaScript can process them.
 *
 * Targets:
 * - /youtubei/v1/player (video player data)
 * - /youtubei/v1/next (up next / related videos)
 */
static const char *YT_INTERCEPTOR_SCRIPT =
    "(function() {\n"
    "  'use strict';\n"
    "\n"
    "  console.log('[YT AdBlock] Initializing YouTube ad interceptor...');\n"
    "\n"
    "  /* ===================================================================\n"
    "   * UTILITY FUNCTIONS\n"
    "   * ================================================================ */\n"
    "\n"
    "  /**\n"
    "   * Recursively removes ONLY ad-related properties from an object\n"
    "   * Preserves all structural keys needed for recommendations/related "
    "videos\n"
    "   */\n"
    "  function removeAdProperties(obj, depth = 0) {\n"
    "    if (!obj || typeof obj !== 'object') return obj;\n"
    "\n"
    "    // Keys that must NEVER be deleted (structural/content keys)\n"
    "    const preserveKeys = [\n"
    "      'contents',\n"
    "      'currentVideoEndpoint',\n"
    "      'watchNextResponse',\n"
    "      'secondaryResults',\n"
    "      'results',\n"
    "      'items',\n"
    "      'continuationItems',\n"
    "      'videoDetails',\n"
    "      'streamingData',\n"
    "      'playerResponse'\n"
    "    ];\n"
    "\n"
    "    // Only remove keys that explicitly indicate ads\n"
    "    const keysToRemove = [];\n"
    "    let preservedCount = 0;\n"
    "    \n"
    "    for (const key in obj) {\n"
    "      if (!obj.hasOwnProperty(key)) continue;\n"
    "      \n"
    "      // Never touch preserved structural keys\n"
    "      if (preserveKeys.includes(key)) {\n"
    "        preservedCount++;\n"
    "        if (depth === 0) {\n"
    "          console.log('[YT AdBlock] ✓ Preserving key:', key);\n"
    "        }\n"
    "        continue;\n"
    "      }\n"
    "      \n"
    "      // Remove only if key explicitly indicates ads\n"
    "      // Check for: 'ad*', 'promo*', '*Ad*', '*Ads*' (camelCase)\n"
    "      const lowerKey = key.toLowerCase();\n"
    "      const isAdKey = (\n"
    "        lowerKey.startsWith('ad') ||           // adPlacements, adSlots\n"
    "        lowerKey.startsWith('promo') ||         // promoted\n"
    "        key.includes('Ad') ||                   // playerAd, "
    "hasAllowedInstreamAd\n"
    "        key.includes('Ads')                     // playerAds\n"
    "      );\n"
    "      \n"
    "      if (isAdKey) {\n"
    "        // Extra safety: don't remove 'adapter' or other non-ad words\n"
    "        if (!lowerKey.includes('adapter') && \n"
    "            !lowerKey.includes('address') &&\n"
    "            !lowerKey.includes('adjust') &&\n"
    "            !lowerKey.includes('oadcast')) {  // Don't catch 'broadcast'\n"
    "          keysToRemove.push(key);\n"
    "          if (depth === 0) {\n"
    "            console.log('[YT AdBlock] ○ Neutralizing ad key:', key, "
    "'(setting to [])');\n"
    "          }\n"
    "        }\n"
    "      }\n"
    "    }\n"
    "\n"
    "    // Log summary at top level\n"
    "    if (depth === 0 && (keysToRemove.length > 0 || preservedCount > 0)) "
    "{\n"
    "      console.log('[YT AdBlock] Summary: Neutralized', "
    "keysToRemove.length, 'ad keys, Preserved', preservedCount, 'structural "
    "keys');\n"
    "    }\n"
    "\n"
    "    // IMPORTANT: Don't DELETE ad keys, set them to empty arrays\n"
    "    // YouTube expects these fields to exist for validation\n"
    "    for (const key of keysToRemove) {\n"
    "      // Set to empty array instead of deleting\n"
    "      obj[key] = [];\n"
    "    }\n"
    "\n"
    "    // Recursively process arrays\n"
    "    if (Array.isArray(obj)) {\n"
    "      return obj.map(item => removeAdProperties(item, depth + 1));\n"
    "    }\n"
    "\n"
    "    // Recursively process nested objects\n"
    "    for (const key in obj) {\n"
    "      if (obj.hasOwnProperty(key) && \n"
    "          typeof obj[key] === 'object' && \n"
    "          obj[key] !== null) {\n"
    "        obj[key] = removeAdProperties(obj[key], depth + 1);\n"
    "      }\n"
    "    }\n"
    "\n"
    "    return obj;\n"
    "  }\n"
    "\n"
    "  /**\n"
    "   * Checks if a URL is a YouTube API endpoint we want to intercept\n"
    "   * Only intercept /player endpoint which contains video ad data\n"
    "   * Avoid intercepting /browse and /next which have recommendations\n"
    "   */\n"
    "  function isTargetURL(url) {\n"
    "    return url.includes('/youtubei/v1/player');\n"
    "  }\n"
    "\n"
    "  /* ===================================================================\n"
    "   * FETCH API INTERCEPTION\n"
    "   * ================================================================ */\n"
    "\n"
    "  const originalFetch = window.fetch;\n"
    "\n"
    "  window.fetch = function(...args) {\n"
    "    const url = typeof args[0] === 'string' ? args[0] : args[0]?.url || "
    "'';\n"
    "\n"
    "    // Call original fetch\n"
    "    return originalFetch.apply(this, args).then(response => {\n"
    "      // Only intercept YouTube API calls\n"
    "      if (!isTargetURL(url)) {\n"
    "        return response;\n"
    "      }\n"
    "\n"
    "      console.log('[YT AdBlock] "
    "═══════════════════════════════════════');\n"
    "      console.log('[YT AdBlock] Intercepted fetch:', url);\n"
    "\n"
    "      // Clone the response to read it without consuming\n"
    "      return response.clone().text().then(text => {\n"
    "        try {\n"
    "          // Parse JSON\n"
    "          const data = JSON.parse(text);\n"
    "          \n"
    "          // Log top-level keys BEFORE cleaning\n"
    "          const beforeKeys = Object.keys(data);\n"
    "          console.log('[YT AdBlock] Keys BEFORE:', beforeKeys);\n"
    "\n"
    "          // Remove ad properties\n"
    "          const cleaned = removeAdProperties(data);\n"
    "          \n"
    "          // Log top-level keys AFTER cleaning\n"
    "          const afterKeys = Object.keys(cleaned);\n"
    "          console.log('[YT AdBlock] Keys AFTER:', afterKeys);\n"
    "\n"
    "          console.log('[YT AdBlock] ✓ Neutralized ad data (set to empty "
    "arrays)');\n"
    "          console.log('[YT AdBlock] "
    "═══════════════════════════════════════');\n"
    "\n"
    "          // Create new response with cleaned data\n"
    "          const cleanedText = JSON.stringify(cleaned);\n"
    "          return new Response(cleanedText, {\n"
    "            status: response.status,\n"
    "            statusText: response.statusText,\n"
    "            headers: response.headers\n"
    "          });\n"
    "        } catch (e) {\n"
    "          // If parsing fails, return original response\n"
    "          console.warn('[YT AdBlock] ✗ Failed to parse response:', e);\n"
    "          console.log('[YT AdBlock] "
    "═══════════════════════════════════════');\n"
    "          return response;\n"
    "        }\n"
    "      }).catch(err => {\n"
    "        console.error('[YT AdBlock] ✗ Error reading response:', err);\n"
    "        console.log('[YT AdBlock] "
    "═══════════════════════════════════════');\n"
    "        return response;\n"
    "      });\n"
    "    });\n"
    "  };\n"
    "\n"
    "  console.log('[YT AdBlock] Fetch API hooked');\n"
    "\n"
    "  /* ===================================================================\n"
    "   * XMLHttpRequest INTERCEPTION (Backup)\n"
    "   * ================================================================ */\n"
    "\n"
    "  const XHR = XMLHttpRequest.prototype;\n"
    "  const originalOpen = XHR.open;\n"
    "  const originalSend = XHR.send;\n"
    "\n"
    "  XHR.open = function(method, url, ...rest) {\n"
    "    this._ytAdblockURL = url;\n"
    "    return originalOpen.call(this, method, url, ...rest);\n"
    "  };\n"
    "\n"
    "  XHR.send = function(...args) {\n"
    "    if (isTargetURL(this._ytAdblockURL || '')) {\n"
    "      console.log('[YT AdBlock] Intercepted XHR:', "
    "this._ytAdblockURL.substring(0, 80) + '...');\n"
    "\n"
    "      // Hook the response handler\n"
    "      this.addEventListener('readystatechange', function() {\n"
    "        if (this.readyState === 4 && this.responseText) {\n"
    "          try {\n"
    "            const data = JSON.parse(this.responseText);\n"
    "            const cleaned = removeAdProperties(data);\n"
    "            const cleanedText = JSON.stringify(cleaned);\n"
    "\n"
    "            // Override response properties\n"
    "            Object.defineProperty(this, 'responseText', {\n"
    "              writable: true,\n"
    "              value: cleanedText\n"
    "            });\n"
    "            Object.defineProperty(this, 'response', {\n"
    "              writable: true,\n"
    "              value: cleanedText\n"
    "            });\n"
    "\n"
    "            console.log('[YT AdBlock] Cleaned ad data from XHR "
    "response');\n"
    "          } catch (e) {\n"
    "            // Silently fail if parsing doesn't work\n"
    "          }\n"
    "        }\n"
    "      });\n"
    "    }\n"
    "\n"
    "    return originalSend.apply(this, args);\n"
    "  };\n"
    "\n"
    "  console.log('[YT AdBlock] XMLHttpRequest hooked');\n"
    "\n"
    "  /* ===================================================================\n"
    "   * ANTI-ADBLOCK BYPASSES\n"
    "   * ================================================================ */\n"
    "\n"
    "  // Prevent YouTube from detecting adblock by overriding detection "
    "properties\n"
    "  try {\n"
    "    // Override adblock detection flags\n"
    /* Object.defineProperty(Object.prototype, 'adBlocksFound', {\n"
    "      configurable: false,\n"
    "      get: function() { return false; },\n"
    "      set: function() {}\n"
    "    });\n"
    "\n"
    "    Object.defineProperty(Object.prototype, 'hasAllowedInstreamAd', {\n"
    "      configurable: false,\n"
    "      get: function() { return true; },\n"
    "      set: function() {}\n"
    "    });\n"
    "\n" */
    "    console.log('[YT AdBlock] Anti-adblock bypasses installed');\n"
    "  } catch (e) {\n"
    "    console.warn('[YT AdBlock] Could not install anti-adblock bypasses:', "
    "e);\n"
    "  }\n"
    "\n"
    "  /* ===================================================================\n"
    "   * CONFIGURATION OVERRIDE\n"
    "   * ================================================================ */\n"
    "\n"
    "  // Try to override YouTube configuration when it loads\n"
    "  const configOverride = setInterval(function() {\n"
    "    try {\n"
    "      if (window.ytplayer && window.ytplayer.config) {\n"
    "        const config = window.ytplayer.config;\n"
    "        \n"
    "        // Remove ad-related config\n"
    "        if (config.args) {\n"
    "          delete config.args.ad_device;\n"
    "          delete config.args.ad_flags;\n"
    "          delete config.args.ad_logging_flag;\n"
    "          delete config.args.ad_preroll;\n"
    "          delete config.args.ad_slots;\n"
    "          delete config.args.ad_tag;\n"
    "          console.log('[YT AdBlock] Cleaned ytplayer.config.args');\n"
    "        }\n"
    "        \n"
    "        clearInterval(configOverride);\n"
    "      }\n"
    "    } catch (e) {\n"
    "      // Config not available yet\n"
    "    }\n"
    "  }, 100);\n"
    "\n"
    "  // Stop checking after 10 seconds\n"
    "  setTimeout(() => clearInterval(configOverride), 10000);\n"
    "\n"
    "  console.log('[YT AdBlock] YouTube ad interceptor initialized "
    "successfully');\n"
    "  console.log('[YT AdBlock] URL:', window.location.href);\n"
    "})();\n";

/**
 * Cosmetic Filtering CSS - Ultra-Conservative
 *
 * ONLY hides in-player ad elements and overlays.
 * Does NOT touch the secondary/related videos panel.
 */
static const char *YT_COSMETIC_CSS =
    "/* ===================================================================\n"
    " * IN-PLAYER ADS ONLY - DO NOT TOUCH SIDEBAR\n"
    " * ================================================================ */\n"
    "\n"
    "/* Player ad container (above video) */\n"
    "#player-ads {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Video player ad module */\n"
    ".ytp-ad-module {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Ad overlay containers inside player */\n"
    ".ytp-ad-overlay-container,\n"
    ".ytp-ad-player-overlay,\n"
    ".ytp-ad-player-overlay-flyout-cta {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Ad text and image overlays inside player */\n"
    ".ytp-ad-image-overlay,\n"
    ".ytp-ad-text-overlay,\n"
    ".ytp-ad-overlay-slot {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Generic video-ads container */\n"
    ".video-ads {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Ad overlay close button */\n"
    ".ytp-ad-overlay-close-button,\n"
    ".ytp-ad-overlay-close-container {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Ad text inside player */\n"
    ".ytp-ad-text,\n"
    ".ytp-ad-preview-container {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Skip ad button area */\n"
    ".ytp-ad-skip-button-slot,\n"
    ".ytp-ad-button-slot {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* Visit advertiser button */\n"
    ".ytp-ad-visit-advertiser-button {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* ===================================================================\n"
    " * MASTHEAD ADS (Top banner)\n"
    " * ================================================================ */\n"
    "\n"
    "#masthead-ad {\n"
    "  display: none !important;\n"
    "}\n"
    "\n"
    "/* ===================================================================\n"
    " * IMPORTANT: DO NOT HIDE THESE (Related videos area)\n"
    " * ================================================================ */\n"
    "\n"
    "/* #secondary - NEVER hide this, it contains related videos */\n"
    "/* ytd-watch-next-secondary-results-renderer - NEVER hide */\n"
    "/* #related - NEVER hide this */\n"
    "/* These are commented out to ensure they're NEVER targeted */\n";

/* VP9 Forcing Script - Performance Optimization */
static const char *YT_PERFORMANCE_SCRIPT =
    "(function() {\n"
    "  'use strict';\n"
    "\n"
    "  console.log('[YT AdBlock] Enforcing VP9 codec...');\n"
    "  \n"
    "  const originalIsTypeSupported = MediaSource.isTypeSupported;\n"
    "  \n"
    "  Object.defineProperty(MediaSource, 'isTypeSupported', {\n"
    "      value: function(type) {\n"
    "          // Always check blocked codecs first\n"
    "          if (type.includes('avc1')) return false; // disable H.264\n"
    "          if (type.includes('av01')) return false; // disable AV1\n"
    "          \n"
    "          // If it's VP9, we want to encourage it, but arguably we should "
    "only allow it if the browser claims support\n"
    "          // forcing true might break if the system actually lacks the "
    "decoder.\n"
    "          // However, user asked to force it. Let's assume they have it.\n"
    "          // Safer approach: Let original decide for VP9, but since we "
    "disabled others, it should be picked.\n"
    "          // BUT user said 'force VP9'. Let's stick to true for VP9 to be "
    "sure.\n"
    "          if (type.includes('vp9') || type.includes('vp09')) return "
    "true;\n"
    "\n"
    "          // IMPORTANT: Fallback to original for audio (mp4a, opus, etc) "
    "and other types\n"
    "          // The previous version returned false for everything else, "
    "breaking audio!\n"
    "          return originalIsTypeSupported.call(this, type);\n"
    "      }\n"
    "  });\n"
    "})();\n";

/**
 * yt_adblock_manager_new:
 */
YTAdblockManager *yt_adblock_manager_new(void) {
  YTAdblockManager *manager = g_new0(YTAdblockManager, 1);

  manager->enabled = TRUE;
  manager->ref_count = 1;

  /* Create the JavaScript interceptor script */
  manager->interceptor_script = webkit_user_script_new(
      YT_INTERCEPTOR_SCRIPT,
      WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,       /* Inject in all frames */
      WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, /* Before page scripts */
      YT_URL_PATTERNS,                             /* Only on YouTube */
      NULL                                         /* No blocklist */
  );

  if (manager->interceptor_script == NULL) {
    g_warning("[YT AdBlock] Failed to create user script");
    g_free(manager);
    return NULL;
  }

  /* Create the CSS stylesheet */
  manager->cosmetic_stylesheet = webkit_user_style_sheet_new(
      YT_COSMETIC_CSS, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
      WEBKIT_USER_STYLE_LEVEL_USER, /* User-level to override page styles */
      YT_URL_PATTERNS,              /* Only on YouTube */
      NULL                          /* No blocklist */
  );

  if (manager->cosmetic_stylesheet == NULL) {
    g_warning("[YT AdBlock] Failed to create user stylesheet");
    webkit_user_script_unref(manager->interceptor_script);
    g_free(manager);
    return NULL;
  }

  g_print("[YT AdBlock] Manager initialized successfully\n");

  return manager;
}

/**
 * yt_adblock_manager_free:
 */
void yt_adblock_manager_free(YTAdblockManager *manager) {
  if (manager == NULL)
    return;

  manager->ref_count--;
  if (manager->ref_count > 0)
    return;

  if (manager->interceptor_script != NULL) {
    webkit_user_script_unref(manager->interceptor_script);
  }

  if (manager->cosmetic_stylesheet != NULL) {
    webkit_user_style_sheet_unref(manager->cosmetic_stylesheet);
  }

  g_free(manager);

  g_print("[YT AdBlock] Manager freed\n");
}

/**
 * yt_adblock_manager_apply_to_webview:
 */
gboolean yt_adblock_manager_apply_to_webview(YTAdblockManager *manager,
                                             WebKitWebView *web_view) {
  if (manager == NULL || web_view == NULL) {
    g_warning("[YT AdBlock] Invalid parameters");
    return FALSE;
  }

  if (!manager->enabled) {
    g_print("[YT AdBlock] YouTube ad blocking is disabled, skipping "
            "injection\n");
    return TRUE;
  }

  /* Get the user content manager from the WebView */
  WebKitUserContentManager *content_manager =
      webkit_web_view_get_user_content_manager(web_view);

  if (content_manager == NULL) {
    g_warning("[YT AdBlock] Failed to get user content manager");
    return FALSE;
  }

  /* Add the JavaScript interceptor script */
  webkit_user_content_manager_add_script(content_manager,
                                         manager->interceptor_script);

  /* Create and add the performance script directly here to save struct
   * modification complexity */
  /* Since we don't store it in the struct, we create a temporary one or relies
   * on ref counting if stored. */
  /* Actually, let's keep it simple and just create it on the fly if we don't
   * want to change the struct definition private part too much? */
  /* No, better to add it to the struct properly. But wait, I cannot edit the
   * struct definition above in this replace call easily because it's far away.
   */
  /* Alternative: Just append the performance script code to the interceptor
   * script? */
  /* That's the cleanest way! Append YT_PERFORMANCE_SCRIPT to
   * YT_INTERCEPTOR_SCRIPT. */

  /* Retrying with APPENDING strategy to avoid struct changes which are messy
   * with replace_file_content on large files if not careful. */
  /* Wait, replace_file_content requires contiguous blocks. */

  /* Let's just create a temporary script here and add it. It's safe because
   * content manager takes a ref. */
  /* But webkit_user_script_new returns a ref'd object we own. We must unref it
   * after adding. */

  WebKitUserScript *perf_script = webkit_user_script_new(
      YT_PERFORMANCE_SCRIPT, WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
      WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, YT_URL_PATTERNS, NULL);

  if (perf_script) {
    webkit_user_content_manager_add_script(content_manager, perf_script);
    webkit_user_script_unref(perf_script); /* Manager took its ref */
    g_print("[YT AdBlock] Performance script injected\n");
  }

  /* Add the CSS stylesheet */
  webkit_user_content_manager_add_style_sheet(content_manager,
                                              manager->cosmetic_stylesheet);

  g_print("[YT AdBlock] Scripts and styles applied to WebView\n");

  return TRUE;
}

/**
 * yt_adblock_manager_set_enabled:
 */
void yt_adblock_manager_set_enabled(YTAdblockManager *manager,
                                    gboolean enabled) {
  if (manager == NULL)
    return;

  manager->enabled = enabled;
  g_print("[YT AdBlock] YouTube ad blocking %s\n",
          enabled ? "enabled" : "disabled");
}

/**
 * yt_adblock_manager_is_enabled:
 */
gboolean yt_adblock_manager_is_enabled(YTAdblockManager *manager) {
  if (manager == NULL)
    return FALSE;

  return manager->enabled;
}
