"use strict";

import {Controller} from "@hotwired/stimulus";
import "@tailwindplus/elements";

export default class extends Controller {
  static targets = ["dialog", "dynamicContent", "panel"];

  connect() {
    this.observer = null;
    this.hide = this.hide.bind(this);
    this.preventCancel = this.preventCancel.bind(this);
    this.loadAttributes = this.loadAttributes.bind(this);
    this.handleToggle = this.handleToggle.bind(this);
    this.clearContent = this.clearContent.bind(this);
    this.handleLiveConnect = this.handleLiveConnect.bind(this);

    window.addEventListener("turbo:before-visit", this.hide);
    window.addEventListener("turbo:before-render", this.hide);
    this.element.addEventListener("cancel", this.preventCancel);
    this.element.addEventListener("beforetoggle", this.preventCancel);
    this.element.addEventListener("toggle", this.handleToggle);
    this.element.addEventListener("close", this.clearContent);
    this.element.addEventListener("turbo:before-frame-render", this.loadAttributes);
    // `live:connect` bubbles up from any Live Component that mounts inside
    // the modal. We use it to register a response:error hook on each LC so
    // we can intercept 404s (the entity backing the LC was deleted while
    // the modal was open) and close the modal cleanly instead of letting
    // the default Live Component error overlay render the 404 HTML in a
    // full-screen iframe over our existing modal.
    this.element.addEventListener("live:connect", this.handleLiveConnect);
    if (this.hasDialogTarget) {
      this.dialogTarget.addEventListener("cancel", this.preventCancel);
      this.dialogTarget.addEventListener("close", this.clearContent);
    }

    if (this.hasDynamicContentTarget) {
      this.observer = new MutationObserver(() => this.updateState());
      this.observer.observe(this.dynamicContentTarget, {
        childList: true,
        characterData: true,
        subtree: true,
        attributes: true,
      });
      this.observer.observe(this.element, {
        attributes: true,
      });

      this.updateState();
    }
  }

  handleLiveConnect(event) {
    const controller = event.detail?.controller;
    if (!controller || typeof controller.on !== "function") {
      return;
    }

    // `response:error` is fired for ANY non-2xx response. We only auto-close
    // on 404 — other statuses (422 for validation, 500 for bugs) keep the
    // default error overlay so problems stay visible and debuggable. The
    // 404 case is by definition "the thing the modal was editing is gone",
    // which is the only status where closing the modal is the right answer.
    const onResponseError = (backendResponse, controls) => {
      const status = backendResponse?.response?.status;
      if (status !== 404) {
        return;
      }

      // Suppress the live-component-error iframe overlay — we're handling
      // the failure ourselves by closing the modal.
      controls.displayError = false;

      // clearContent() empties dynamicContentTarget; the MutationObserver
      // in updateState() then notices the empty innerHTML and calls
      // this.element.hide(). One side-effect: any preventCancel guard
      // still applies, but for a 404 we want the modal gone regardless.
      this.shouldPreventCancel = false;
      this.clearContent();
      this.element.hide();

      this.notifyEntityGone();
    };

    controller.on("response:error", onResponseError);
  }

  notifyEntityGone() {
    // Fetch the admin notification turbo-stream and append it. Mirrors the
    // path taken by ferienpass--admin-flash-message::renderFlash so the
    // toast shape matches every other flash on the site.
    const url = new URL("/admin/flash-message", window.location.origin);
    url.searchParams.set("type", "error");
    url.searchParams.set("title", "Dieser Datensatz existiert nicht mehr.");

    fetch(url, { headers: { Accept: "text/vnd.turbo-stream.html" } })
      .then((response) => (response.ok ? response.text() : null))
      .then((stream) => {
        if (stream) {
          document.body.insertAdjacentHTML("beforeend", stream);
        }
      })
      .catch(() => {
        // The toast is a nice-to-have; if the fetch fails (e.g. session
        // expired, network blip) the modal still closes cleanly.
      });
  }

  updateState() {
    this.shouldPreventCancel =
      this.dynamicContentTarget.hasAttribute("data-modal-prevent-cancel") ||
      this.dynamicContentTarget.dataset.modalPreventCancel === "true" ||
      this.element.hasAttribute("data-modal-prevent-cancel") ||
      this.element.dataset.modalPreventCancel === "true";

    const shouldOpen = this.dynamicContentTarget.innerHTML.trim().length > 0;
    if (shouldOpen) {
      this.element.show();
    } else {
      this.element.hide();
    }
  }

  loadAttributes(event) {
    if (event.target !== this.dynamicContentTarget) {
      return;
    }

    const { newFrame } = event.detail;

    const syncAttr = (target) => {
      Array.from(target.attributes)
        .filter((attr) => attr.name.startsWith("data-modal-"))
        .forEach((attr) => target.removeAttribute(attr.name));

      Array.from(newFrame.attributes)
        .filter((attr) => attr.name.startsWith("data-modal-"))
        .forEach((attr) => target.setAttribute(attr.name, attr.value));
    };

    syncAttr(this.dynamicContentTarget);
    syncAttr(this.element);
  }

  handleToggle(event) {
    if (event.newState === "closed") {
      this.clearContent();
    }
  }

  clearContent() {
    if (this.hasDynamicContentTarget) {
      this.dynamicContentTarget.innerHTML = "";
      this.dynamicContentTarget.removeAttribute("src");
    }
  }

  preventCancel(event) {
    const isClosing =
      event.type === "beforetoggle" && event.newState === "closed";

    if (this.shouldPreventCancel) {
      if (isClosing || event.type === "cancel") {
        event.preventDefault();
        return;
      }
    }

    if (isClosing) {
      this.clearContent();
    }
  }

  disconnect() {
    window.removeEventListener("turbo:before-visit", this.hide);
    window.removeEventListener("turbo:before-render", this.hide);
    this.element.removeEventListener("cancel", this.preventCancel);
    this.element.removeEventListener("beforetoggle", this.preventCancel);
    this.element.removeEventListener("toggle", this.handleToggle);
    this.element.removeEventListener("close", this.clearContent);
    this.element.removeEventListener(
      "turbo:before-frame-render",
      this.loadAttributes,
    );
    this.element.removeEventListener("live:connect", this.handleLiveConnect);
    if (this.hasDialogTarget) {
      this.dialogTarget.removeEventListener("cancel", this.preventCancel);
      this.dialogTarget.removeEventListener("close", this.clearContent);
    }

    if (this.observer) {
      this.observer.disconnect();
    }
    this.hide();
  }

  hide() {
    this.element.hide();
  }

  dispatch(event) {
    const { event: eventName } = event.params;
    if (eventName) {
      window.dispatchEvent(
        new CustomEvent(eventName, { bubbles: true, detail: event.params }),
      );
    }
  }
}
