// RBXAim Content Script
// Applies the selected custom cursor to regular browser pages and matching frames.

(() => {
  'use strict';

  const INSTANCE_KEY = '__rbxaimCursorControllerV2';
  const STYLE_ID = 'rbxaim-custom-cursor-style';
  const SHADOW_STYLE_ATTR = 'data-rbxaim-cursor-style';
  const CURSOR_SIZE = 48;
  const DEFAULT_STYLE = 'none';
  const DEFAULT_OPACITY = 92;
  const STORAGE_KEYS = ['rbxaim_crosshair', 'rbxaim_opacity'];

  if (globalThis[INSTANCE_KEY]) {
    globalThis[INSTANCE_KEY].refresh();
    return;
  }

  let documentStyle = null;
  let cursorValue = '';
  let updateVersion = 0;
  let updateQueued = false;
  const shadowStyles = new Set();
  const observedRoots = new WeakSet();
  const rootObservers = new Set();

  function cursorCss(forShadowRoot = false) {
    if (!cursorValue) return '';
    const priorityRoot = forShadowRoot
      ? ':host(:not(#rbxaim-cursor-priority-a):not(#rbxaim-cursor-priority-b):not(#rbxaim-cursor-priority-c))'
      : ':root:not(#rbxaim-cursor-priority-a):not(#rbxaim-cursor-priority-b):not(#rbxaim-cursor-priority-c)';
    const selector = `${priorityRoot}, ${priorityRoot} *, ${priorityRoot} *::before, ${priorityRoot} *::after`;

    return `${selector} { cursor: ${cursorValue} !important; }`;
  }

  function ensureDocumentStyle() {
    if (!documentStyle || !documentStyle.isConnected) {
      documentStyle = document.getElementById(STYLE_ID);
    }

    if (!documentStyle) {
      documentStyle = document.createElement('style');
      documentStyle.id = STYLE_ID;
      (document.head || document.documentElement).appendChild(documentStyle);
    } else if (documentStyle.parentNode === document.head && documentStyle.nextSibling) {
      document.head.appendChild(documentStyle);
    }

    const css = cursorCss(false);
    if (documentStyle.textContent !== css) documentStyle.textContent = css;
    return documentStyle;
  }

  function ensureShadowStyle(root) {
    let style = root.querySelector(`style[${SHADOW_STYLE_ATTR}]`);

    if (!style) {
      style = document.createElement('style');
      style.setAttribute(SHADOW_STYLE_ATTR, '');
      root.appendChild(style);
      shadowStyles.add(style);
    } else if (style.nextSibling) {
      root.appendChild(style);
    }

    const css = cursorCss(true);
    if (style.textContent !== css) style.textContent = css;
    return style;
  }

  function observeRoot(root) {
    if (!root || observedRoots.has(root)) return;

    const observer = new MutationObserver((mutations) => {
      if (root === document || root === document.documentElement) ensureDocumentStyle();
      else if (root instanceof ShadowRoot) ensureShadowStyle(root);

      mutations.forEach((mutation) => {
        mutation.addedNodes.forEach(scanForShadowRoots);
      });
    });

    observer.observe(root, { childList: true, subtree: true });
    observedRoots.add(root);
    rootObservers.add(observer);
  }

  function registerShadowRoot(root) {
    if (!root) return;
    ensureShadowStyle(root);
    observeRoot(root);
    scanForShadowRoots(root);
  }

  function scanForShadowRoots(node) {
    if (!node) return;

    if (node instanceof Element && node.shadowRoot) registerShadowRoot(node.shadowRoot);

    if (typeof node.querySelectorAll === 'function') {
      node.querySelectorAll('*').forEach((element) => {
        if (element.shadowRoot) registerShadowRoot(element.shadowRoot);
      });
    }
  }

  function applyCursorValue(nextCursorValue) {
    cursorValue = nextCursorValue;
    ensureDocumentStyle();

    shadowStyles.forEach((style) => {
      if (!style.isConnected) {
        shadowStyles.delete(style);
        return;
      }

      const css = cursorCss(true);
      if (style.textContent !== css) style.textContent = css;
    });

    scanForShadowRoots(document);
  }

  function sanitizeStyleName(value) {
    const fileName = typeof value === 'string' ? value.trim() : '';
    if (fileName === 'none') return 'none';
    return /^[a-z0-9][a-z0-9._-]*\.png$/i.test(fileName) ? fileName : DEFAULT_STYLE;
  }

  function normalizeOpacity(value) {
    const numericValue = Number(value);
    if (!Number.isFinite(numericValue)) return DEFAULT_OPACITY / 100;
    return Math.min(100, Math.max(10, numericValue)) / 100;
  }

  function loadImage(url) {
    return new Promise((resolve, reject) => {
      const image = new Image();
      image.decoding = 'async';
      image.onload = () => resolve(image);
      image.onerror = () => reject(new Error(`Unable to load cursor asset: ${url}`));
      image.src = url;
    });
  }

  function renderCursor(styleName, opacity, version) {
    if (styleName === 'none') {
      applyCursorValue('');
      return;
    }

    const imageUrl = chrome.runtime.getURL(`icons/${styleName}`);

    loadImage(imageUrl)
      .then((image) => {
        if (version !== updateVersion) return;

        const canvas = document.createElement('canvas');
        canvas.width = CURSOR_SIZE;
        canvas.height = CURSOR_SIZE;

        const context = canvas.getContext('2d');
        if (!context) throw new Error('Canvas 2D context is unavailable');

        const sourceWidth = image.naturalWidth || CURSOR_SIZE;
        const sourceHeight = image.naturalHeight || CURSOR_SIZE;
        const scale = Math.min(CURSOR_SIZE / sourceWidth, CURSOR_SIZE / sourceHeight);
        const drawWidth = Math.max(1, Math.round(sourceWidth * scale));
        const drawHeight = Math.max(1, Math.round(sourceHeight * scale));
        const drawX = Math.round((CURSOR_SIZE - drawWidth) / 2);
        const drawY = Math.round((CURSOR_SIZE - drawHeight) / 2);

        context.clearRect(0, 0, CURSOR_SIZE, CURSOR_SIZE);
        context.globalAlpha = opacity;
        context.imageSmoothingEnabled = true;
        context.imageSmoothingQuality = 'high';
        context.drawImage(image, drawX, drawY, drawWidth, drawHeight);

        const dataUri = canvas.toDataURL('image/png');
        if (version !== updateVersion) return;

        const hotspot = Math.floor(CURSOR_SIZE / 2);
        applyCursorValue(`url("${dataUri}") ${hotspot} ${hotspot}, auto`);
      })
      .catch((error) => {
        if (version === updateVersion) console.warn('[RBXAim] Cursor update failed:', error);
      });
  }

  function updateCrosshair() {
    if (!document.documentElement) return;
    const version = ++updateVersion;

    try {
      chrome.storage.local.get(STORAGE_KEYS, (data) => {
        if (chrome.runtime.lastError) {
          console.warn('[RBXAim] Unable to read cursor settings:', chrome.runtime.lastError.message);
          return;
        }

        if (version !== updateVersion) return;
        const styleName = sanitizeStyleName(data?.rbxaim_crosshair);
        const opacity = normalizeOpacity(data?.rbxaim_opacity);
        renderCursor(styleName, opacity, version);
      });
    } catch (error) {
      console.warn('[RBXAim] Extension context is unavailable:', error);
    }
  }

  function scheduleUpdate() {
    if (updateQueued) return;
    updateQueued = true;
    queueMicrotask(() => {
      updateQueued = false;
      updateCrosshair();
    });
  }

  function start() {
    if (!document.documentElement) return;
    ensureDocumentStyle();
    observeRoot(document.documentElement);
    scanForShadowRoots(document);
    updateCrosshair();
  }

  globalThis[INSTANCE_KEY] = { refresh: scheduleUpdate };

  if (document.documentElement) {
    start();
  } else {
    const bootstrapObserver = new MutationObserver(() => {
      if (!document.documentElement) return;
      bootstrapObserver.disconnect();
      start();
    });
    bootstrapObserver.observe(document, { childList: true });
  }

  chrome.storage.onChanged.addListener((changes, areaName) => {
    if (areaName !== 'local') return;
    if (changes.rbxaim_crosshair || changes.rbxaim_opacity) scheduleUpdate();
  });

  chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message?.action !== 'UPDATE_CROSSHAIR') return;
    scheduleUpdate();
    sendResponse({ ok: true });
  });
})();
