(() => {
  "use strict";

  const MARKER_SHAPES = new Set(["rectangle", "square", "round"]);
  const DEFAULT_WIDTH = 9;
  const DEFAULT_HEIGHT = 7;
  const DEFAULT_FILL_COLOR = "#ffffff";
  const DEFAULT_TEXT_COLOR = "#1d2327";
  const ROTATION_STEP = 15;

  const clamp = (value, minimum, maximum) =>
    Math.min(maximum, Math.max(minimum, Number(value) || 0));
  const round = (value, precision = 2) => {
    const multiplier = 10 ** precision;

    return Math.round(Number(value) * multiplier) / multiplier;
  };
  const normalizeRotation = (value) => {
    const rotation = Number(value) || 0;

    return ((((rotation + 180) % 360) + 360) % 360) - 180;
  };
  const snapRotation = (value) =>
    normalizeRotation(
      Math.round(Number(value) / ROTATION_STEP) * ROTATION_STEP,
    );
  const normalizeColor = (value) => {
    const color = String(value || "").trim();

    return /^#[0-9a-f]{6}$/i.test(color) ? color.toLowerCase() : "";
  };

  const setCssVariable = (element, property, value) => {
    const inlineStyles = element.style;

    inlineStyles.setProperty(property, String(value));
  };
  const removeCssVariable = (element, property) => {
    const inlineStyles = element.style;

    inlineStyles.removeProperty(property);
  };

  const getDefaultPosition = (index, total) => {
    const columns = Math.min(4, Math.max(1, total));
    const rows = Math.ceil(total / columns);
    const column = index % columns;
    const row = Math.floor(index / columns);

    return {
      x: ((column + 1) / (columns + 1)) * 100,
      y: ((row + 1) / (rows + 1)) * 100,
    };
  };

  const normalizeGeometry = (geometry, fallbackPosition) => {
    const source = geometry && typeof geometry === "object" ? geometry : {};
    const shape = MARKER_SHAPES.has(String(source.shape || ""))
      ? String(source.shape)
      : "rectangle";
    const width = round(clamp(source.width || DEFAULT_WIDTH, 4, 60));
    const height =
      shape === "square" || shape === "round"
        ? width
        : round(clamp(source.height || DEFAULT_HEIGHT, 4, 60));

    return {
      x: round(
        clamp(
          Number.isFinite(Number(source.x)) ? source.x : fallbackPosition.x,
          width / 2,
          100 - width / 2,
        ),
      ),
      y: round(
        clamp(
          Number.isFinite(Number(source.y)) ? source.y : fallbackPosition.y,
          height / 2,
          100 - height / 2,
        ),
      ),
      width,
      height,
      rotation: snapRotation(source.rotation || 0),
      shape,
      fill: normalizeColor(source.fill),
      text_color: normalizeColor(source.text_color),
    };
  };

  const applyGeometry = (marker, geometry) => {
    marker.classList.remove(
      "foodord-reservations-floor-plan__marker--rectangle",
      "foodord-reservations-floor-plan__marker--square",
      "foodord-reservations-floor-plan__marker--round",
    );
    marker.classList.add(
      `foodord-reservations-floor-plan__marker--${geometry.shape}`,
    );
    setCssVariable(marker, "--foodord-grid-x", `${geometry.x}%`);
    setCssVariable(marker, "--foodord-grid-y", `${geometry.y}%`);
    setCssVariable(marker, "--foodord-grid-width", `${geometry.width}%`);
    setCssVariable(marker, "--foodord-grid-height", `${geometry.height}%`);
    setCssVariable(
      marker,
      "--foodord-grid-rotation",
      `${geometry.rotation}deg`,
    );
    if (geometry.fill) {
      setCssVariable(marker, "--foodord-image-marker-fill", geometry.fill);
    } else {
      removeCssVariable(marker, "--foodord-image-marker-fill");
    }
    if (geometry.text_color) {
      setCssVariable(
        marker,
        "--foodord-image-marker-text-color",
        geometry.text_color,
      );
    } else {
      removeCssVariable(marker, "--foodord-image-marker-text-color");
    }
  };

  const initPlanEditor = ({ plan, layout, syncLayouts }) => {
    const editor = plan.querySelector(
      "[data-foodord-reservation-floor-plan-image-editor]",
    );
    const workspace = plan.querySelector(
      ".foodord-reservations-floor-plan__image-workspace",
    );
    const sidebar = plan.querySelector(
      ".foodord-reservations-floor-plan__image-sidebar",
    );
    const canvas = plan.querySelector(
      "[data-foodord-reservation-floor-plan-canvas]",
    );
    const markersLayer = plan.querySelector(
      "[data-foodord-reservation-floor-plan-markers]",
    );
    const markers = Array.from(
      plan.querySelectorAll("[data-foodord-reservation-floor-plan-marker]"),
    );
    const inspector = plan.querySelector(
      "[data-foodord-reservation-image-marker-inspector]",
    );
    const selectionActions = plan.querySelector(
      "[data-foodord-reservation-image-marker-actions]",
    );
    const inspectorTitle = plan.querySelector(
      "[data-foodord-reservation-image-marker-title]",
    );
    const fields = Array.from(
      plan.querySelectorAll("[data-foodord-reservation-image-marker-property]"),
    );
    const abortController = new AbortController();
    let selectedTableId = "";

    if (
      !editor ||
      !workspace ||
      !sidebar ||
      !canvas ||
      !markersLayer ||
      !inspector ||
      !selectionActions
    ) {
      return null;
    }

    layout.tables =
      layout.tables && typeof layout.tables === "object" ? layout.tables : {};

    const getMarker = (tableId) =>
      markers.find(
        (marker) =>
          marker.dataset.foodordReservationFloorPlanMarker === String(tableId),
      ) || null;
    const getGeometry = () =>
      selectedTableId ? layout.tables[selectedTableId] || null : null;

    const updateInspector = () => {
      const geometry = getGeometry();
      const marker = getMarker(selectedTableId);
      const hasSelection = Boolean(geometry);

      editor.classList.toggle("has-marker-selection", hasSelection);
      sidebar.hidden = !hasSelection;
      inspector.hidden = !hasSelection;
      selectionActions.hidden = !hasSelection;
      if (!hasSelection) {
        return;
      }

      if (inspectorTitle && marker) {
        const name = marker.querySelector(
          ".foodord-reservations-floor-plan__marker-name",
        );
        inspectorTitle.textContent = name ? name.textContent.trim() : "";
      }

      fields.forEach((field) => {
        const property = field.dataset.foodordReservationImageMarkerProperty;
        if (
          !property ||
          !Object.prototype.hasOwnProperty.call(geometry, property)
        ) {
          return;
        }

        if (property === "fill") {
          field.value = geometry.fill || DEFAULT_FILL_COLOR;
        } else if (property === "text_color") {
          field.value = geometry.text_color || DEFAULT_TEXT_COLOR;
        } else {
          field.value = String(geometry[property]);
        }
      });
    };

    const clearSelection = () => {
      markers.forEach((marker) => marker.classList.remove("is-selected"));
      selectedTableId = "";
      updateInspector();
    };

    const selectMarker = (tableId) => {
      const marker = getMarker(tableId);
      if (!marker || !layout.tables[tableId]) {
        return;
      }

      selectedTableId = String(tableId);
      markers.forEach((item) =>
        item.classList.toggle("is-selected", item === marker),
      );
      updateInspector();
    };

    const render = () => {
      markers.forEach((marker, index) => {
        const tableId = marker.dataset.foodordReservationFloorPlanMarker || "";
        const geometry = normalizeGeometry(
          layout.tables[tableId],
          getDefaultPosition(index, markers.length),
        );

        layout.tables[tableId] = geometry;
        applyGeometry(marker, geometry);
      });

      if (selectedTableId && !layout.tables[selectedTableId]) {
        clearSelection();
      } else {
        updateInspector();
      }
    };

    const updateSelectedMarker = () => {
      const marker = getMarker(selectedTableId);
      const geometry = getGeometry();

      if (marker && geometry) {
        applyGeometry(marker, geometry);
      }
      updateInspector();
    };

    const startMoveOrResize = (event, marker, isResize) => {
      const tableId = marker.dataset.foodordReservationFloorPlanMarker || "";
      const geometry = layout.tables[tableId];
      const canvasRect = canvas.getBoundingClientRect();

      if (
        !geometry ||
        !canvasRect.width ||
        !canvasRect.height ||
        canvas.classList.contains("is-empty")
      ) {
        return;
      }

      event.preventDefault();
      event.stopPropagation();
      selectMarker(tableId);
      marker.setPointerCapture(event.pointerId);
      marker.classList.add(isResize ? "is-resizing" : "is-dragging");

      const start = {
        clientX: event.clientX,
        clientY: event.clientY,
        x: geometry.x,
        y: geometry.y,
        width: geometry.width,
        height: geometry.height,
      };

      const update = (pointerEvent) => {
        const deltaX =
          ((pointerEvent.clientX - start.clientX) / canvasRect.width) * 100;
        const deltaY =
          ((pointerEvent.clientY - start.clientY) / canvasRect.height) * 100;

        if (isResize) {
          const widthChange = deltaX * 2;
          const heightChange = deltaY * 2;
          const resizeFromWidth =
            Math.abs(widthChange / start.width) >=
            Math.abs(heightChange / start.height);

          if (geometry.shape === "square" || geometry.shape === "round") {
            const size = round(
              clamp(
                start.width + (resizeFromWidth ? widthChange : heightChange),
                4,
                60,
              ),
            );
            geometry.width = size;
            geometry.height = size;
          } else {
            const aspectRatio = start.width / start.height;
            let width = resizeFromWidth
              ? clamp(start.width + widthChange, 4, 60)
              : clamp(start.height + heightChange, 4, 60) * aspectRatio;
            let height = width / aspectRatio;

            if (height > 60) {
              height = 60;
              width = height * aspectRatio;
            } else if (height < 4) {
              height = 4;
              width = height * aspectRatio;
            }
            geometry.width = round(clamp(width, 4, 60));
            geometry.height = round(height);
          }

          geometry.x = round(
            clamp(geometry.x, geometry.width / 2, 100 - geometry.width / 2),
          );
          geometry.y = round(
            clamp(geometry.y, geometry.height / 2, 100 - geometry.height / 2),
          );
        } else {
          geometry.x = round(
            clamp(
              start.x + deltaX,
              geometry.width / 2,
              100 - geometry.width / 2,
            ),
          );
          geometry.y = round(
            clamp(
              start.y + deltaY,
              geometry.height / 2,
              100 - geometry.height / 2,
            ),
          );
        }

        applyGeometry(marker, geometry);
        updateInspector();
      };

      const stop = () => {
        marker.classList.remove("is-dragging", "is-resizing");
        marker.removeEventListener("pointermove", update);
        marker.removeEventListener("pointerup", stop);
        marker.removeEventListener("pointercancel", stop);
        syncLayouts();
      };

      marker.addEventListener("pointermove", update);
      marker.addEventListener("pointerup", stop);
      marker.addEventListener("pointercancel", stop);
    };

    const startRotation = (event, marker) => {
      const tableId = marker.dataset.foodordReservationFloorPlanMarker || "";
      const geometry = layout.tables[tableId];
      const markerRect = marker.getBoundingClientRect();

      if (!geometry || !markerRect.width || !markerRect.height) {
        return;
      }

      event.preventDefault();
      event.stopPropagation();
      selectMarker(tableId);
      marker.setPointerCapture(event.pointerId);
      marker.classList.add("is-rotating");

      const centerX = markerRect.left + markerRect.width / 2;
      const centerY = markerRect.top + markerRect.height / 2;
      const startRotationValue = Number(geometry.rotation) || 0;
      let previousAngle = Math.atan2(
        event.clientY - centerY,
        event.clientX - centerX,
      );
      let accumulatedAngle = 0;

      const update = (pointerEvent) => {
        const currentAngle = Math.atan2(
          pointerEvent.clientY - centerY,
          pointerEvent.clientX - centerX,
        );
        let angleChange = currentAngle - previousAngle;

        if (angleChange > Math.PI) {
          angleChange -= Math.PI * 2;
        } else if (angleChange < -Math.PI) {
          angleChange += Math.PI * 2;
        }

        accumulatedAngle += angleChange;
        previousAngle = currentAngle;
        geometry.rotation = snapRotation(
          startRotationValue + (accumulatedAngle * 180) / Math.PI,
        );
        applyGeometry(marker, geometry);
        updateInspector();
      };

      const stop = () => {
        marker.classList.remove("is-rotating");
        marker.removeEventListener("pointermove", update);
        marker.removeEventListener("pointerup", stop);
        marker.removeEventListener("pointercancel", stop);
        syncLayouts();
      };

      marker.addEventListener("pointermove", update);
      marker.addEventListener("pointerup", stop);
      marker.addEventListener("pointercancel", stop);
    };

    const rotateSelected = (degrees) => {
      const geometry = getGeometry();
      if (!geometry) {
        return;
      }

      geometry.rotation = snapRotation(geometry.rotation + degrees);
      updateSelectedMarker();
      syncLayouts();
    };

    markersLayer.addEventListener(
      "pointerdown",
      (event) => {
        const target = event.target instanceof Element ? event.target : null;
        const marker = target
          ? target.closest("[data-foodord-reservation-floor-plan-marker]")
          : null;
        if (!marker) {
          return;
        }

        if (target.closest("[data-foodord-reservation-image-marker-rotate]")) {
          startRotation(event, marker);
          return;
        }

        startMoveOrResize(
          event,
          marker,
          Boolean(
            target.closest("[data-foodord-reservation-image-marker-resize]"),
          ),
        );
      },
      { signal: abortController.signal },
    );

    markersLayer.addEventListener(
      "click",
      (event) => {
        const target = event.target instanceof Element ? event.target : null;
        const marker = target
          ? target.closest("[data-foodord-reservation-floor-plan-marker]")
          : null;
        if (marker) {
          selectMarker(marker.dataset.foodordReservationFloorPlanMarker || "");
        }
      },
      { signal: abortController.signal },
    );

    canvas.addEventListener(
      "pointerdown",
      (event) => {
        if (event.target === canvas || event.target === markersLayer) {
          clearSelection();
        }
      },
      { signal: abortController.signal },
    );

    inspector.addEventListener(
      "input",
      (event) => {
        const field =
          event.target instanceof HTMLInputElement ? event.target : null;
        const geometry = getGeometry();
        const property = field
          ? field.dataset.foodordReservationImageMarkerProperty || ""
          : "";
        if (
          !field ||
          !geometry ||
          !["width", "height", "rotation", "fill", "text_color"].includes(
            property,
          )
        ) {
          return;
        }

        if (property === "fill" || property === "text_color") {
          const color = normalizeColor(field.value);
          if (!color) {
            return;
          }
          geometry[property] = color;
        } else if (property === "rotation") {
          geometry.rotation = snapRotation(field.value);
        } else {
          const size = round(clamp(field.value, 4, 60));
          geometry[property] = size;
          if (geometry.shape === "square" || geometry.shape === "round") {
            geometry.width = size;
            geometry.height = size;
          }
        }

        if (property !== "fill" && property !== "text_color") {
          geometry.x = round(
            clamp(geometry.x, geometry.width / 2, 100 - geometry.width / 2),
          );
          geometry.y = round(
            clamp(geometry.y, geometry.height / 2, 100 - geometry.height / 2),
          );
        }
        updateSelectedMarker();
        syncLayouts();
      },
      { signal: abortController.signal },
    );

    inspector.addEventListener(
      "change",
      (event) => {
        const field =
          event.target instanceof HTMLSelectElement ? event.target : null;
        const geometry = getGeometry();
        if (
          !field ||
          !geometry ||
          field.dataset.foodordReservationImageMarkerProperty !== "shape"
        ) {
          return;
        }

        const previousShape = geometry.shape;
        geometry.shape = MARKER_SHAPES.has(field.value)
          ? field.value
          : "rectangle";
        if (geometry.shape === "square" || geometry.shape === "round") {
          geometry.height = geometry.width;
        } else if (
          previousShape !== "rectangle" &&
          Math.abs(geometry.width - geometry.height) < 0.01
        ) {
          geometry.height = round(clamp(geometry.width * 0.75, 4, 60));
        }

        updateSelectedMarker();
        syncLayouts();
      },
      { signal: abortController.signal },
    );

    plan
      .querySelector("[data-foodord-reservation-image-marker-deselect]")
      ?.addEventListener("click", clearSelection, {
        signal: abortController.signal,
      });
    selectionActions
      .querySelector("[data-foodord-reservation-image-marker-rotate-left]")
      ?.addEventListener("click", () => rotateSelected(-ROTATION_STEP), {
        signal: abortController.signal,
      });
    selectionActions
      .querySelector("[data-foodord-reservation-image-marker-rotate-right]")
      ?.addEventListener("click", () => rotateSelected(ROTATION_STEP), {
        signal: abortController.signal,
      });

    const sidebarMedia = window.matchMedia("(min-width: 1181px)");
    const syncSidebarHeight = () => {
      if (!sidebarMedia.matches || plan.hidden) {
        removeCssVariable(sidebar, "--foodord-grid-sidebar-max-height");
        return;
      }

      const workspaceHeight = Math.round(
        workspace.getBoundingClientRect().height,
      );
      if (workspaceHeight > 0) {
        setCssVariable(
          sidebar,
          "--foodord-grid-sidebar-max-height",
          `${workspaceHeight}px`,
        );
      }
    };
    const workspaceObserver =
      typeof ResizeObserver === "function"
        ? new ResizeObserver(syncSidebarHeight)
        : null;

    workspaceObserver?.observe(workspace);
    sidebarMedia.addEventListener("change", syncSidebarHeight);
    window.addEventListener("resize", syncSidebarHeight);
    render();
    syncSidebarHeight();

    return {
      render,
      destroy() {
        abortController.abort();
        workspaceObserver?.disconnect();
        sidebarMedia.removeEventListener("change", syncSidebarHeight);
        window.removeEventListener("resize", syncSidebarHeight);
        clearSelection();
      },
    };
  };

  window.FoodordReservationImagePlanMarkerEditor = {
    init({ root, getLayout, syncLayouts }) {
      const controllers = new Map();

      root
        .querySelectorAll("[data-foodord-reservation-floor-plan]")
        .forEach((plan) => {
          const floorId = plan.dataset.foodordReservationFloorPlan || "";
          const controller = initPlanEditor({
            plan,
            layout: getLayout(floorId),
            syncLayouts,
          });

          if (controller) {
            controllers.set(floorId, controller);
          }
        });

      return {
        refreshPlan(floorId) {
          controllers.get(String(floorId))?.render();
        },
        destroy() {
          controllers.forEach((controller) => controller.destroy());
          controllers.clear();
        },
      };
    },
  };
})();
