import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { createWebcamFacePanel } from "../profile/index.ts";

type Listener = () => void;

class FakeElement {
  readonly children: FakeElement[] = [];
  readonly attributes = new Map<string, string>();
  readonly listeners = new Map<string, Listener[]>();
  parentElement: FakeElement | null = null;
  className = "";
  textContent = "";
  hidden = false;
  disabled = false;
  type = "";
  readonly ownerDocument: FakeDocument;
  readonly tagName: string;

  constructor(ownerDocument: FakeDocument, tagName: string) {
    this.ownerDocument = ownerDocument;
    this.tagName = tagName.toUpperCase();
  }
  append(...nodes: FakeElement[]): void {
    for (const node of nodes) {
      node.parentElement = this;
      this.children.push(node);
    }
  }
  setAttribute(name: string, value: string): void { this.attributes.set(name, value); }
  getAttribute(name: string): string | null { return this.attributes.get(name) ?? null; }
  addEventListener(type: string, listener: Listener): void {
    const listeners = this.listeners.get(type) ?? [];
    listeners.push(listener);
    this.listeners.set(type, listeners);
  }
  dispatch(type: string): void { for (const listener of this.listeners.get(type) ?? []) listener(); }
  focus(): void { this.ownerDocument.activeElement = this; }
  remove(): void {
    if (!this.parentElement) return;
    const index = this.parentElement.children.indexOf(this);
    if (index >= 0) this.parentElement.children.splice(index, 1);
    this.parentElement = null;
  }
  find(attribute: string, value: string): FakeElement {
    if (this.attributes.get(attribute) === value) return this;
    for (const child of this.children) {
      try { return child.find(attribute, value); } catch { /* keep looking */ }
    }
    throw new Error(`missing [${attribute}=${value}]`);
  }
  descendants(tagName: string): FakeElement[] {
    return this.children.flatMap((child) => [
      ...(child.tagName === tagName.toUpperCase() ? [child] : []),
      ...child.descendants(tagName),
    ]);
  }
}

class FakeDocument {
  activeElement: FakeElement | null = null;
  createElement(tagName: string): FakeElement { return new FakeElement(this, tagName); }
}

function setup(supported = true) {
  const document = new FakeDocument();
  const container = document.createElement("div");
  const indicatorContainer = document.createElement("div");
  let starts = 0;
  let stops = 0;
  const panel = createWebcamFacePanel({
    container: container as unknown as HTMLElement,
    indicatorContainer: indicatorContainer as unknown as HTMLElement,
    supported,
    onStart: () => { starts += 1; },
    onStop: () => { stops += 1; },
  });
  return { container, indicatorContainer, panel, starts: () => starts, stops: () => stops };
}

describe("webcam face controls", () => {
  it("starts off explicitly off and emits capture intent only from the Start button", () => {
    const { panel, starts, stops, indicatorContainer } = setup();
    const root = panel.root as unknown as FakeElement;
    const start = root.find("data-action", "start-camera");
    const stop = root.find("data-action", "stop-camera");
    assert.deepEqual(panel.state(), {
      status: "off",
      message: "Camera is off. Nothing is captured or stored.",
      indicatorVisible: false,
    });
    assert.equal((panel.indicator as unknown as FakeElement).hidden, true);
    assert.equal(indicatorContainer.children.length, 1);
    stop.dispatch("click");
    assert.equal(stops(), 0);
    start.dispatch("click");
    assert.equal(starts(), 1);
    assert.equal(stops(), 0);
  });

  it("keeps a visible active indicator with one-click Stop outside the dialog", () => {
    const { panel, stops } = setup();
    const root = panel.root as unknown as FakeElement;
    const start = root.find("data-action", "start-camera");
    const stop = root.find("data-action", "stop-camera");
    start.focus();
    panel.update("active");
    assert.equal(panel.state().indicatorVisible, true);
    assert.equal(root.getAttribute("data-status"), "active");
    const indicator = panel.indicator as unknown as FakeElement;
    assert.equal(indicator.hidden, false);
    assert.equal(indicator.getAttribute("role"), "region");
    assert.equal(indicator.getAttribute("aria-label"), "Camera active");
    assert.equal(indicator.descendants("span")[1]?.getAttribute("role"), "status");
    assert.equal(start.hidden, true);
    assert.equal(stop.hidden, false);
    assert.equal(start.ownerDocument.activeElement, stop, "focus follows the asynchronous state change");
    indicator.find("data-action", "stop-camera-indicator").dispatch("click");
    assert.equal(stops(), 1);
  });

  it("handles requesting, denial text, unsupported browsers, and disposal defensively", () => {
    const first = setup();
    const start = (first.panel.root as unknown as FakeElement).find("data-action", "start-camera");
    first.panel.update("requesting");
    assert.equal(start.disabled, true);
    start.dispatch("click");
    assert.equal(first.starts(), 0);
    const denial = "Camera permission was denied. Nothing was captured.";
    first.panel.update("error", denial);
    assert.equal(first.panel.state().message, denial);

    const unsupported = setup(false);
    const unsupportedStart = (unsupported.panel.root as unknown as FakeElement).find("data-action", "start-camera");
    assert.equal(unsupported.panel.state().status, "unsupported");
    assert.equal(unsupportedStart.disabled, true);
    unsupportedStart.dispatch("click");
    assert.equal(unsupported.starts(), 0);
    assert.throws(() => first.panel.update("bogus" as never), RangeError);
    first.panel.dispose();
    first.panel.dispose();
    assert.equal(first.container.children.length, 0);
    assert.equal(first.indicatorContainer.children.length, 0);
  });
});
