(() => {
  "use strict";

  const REPORT_CLIENT_PHASE = "45D.3";
  const POLL_MS = 1000;
  const RESOLVED_CARD_VISIBLE_MS = 6000;
  const REPORT_ENDPOINT = "/api/scoreboard/inaccessible-reports";
  const DECISION_ENDPOINT = reportId =>
    `/api/scoreboard/inaccessible-reports/${encodeURIComponent(reportId)}/decision`;

  // Signal to the large scoreboard renderer that this isolated companion owns
  // the report tray and report popups. This prevents layout refreshes elsewhere
  // on the scoreboard from hiding or replacing the review controls.
  window.QRTagScoreboardReportsCompanion = true;
  window.QRTagScoreboardReportsPhase = REPORT_CLIENT_PHASE;

  const knownStatuses = new Map();
  const resolvedFirstSeenAt = new Map();
  let primed = false;
  let pollInFlight = false;
  let latestReports = [];
  let lastFetchSource = "starting";

  function updateRuntimeBadge(state, detail = "") {
    const badge = document.getElementById("scoreboardRuntimeBadge");
    const label = document.getElementById("scoreboardReportLinkState");
    if (!badge || !label) return;
    badge.dataset.reportState = state;
    label.textContent = detail || state;
  }

  async function verifyRuntime() {
    try {
      const response = await fetch(`/api/scoreboard/runtime?ts=${Date.now()}`, {
        cache: "no-store",
        headers: { "Cache-Control": "no-cache", "Pragma": "no-cache" }
      });
      if (!response.ok) throw new Error(`runtime ${response.status}`);
      const runtime = await response.json();
      const phase = String(runtime?.phase || "unknown");
      updateRuntimeBadge("online", `Report link online • ${phase}`);
    } catch {
      updateRuntimeBadge("offline", "Report link offline");
    }
  }

  function escapeHtml(value) {
    return String(value ?? "")
      .replaceAll("&", "&amp;")
      .replaceAll("<", "&lt;")
      .replaceAll(">", "&gt;")
      .replaceAll('"', "&quot;")
      .replaceAll("'", "&#039;");
  }

  function ensureTray() {
    let tray = document.getElementById("scoreboardInaccessibleReportTray");
    if (!tray) {
      tray = document.createElement("aside");
      tray.id = "scoreboardInaccessibleReportTray";
      tray.className = "scoreboard-inaccessible-report-tray hidden";
      tray.setAttribute("aria-live", "assertive");
      tray.setAttribute("aria-label", "Objective reports");
      tray.innerHTML = `
        <div class="scoreboard-inaccessible-report-header">
          <span>Objective Reports</span>
          <span id="scoreboardInaccessibleReportCount" class="scoreboard-inaccessible-report-count">0</span>
        </div>
        <div id="scoreboardInaccessibleReportList" class="scoreboard-inaccessible-report-list"></div>
      `;
      document.body.appendChild(tray);
    }

    tray.dataset.reportClientVersion = REPORT_CLIENT_PHASE;
    return tray;
  }

  function normalizeReport(report) {
    return {
      ...report,
      reportId: String(report?.reportId || report?.actionId || "").trim(),
      reporterHandle: String(report?.reporterHandle || "Player").trim() || "Player",
      objectiveName: String(report?.objectiveName || "Objective").trim() || "Objective",
      status: String(report?.status || "pending").trim().toLowerCase(),
      requiresHostDecision: report?.requiresHostDecision === true ||
        ["true", "1", "yes", "on"].includes(String(report?.requiresHostDecision || "").toLowerCase())
    };
  }

  function deriveReportsFromEvents(events) {
    const reports = new Map();
    const ordered = Array.isArray(events)
      ? events.slice().sort((a, b) => new Date(a?.createdAtUtc || 0) - new Date(b?.createdAtUtc || 0))
      : [];

    const customValue = (event, key) => {
      const bag = event?.customData || event?.customFields || {};
      const match = Object.keys(bag).find(candidate => candidate.toLowerCase() === key.toLowerCase());
      return match ? bag[match] : undefined;
    };

    ordered.forEach(event => {
      const type = String(event?.type || "").toLowerCase();
      const reportId = String(customValue(event, "reportId") || event?.reportId || "").trim();
      if (!reportId) return;

      if (type === "inaccessible_report_received") {
        reports.set(reportId, normalizeReport({
          reportId,
          createdAtUtc: event?.createdAtUtc,
          reporterPlayerId: event?.playerId,
          reporterHandle: event?.playerHandle,
          objectiveId: event?.objectiveId,
          objectiveName: event?.objectiveName,
          objectiveType: customValue(event, "objectiveType") || "",
          status: "pending",
          requiresHostDecision: customValue(event, "requiresHostDecision"),
          message: "Waiting for the host to accept or decline this relocation request."
        }));
        return;
      }

      const existing = reports.get(reportId);
      if (!existing) return;
      const outcome = {
        inaccessible_objective_relocated: "accepted",
        inaccessible_report_declined: "declined",
        inaccessible_report_rejected: "rejected",
        ghost_inaccessible_retargeted: "auto_resolved"
      }[type];
      if (!outcome) return;

      reports.set(reportId, normalizeReport({
        ...existing,
        status: outcome,
        message: event?.message || existing.message,
        resolvedAtUtc: event?.createdAtUtc
      }));
    });

    return Array.from(reports.values());
  }

  function mergeReports(...sources) {
    const merged = new Map();
    sources.forEach(source => {
      if (!Array.isArray(source)) return;
      source.forEach(raw => {
        const report = normalizeReport(raw);
        if (!report.reportId) return;
        merged.set(report.reportId, { ...(merged.get(report.reportId) || {}), ...report });
      });
    });
    return Array.from(merged.values());
  }

  async function fetchReports() {
    let directReports = [];
    let activeReports = [];
    let directOk = false;
    let activeOk = false;

    try {
      const response = await fetch(`${REPORT_ENDPOINT}?ts=${Date.now()}`, {
        cache: "no-store",
        headers: { "Cache-Control": "no-cache", "Pragma": "no-cache" }
      });
      if (response.ok) {
        const payload = await response.json();
        if (Array.isArray(payload?.reports)) {
          directReports = payload.reports;
          directOk = true;
        }
      }
    } catch {
      // The active-game response below remains a local-only recovery source.
    }

    try {
      const response = await fetch(`/api/games/active?ts=${Date.now()}`, {
        cache: "no-store",
        headers: { "Cache-Control": "no-cache", "Pragma": "no-cache" }
      });
      if (response.ok) {
        const game = await response.json();
        activeReports = mergeReports(
          deriveReportsFromEvents(game?.events),
          Array.isArray(game?.inaccessibleReports) ? game.inaccessibleReports : []
        );
        activeOk = true;
      }
    } catch {
      // Direct reports can still keep the review UI alive.
    }

    if (!directOk && !activeOk) {
      lastFetchSource = "offline";
      return [];
    }

    lastFetchSource = directOk && activeOk
      ? "direct+active-game"
      : directOk ? "direct" : "active-game";
    return mergeReports(activeReports, directReports);
  }

  function isPendingReport(report) {
    return String(report?.status || "pending").toLowerCase() === "pending";
  }

  function resolvedTimestampMs(report, nowMs) {
    const reportedResolvedAt = Date.parse(String(report?.resolvedAtUtc || report?.updatedAtUtc || ""));
    if (Number.isFinite(reportedResolvedAt)) return reportedResolvedAt;

    const reportId = String(report?.reportId || "");
    if (!reportId) return nowMs;
    if (!resolvedFirstSeenAt.has(reportId)) resolvedFirstSeenAt.set(reportId, nowMs);
    return resolvedFirstSeenAt.get(reportId);
  }

  function shouldRenderReport(report, nowMs) {
    const reportId = String(report?.reportId || "");
    if (isPendingReport(report)) {
      if (reportId) resolvedFirstSeenAt.delete(reportId);
      return true;
    }

    return nowMs - resolvedTimestampMs(report, nowMs) < RESOLVED_CARD_VISIBLE_MS;
  }

  function statusLabel(status) {
    return {
      auto_resolved: "Moved",
      cooldown_rejected: "Cooldown",
      rejected: "Rejected",
      accepted: "Accepted",
      declined: "Declined",
      pending: "Pending"
    }[status] || status || "Pending";
  }

  function renderCard(report) {
    const canDecide = report.status === "pending" && report.requiresHostDecision;
    const detail = report.message || report.resolution || `${report.objectiveName} was reported inaccessible.`;
    return `
      <article class="scoreboard-inaccessible-report-card status-${escapeHtml(report.status)}" data-report-card-id="${escapeHtml(report.reportId)}">
        <div class="scoreboard-inaccessible-report-title">
          <strong>${escapeHtml(report.reporterHandle)}</strong>
          <span>${escapeHtml(statusLabel(report.status))}</span>
        </div>
        <div class="scoreboard-inaccessible-report-objective">${escapeHtml(report.objectiveName)}</div>
        <div class="scoreboard-inaccessible-report-message">${escapeHtml(detail)}</div>
        ${canDecide ? `
          <div class="scoreboard-inaccessible-report-actions">
            <button type="button" class="scoreboard-report-accept" data-report-id="${escapeHtml(report.reportId)}" data-report-decision="accept">Relocate</button>
            <button type="button" class="scoreboard-report-decline" data-report-id="${escapeHtml(report.reportId)}" data-report-decision="decline">Decline</button>
          </div>
        ` : ""}
      </article>
    `;
  }

  function render(reports) {
    const tray = ensureTray();
    const list = tray.querySelector("#scoreboardInaccessibleReportList");
    const count = tray.querySelector("#scoreboardInaccessibleReportCount");
    if (!list || !count) return;

    const nowMs = Date.now();
    const reportIds = new Set(reports.map(report => String(report?.reportId || "")).filter(Boolean));
    for (const reportId of resolvedFirstSeenAt.keys()) {
      if (!reportIds.has(reportId)) resolvedFirstSeenAt.delete(reportId);
    }

    const ordered = reports.slice().sort((a, b) => {
      const pendingDelta = Number(isPendingReport(b)) - Number(isPendingReport(a));
      return pendingDelta || new Date(b.createdAtUtc || 0) - new Date(a.createdAtUtc || 0);
    });
    const renderable = ordered.filter(report => shouldRenderReport(report, nowMs));
    const visible = renderable.slice(0, 4);
    const pendingCount = renderable.filter(isPendingReport).length;

    if (visible.length === 0) {
      tray.classList.add("hidden");
      tray.style.removeProperty("display");
      list.innerHTML = "";
      count.textContent = "0";
      count.classList.remove("pending");
      return;
    }

    tray.classList.remove("hidden");
    tray.style.setProperty("display", "block", "important");
    count.textContent = String(pendingCount || visible.length);
    count.classList.toggle("pending", pendingCount > 0);
    list.innerHTML = visible.map(renderCard).join("");
  }

  function showPopup(message, type = "inaccessible_report_received") {
    if (typeof window.showScoreboardGameNotification === "function") {
      window.showScoreboardGameNotification(message, type);
      return;
    }

    let layer = document.getElementById("scoreboardNotificationLayer");
    if (!layer) {
      layer = document.createElement("div");
      layer.id = "scoreboardNotificationLayer";
      layer.className = "scoreboard-notification-layer";
      document.body.appendChild(layer);
    }

    const notification = document.createElement("div");
    notification.className = "scoreboard-game-notification objective";
    notification.innerHTML = `
      <div class="scoreboard-game-notification-kicker">Host Review Needed</div>
      <div class="scoreboard-game-notification-message">${escapeHtml(message)}</div>
    `;
    layer.appendChild(notification);
    window.setTimeout(() => {
      notification.classList.add("leaving");
      window.setTimeout(() => notification.remove(), 450);
    }, 10000);
  }

  function processNotifications(reports) {
    const ordered = reports.slice().sort((a, b) => new Date(a.createdAtUtc || 0) - new Date(b.createdAtUtc || 0));
    if (!primed) {
      ordered.forEach(report => knownStatuses.set(report.reportId, report.status));
      primed = true;
      const pending = ordered.filter(report => report.status === "pending");
      if (pending.length > 0) {
        const latest = pending[pending.length - 1];
        showPopup(`${latest.reporterHandle} reported ${latest.objectiveName} inaccessible — host review needed`);
      }
      return;
    }

    ordered.forEach(report => {
      const previous = knownStatuses.get(report.reportId);
      knownStatuses.set(report.reportId, report.status);
      if (previous === report.status) return;

      if (!previous && report.status === "pending") {
        showPopup(`${report.reporterHandle} reported ${report.objectiveName} inaccessible — host review needed`);
      } else if (previous === "pending" && report.status === "accepted") {
        showPopup(`${report.objectiveName} relocation approved`, "inaccessible_objective_relocated");
      } else if (previous === "pending" && report.status === "declined") {
        showPopup(`${report.objectiveName} relocation declined`, "inaccessible_report_declined");
      } else if (previous === "pending" && report.status === "rejected") {
        showPopup(report.message || `${report.objectiveName} report rejected`, "inaccessible_report_rejected");
      }
    });
  }

  async function poll() {
    if (pollInFlight) return;
    pollInFlight = true;
    try {
      latestReports = mergeReports(await fetchReports());
      render(latestReports);
      processNotifications(latestReports);
      if (lastFetchSource === "offline") {
        updateRuntimeBadge("offline", "Report link offline");
      } else {
        const pendingCount = latestReports.filter(report => report.status === "pending").length;
        updateRuntimeBadge(
          pendingCount > 0 ? "pending" : "online",
          pendingCount > 0
            ? `Report link online • ${pendingCount} pending`
            : `Report link online • ${REPORT_CLIENT_PHASE}`
        );
      }
    } finally {
      pollInFlight = false;
    }
  }

  async function submitDecision(reportId, decision, button) {
    if (!reportId || !decision || button?.disabled) return;
    const card = button.closest(".scoreboard-inaccessible-report-card");
    const originalText = button.textContent;
    card?.querySelectorAll("button").forEach(candidate => { candidate.disabled = true; });
    button.textContent = decision === "accept" ? "Moving..." : "Declining...";

    try {
      const response = await fetch(DECISION_ENDPOINT(reportId), {
        method: "POST",
        cache: "no-store",
        headers: { "Content-Type": "application/json", "Cache-Control": "no-cache" },
        body: JSON.stringify({ decision })
      });
      const payload = await response.json().catch(() => ({}));
      if (!response.ok) throw new Error(payload.message || payload.error || "Could not process report.");
      await poll();
    } catch (error) {
      card?.querySelectorAll("button").forEach(candidate => { candidate.disabled = false; });
      button.textContent = originalText;
      const message = card?.querySelector(".scoreboard-inaccessible-report-message");
      if (message) message.textContent = error?.message || "Could not process report.";
    }
  }

  function start() {
    updateRuntimeBadge("checking", "Report link checking…");
    verifyRuntime();
    const tray = ensureTray();
    tray.addEventListener("click", event => {
      const button = event.target?.closest?.("[data-report-decision]");
      if (!button) return;
      submitDecision(
        button.getAttribute("data-report-id") || "",
        button.getAttribute("data-report-decision") || "",
        button
      );
    });
    poll();
    window.setInterval(poll, POLL_MS);
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", start, { once: true });
  } else {
    start();
  }
})();
