(function () {
  class ImageUploader {
    constructor(container, options = {}) {
      this.container = container;
      // Ensure container is relatively positioned.
      if (getComputedStyle(container).position === "static") {
        container.style.position = "relative";
      }
      // Set default options.
      this.options = Object.assign(
        {
          uploadUrl: "upload.php",
          cropRatio: 1, // default: square crop
          removeUrl: "removeimage.php",
        },
        options
      );
      // Override with data attributes if provided.
      if (container.dataset.service) {
        this.options.uploadUrl = container.dataset.service;
      } else if (container.dataset.uploadUrl) {
        this.options.uploadUrl = container.dataset.uploadUrl;
      }
      if (container.dataset.cropRatio) {
        this.options.cropRatio = parseFloat(container.dataset.cropRatio);
      }
      if (container.dataset.removeUrl) {
        this.options.removeUrl = container.dataset.removeUrl;
      }

      // Create hidden file input.
      this.fileInput = document.createElement("input");
      this.fileInput.type = "file";
      this.fileInput.accept = "image/*";
      this.fileInput.style.display = "none";
      container.appendChild(this.fileInput);

      // Create placeholder message.
      this.messageElem = document.createElement("div");
      this.messageElem.className = "upload-message";
      this.messageElem.textContent = container.dataset.label || "Click or drag to upload";
      container.appendChild(this.messageElem);

      // Create preview container.
      this.previewElem = document.createElement("div");
      this.previewElem.className = "preview";
      Object.assign(this.previewElem.style, {
        width: "100%",
        height: "100%",
        overflow: "hidden",
      });
      container.appendChild(this.previewElem);

      // Create upload icon button.
      this.uploadIcon = document.createElement("div");
      this.uploadIcon.className = "upload-icon";
      this.uploadIcon.innerHTML = '<span class="btn btn-sm btn-secondary"><i class="fad fa-upload"></i></span>';
      // We remove individual absolute positioning here so that in the three‑button layout the container will manage the spacing.
      // (For example, we leave the styling to the arrangeBottomIcons() function when an image is loaded.)

      // (Crop and delete icons will be created later.)
      this.cropIcon = null;
      this.deleteIcon = null;

      // If an initial image URL is provided, load it.
      if (container.dataset.initialImage) {
        this.loadExistingImage(container.dataset.initialImage);
      } else {
        // If no image is present, keep the upload icon in its original position.
        this.uploadIcon.style.position = "absolute";
        this.uploadIcon.style.bottom = "5px";
        this.uploadIcon.style.left = "50%";
        this.uploadIcon.style.transform = "translateX(-50%)";
        this.uploadIcon.style.cursor = "pointer";
        container.appendChild(this.uploadIcon);
      }

      // Create the crop modal.
      this.createCropModal();

      // Internal state for cropping.
      this.image = new Image();
      this.cropRect = { x: 0, y: 0, width: 0, height: 0 };
      this.isDragging = false;
      this.isResizing = false;
      this.resizingHandle = null; // "tl", "tr", "bl", "br"
      this.dragOffset = { x: 0, y: 0 };
      this.anchor = null; // Fixed corner during resizing

      // We'll use a constant margin of 20px.
      this.margin = 20;
      this.imgScaledWidth = 0;
      this.imgScaledHeight = 0;

      this.bindEvents();
    }

    createCropModal() {
      // Create the modal overlay.
      this.cropModal = document.createElement("div");
      this.cropModal.className = "crop-modal";
      Object.assign(this.cropModal.style, {
        position: "fixed",
        top: "0",
        left: "0",
        right: "0",
        bottom: "0",
        background: "rgba(0,0,0,0.7)",
        display: "none",
        justifyContent: "center",
        alignItems: "center",
        zIndex: "1000",
      });

      // Create modal content container with padding.
      const modalContent = document.createElement("div");
      modalContent.className = "modal-content";
      Object.assign(modalContent.style, {
        background: "#fff",
        padding: "20px",
        borderRadius: "8px",
        width: "auto",
        display: "flex",
        flexDirection: "column",
        alignItems: "center",
      });

      // Create a container for the canvas.
      const canvasContainer = document.createElement("div");
      canvasContainer.style.width = "100%";
      canvasContainer.style.display = "flex";
      canvasContainer.style.justifyContent = "center";
      // Create the cropping canvas.
      this.cropCanvas = document.createElement("canvas");
      this.cropCanvas.className = "crop-canvas";
      this.cropCanvas.style.border = "1px solid #ccc";
      canvasContainer.appendChild(this.cropCanvas);

      // Create crop buttons container.
      const cropButtons = document.createElement("div");
      cropButtons.className = "crop-buttons";
      Object.assign(cropButtons.style, {
        marginTop: "10px",
        width: "100%",
        display: "flex",
        justifyContent: "center",
      });

      this.cropCancel = document.createElement("button");
      this.cropCancel.className = "btn btn-secondary";
      this.cropCancel.textContent = "Cancel";
      this.cropConfirm = document.createElement("button");
      this.cropConfirm.className = "btn btn-secondary";
      this.cropConfirm.textContent = "Crop";
      this.cropConfirm.style.marginLeft = "10px";

      cropButtons.appendChild(this.cropCancel);
      cropButtons.appendChild(this.cropConfirm);

      modalContent.appendChild(canvasContainer);
      modalContent.appendChild(cropButtons);
      this.cropModal.appendChild(modalContent);
      document.body.appendChild(this.cropModal);

      this.canvasCtx = this.cropCanvas.getContext("2d");
    }

    bindEvents() {
      this.container.addEventListener("click", () => {
        this.fileInput.click();
      });
      this.container.addEventListener("dragover", (e) => {
        e.preventDefault();
        this.container.classList.add("hover");
      });
      this.container.addEventListener("dragleave", (e) => {
        e.preventDefault();
        this.container.classList.remove("hover");
      });
      this.container.addEventListener("drop", (e) => {
        e.preventDefault();
        this.container.classList.remove("hover");
        const files = e.dataTransfer.files;
        if (files && files[0]) {
          this.handleFile(files[0]);
        }
      });
      this.fileInput.addEventListener("change", () => {
        if (this.fileInput.files && this.fileInput.files[0]) {
          this.handleFile(this.fileInput.files[0]);
        }
      });

      // --- Crop modal events ---
      this.cropCanvas.addEventListener("mousedown", (e) => {
        const rect = this.cropCanvas.getBoundingClientRect();
        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;
        const threshold = 10;
        let handle = null;
        if (Math.hypot(mouseX - this.cropRect.x, mouseY - this.cropRect.y) < threshold) {
          handle = "tl";
        } else if (Math.hypot(mouseX - (this.cropRect.x + this.cropRect.width), mouseY - this.cropRect.y) < threshold) {
          handle = "tr";
        } else if (Math.hypot(mouseX - this.cropRect.x, mouseY - (this.cropRect.y + this.cropRect.height)) < threshold) {
          handle = "bl";
        } else if (Math.hypot(mouseX - (this.cropRect.x + this.cropRect.width), mouseY - (this.cropRect.y + this.cropRect.height)) < threshold) {
          handle = "br";
        }
        if (handle) {
          this.isResizing = true;
          this.resizingHandle = handle;
          if (handle === "tl") {
            this.anchor = { x: this.cropRect.x + this.cropRect.width, y: this.cropRect.y + this.cropRect.height };
          } else if (handle === "tr") {
            this.anchor = { x: this.cropRect.x, y: this.cropRect.y + this.cropRect.height };
          } else if (handle === "bl") {
            this.anchor = { x: this.cropRect.x + this.cropRect.width, y: this.cropRect.y };
          } else if (handle === "br") {
            this.anchor = { x: this.cropRect.x, y: this.cropRect.y };
          }
        } else if (mouseX >= this.cropRect.x && mouseX <= this.cropRect.x + this.cropRect.width && mouseY >= this.cropRect.y && mouseY <= this.cropRect.y + this.cropRect.height) {
          this.isDragging = true;
          this.dragOffset.x = mouseX - this.cropRect.x;
          this.dragOffset.y = mouseY - this.cropRect.y;
        }
      });

      this.cropCanvas.addEventListener("mousemove", (e) => {
        const rect = this.cropCanvas.getBoundingClientRect();
        const mouseX = e.clientX - rect.left;
        const mouseY = e.clientY - rect.top;
        if (!this.isDragging && !this.isResizing) {
          const threshold = 10;
          let cursor = "default";
          if (Math.hypot(mouseX - this.cropRect.x, mouseY - this.cropRect.y) < threshold) {
            cursor = "nwse-resize";
          } else if (Math.hypot(mouseX - (this.cropRect.x + this.cropRect.width), mouseY - this.cropRect.y) < threshold) {
            cursor = "nesw-resize";
          } else if (Math.hypot(mouseX - this.cropRect.x, mouseY - (this.cropRect.y + this.cropRect.height)) < threshold) {
            cursor = "nesw-resize";
          } else if (Math.hypot(mouseX - (this.cropRect.x + this.cropRect.width), mouseY - (this.cropRect.y + this.cropRect.height)) < threshold) {
            cursor = "nwse-resize";
          } else if (mouseX >= this.cropRect.x && mouseX <= this.cropRect.x + this.cropRect.width && mouseY >= this.cropRect.y && mouseY <= this.cropRect.y + this.cropRect.height) {
            cursor = "move";
          }
          this.cropCanvas.style.cursor = cursor;
        }
        if (this.isResizing) {
          const newRect = this.calculateResizedCropRect({ x: mouseX, y: mouseY });
          // Constrain within the image area (image drawn from margin to margin+scaled dims).
          const minX = this.margin;
          const minY = this.margin;
          const maxX = this.margin + this.imgScaledWidth;
          const maxY = this.margin + this.imgScaledHeight;
          if (newRect.x < minX) newRect.x = minX;
          if (newRect.y < minY) newRect.y = minY;
          if (newRect.x + newRect.width > maxX) newRect.width = maxX - newRect.x;
          if (newRect.y + newRect.height > maxY) newRect.height = maxY - newRect.y;
          this.cropRect = newRect;
          this.drawCropOverlay();
        } else if (this.isDragging) {
          this.cropRect.x = mouseX - this.dragOffset.x;
          this.cropRect.y = mouseY - this.dragOffset.y;
          const minX = this.margin;
          const minY = this.margin;
          const maxX = this.margin + this.imgScaledWidth;
          const maxY = this.margin + this.imgScaledHeight;
          if (this.cropRect.x < minX) this.cropRect.x = minX;
          if (this.cropRect.y < minY) this.cropRect.y = minY;
          if (this.cropRect.x + this.cropRect.width > maxX) this.cropRect.x = maxX - this.cropRect.width;
          if (this.cropRect.y + this.cropRect.height > maxY) this.cropRect.y = maxY - this.cropRect.height;
          this.drawCropOverlay();
        }
      });

      this.cropCanvas.addEventListener("mouseup", () => {
        this.isDragging = false;
        this.isResizing = false;
      });
      this.cropCanvas.addEventListener("mouseleave", () => {
        this.isDragging = false;
        this.isResizing = false;
      });

      this.cropCancel.addEventListener("click", () => {
        this.hideCropModal();
      });
      this.cropConfirm.addEventListener("click", () => {
        this.cropImage();
      });
    }

    calculateResizedCropRect(mouse) {
      let candidateWidth = Math.abs(mouse.x - this.anchor.x);
      let candidateHeight = Math.abs(mouse.y - this.anchor.y);
      let desiredRatio = this.options.cropRatio;
      let newWidth, newHeight;
      if (candidateWidth / candidateHeight > desiredRatio) {
        newHeight = candidateHeight;
        newWidth = newHeight * desiredRatio;
      } else {
        newWidth = candidateWidth;
        newHeight = newWidth / desiredRatio;
      }
      let newX, newY;
      if (this.resizingHandle === "tl") {
        newX = this.anchor.x - newWidth;
        newY = this.anchor.y - newHeight;
      } else if (this.resizingHandle === "tr") {
        newX = this.anchor.x;
        newY = this.anchor.y - newHeight;
      } else if (this.resizingHandle === "bl") {
        newX = this.anchor.x - newWidth;
        newY = this.anchor.y;
      } else if (this.resizingHandle === "br") {
        newX = this.anchor.x;
        newY = this.anchor.y;
      }
      return { x: newX, y: newY, width: newWidth, height: newHeight };
    }

    handleFile(file) {
      if (!file.type.startsWith("image/")) return;
      const reader = new FileReader();
      reader.onload = (e) => {
        let newImg = new Image();
        newImg.onload = () => {
          this.image = newImg;
          this.openCropModal();
        };
        newImg.src = e.target.result;
      };
      reader.readAsDataURL(file);
    }

    openCropModal() {
      const maxWidth = 800,
        maxHeight = 600;
      let imgWidth = this.image.naturalWidth;
      let imgHeight = this.image.naturalHeight;
      let scale = Math.min(maxWidth / imgWidth, maxHeight / imgHeight, 1);
      this.imgScaledWidth = imgWidth * scale;
      this.imgScaledHeight = imgHeight * scale;
      // Set canvas size = image size plus 40px (20px margin each side).
      this.cropCanvas.width = this.imgScaledWidth + 40;
      this.cropCanvas.height = this.imgScaledHeight + 40;
      this.cropCanvas.style.width = this.cropCanvas.width + "px";
      this.cropCanvas.style.height = this.cropCanvas.height + "px";
      this.canvasCtx.clearRect(0, 0, this.cropCanvas.width, this.cropCanvas.height);
      // Draw the image at (margin, margin).
      this.canvasCtx.drawImage(this.image, this.margin, this.margin, this.imgScaledWidth, this.imgScaledHeight);
      // Initialize the crop rectangle to 80% of the image area.
      const canvasImageWidth = this.cropCanvas.width - 2 * this.margin;
      const canvasImageHeight = this.cropCanvas.height - 2 * this.margin;
      let initWidth = canvasImageWidth * 0.8;
      let initHeight = initWidth / this.options.cropRatio;
      if (initHeight > canvasImageHeight * 0.8) {
        initHeight = canvasImageHeight * 0.8;
        initWidth = initHeight * this.options.cropRatio;
      }
      this.cropRect = {
        x: this.margin + (canvasImageWidth - initWidth) / 2,
        y: this.margin + (canvasImageHeight - initHeight) / 2,
        width: initWidth,
        height: initHeight,
      };
      this.drawCropOverlay();
      this.cropModal.style.display = "flex";
    }

    drawCropOverlay() {
      this.canvasCtx.clearRect(0, 0, this.cropCanvas.width, this.cropCanvas.height);
      this.canvasCtx.drawImage(this.image, this.margin, this.margin, this.imgScaledWidth, this.imgScaledHeight);
      // Draw the crop rectangle (the area selected by the user) with 80% opacity fill.
      this.canvasCtx.fillStyle = "rgba(255,255,255,0.8)";
      this.canvasCtx.fillRect(this.cropRect.x, this.cropRect.y, this.cropRect.width, this.cropRect.height);
      // Draw border.
      this.canvasCtx.strokeStyle = "#fff";
      this.canvasCtx.lineWidth = 2;
      this.canvasCtx.strokeRect(this.cropRect.x, this.cropRect.y, this.cropRect.width, this.cropRect.height);
      // Draw circular handles at the corners.
      const handleRadius = 5;
      const drawHandle = (x, y) => {
        this.canvasCtx.beginPath();
        this.canvasCtx.arc(x, y, handleRadius, 0, 2 * Math.PI);
        this.canvasCtx.fillStyle = "rgba(255,255,255,0.8)";
        this.canvasCtx.fill();
        this.canvasCtx.lineWidth = 2;
        this.canvasCtx.strokeStyle = "#000";
        this.canvasCtx.stroke();
      };
      drawHandle(this.cropRect.x, this.cropRect.y);
      drawHandle(this.cropRect.x + this.cropRect.width, this.cropRect.y);
      drawHandle(this.cropRect.x, this.cropRect.y + this.cropRect.height);
      drawHandle(this.cropRect.x + this.cropRect.width, this.cropRect.y + this.cropRect.height);
    }

    hideCropModal() {
      this.cropModal.style.display = "none";
    }

    cropImage() {
      const scale = this.image.naturalWidth / this.imgScaledWidth;
      const cropX = this.cropRect.x - this.margin;
      const cropY = this.cropRect.y - this.margin;
      const tempCanvas = document.createElement("canvas");
      tempCanvas.width = this.cropRect.width * scale;
      tempCanvas.height = this.cropRect.height * scale;
      const tempCtx = tempCanvas.getContext("2d");
      tempCtx.drawImage(this.image, cropX * scale, cropY * scale, this.cropRect.width * scale, this.cropRect.height * scale, 0, 0, this.cropRect.width * scale, this.cropRect.height * scale);
      const croppedDataURL = tempCanvas.toDataURL("image/png");
      this.showPreview(croppedDataURL);
      this.uploadCroppedImage(croppedDataURL);
      this.hideCropModal();
    }

    showPreview(dataURL) {
      const previewImage = document.createElement("img");
      previewImage.src = dataURL;
      previewImage.alt = "Cropped Image";
      Object.assign(previewImage.style, {
        width: "100%",
        height: "100%",
        objectFit: "cover",
        display: "block",
      });
      this.previewElem.innerHTML = "";
      this.previewElem.appendChild(previewImage);
      this.messageElem.style.display = "none";
      // Arrange bottom icons in a container.
      this.arrangeBottomIcons();
    }

    arrangeBottomIcons() {
      // Remove any existing bottom icons container.
      let bottomContainer = this.container.querySelector(".bottom-icons");
      if (!bottomContainer) {
        bottomContainer = document.createElement("div");
        bottomContainer.className = "bottom-icons";
        Object.assign(bottomContainer.style, {
          position: "absolute",
          bottom: "5px",
          left: "50%",
          transform: "translateX(-50%)",
          display: "flex",
          justifyContent: "space-between",
          alignItems: "center",
          width: "200px", // Adjust this width as needed.
        });
        this.container.appendChild(bottomContainer);
      } else {
        bottomContainer.innerHTML = "";
      }
      // Remove individual icon positioning if they were previously appended.
      this.uploadIcon.style.position = "";
      this.cropIcon && (this.cropIcon.style.position = "");
      this.deleteIcon && (this.deleteIcon.style.position = "");

      // Append the three icons.
      // Ensure uploadIcon exists (it was created in the constructor).
      bottomContainer.appendChild(this.uploadIcon);
      if (!this.cropIcon) {
        this.addCropIcon();
      }
      bottomContainer.appendChild(this.cropIcon);
      if (!this.deleteIcon) {
        this.addDeleteIcon();
      }
      bottomContainer.appendChild(this.deleteIcon);
    }

    addCropIcon() {
      if (this.cropIcon) return;
      this.cropIcon = document.createElement("div");
      this.cropIcon.className = "crop-icon";
      this.cropIcon.innerHTML = '<span class="btn btn-sm btn-secondary"><i class="fad fa-crop"></i></span>';
      this.cropIcon.addEventListener("click", (e) => {
        e.stopPropagation();
        const currentImg = this.previewElem.querySelector("img");
        if (currentImg && currentImg.src) {
          this.image.onload = () => {
            this.openCropModal();
          };
          this.image.src = currentImg.src;
          this.openCropModal();
        }
      });
    }

    addDeleteIcon() {
      if (this.deleteIcon) return;
      this.deleteIcon = document.createElement("div");
      this.deleteIcon.className = "delete-icon";
      this.deleteIcon.innerHTML = '<span class="btn btn-sm btn-danger"><i class="fad fa-trash"></i></span>';
      this.deleteIcon.addEventListener("click", (e) => {
        e.stopPropagation();
        this.removeImage();
      });
    }

    removeImage() {
      if (!confirm("Are you sure you want to remove this image?")) return;
      const filename = this.container.dataset.filename || "";
      const formData = new FormData();
      formData.append("filename", filename);
      fetch(this.options.removeUrl, {
        method: "POST",
        body: formData,
      })
        .then((response) => response.json())
        .then((data) => {
          if (data.success) {
            this.previewElem.innerHTML = "";
            this.messageElem.style.display = "block";
            // Remove bottom icons container.
            let bottomContainer = this.container.querySelector(".bottom-icons");
            if (bottomContainer) {
              bottomContainer.remove();
            }
            // Reset uploadIcon back to its original absolute positioning.
            this.uploadIcon.style.position = "absolute";
            this.uploadIcon.style.bottom = "5px";
            this.uploadIcon.style.left = "50%";
            this.uploadIcon.style.transform = "translateX(-50%)";
          } else {
            alert("Error removing image.");
          }
        })
        .catch((error) => {
          console.error("Remove error:", error);
          alert("Error removing image.");
        });
    }

    loadExistingImage(url) {
      const previewImage = document.createElement("img");
      previewImage.src = url;
      previewImage.alt = "Uploaded Image";
      Object.assign(previewImage.style, {
        width: "100%",
        height: "100%",
        objectFit: "cover",
        display: "block",
      });
      this.previewElem.innerHTML = "";
      this.previewElem.appendChild(previewImage);
      this.messageElem.style.display = "none";
      this.arrangeBottomIcons();
    }

    dataURLtoBlob(dataurl) {
      const arr = dataurl.split(",");
      const mime = arr[0].match(/:(.*?);/)[1];
      const bstr = atob(arr[1]);
      let n = bstr.length;
      const u8arr = new Uint8Array(n);
      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      return new Blob([u8arr], { type: mime });
    }

    uploadCroppedImage(dataURL) {
      const blob = this.dataURLtoBlob(dataURL);
      const formData = new FormData();
      formData.append("image", blob, "cropped.png");
      if (this.container.dataset.metaReportid) {
        formData.append("meta_reportid", this.container.dataset.metaReportid);
      }
      if (this.container.dataset.metaBoardid) {
        formData.append("meta_boardid", this.container.dataset.metaBoardid);
      }
      if (this.container.dataset.metaGroup) {
        formData.append("meta_group", this.container.dataset.metaGroup);
      }
      if (this.container.dataset.metaObservation) {
        formData.append("meta_observation", this.container.dataset.metaObservation);
      }
      if (this.container.dataset.metaImagenum) {
        formData.append("meta_imagenum", this.container.dataset.metaImagenum);
      }
      fetch(this.options.uploadUrl, {
        method: "POST",
        body: formData,
      })
        .then((response) => response.json())
        .then((data) => {
          console.log("Upload success:", data);
          if (data.filename) {
            this.container.dataset.filename = data.filename;
          }
          this.showUploadSuccessIndicator();
        })
        .catch((error) => {
          console.error("Upload error:", error);
          this.showUploadFailIndicator("Upload failed. Please try again.");
        });
    }

    showUploadSuccessIndicator() {
      const indicator = document.createElement("div");
      indicator.className = "upload-success";
      indicator.innerHTML = '<i class="fad fa-check"></i>';
      Object.assign(indicator.style, {
        position: "absolute",
        top: "5px",
        right: "5px",
        width: "24px",
        height: "24px",
        background: "green",
        borderRadius: "50%",
        color: "#fff",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        fontSize: "16px",
        opacity: "1",
        transition: "opacity 1s ease-out",
        zIndex: "10",
      });
      this.container.appendChild(indicator);
      setTimeout(() => {
        indicator.style.opacity = "0";
        indicator.addEventListener("transitionend", () => {
          if (indicator.parentNode) {
            indicator.parentNode.removeChild(indicator);
          }
        });
      }, 2000);
    }

    showUploadFailIndicator(message) {
      const alertDiv = document.createElement("div");
      alertDiv.className = "upload-fail-alert alert alert-danger";
      Object.assign(alertDiv.style, {
        position: "absolute",
        top: "5px",
        right: "5px",
        zIndex: "10",
        minWidth: "150px",
        textAlign: "center",
        opacity: "1",
        transition: "opacity 1s ease-out",
      });
      alertDiv.textContent = message || "Upload failed";
      this.container.appendChild(alertDiv);
      setTimeout(() => {
        alertDiv.style.opacity = "0";
        alertDiv.addEventListener("transitionend", () => {
          if (alertDiv.parentNode) {
            alertDiv.parentNode.removeChild(alertDiv);
          }
        });
      }, 3000);
    }
  }

  function initializeImageUploaders(globalOptions = {}) {
    const elements = document.querySelectorAll(".imup");
    elements.forEach((element) => {
      new ImageUploader(element, globalOptions);
    });
  }

  window.initializeImageUploaders = initializeImageUploaders;
})();
