"use strict";

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

const repositoryRoot = path.resolve(__dirname, "..", "..");
const modulePath = path.join(repositoryRoot, "src",
  "MBN_STOCK_WEBVIEW.LegacyParityApp", "Web", "legacy-modal-focus.js");
const modalFocus = require(modulePath);

class FakeElement {
  constructor(documentObject, tagName, attributes) {
    this.ownerDocument = documentObject;
    this.tagName = String(tagName || "div").toUpperCase();
    this.parentElement = null;
    this.children = [];
    this.hidden = false;
    this.inert = false;
    this.disabled = false;
    this.style = { display: "block", visibility: "visible", contentVisibility: "visible" };
    this.attributes = new Map();
    Object.entries(attributes || {}).forEach(([name, value]) => {
      this.attributes.set(name, String(value));
    });
  }

  append(...elements) {
    elements.forEach(element => {
      element.parentElement = this;
      this.children.push(element);
    });
  }

  contains(element) {
    return element === this || this.children.some(child => child.contains(element));
  }

  focus() {
    this.ownerDocument.activeElement = this;
  }

  getAttribute(name) {
    return this.attributes.has(name) ? this.attributes.get(name) : null;
  }

  hasAttribute(name) {
    return this.attributes.has(name);
  }

  setAttribute(name, value) {
    this.attributes.set(name, String(value));
  }

  querySelector(selector) {
    const descendants = this.querySelectorAll("*");
    if (selector.includes("data-modal-initial-focus")) {
      return descendants.find(element =>
        element.hasAttribute("data-modal-initial-focus") || element.hasAttribute("autofocus")) || null;
    }
    if (selector[0] === "#") {
      return descendants.find(element => element.getAttribute("id") === selector.slice(1)) || null;
    }
    return null;
  }

  querySelectorAll() {
    return this.children.flatMap(child => [child, ...child.querySelectorAll("*")]);
  }
}

class FakeDocument {
  constructor() {
    this.body = new FakeElement(this, "body");
    this.activeElement = this.body;
    this.listeners = new Map();
    this.defaultView = {
      getComputedStyle(element) { return element.style; }
    };
  }

  addEventListener(type, listener) {
    if (!this.listeners.has(type)) this.listeners.set(type, new Set());
    this.listeners.get(type).add(listener);
  }

  removeEventListener(type, listener) {
    if (this.listeners.has(type)) this.listeners.get(type).delete(listener);
  }

  contains(element) {
    return this.body.contains(element);
  }

  querySelectorAll() {
    return this.body.querySelectorAll("*").filter(element => {
      const role = element.getAttribute("role");
      return role === "dialog" || role === "alertdialog";
    });
  }
}

function element(documentObject, tagName, attributes) {
  return new FakeElement(documentObject, tagName, attributes);
}

function modalFixture() {
  const documentObject = new FakeDocument();
  const opener = element(documentObject, "button", { id: "opener" });
  const backdrop = element(documentObject, "div");
  const dialog = element(documentObject, "section", { role: "dialog" });
  const first = element(documentObject, "button", { id: "first" });
  const preferred = element(documentObject, "input", { id: "preferred" });
  const last = element(documentObject, "button", { id: "last" });
  dialog.append(first, preferred, last);
  backdrop.append(dialog);
  documentObject.body.append(opener, backdrop);
  backdrop.hidden = true;
  opener.focus();
  return { documentObject, opener, backdrop, dialog, first, preferred, last };
}

function keyEvent(key, shiftKey) {
  return {
    key,
    shiftKey: !!shiftKey,
    defaultPrevented: false,
    preventDefault() { this.defaultPrevented = true; }
  };
}

test("exports the same API as a CommonJS module and a browser global", () => {
  assert.equal(typeof modalFocus.create, "function");
  const source = fs.readFileSync(modulePath, "utf8");
  const context = { globalThis: {} };
  vm.runInNewContext(source, context);
  assert.equal(typeof context.globalThis.LegacyModalFocus.create, "function");
});

test("selects the last visible modal and ignores hidden modal ancestors", () => {
  const fixture = modalFixture();
  const alertBackdrop = element(fixture.documentObject, "div");
  const alert = element(fixture.documentObject, "div", { role: "alertdialog" });
  alertBackdrop.append(alert);
  fixture.documentObject.body.append(alertBackdrop);

  assert.equal(modalFocus.findVisibleModal(fixture.documentObject), alert);
  alertBackdrop.hidden = true;
  assert.equal(modalFocus.findVisibleModal(fixture.documentObject), null);
  fixture.backdrop.hidden = false;
  assert.equal(modalFocus.findVisibleModal(fixture.documentObject), fixture.dialog);
});

test("focusable discovery excludes disabled, hidden, inert, and negative-tab controls", () => {
  const fixture = modalFixture();
  fixture.backdrop.hidden = false;
  fixture.first.disabled = true;
  fixture.preferred.setAttribute("tabindex", "-1");
  const hiddenParent = element(fixture.documentObject, "div");
  hiddenParent.hidden = true;
  const hiddenButton = element(fixture.documentObject, "button");
  hiddenParent.append(hiddenButton);
  const inertParent = element(fixture.documentObject, "div", { inert: "" });
  const inertButton = element(fixture.documentObject, "button");
  inertParent.append(inertButton);
  fixture.dialog.append(hiddenParent, inertParent);

  assert.deepEqual(modalFocus.getFocusableElements(
    fixture.dialog, fixture.documentObject), [fixture.last]);
});

test("open focuses the preferred control and close restores the opener", () => {
  const fixture = modalFixture();
  const manager = modalFocus.create({ document: fixture.documentObject });
  fixture.backdrop.hidden = false;
  manager.sync("#preferred");
  assert.equal(fixture.documentObject.activeElement, fixture.preferred);
  assert.equal(manager.getActiveModal(), fixture.dialog);

  fixture.backdrop.hidden = true;
  manager.sync();
  assert.equal(fixture.documentObject.activeElement, fixture.opener);
  assert.equal(manager.getActiveModal(), null);
});

test("open falls back to the first enabled visible control", () => {
  const fixture = modalFixture();
  const manager = modalFocus.create({ document: fixture.documentObject });
  fixture.backdrop.hidden = false;
  manager.sync("#missing");
  assert.equal(fixture.documentObject.activeElement, fixture.first);
});

test("Tab and Shift+Tab wrap inside the active modal without handling Escape", () => {
  const fixture = modalFixture();
  const manager = modalFocus.create({ document: fixture.documentObject });
  fixture.backdrop.hidden = false;
  manager.sync();

  fixture.last.focus();
  const forward = keyEvent("Tab", false);
  assert.equal(manager.handleKeydown(forward), true);
  assert.equal(forward.defaultPrevented, true);
  assert.equal(fixture.documentObject.activeElement, fixture.first);

  const backward = keyEvent("Tab", true);
  assert.equal(manager.handleKeydown(backward), true);
  assert.equal(backward.defaultPrevented, true);
  assert.equal(fixture.documentObject.activeElement, fixture.last);

  fixture.preferred.focus();
  const middle = keyEvent("Tab", false);
  assert.equal(manager.handleKeydown(middle), false);
  assert.equal(middle.defaultPrevented, false);
  assert.equal(fixture.documentObject.activeElement, fixture.preferred);

  const escape = keyEvent("Escape", false);
  assert.equal(manager.handleKeydown(escape), false);
  assert.equal(escape.defaultPrevented, false);
  assert.equal(manager.getActiveModal(), fixture.dialog);
});

test("a dialog without controls retains Tab focus on its container", () => {
  const documentObject = new FakeDocument();
  const dialog = element(documentObject, "section", { role: "dialog" });
  documentObject.body.append(dialog);
  const manager = modalFocus.create({ document: documentObject });
  manager.sync();
  const tab = keyEvent("Tab", false);
  assert.equal(manager.handleKeydown(tab), true);
  assert.equal(tab.defaultPrevented, true);
  assert.equal(dialog.getAttribute("tabindex"), "-1");
  assert.equal(documentObject.activeElement, dialog);
});

test("closing a nested alert restores its modal opener before the root opener", () => {
  const fixture = modalFixture();
  const manager = modalFocus.create({ document: fixture.documentObject });
  fixture.backdrop.hidden = false;
  manager.sync("#preferred");

  const alertBackdrop = element(fixture.documentObject, "div");
  const alert = element(fixture.documentObject, "div", { role: "alertdialog" });
  const alertOk = element(fixture.documentObject, "button", { id: "alert-ok" });
  alert.append(alertOk);
  alertBackdrop.append(alert);
  fixture.documentObject.body.append(alertBackdrop);
  manager.sync("#alert-ok");
  assert.equal(fixture.documentObject.activeElement, alertOk);

  alertBackdrop.hidden = true;
  manager.sync();
  assert.equal(manager.getActiveModal(), fixture.dialog);
  assert.equal(fixture.documentObject.activeElement, fixture.preferred);

  fixture.backdrop.hidden = true;
  manager.sync();
  assert.equal(fixture.documentObject.activeElement, fixture.opener);
});

test("rememberOpener preserves the owner when application code changes focus during open", () => {
  const fixture = modalFixture();
  const manager = modalFocus.create({ document: fixture.documentObject });
  manager.rememberOpener(fixture.dialog);
  fixture.backdrop.hidden = false;
  fixture.first.focus();
  manager.sync("#preferred");
  fixture.backdrop.hidden = true;
  manager.sync();
  assert.equal(fixture.documentObject.activeElement, fixture.opener);
});

test("an opener resolver restores a logically equivalent control after DOM replacement", () => {
  const fixture = modalFixture();
  const manager = modalFocus.create({ document: fixture.documentObject });
  let currentOpener = fixture.opener;
  manager.rememberOpener(fixture.dialog, () => currentOpener);
  fixture.backdrop.hidden = false;
  manager.sync("#preferred");

  const replacement = element(fixture.documentObject, "button", { id: "opener" });
  const openerIndex = fixture.documentObject.body.children.indexOf(fixture.opener);
  fixture.opener.parentElement = null;
  replacement.parentElement = fixture.documentObject.body;
  fixture.documentObject.body.children[openerIndex] = replacement;
  currentOpener = replacement;

  fixture.backdrop.hidden = true;
  manager.sync();
  assert.equal(fixture.documentObject.activeElement, replacement);
  assert.notEqual(fixture.documentObject.activeElement, fixture.opener);
});
