"use strict";

const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");

const repositoryRoot = path.resolve(__dirname, "..", "..");
const app = fs.readFileSync(path.join(
  repositoryRoot,
  "src",
  "MBN_STOCK_WEBVIEW.LegacyParityApp",
  "Web",
  "app.js"), "utf8");
const styles = fs.readFileSync(path.join(
  repositoryRoot,
  "src",
  "MBN_STOCK_WEBVIEW.LegacyParityApp",
  "Web",
  "styles.css"), "utf8");

function sliceBetween(startMarker, endMarker) {
  const start = app.indexOf(startMarker);
  const end = app.indexOf(endMarker, start + startMarker.length);
  assert.ok(start >= 0, `${startMarker} must exist`);
  assert.ok(end > start, `${endMarker} must follow ${startMarker}`);
  return app.slice(start, end);
}

function list(items) {
  items.item = function (index) { return items[index] || null; };
  return items;
}

function option(index, selected) {
  const attributes = new Map([["aria-selected", selected ? "true" : "false"]]);
  const classes = new Set(selected ? ["selected"] : []);
  return {
    dataset: { resultIndex: String(index) },
    offsetHeight: 24,
    tabIndex: selected ? 0 : -1,
    focused: false,
    scrolled: false,
    classList: {
      toggle(name, enabled) {
        if (enabled) classes.add(name);
        else classes.delete(name);
      },
      contains(name) { return classes.has(name); }
    },
    getAttribute(name) { return attributes.get(name) || null; },
    setAttribute(name, value) { attributes.set(name, String(value)); },
    focus() { this.focused = true; },
    scrollIntoView() { this.scrolled = true; }
  };
}

function keyboardHarness(selectedIndex = -1, count = 30) {
  const options = list(Array.from(
    { length: count },
    (_, index) => option(index, index === selectedIndex)));
  const stockResults = {
    clientHeight: 240,
    querySelectorAll() { return options; }
  };
  const source = sliceBetween(
    "function stockResultPageOffset",
    "function selectStockResult");
  const factory = new Function("stockResults", `
    ${source}
    return { stockResultPageOffset, stockResultKeyTarget };
  `);
  return Object.assign({ options }, factory(stockResults));
}

test("stock result arrows and boundaries match the native single-select ListBox", () => {
  const middle = keyboardHarness(10);
  assert.equal(middle.stockResultKeyTarget(middle.options, "ArrowUp").dataset.resultIndex, "9");
  assert.equal(middle.stockResultKeyTarget(middle.options, "ArrowDown").dataset.resultIndex, "11");
  assert.equal(middle.stockResultKeyTarget(middle.options, "Home").dataset.resultIndex, "0");
  assert.equal(middle.stockResultKeyTarget(middle.options, "End").dataset.resultIndex, "29");

  const first = keyboardHarness(0);
  assert.equal(first.stockResultKeyTarget(first.options, "ArrowUp").dataset.resultIndex, "0");
  const last = keyboardHarness(29);
  assert.equal(last.stockResultKeyTarget(last.options, "ArrowDown").dataset.resultIndex, "29");
  assert.equal(last.stockResultKeyTarget(last.options, "Enter"), null);
});

test("PageUp and PageDown move nine rows in the original ten-row viewport", () => {
  const middle = keyboardHarness(13);
  assert.equal(middle.stockResultPageOffset(middle.options.item(0)), 9);
  assert.equal(middle.stockResultKeyTarget(middle.options, "PageUp").dataset.resultIndex, "4");
  assert.equal(middle.stockResultKeyTarget(middle.options, "PageDown").dataset.resultIndex, "22");

  const nearStart = keyboardHarness(4);
  assert.equal(nearStart.stockResultKeyTarget(nearStart.options, "PageUp").dataset.resultIndex, "0");
  const nearEnd = keyboardHarness(25);
  assert.equal(nearEnd.stockResultKeyTarget(nearEnd.options, "PageDown").dataset.resultIndex, "29");

  const none = keyboardHarness(-1);
  assert.equal(none.stockResultKeyTarget(none.options, "ArrowDown").dataset.resultIndex, "0");
  assert.equal(none.stockResultKeyTarget(none.options, "PageDown").dataset.resultIndex, "9");
});

test("stock selection uses one roving tab stop and preserves focused selection", () => {
  const options = list([option(0, true), option(1, false), option(2, false)]);
  const sent = [];
  const stockResults = { querySelectorAll() { return options; } };
  const source = sliceBetween(
    "function selectStockResult",
    "function createStockResultElement");
  const selectStockResult = new Function("stockResults", "send", "uiBusy", `
    ${source}
    return selectStockResult;
  `)(stockResults, (type, payload) => sent.push({ type, payload }), false);

  selectStockResult(options.item(2), true);
  assert.deepEqual(options.map(row => row.tabIndex), [-1, -1, 0]);
  assert.deepEqual(options.map(row => row.getAttribute("aria-selected")),
    ["false", "false", "true"]);
  assert.equal(options.item(2).focused, true);
  assert.equal(options.item(2).scrolled, true);
  assert.deepEqual(sent, [{ type: "select-stock", payload: { resultIndex: 2 } }]);

  // A second click from a browser dblclick gesture only reaffirms selection;
  // it neither emits another selection transition nor appends a playlist row.
  selectStockResult(options.item(2), false);
  assert.equal(sent.length, 1);
});

test("busy stock results cannot create a visual-only selection", () => {
  const options = list([option(0, true), option(1, false)]);
  options.forEach(row => { row.disabled = true; });
  const sent = [];
  const stockResults = { querySelectorAll() { return options; } };
  const source = sliceBetween(
    "function selectStockResult",
    "function createStockResultElement");
  const selectStockResult = new Function("stockResults", "send", "uiBusy", `
    ${source}
    return selectStockResult;
  `)(stockResults, (type, payload) => sent.push({ type, payload }), true);

  selectStockResult(options.item(1), true);

  assert.deepEqual(options.map(row => row.getAttribute("aria-selected")),
    ["true", "false"]);
  assert.equal(options.item(1).focused, false);
  assert.deepEqual(sent, []);
});

test("stock option keydown consumes list navigation but lets Home and End bubble", () => {
  const factory = sliceBetween(
    "function createStockResultElement",
    "function renderStockResults");
  for (const key of ["ArrowUp", "ArrowDown", "PageUp", "PageDown", "Home", "End"]) {
    assert.match(app, new RegExp(`key === \\"${key}\\"`));
  }
  assert.match(factory, /event\.preventDefault\(\)/);
  assert.doesNotMatch(factory, /stopPropagation|stopImmediatePropagation/);
  assert.doesNotMatch(factory, /dblclick|add-playlist|append-playlist|cut-pointer-down/);
  assert.match(styles,
    /\.stock-result \{[^}]*height: 24px;[^}]*line-height: 24px;/s);
  assert.match(styles, /\.stock-results \{ height: 242px; \}/);
});

test("rendering keeps exactly one tab stop, falling back to the first unselected row", () => {
  const renderer = sliceBetween(
    "function renderStockResults",
    "function pointerPosition");
  assert.match(renderer, /button\.tabIndex = isSelected \? 0 : -1/);
  assert.match(renderer, /button\.disabled = state\.isBusy === true/);
  assert.match(renderer, /if \(!hasSelectedResult && stockResults\.children\.length > 0\)/);
  assert.match(renderer, /stockResults\.children\.item\(0\)\.tabIndex = 0/);
  assert.doesNotMatch(renderer, /\.focus\(/);
});
