"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");

function section(startMarker, endMarker) {
  const start = app.indexOf(startMarker);
  const end = app.indexOf(endMarker, start + startMarker.length);
  assert.ok(start >= 0 && end > start,
    `Unable to extract ${startMarker} through ${endMarker}`);
  return app.slice(start, end);
}

function pendingHelpers() {
  const source = section(
    "function pendingSelectionContainer",
    "function clearManualResultPendingSelection");
  return new Function(`
    ${source}
    return {
      createPendingRowSelection,
      applyPendingRowSelection,
      restorePendingRowSelection,
      lockPendingControls,
      applyPendingControlLocks,
      releasePendingControlLocks,
      finishPendingRowSelection
    };
  `)();
}

function classList(selected) {
  const values = new Set(selected ? ["selected"] : []);
  return {
    contains(name) { return values.has(name); },
    toggle(name, enabled) {
      if (enabled) values.add(name);
      else values.delete(name);
    }
  };
}

function row(id, selected) {
  const attributes = new Map([["aria-selected", selected ? "true" : "false"]]);
  return {
    id,
    isConnected: true,
    tabIndex: selected ? 0 : -1,
    classList: classList(selected),
    focused: false,
    closest() { return this.container; },
    focus() { this.focused = true; },
    getAttribute(name) { return attributes.has(name) ? attributes.get(name) : null; },
    setAttribute(name, value) { attributes.set(name, String(value)); }
  };
}

function list(rows) {
  const attributes = new Map();
  const container = {
    isConnected: true,
    querySelectorAll() { return rows; },
    getAttribute(name) { return attributes.has(name) ? attributes.get(name) : null; },
    setAttribute(name, value) { attributes.set(name, String(value)); },
    removeAttribute(name) { attributes.delete(name); }
  };
  rows.forEach(value => { value.container = container; });
  return container;
}

function control(disabled) {
  return {
    disabled,
    dataset: {},
    isConnected: true
  };
}

test("single click paints selection and locks only dependent controls immediately", () => {
  const helpers = pendingHelpers();
  const first = row("first", true);
  const second = row("second", false);
  const container = list([first, second]);
  container.setAttribute("aria-activedescendant", "first");
  const editor = control(false);
  const action = control(false);
  const resultRow = control(false);
  const root = {
    querySelectorAll() { return [editor, action]; }
  };

  const pending = helpers.createPendingRowSelection(second, "button", "second-id");
  pending.lockedControls = helpers.lockPendingControls(root, "dependent");

  assert.equal(first.classList.contains("selected"), false);
  assert.equal(first.getAttribute("aria-selected"), "false");
  assert.equal(second.classList.contains("selected"), true);
  assert.equal(second.getAttribute("aria-selected"), "true");
  assert.equal(second.focused, true);
  assert.equal(container.getAttribute("aria-activedescendant"), "second");
  assert.equal(editor.disabled, true);
  assert.equal(action.disabled, true);
  assert.equal(resultRow.disabled, false,
    "the result row remains available for the second click of a double-click");

  helpers.finishPendingRowSelection(pending, true);
  assert.equal(first.classList.contains("selected"), true);
  assert.equal(second.classList.contains("selected"), false);
  assert.equal(container.getAttribute("aria-activedescendant"), "first");
  assert.equal(editor.disabled, false);
  assert.equal(action.disabled, false);
});

test("failed send restoration cooperates with transient busy and later business-disabled state", () => {
  const helpers = pendingHelpers();
  const action = control(false);
  const root = { querySelectorAll() { return [action]; } };
  const locked = helpers.lockPendingControls(root, "dependent");
  assert.equal(action.disabled, true);

  // setTransientControlsBusy captured the pending lock while a native request ran.
  action.dataset.busyDisabled = "true";
  helpers.releasePendingControlLocks(locked);
  assert.equal(action.disabled, true,
    "pending cleanup must not enable a control while the host is still busy");
  assert.equal(action.dataset.busyDisabled, "false",
    "the busy release will restore the pre-pending semantic value");

  action.disabled = action.dataset.busyDisabled === "true";
  delete action.dataset.busyDisabled;
  assert.equal(action.disabled, false);

  // The authoritative response is applied after pending cleanup. Its new business
  // rule must win instead of being overwritten by the pre-click value above.
  action.disabled = true;
  assert.equal(action.disabled, true);

  const manualRender = section(
    "function renderManualFinancial(state)",
    "function isCanonicalNamedPlaylistTitle");
  const themeRender = section("function renderTheme(state)", "function renderExpert(state)");
  assert.ok(
    manualRender.indexOf("clearManualResultPendingSelection(!accepted)") <
      manualRender.indexOf("setTransientControlsBusy(manualDialog, state.isBusy === true)"));
  assert.ok(
    themeRender.indexOf("clearThemeResultPendingSelection(") <
      themeRender.indexOf("updateThemeWorkspace(existingWorkspace, theme)"));
});

test("all four delayed paths expose immediate pending UI and explicit failure cleanup", () => {
  for (const helper of [
    "beginManualResultPendingSelection",
    "beginThemeResultPendingSelection",
    "beginComparisonTargetPendingSelection",
    "beginManualViResultPendingSelection"
  ]) {
    assert.match(app, new RegExp(`${helper}\\(`));
  }
  for (const cleanup of [
    "clearManualResultPendingSelection\\(true\\)",
    "clearThemeResultPendingSelection\\(true\\)",
    "clearComparisonTargetPendingSelection\\(true\\)",
    "clearManualViResultPendingSelection\\(true\\)"
  ]) {
    assert.match(app, new RegExp(cleanup));
  }
  assert.match(app,
    /button\[data-manual-action-id\][\s\S]*button\[data-theme-action-id\]/u);
});

test("click-click-dblclick handlers keep delayed select out of atomic activation", () => {
  const manualDouble = section(
    "manualWorkspace.addEventListener(\"dblclick\"",
    "manualWorkspace.addEventListener(\"click\"");
  const treeDouble = section(
    "categoryTree.addEventListener(\"dblclick\"",
    "categoryTree.addEventListener(\"click\"");
  const manualLists = section("function renderManualLists", "function openNamedPlaylist");

  assert.match(manualDouble, /activate-manual-financial-result/u);
  assert.doesNotMatch(manualDouble, /load-manual-financial/u);
  assert.match(treeDouble, /activate-theme-result/u);
  assert.doesNotMatch(treeDouble, /send\("select-theme"/u);
  assert.match(treeDouble, /choose-comparison-domestic/u);
  assert.doesNotMatch(treeDouble, /send\("select-comparison-domestic"/u);
  assert.match(manualLists,
    /addEventListener\("dblclick"[\s\S]*send\("add-manual-vi-result"/u);
  assert.match(manualLists,
    /addEventListener\("click"[\s\S]*deferLegacySingleClick/u);
});

test("modal and tab exits cancel timers and discard pending presentation state", () => {
  const manualClose = section("function closeManualFinancial", "manualClose.addEventListener");
  const listClose = section("function closeManualList", "manualListClose.addEventListener");
  const tabClick = section(
    "marketTabs.addEventListener(\"click\"",
    "marketTabs.addEventListener(\"dragstart\"");

  assert.match(manualClose, /clearManualResultPendingSelection\(true\)/u);
  assert.match(listClose, /clearManualViResultPendingSelection\(true\)/u);
  assert.match(tabClick, /clearComparisonTargetPendingSelection\(true\)/u);
  assert.match(tabClick, /clearThemeResultPendingSelection\(true\)/u);
});
