// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
//
// SPDX-License-Identifier: GPL-2.0-or-later

// Geometry of the spectrum area while tuning. Everything above the spectrum is
// driven by what happens to be in the visible range — band plan allocations,
// bookmarks — and the strips that show them used to appear and disappear with
// it, so tuning across a band edge moved the whole page under the operator's
// cursor. Nothing else in the suite serves a rig with a spectrum.

import assert from "node:assert/strict";
import { chromium } from "playwright-core";
import { startBrowser, startWebFixture } from "./web-fixture.mjs";

/* global document */

const BOOKMARKS = [
  { id: "b1", name: "40m FT8", freq_hz: 7074000, mode: "DIG", category: "Digital", comment: "", locator: "" },
  { id: "b2", name: "40m CW", freq_hz: 7030000, mode: "CW", category: "", comment: "", locator: "" },
];
const BANDPLAN = {
  iaru1: {
    bands: [{
      name: "40m",
      low_hz: 7000000,
      high_hz: 7200000,
      segments: [
        { low_hz: 7000000, high_hz: 7040000, mode: "CW", label: "CW" },
        { low_hz: 7040000, high_hz: 7200000, mode: "All", label: "All modes" },
      ],
    }],
  },
};
// 40m has both bookmarks and allocations; 20m has neither.
const BAND_WITH_CONTENT = 7074000;
const BAND_WITHOUT_CONTENT = 14074000;

const fixture = await startWebFixture({
  spectrum: true,
  bookmarks: BOOKMARKS,
  bandplan: BANDPLAN,
  bandplanEnabled: true,
});
const { browser, page, runtimeErrors } = await startBrowser(chromium);

function readGeometry() {
  const top = (selector) => {
    const el = document.querySelector(selector);
    return el ? Math.round(el.getBoundingClientRect().top) : null;
  };
  const axis = document.getElementById("spectrum-bookmark-axis");
  const strip = document.getElementById("spectrum-bandplan-strip");
  return {
    chips: axis.querySelectorAll(".spectrum-bookmark-chip").length,
    axisEmpty: axis.classList.contains("bm-axis-empty"),
    stripReserved: strip.classList.contains("bp-visible"),
    stripEmpty: strip.classList.contains("bp-empty"),
    overviewTop: top(".overview-strip"),
    spectrumTop: top("#spectrum-panel"),
    controlsTop: top(".controls-row"),
    footerTop: top(".footer"),
    docHeight: document.documentElement.scrollHeight,
  };
}

const layoutOf = (geometry) => ({
  overviewTop: geometry.overviewTop,
  spectrumTop: geometry.spectrumTop,
  controlsTop: geometry.controlsTop,
  footerTop: geometry.footerTop,
  docHeight: geometry.docHeight,
});

async function tuneTo(hz) {
  fixture.setCenterHz(hz);
  await page.waitForTimeout(900);
  return page.evaluate(readGeometry);
}

try {
  await page.setViewportSize({ width: 1600, height: 950 });
  await page.goto(fixture.origin, { waitUntil: "domcontentloaded" });
  await page.locator("#content").waitFor({ state: "visible" });
  await page.locator("#spectrum-panel").waitFor({ state: "visible" });
  await page.waitForTimeout(1500);

  const populated = await tuneTo(BAND_WITH_CONTENT);
  assert.equal(populated.chips, BOOKMARKS.length, `expected both bookmarks, saw ${populated.chips}`);
  assert.equal(populated.axisEmpty, false, "bookmark rail claims to be empty with chips in it");
  assert.equal(populated.stripReserved, true, "band plan strip is missing on a band with allocations");
  assert.equal(populated.stripEmpty, false, "band plan strip claims to be empty with segments in it");

  const bare = await tuneTo(BAND_WITHOUT_CONTENT);
  assert.equal(bare.chips, 0, `expected no bookmarks on ${BAND_WITHOUT_CONTENT}Hz, saw ${bare.chips}`);
  assert.equal(bare.axisEmpty, true, "bookmark rail should show its placeholder");
  assert.equal(bare.stripReserved, true, "band plan strip gave its height back");
  assert.equal(bare.stripEmpty, true, "band plan strip should be marked empty");

  // The point of both placeholders: tuning must not move anything.
  assert.deepEqual(layoutOf(bare), layoutOf(populated),
    `tuning off the band moved the page: ${JSON.stringify(populated)} -> ${JSON.stringify(bare)}`);

  const back = await tuneTo(BAND_WITH_CONTENT);
  assert.equal(back.chips, BOOKMARKS.length, "bookmarks did not come back");
  assert.deepEqual(layoutOf(back), layoutOf(populated), "tuning back moved the page");

  // The rail covers the top of the overview, so only the chips may take
  // pointer events — the rest has to fall through to the plot behind it.
  const hits = await page.evaluate(() => {
    const chip = document.querySelector("#spectrum-bookmark-axis .spectrum-bookmark-chip");
    const chipRect = chip.getBoundingClientRect();
    const axisRect = document.getElementById("spectrum-bookmark-axis").getBoundingClientRect();
    const onChip = document.elementFromPoint(chipRect.left + chipRect.width / 2, chipRect.top + chipRect.height / 2);
    const besideChip = document.elementFromPoint(axisRect.right - 30, axisRect.top + 10);
    return {
      chip: onChip?.closest(".spectrum-bookmark-chip") ? "chip" : (onChip?.id || onChip?.tagName),
      besideChip: besideChip?.id || besideChip?.tagName,
    };
  });
  assert.equal(hits.chip, "chip", `chip is not clickable, hit ${hits.chip}`);
  assert.equal(hits.besideChip, "overview-canvas", `rail swallows events, hit ${hits.besideChip}`);

  assert.deepEqual(runtimeErrors, []);
} finally {
  await browser.close();
  await fixture.close();
}
