// WIP, just finding all the boxes and glue, implementation is woefully incomplete

import type { DOMAttributes } from "react";
import { assign, createMachine, interpret } from "@xstate/fsm";
import invariant from "tiny-invariant";

type CustomElement<T> = Partial<
  T & DOMAttributes<T> & { children: any; class: string }
>;

type MachineContext = { input?: HTMLInputElement; button?: HTMLButtonElement };

type MachineEvents =
  | { type: "INIT"; input: HTMLInputElement; button: HTMLButtonElement }
  | { type: "BUTTON_CLICK" }
  | { type: "ESCAPE" }
  | { type: "ARROW_DOWN" }
  | { type: "ARROW_UP" }
  | { type: "OUTER_INTERACTION" };

const machine = createMachine<MachineContext, MachineEvents>({
  id: "amalgo-box",
  initial: "idle",
  states: {
    idle: {
      on: {
        INIT: {
          target: "closed",
          actions: assign((_, event) => ({
            input: event.input,
            button: event.button,
          })),
        },
      },
    },

    closed: {
      entry: () => {
        document.body.style.overflow = "";
      },
      on: {
        BUTTON_CLICK: "open",
      },
    },

    open: {
      entry: ctx => {
        requestAnimationFrame(() => {
          ctx.input?.focus();
        });
        document.body.style.overflow = "hidden";
      },
      on: {
        BUTTON_CLICK: "closed",
        OUTER_INTERACTION: "closed",
        ESCAPE: {
          target: "closed",
          actions: ctx => {
            requestAnimationFrame(() => {
              ctx.button?.focus();
            });
          },
        },
      },
    },
  },
});

class AmalgoBox extends HTMLElement {
  context = interpret(machine);

  connectedCallback() {
    const service = (this.context = interpret(machine).start());

    const input = this.querySelector("input");
    const button = this.querySelector("button");
    invariant(input, "need an <input />");
    invariant(button, "need an <button />");

    service.send({ type: "INIT", input, button });

    service.subscribe(state => {
      this.setAttribute("state", state.value);

      if (state.value === "open") {
        document.addEventListener("mousedown", this.outerEvent);
        document.addEventListener("touchstart", this.outerEvent);
        document.addEventListener("focusin", this.outerEvent);
        document.addEventListener("keydown", this.keydownEvent);
      } else if (state.value === "closed") {
        document.removeEventListener("mousedown", this.outerEvent);
        document.removeEventListener("touchstart", this.outerEvent);
        document.removeEventListener("focusin", this.outerEvent);
        document.removeEventListener("keydown", this.keydownEvent);
      }
    });
  }

  keydownEvent = (event: KeyboardEvent) => {
    if (event.key === "Escape") {
      this.context.send("ESCAPE");
    }
  };

  outerEvent = (event: Event) => {
    const interactedWithin =
      event.target instanceof Node && this.contains(event.target);
    if (!interactedWithin) {
      this.context.send("OUTER_INTERACTION");
    }
  };
}

class AmalgoElement extends HTMLElement {
  getContext() {
    let parent = this.closest("amalgo-box") as AmalgoBox | undefined;
    if (!parent) throw new Error("Must be child of <amalgo-box>");
    return parent.context;
  }
}

class Button extends AmalgoElement {
  connectedCallback() {
    let button = this.childNodes[0];
    invariant(button instanceof HTMLButtonElement);
    button.addEventListener("click", () => {
      this.getContext().send("BUTTON_CLICK");
    });
  }
}

class Input extends AmalgoElement {}

class Popover extends AmalgoElement {
  connectedCallback() {
    this.getContext().subscribe(state => {
      if (state.value === "closed") {
        this.hidden = true;
      } else if (state.value === "open") {
        this.hidden = false;
      }
    });
  }
}

class Menu extends AmalgoElement {}

class Option extends AmalgoElement {}

////////////////////////////////////////////////////////////////////////////////
declare global {
  namespace JSX {
    interface IntrinsicElements {
      ["amalgo-box"]: CustomElement<AmalgoBox>;
      ["amalgo-button"]: CustomElement<Button>;
      ["amalgo-input"]: CustomElement<Input>;
      ["amalgo-popover"]: CustomElement<Popover>;
      ["amalgo-menu"]: CustomElement<Menu>;
      ["amalgo-option"]: CustomElement<Option>;
    }
  }
}

window.customElements.define("amalgo-box", AmalgoBox);
window.customElements.define("amalgo-button", Button);
window.customElements.define("amalgo-input", Input);
window.customElements.define("amalgo-popover", Popover);
window.customElements.define("amalgo-menu", Menu);
window.customElements.define("amalgo-option", Option);