import {
  hostCore
} from "./chunk-KL66PICH.js";

// src/plugins/ftx-family.ts
var bridge = window;
function formatBarTime(timestampMs) {
  if (!timestampMs) return "--:--:--";
  return new Date(timestampMs).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit" });
}
function finiteNumber(value) {
  const number = typeof value === "number" ? value : Number(value);
  return Number.isFinite(number) ? number : null;
}
function isAlphaNumeric(value) {
  return value !== void 0 && /[A-Za-z0-9]/.test(value);
}
function isGrid(value) {
  const normalized = value.trim().toUpperCase();
  return /^[A-R]{2}\d{2}(?:[A-X]{2})?$/.test(normalized) && normalized !== "RR73" && normalized !== "73" && normalized !== "RR";
}
function escapeFtxHtml(input) {
  return input.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
}
function extractFtxGrids(message) {
  return [...new Set(message.toUpperCase().split(/[^A-Z0-9]+/).filter(isGrid))];
}
function tokenize(message) {
  return message.toUpperCase().split(/[^A-Z0-9/]+/).filter(Boolean);
}
function isCallsign(token) {
  return token !== void 0 && token.length >= 3 && token.length <= 12 && !["CQ", "DE", "QRZ", "DX"].includes(token) && !isGrid(token) && /^[A-Z0-9/]{1,5}\d[A-Z0-9/]{1,6}$/.test(token);
}
function extractFtxLocatorDetails(message) {
  const tokens = tokenize(message);
  const grids = extractFtxGrids(message);
  const gridIndex = tokens.findIndex(isGrid);
  const callsigns = tokens.slice(0, gridIndex < 0 ? tokens.length : gridIndex).filter(isCallsign);
  const directed = callsigns.length >= 2 && !["CQ", "DE", "QRZ"].includes(tokens[0] ?? "");
  const source = directed ? callsigns[1] ?? null : callsigns[0] ?? null;
  const target = directed ? callsigns[0] ?? null : null;
  return grids.map((grid) => ({ grid, station: source, source, target }));
}
function extractFtxCallsign(message) {
  return extractFtxLocatorDetails(message)[0]?.station ?? tokenize(message).find(isCallsign) ?? null;
}
function renderFtxMessage(message) {
  let html = "";
  let index = 0;
  while (index < message.length) {
    if (!isAlphaNumeric(message[index])) {
      html += escapeFtxHtml(message[index] ?? "");
      index += 1;
      continue;
    }
    let end = index + 1;
    while (end < message.length && isAlphaNumeric(message[end])) end += 1;
    const token = message.slice(index, end);
    const grid = token.toUpperCase();
    html += isGrid(grid) ? `<span class="ft8-locator" data-locator-grid="${grid}" role="button" tabindex="0" aria-label="Show locator ${grid} on map">${grid}</span>` : escapeFtxHtml(token);
    index = end;
  }
  return html;
}
function installFtxCompatibilityHelpers() {
  bridge.renderFt8Message = renderFtxMessage;
  bridge.ft8EscapeHtml = escapeFtxHtml;
  bridge.ft8ExtractLocatorDetails = extractFtxLocatorDetails;
  bridge.ft8ExtractAllGrids = extractFtxGrids;
  bridge.ft8ExtractLikelyCallsign = extractFtxCallsign;
}
function initializeFt8FamilyBar() {
  const labels = { ft8: "FT8", ft4: "FT4", ft2: "FT2" };
  const builders = {};
  const dismissed = { ft8: 0, ft4: 0, ft2: 0 };
  const overlay = document.getElementById("ft8-bar-overlay");
  let active = "ft8";
  const update = () => {
    if (!overlay) return;
    const mode = (document.getElementById("mode")?.value ?? "").toUpperCase();
    const result = builders[active]?.();
    if (mode !== "DIG" && mode !== "USB" || !result || result.count === 0 || result.newestTsMs <= dismissed[active]) {
      overlay.style.display = "none";
      overlay.innerHTML = "";
      return;
    }
    const label = labels[active];
    overlay.innerHTML = `<div class="aprs-bar-header"><span class="aprs-bar-title"><span class="aprs-bar-title-word">${label}</span><span class="aprs-bar-title-word">Live</span></span><span class="aprs-bar-actions"><span class="aprs-bar-window">Last 15 minutes</span><span class="aprs-bar-clear-wrap"><span class="aprs-bar-clear" role="button" tabindex="0" onclick="window.clearFt8Bar()" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();window.clearFt8Bar();}" aria-label="Clear ${label} overlay">Clear</span></span><button class="aprs-bar-close" type="button" onclick="window.closeFt8Bar()" aria-label="Close ${label} overlay">&times;</button></span></div>${result.html}`;
    overlay.style.display = "flex";
  };
  bridge.registerFt8FamilyBarRenderer = (decoder, builder) => {
    builders[decoder] = builder;
  };
  bridge.setFt8FamilyBarDecoder = (decoder) => {
    active = decoder;
    update();
  };
  bridge.updateFt8Bar = update;
  bridge.clearFt8Bar = () => {
    bridge.trxPluginRuntime.reset(active);
  };
  bridge.closeFt8Bar = () => {
    dismissed[active] = Date.now();
    update();
  };
}
function initializeFtxDecoder(config) {
  const { id, label, periodMs, periodDigits = 1 } = config;
  const status = document.getElementById(`${id}-status`);
  const period = document.getElementById(`${id}-period`);
  const messagesElement = document.getElementById(`${id}-messages`);
  const filterInput = document.getElementById(`${id}-filter`);
  let filterText = "";
  let history = [];
  const renderMessage = (message) => {
    return bridge.renderFt8Message?.(message) ?? renderFtxMessage(message);
  };
  const retentionMs = () => bridge.getDecodeHistoryRetentionMs?.() ?? 864e5;
  const prune = () => {
    const cutoff = Date.now() - retentionMs();
    history = history.filter((message) => (finiteNumber(message._tsMs ?? message.ts_ms) ?? 0) >= cutoff);
  };
  const schedule = (job) => {
    if (bridge.trxScheduleUiFrameJob) bridge.trxScheduleUiFrameJob(`${id}-history`, job);
    else job();
  };
  const displayFrequency = (value) => {
    const raw = finiteNumber(value);
    if (raw === null) return null;
    const base = finiteNumber(bridge.ft8BaseHz);
    return base !== null && base > 0 && raw >= 0 && raw < 1e5 ? base + raw : raw;
  };
  const renderRow = (message) => {
    const row = document.createElement("div");
    row.className = "ft8-row";
    const raw = message.message ?? "";
    row.dataset.message = raw.toUpperCase();
    row.dataset.decoder = id;
    const storedFrequency = finiteNumber(message.freq_hz);
    row.dataset.storedFreqHz = storedFrequency === null ? "" : String(storedFrequency);
    const snr = finiteNumber(message.snr_db);
    const delta = finiteNumber(message.dt_s);
    const frequency = displayFrequency(message.freq_hz);
    const timestamp = finiteNumber(message._tsMs ?? message.ts_ms);
    const time = timestamp === null ? "--:--:--" : new Date(timestamp).toLocaleTimeString([], {
      hour: "2-digit",
      minute: "2-digit",
      second: "2-digit"
    });
    row.innerHTML = `<span class="ft8-time">${time}</span><span class="ft8-snr">${snr?.toFixed(1) ?? "--"}</span><span class="ft8-dt">${delta?.toFixed(2) ?? "--"}</span><span class="ft8-freq">${frequency?.toFixed(0) ?? "--"}</span><span class="ft8-msg">${renderMessage(raw)}</span>`;
    return row;
  };
  const render = () => {
    prune();
    if (!messagesElement) return;
    const fragment = document.createDocumentFragment();
    let count = 0;
    for (const message of history) {
      if (count >= 200) break;
      if (filterText && !(message.message ?? "").toUpperCase().includes(filterText)) continue;
      fragment.appendChild(renderRow(message));
      count += 1;
    }
    messagesElement.replaceChildren(fragment);
  };
  const normalize = (message) => {
    const raw = message.message ?? "";
    const locatorDetails = bridge.ft8ExtractLocatorDetails?.(raw) ?? extractFtxLocatorDetails(raw);
    const grids = locatorDetails.length > 0 ? locatorDetails.map(({ grid }) => grid) : bridge.ft8ExtractAllGrids?.(raw) ?? extractFtxGrids(raw);
    const station = bridge.ft8ExtractLikelyCallsign?.(raw) ?? extractFtxCallsign(raw);
    const frequency = displayFrequency(message.freq_hz);
    if (grids.length > 0) {
      bridge.mapAddLocator?.(raw, grids, id, station, {
        ...message,
        freq_hz: frequency ?? message.freq_hz,
        locator_details: locatorDetails
      });
    }
    return {
      receiver: bridge.getDecodeRigMeta?.() ?? null,
      ts_ms: message.ts_ms,
      snr_db: message.snr_db,
      dt_s: message.dt_s,
      freq_hz: frequency ?? message.freq_hz,
      message: message.message,
      _tsMs: finiteNumber(message.ts_ms) ?? Date.now()
    };
  };
  const receiveBatch = (messages) => {
    if (messages.length === 0) return;
    if (status) status.textContent = "Receiving";
    history = messages.map(normalize).reverse().concat(history);
    prune();
    bridge.setFt8FamilyBarDecoder?.(id);
    bridge.updateFt8Bar?.();
    schedule(render);
  };
  const reset = () => {
    history = [];
    bridge.updateFt8Bar?.();
    render();
    bridge.clearMapMarkersByType?.(id);
  };
  const barFrames = () => {
    const recent = history.filter((message) => (finiteNumber(message._tsMs ?? message.ts_ms) ?? 0) >= Date.now() - 9e5).slice(0, 8);
    let html = "";
    for (const message of recent) {
      const timestamp = finiteNumber(message._tsMs ?? message.ts_ms);
      const time = timestamp === null ? "" : `<span class="aprs-bar-time">${formatBarTime(timestamp)}</span>`;
      const snr = finiteNumber(message.snr_db);
      const delta = finiteNumber(message.dt_s);
      const frequency = displayFrequency(message.freq_hz);
      const detail = [snr === null ? "-- dB" : `${snr.toFixed(1)} dB`, delta === null ? null : `dt ${delta.toFixed(2)}`, frequency === null ? null : `${frequency.toFixed(0)} Hz`].filter((part) => part !== null).join(" · ");
      html += `<div class="aprs-bar-frame"><div class="aprs-bar-frame-main">${time}<span class="aprs-bar-call">${renderMessage(message.message ?? "")}</span>${detail ? ` · ${detail}` : ""}</div></div>`;
    }
    return { count: recent.length, newestTsMs: recent.reduce((latest, message) => Math.max(latest, finiteNumber(message._tsMs ?? message.ts_ms) ?? 0), 0), html };
  };
  bridge.trxPluginRuntime.registerDecoder({
    id,
    onMessage: (message) => {
      receiveBatch([message]);
    },
    onBatch: receiveBatch,
    restore: receiveBatch,
    prune: () => {
      prune();
      render();
    },
    reset
  });
  bridge.registerFt8FamilyBarRenderer?.(id, barFrames);
  const updatePeriod = () => {
    if (period) period.textContent = `Next slot ${((periodMs - Date.now() % periodMs) / 1e3).toFixed(periodDigits)}s`;
  };
  updatePeriod();
  window.setInterval(updatePeriod, 250);
  filterInput?.addEventListener("input", () => {
    filterText = filterInput.value.trim().toUpperCase();
    render();
  });
  messagesElement?.addEventListener("click", (event) => {
    if (!(event.target instanceof Element)) return;
    const grid = event.target.closest(".ft8-locator[data-locator-grid]")?.dataset.locatorGrid;
    if (grid) {
      bridge.navigateToMapLocator?.(grid.toUpperCase(), id);
      event.preventDefault();
    }
  });
  const toggle = document.getElementById(`${id}-decode-toggle-btn`);
  toggle?.addEventListener("click", () => {
    void (async () => {
      try {
        await bridge.takeSchedulerControlForDecoderDisable?.(toggle);
        await hostCore.postPath(`/toggle_${id}_decode`);
      } catch (error) {
        console.error(`${label} toggle failed`, error);
      }
    })();
  });
  document.getElementById(`settings-clear-${id}-history`)?.addEventListener("click", () => {
    void (async () => {
      if (!await bridge.trxUi.confirm({ title: `Clear ${label} history?`, message: `All stored ${label} decodes will be permanently removed.`, confirmLabel: "Clear history" })) return;
      try {
        await hostCore.postPath(`/clear_${id}_decode`);
        reset();
      } catch (error) {
        console.error(`${label} history clear failed`, error);
      }
    })();
  });
}

export {
  installFtxCompatibilityHelpers,
  initializeFt8FamilyBar,
  initializeFtxDecoder
};
