const behaviorRegistry = {};
const debug = false;

const mutationObserver = new MutationObserver((mutations) => {
  mutations.forEach((mutation) => {
    mutation.removedNodes.forEach((node) => {
      if (node instanceof HTMLElement && !node.isConnected) {
        destroyBehaviors(node);
      }
    });
    mutation.addedNodes.forEach((node) => {
      if (node instanceof HTMLElement && node.isConnected) {
        applyBehaviors(node);
      }
    });
  });
});

// Update behaviors on Turbo events
// - turbo:load: initial page load, only listen once, afterward can rely on turbo:render
// - turbo:render: after page navigation, including back/forward, and failed form submissions
// - turbo:before-cache: before page navigation, reset DOM before caching
document.addEventListener(
  "turbo:load",
  () => {
    mutationObserver.observe(document.body, {
      childList: true,
      subtree: true,
    });

    applyBehaviors(document.body);
  },
  { once: true },
);

document.addEventListener("turbo:render", () => {
  mutationObserver.observe(document.body, {
    childList: true,
    subtree: true,
  });

  applyBehaviors(document.body);
});

document.addEventListener("turbo:before-cache", () => {
  destroyBehaviors(document.body);
});

export class Behavior {
  constructor(element) {
    this.element = element;
  }

  destroy() {}
}

export function registerBehavior(name, behavior) {
  behaviorRegistry[name] = behavior;
}

export function applyBehaviors(container, behaviorNames = null) {
  if (!behaviorNames) {
    behaviorNames = Object.keys(behaviorRegistry);
  }

  behaviorNames.forEach((behaviorName) => {
    const behavior = behaviorRegistry[behaviorName];
    const elements = Array.from(
      container.querySelectorAll(`[${behaviorName}]`),
    );

    // Include the container element if it has the behavior
    if (container.hasAttribute && container.hasAttribute(behaviorName)) {
      elements.push(container);
    }

    elements.forEach((element) => {
      element.__behaviors = element.__behaviors || [];
      const hasBehavior = element.__behaviors.some(
        (b) => b instanceof behavior,
      );

      if (hasBehavior) {
        return;
      }

      const behaviorInstance = new behavior(element);
      element.__behaviors.push(behaviorInstance);
      if (debug) {
        console.log(
          `[Behavior] ${behaviorInstance.constructor.name} initialized`,
        );
      }
    });
  });
}

export function destroyBehaviors(element) {
  const behaviorNames = Object.keys(behaviorRegistry);

  behaviorNames.forEach((behaviorName) => {
    const elements = Array.from(element.querySelectorAll(`[${behaviorName}]`));
    elements.push(element);

    elements.forEach((element) => {
      if (!element.__behaviors) {
        return;
      }

      element.__behaviors.forEach((behavior) => {
        behavior.destroy();
        if (debug) {
          console.log(`[Behavior] ${behavior.constructor.name} destroyed`);
        }
      });
      delete element.__behaviors;
    });
  });
}
