(() => {
  const SOURCE = "ADDC_AUTODARTS_PAGE_WEBSOCKET_HOOK";
  const TARGET = "ADDC_AUTODARTS_CONTENT";
  const SUBSCRIBE_HOST = "play.ws.autodarts.io";
  const SUBSCRIBE_PATH = "/ms/v0/subscribe";

  const emit = (payload) => {
    window.postMessage({
      source: SOURCE,
      target: TARGET,
      matchPageUrl: window.location.href,
      ...payload
    }, window.location.origin);
  };

  if (window.__addcAutodartsWebSocketHookInstalled) {
    emit({
      type: "PAGE_HOOK_INJECTED",
      method: "already_installed",
      detectedAt: new Date().toISOString()
    });
    replayHookState();
    return;
  }
  window.__addcAutodartsWebSocketHookInstalled = true;
  window.__addcAutodartsWebSocketHookState = window.__addcAutodartsWebSocketHookState || {
    capturedSockets: [],
    detectedCount: 0,
    mirroredSockets: 0,
    mirroredMessages: 0,
    mirrorAttachedAt: ""
  };

  const urlText = (value) => {
    if (typeof value === "string") return value;
    if (value instanceof URL) return value.href;
    return String(value || "");
  };

  const isAutodartsSubscribeUrl = (value) => {
    const text = urlText(value);
    return text.includes(SUBSCRIBE_HOST) && text.includes(SUBSCRIBE_PATH);
  };

  const hookedConstructors = [];

  function installConstructor(name) {
    const nativeConstructor = window[name];
    if (typeof nativeConstructor !== "function") return;

    function ADDCAutodartsWebSocket(url, protocols) {
      const socket = protocols === undefined
        ? new nativeConstructor(url)
        : new nativeConstructor(url, protocols);
      try {
        if (isAutodartsSubscribeUrl(url)) {
          const websocketUrl = urlText(url);
          const mirrorAttached = attachSocketMirror(socket);
          window.__addcAutodartsWebSocketHookState.detectedCount += 1;
          window.__addcAutodartsWebSocketHookState.capturedSockets.unshift({
            websocketUrl,
            matchPageUrl: window.location.href,
            detectedAt: new Date().toISOString(),
            constructorName: name,
            pageMirrorAttached: mirrorAttached,
            mirrorAttachedCount: window.__addcAutodartsWebSocketHookState.mirroredSockets || 0,
            mirrorAttachedAt: window.__addcAutodartsWebSocketHookState.mirrorAttachedAt || ""
          });
          window.__addcAutodartsWebSocketHookState.capturedSockets =
            window.__addcAutodartsWebSocketHookState.capturedSockets.slice(0, 4);
          const metadata = websocketUrlMetadata(websocketUrl);
          console.info("page hook websocket URL captured", metadata);
          emit({
            type: "WEBSOCKET_DETECTED",
            websocketUrl,
            websocketUrlLength: metadata.websocketUrlLength,
            websocketUrlHashPrefix: metadata.websocketUrlHashPrefix,
            constructorName: name,
            pageMirrorAttached: mirrorAttached,
            mirrorAttachedCount: window.__addcAutodartsWebSocketHookState.mirroredSockets || 0,
            detectedAt: new Date().toISOString()
          });
          emit({
            type: "WEBSOCKET_URL_CAPTURED",
            constructorName: name,
            detectedAt: new Date().toISOString()
          });
          console.info("Autodarts live connection detected");
        }
      } catch {
        // Keep Autodarts gameplay untouched if the helper cannot inspect the URL.
      }

      return socket;
    }

    ADDCAutodartsWebSocket.prototype = nativeConstructor.prototype;
    Object.setPrototypeOf(ADDCAutodartsWebSocket, nativeConstructor);

    for (const key of ["CONNECTING", "OPEN", "CLOSING", "CLOSED"]) {
      try {
        Object.defineProperty(ADDCAutodartsWebSocket, key, {
          configurable: false,
          enumerable: true,
          value: nativeConstructor[key]
        });
      } catch {}
    }

    try {
      Object.defineProperty(window, name, {
        configurable: true,
        enumerable: true,
        writable: true,
        value: ADDCAutodartsWebSocket
      });
      hookedConstructors.push(name);
    } catch {
      try {
        window[name] = ADDCAutodartsWebSocket;
        hookedConstructors.push(name);
      } catch {}
    }
  }

  installConstructor("WebSocket");
  installConstructor("MozWebSocket");

  if (hookedConstructors.length === 0) {
    emit({
      type: "PAGE_HOOK_ERROR",
      error: "No WebSocket constructor available to hook."
    });
    return;
  }

  emit({
    type: "WEBSOCKET_CONSTRUCTOR_HOOKED",
    hookedConstructors,
    detectedAt: new Date().toISOString()
  });
  emit({
    type: "PAGE_HOOK_INJECTED",
    method: "main_world",
    detectedAt: new Date().toISOString()
  });
  replayHookState();
  setTimeout(replayHookState, 100);
  setTimeout(replayHookState, 500);
  startVisibleScoreboardMirror();
  setTimeout(() => {
    if (window.__addcAutodartsWebSocketHookState.detectedCount > 0) return;
    if (!/play\.autodarts\.io/.test(window.location.host)) return;
    emit({
      type: "WEBSOCKET_RECONNECT_DETECTION",
      reason: "No Autodarts socket observed after hook; waiting for reconnect or page refresh.",
      detectedAt: new Date().toISOString()
    });
  }, 2500);

  function replayHookState() {
    emitCapturedSocket();
    if ((window.__addcAutodartsWebSocketHookState?.mirroredSockets || 0) > 0) {
      emit({
        type: "PAGE_MIRROR_ATTACHED",
        mirrorAttachedCount: window.__addcAutodartsWebSocketHookState.mirroredSockets,
        detectedAt: window.__addcAutodartsWebSocketHookState.mirrorAttachedAt || new Date().toISOString()
      });
    }
  }

  function emitCapturedSocket() {
    const captured = window.__addcAutodartsWebSocketHookState?.capturedSockets?.[0];
    if (!captured?.websocketUrl) return;
    const metadata = websocketUrlMetadata(captured.websocketUrl);
    emit({
      type: "WEBSOCKET_DETECTED",
      websocketUrl: captured.websocketUrl,
      websocketUrlLength: metadata.websocketUrlLength,
      websocketUrlHashPrefix: metadata.websocketUrlHashPrefix,
      constructorName: captured.constructorName || "WebSocket",
      pageMirrorAttached: captured.pageMirrorAttached === true || (window.__addcAutodartsWebSocketHookState?.mirroredSockets || 0) > 0,
      mirrorAttachedCount: captured.mirrorAttachedCount || window.__addcAutodartsWebSocketHookState?.mirroredSockets || 0,
      detectedAt: captured.detectedAt || new Date().toISOString()
    });
  }

  function attachSocketMirror(socket) {
    if (!socket) return false;
    if (socket.__addcMirrorAttached) return true;
    try {
      Object.defineProperty(socket, "__addcMirrorAttached", {
        configurable: true,
        enumerable: false,
        value: true
      });
    } catch {
      socket.__addcMirrorAttached = true;
    }

    try {
      if (!Number.isFinite(window.__addcAutodartsWebSocketHookState.mirroredSockets)) {
        window.__addcAutodartsWebSocketHookState.mirroredSockets = 0;
      }
      window.__addcAutodartsWebSocketHookState.mirroredSockets += 1;
      window.__addcAutodartsWebSocketHookState.mirrorAttachedAt = new Date().toISOString();
      attachSocketListener(socket, "open", () => emit({
        type: "WEBSOCKET_OPEN",
        detectedAt: new Date().toISOString()
      }));
      attachSocketListener(socket, "close", (event) => emit({
        type: "WEBSOCKET_CLOSE",
        closeCode: event.code,
        closeReasonLength: String(event.reason || "").length,
        detectedAt: new Date().toISOString()
      }));
      attachSocketListener(socket, "error", () => emit({
        type: "WEBSOCKET_ERROR",
        detectedAt: new Date().toISOString()
      }));
      attachSocketListener(socket, "message", (event) => {
        mirrorSocketMessage(event.data);
      });
      emit({
        type: "PAGE_MIRROR_ATTACHED",
        mirrorAttachedCount: window.__addcAutodartsWebSocketHookState.mirroredSockets,
        detectedAt: window.__addcAutodartsWebSocketHookState.mirrorAttachedAt
      });
      setTimeout(() => emit({
        type: "PAGE_MIRROR_ATTACHED",
        mirrorAttachedCount: window.__addcAutodartsWebSocketHookState.mirroredSockets,
        detectedAt: window.__addcAutodartsWebSocketHookState.mirrorAttachedAt
      }), 50);
      console.info("Autodarts page socket mirror attached");
      return true;
    } catch (error) {
      emit({
        type: "PAGE_MIRROR_ATTACH_FAILED",
        error: String(error?.message || error || "unknown").slice(0, 120),
        detectedAt: new Date().toISOString()
      });
      return false;
    }
  }

  function attachSocketListener(socket, eventName, listener) {
    if (typeof socket.addEventListener === "function") {
      socket.addEventListener(eventName, listener);
      return;
    }
    const property = `on${eventName}`;
    const previous = socket[property];
    socket[property] = function addcSocketListener(event) {
      try {
        listener.call(this, event);
      } finally {
        if (typeof previous === "function") {
          previous.call(this, event);
        }
      }
    };
  }

  function mirrorSocketMessage(data) {
    try {
      window.__addcAutodartsWebSocketHookState.mirroredMessages += 1;
      if (typeof data === "string") {
        emit({
          type: "WEBSOCKET_MESSAGE",
          frameType: "text",
          payloadText: data,
          byteLength: data.length,
          mirrorMessageCount: window.__addcAutodartsWebSocketHookState.mirroredMessages,
          payloadHashPrefix: safeHashPrefix(data),
          detectedAt: new Date().toISOString()
        });
        return;
      }

      if (data instanceof ArrayBuffer) {
        emitBinaryMessage(new Uint8Array(data), "arraybuffer");
        return;
      }

      if (data instanceof Blob) {
        data.arrayBuffer().then((buffer) => {
          emitBinaryMessage(new Uint8Array(buffer), "blob");
        }).catch(() => {
          emit({
            type: "WEBSOCKET_MESSAGE",
            frameType: "blob",
            byteLength: data.size || 0,
            mirrorMessageCount: window.__addcAutodartsWebSocketHookState.mirroredMessages,
            payloadHashPrefix: "",
            decodeError: "blob_read_failed",
            detectedAt: new Date().toISOString()
          });
        });
      }
    } catch {
      // Never disrupt Autodarts gameplay because diagnostics failed.
    }
  }

  function emitBinaryMessage(bytes, frameType) {
    let binary = "";
    for (let index = 0; index < bytes.length; index += 1) {
      binary += String.fromCharCode(bytes[index]);
    }
    emit({
      type: "WEBSOCKET_MESSAGE",
      frameType,
      payloadBase64: btoa(binary),
      byteLength: bytes.byteLength,
      mirrorMessageCount: window.__addcAutodartsWebSocketHookState.mirroredMessages,
      payloadHashPrefix: safeHashPrefix(binary),
      detectedAt: new Date().toISOString()
    });
  }

  function startVisibleScoreboardMirror() {
    let lastSignature = "";
    let lastForcedAt = 0;
    setInterval(() => {
      try {
        const matchId = matchIdFromLocation();
        if (!matchId) return;
        const scores = visibleScoreCandidates();
        if (scores.length < 2) return;

        const scoreCards = scores
          .slice(0, 2)
          .sort((left, right) => left.x - right.x);
        const ordered = scoreCards.map(candidate => candidate.value);
        const legs = visibleLegCountersForScores(scoreCards);
        const visibleStats = visibleStatsForScores(scoreCards);
        const signature = `${matchId}:${ordered[0]}:${ordered[1]}:${legs[0] ?? "-"}:${legs[1] ?? "-"}:${visibleStats.map(stat => `${stat.average ?? "-"}:${stat.highest_visit ?? "-"}`).join("|")}`;
        const now = Date.now();
        if (signature === lastSignature && now - lastForcedAt < 5000) return;
        lastSignature = signature;
        lastForcedAt = now;

        const payload = JSON.stringify({
          channel: "addc.page.scoreboard",
          topic: `${matchId}.state`,
          data: {
            id: matchId,
            variant: "X01",
            gameScores: ordered,
            scores: [
              { legs: legs[0] ?? 0, ...visibleStats[0] },
              { legs: legs[1] ?? 0, ...visibleStats[1] }
            ],
            stats: visibleStats.map((stat) => ({ matchStats: stat })),
            legs: {
              home: legs[0] ?? 0,
              away: legs[1] ?? 0
            },
            home_legs: legs[0] ?? 0,
            away_legs: legs[1] ?? 0,
            gameFinished: false,
            source: "addc_visible_scoreboard",
            capturedAt: new Date().toISOString()
          }
        });

        window.__addcAutodartsWebSocketHookState.mirroredMessages += 1;
        emit({
          type: "WEBSOCKET_MESSAGE",
          frameType: "page-scoreboard",
          payloadText: payload,
          byteLength: payload.length,
          mirrorMessageCount: window.__addcAutodartsWebSocketHookState.mirroredMessages,
          payloadHashPrefix: safeHashPrefix(payload),
          detectedAt: new Date().toISOString()
        });
      } catch {
        // Keep Autodarts gameplay untouched if scoreboard scraping fails.
      }
    }, 1000);
  }

  function matchIdFromLocation() {
    const match = window.location.pathname.match(/\/matches\/([^/?#]+)/i);
    return match?.[1] || "";
  }

  function visibleScoreCandidates() {
    return Array.from(document.querySelectorAll("body *"))
      .map(element => {
        const text = (element.textContent || "").trim();
        if (!/^\d{1,3}$/.test(text)) return null;
        const value = Number(text);
        if (!Number.isInteger(value) || value < 0 || value > 501) return null;
        const rect = element.getBoundingClientRect();
        if (rect.width <= 20 || rect.height <= 20) return null;
        if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) return null;
        const style = window.getComputedStyle(element);
        const fontSize = Number.parseFloat(style.fontSize || "0");
        if (fontSize < 44 && rect.height < 54) return null;
        return {
          value,
          element,
          x: rect.left + rect.width / 2,
          y: rect.top + rect.height / 2,
          rank: fontSize * 3 + rect.height + rect.width / 10
        };
      })
      .filter(Boolean)
      .sort((left, right) => right.rank - left.rank);
  }

  function visibleLegCountersForScores(scoreCards) {
    return scoreCards.map((scoreCard) => {
      const candidates = Array.from(document.querySelectorAll("body *"))
        .map(element => {
          if (element === scoreCard.element) return null;
          const text = (element.textContent || "").trim();
          if (!/^\d$/.test(text)) return null;
          const value = Number(text);
          if (!Number.isInteger(value) || value < 0 || value > 9) return null;
          const rect = element.getBoundingClientRect();
          if (rect.width < 8 || rect.height < 8 || rect.width > 90 || rect.height > 90) return null;
          if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) return null;

          const centerX = rect.left + rect.width / 2;
          const centerY = rect.top + rect.height / 2;
          const dx = Math.abs(centerX - scoreCard.x);
          const dy = Math.abs(centerY - scoreCard.y);
          if (dx > 260 || dy > 220) return null;

          const style = window.getComputedStyle(element);
          const fontSize = Number.parseFloat(style.fontSize || "0");
          if (fontSize > 44) return null;

          const greenHint = colorLooksGreen(style.color) || colorLooksGreen(style.backgroundColor);
          const nearPlayerBand = centerY > scoreCard.y - 10 && centerY < scoreCard.y + 180;
          const rank = (greenHint ? 1000 : 0) - dx - (dy * 0.8) + fontSize;
          if (!nearPlayerBand && !greenHint) return null;
          return { value, rank };
        })
        .filter(Boolean)
        .sort((left, right) => right.rank - left.rank);

      return candidates[0]?.value ?? 0;
    });
  }

  function visibleStatsForScores(scoreCards) {
    return scoreCards.map((scoreCard) => {
      const nearbyText = nearbyScoreCardText(scoreCard);
      const average = averageFromText(nearbyText);
      const highest = highestFromText(nearbyText);
      return {
        average: average ?? 0,
        highest_visit: highest ?? 0,
        best_checkout: 0,
        dartsThrown: 0,
        checkouts: 0,
        checkoutsHit: 0,
        checkoutPercent: 0,
        plus60: 0,
        plus100: 0,
        plus140: 0,
        plus170: 0,
        total180: 0
      };
    });
  }

  function nearbyScoreCardText(scoreCard) {
    try {
      const chunks = [];
      let current = scoreCard.element;
      for (let depth = 0; current && depth < 5; depth += 1, current = current.parentElement) {
        const text = (current.innerText || current.textContent || "").trim();
        if (text && text.length < 900) chunks.push(text);
      }

      const nearby = Array.from(document.querySelectorAll("body *")).slice(0, 1200)
        .filter((element) => element !== scoreCard.element)
        .map((element) => {
          const text = (element.innerText || element.textContent || "").trim();
          if (!text || text.length > 220) return null;
          const rect = element.getBoundingClientRect();
          if (rect.bottom < 0 || rect.top > window.innerHeight || rect.right < 0 || rect.left > window.innerWidth) return null;
          const centerX = rect.left + rect.width / 2;
          const centerY = rect.top + rect.height / 2;
          const dx = Math.abs(centerX - scoreCard.x);
          const dy = Math.abs(centerY - scoreCard.y);
          if (dx > 360 || dy > 260) return null;
          if (!/[Øø]|avg|average|hi|high|#\d/i.test(text)) return null;
          return { text, rank: dx + dy };
        })
        .filter(Boolean)
        .sort((left, right) => left.rank - right.rank)
        .slice(0, 8)
        .map((item) => item.text);

      return chunks.concat(nearby).join("\n");
    } catch {
      return "";
    }
  }

  function averageFromText(text) {
    const value = firstNumber(text, [
      /[Øø]\s*(\d{1,3}(?:[.,]\d{1,2})?)/i,
      /\bavg(?:erage)?\s*[:=]?\s*(\d{1,3}(?:[.,]\d{1,2})?)/i
    ]);
    return value !== null && value >= 0 && value <= 180 ? value : null;
  }

  function highestFromText(text) {
    const value = firstInteger(text, [
      /\b(?:hi|high|highest)\s*[:=]?\s*(\d{1,3})\b/i,
      /\b#\s*(\d{1,3})\b/i
    ]);
    return value !== null && value >= 0 && value <= 180 ? value : null;
  }

  function firstNumber(text, patterns) {
    for (const pattern of patterns) {
      const match = String(text || "").match(pattern);
      if (!match) continue;
      const value = Number(String(match[1]).replace(",", "."));
      if (Number.isFinite(value)) return value;
    }
    return null;
  }

  function firstInteger(text, patterns) {
    const value = firstNumber(text, patterns);
    return value === null ? null : Math.round(value);
  }

  function colorLooksGreen(value) {
    const match = String(value || "").match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
    if (!match) return false;
    const red = Number(match[1]);
    const green = Number(match[2]);
    const blue = Number(match[3]);
    return green > red + 25 && green > blue + 20;
  }

  function websocketUrlMetadata(value) {
    const text = urlText(value);
    return {
      websocketUrlPresent: Boolean(text),
      websocketUrlLength: text.length,
      websocketUrlHashPrefix: safeHashPrefix(text)
    };
  }

  function safeHashPrefix(value) {
    let hash = 2166136261;
    for (let index = 0; index < value.length; index += 1) {
      hash ^= value.charCodeAt(index);
      hash = Math.imul(hash, 16777619);
    }
    return (hash >>> 0).toString(16).padStart(8, "0").slice(0, 8);
  }
})();
