export { PPTY };

function animDelta(anim) {
  const timing = anim.effect.getComputedTiming();
  return (timing.delay ?? 0) + (timing.duration ?? 0) + (timing.endDelay ?? 0);
}

class PPTY {
  #root;
  #blockOptions;
  #startCallback = _ => { };
  #finishCallback = _ => { };

  constructor(root, blockOptions) {
    this.#root = root;
    this.#blockOptions = blockOptions;
  }

  onstart(handler) {
    this.#startCallback = handler;
    return this;
  }

  onfinish(handler) {
    this.#finishCallback = handler;
    return this;
  }

  run() {
    var delay = 0;

    this.#startCallback(this.#root);

    this.#root
      .querySelectorAll(".ppty-block")
      .forEach((block, index, blocks) => {
        const prompt = block.querySelector(".ppty-prompt");
        const command = block.querySelector(".ppty-command");
        const output = block.querySelector(".ppty-output");

        const options = this.#blockOptions[index];
        const cps = options["cps"]; // chars per second
        const promptDelay = options["promptDelay"] * 1000;
        const commandDelay = options["commandDelay"] * 1000;
        const outputDelay = options["outputDelay"] * 1000;
        const blinkTime = options["blinkTime"] * 2 * 1000; // x2 then x1000

        // WARNING: ensure prompt|command|output != null
        const promptAnim = prompt.animate(
          [{ visibility: "visible" }],
          {
            delay: delay + promptDelay,
            fill: "forwards",
          },
        );
        delay = animDelta(promptAnim);

        const showCursor = () => { command.style.borderRightColor = cursorColor; return true; };
        const hideCursor = () => { command.style.borderRightColor = "transparent"; return false; };
        const startCursorBlink = () => setInterval(() => {
          cursorVisible = cursorVisible ? hideCursor() : showCursor();
        }, blinkTime);

        const cursorColor = command.style.borderRightColor;
        var cursorVisible = true;
        var blinkId = null;
        setTimeout(() => {
          command.style.visibility = "visible";
          blinkId = startCursorBlink();
        }, delay);

        // WARNING: ensure command.textContent != null
        const commandLen = command.textContent.trim().length
        const commandAnim = command.animate(
          [
            {
              width: "0ch",
              visibility: "visible",
            },
            {
              width: `${commandLen}ch`,
              visibility: "visible",
            }
          ],
          {
            duration: commandLen / cps * 1000,
            delay: delay + commandDelay,
            easing: `steps(${commandLen}, end)`,
            fill: "forwards",
          }
        );
        // pause the cursor while typing
        setTimeout(() => {
          clearInterval(blinkId);
          showCursor();
          cursorVisible = true;
        }, delay + commandDelay);
        delay = animDelta(commandAnim);
        setTimeout(() => {
          blinkId = startCursorBlink();
        }, delay);

        // delay until output is visible
        delay += outputDelay;
        setTimeout(() => {
          clearInterval(blinkId);
          hideCursor();
        }, delay);

        // unhide output
        output.animate(
          [{ visibility: "visible" }],
          {
            delay: delay,
            fill: "forwards",
          }
        );

        // hide the cursor after output displays and run the callback
        setTimeout(() => {
          command.style.borderRightColor = "transparent";
          if (index == blocks.length - 1)
            this.#finishCallback(this.#root);
        }, delay);
      });
  }
}



