(function (wp) {
  "use strict";

  var el = wp.element.createElement;
  var useState = wp.element.useState;
  var useEffect = wp.element.useEffect;

  function isDebugEnabled() {
    return !!(window.modeloraAdminData && window.modeloraAdminData.debug);
  }

  function createTraceId(prefix) {
    return [
      "modelora",
      prefix,
      Date.now().toString(36),
      Math.random().toString(36).slice(2, 8),
    ].join("-");
  }

  function logInfo(stage, payload) {
    if (!isDebugEnabled()) {
      return;
    }

    console.info("[MODELORA][ADMIN][" + stage + "]", payload || {});
  }

  function logWarn(stage, payload) {
    if (!isDebugEnabled()) {
      return;
    }

    console.warn("[MODELORA][ADMIN][" + stage + "]", payload || {});
  }

  function logError(stage, payload) {
    if (!isDebugEnabled()) {
      return;
    }

    console.error("[MODELORA][ADMIN][" + stage + "]", payload || {});
  }

  var conversionRuntimePromise = null;

  function getFileExtension(fileName) {
    if (!fileName || typeof fileName !== "string") {
      return "";
    }

    var parts = fileName.split(".");
    if (parts.length < 2) {
      return "";
    }

    return String(parts.pop() || "").toLowerCase();
  }

  function isPreviewableModelExtension(extension) {
    return ["glb", "gltf", "usdz"].indexOf(String(extension || "").toLowerCase()) !== -1;
  }

  function getLoadedModelExtension(state) {
    if (state && state.file && state.file.name) {
      return getFileExtension(state.file.name);
    }

    if (state && state.modelUrl) {
      try {
        return getFileExtension(new URL(state.modelUrl, window.location.href).pathname);
      } catch (e) {
        return getFileExtension(String(state.modelUrl).split("?")[0]);
      }
    }

    return "";
  }

  function sanitizeOutputFileName(raw) {
    var safe = String(raw || "converted-model")
      .replace(/\.[^.]+$/, "")
      .replace(/[^a-zA-Z0-9-_]+/g, "-")
      .replace(/^-+|-+$/g, "");

    if (!safe) {
      safe = "converted-model";
    }

    return safe + ".glb";
  }

  function buildRestEndpointCandidates(path) {
    var normalizedPath = String(path || "").replace(/^\/+/, "");
    var candidates = [];

    function addCandidate(base) {
      var safeBase = String(base || "").trim();
      if (!safeBase) {
        return;
      }

      if (safeBase.slice(-1) !== "/") {
        safeBase += "/";
      }

      var endpoint = safeBase + normalizedPath;
      if (candidates.indexOf(endpoint) === -1) {
        candidates.push(endpoint);
      }
    }

    var localizedRoot = window.modeloraAdminData && window.modeloraAdminData.restUrl ? String(window.modeloraAdminData.restUrl) : "";
    addCandidate(localizedRoot);

    if (window.wpApiSettings && window.wpApiSettings.root) {
      addCandidate(String(window.wpApiSettings.root) + "modelora/v1/");
    }

    if (localizedRoot && window.location && window.location.origin) {
      try {
        var parsed = new URL(localizedRoot, window.location.href);
        addCandidate(String(window.location.origin) + parsed.pathname);
      } catch (e) {
      }
    }

    if (window.location && window.location.origin) {
      addCandidate(String(window.location.origin) + "/wp-json/modelora/v1/");
      addCandidate("/wp-json/modelora/v1/");
    }

    return candidates;
  }

  function requestJsonWithFallback(path, requestOptions) {
    var endpoints = buildRestEndpointCandidates(path);
    var lastError = null;

    function attempt(index) {
      if (index >= endpoints.length) {
        return Promise.reject(lastError || new Error("Failed to fetch"));
      }

      var endpoint = endpoints[index];
      return fetch(endpoint, requestOptions)
        .then(function (res) {
          return res.json().then(function (data) {
            return { status: res.status, data: data, endpoint: endpoint };
          });
        })
        .catch(function (err) {
          var message = err && err.message ? err.message : "Failed to fetch";
          lastError = err instanceof Error ? err : new Error(message);

          logWarn("api_request_retry", {
            path: path,
            endpoint: endpoint,
            attempt: index + 1,
            message: message,
          });

          return attempt(index + 1);
        });
    }

    return attempt(0);
  }

  function escapeShortcodeAttribute(value) {
    return String(value || "")
      .replace(/&/g, "&amp;")
      .replace(/"/g, "&quot;")
      .replace(/\[/g, "&#91;")
      .replace(/\]/g, "&#93;");
  }

  function getPreviewBackgroundStyle(state) {
    if (!state || !state.backgroundEnabled) {
      return "transparent";
    }

    if (state.backgroundImageUrl) {
      return 'url("' + String(state.backgroundImageUrl).replace(/"/g, "%22") + '") center / cover no-repeat';
    }

    if (state.backgroundColor) {
      return state.backgroundColor;
    }

    return "radial-gradient(circle at 22% 18%, #ffffff, #eaf2fb 65%)";
  }

  function openWpImagePicker(onSelect) {
    if (!window.wp || !window.wp.media || typeof onSelect !== "function") {
      return;
    }

    var frame = window.wp.media({
      title: "Select Background Image",
      button: { text: "Use this image" },
      multiple: false,
      library: { type: "image" },
    });

    frame.on("select", function () {
      var selection = frame.state().get("selection").first();
      var data = selection ? selection.toJSON() : null;
      if (data && data.url) {
        onSelect(String(data.url));
      }
    });

    frame.open();
  }

  function copyTextToClipboard(text) {
    var value = String(text || "");

    if (!value) {
      return Promise.resolve(false);
    }

    if (navigator.clipboard && navigator.clipboard.writeText) {
      return navigator.clipboard.writeText(value)
        .then(function () {
          return true;
        })
        .catch(function () {
          var textarea = document.createElement("textarea");
          textarea.value = value;
          textarea.setAttribute("readonly", "readonly");
          textarea.style.position = "fixed";
          textarea.style.left = "-9999px";
          document.body.appendChild(textarea);
          textarea.focus();
          textarea.select();

          var copied = false;
          try {
            copied = !!document.execCommand("copy");
          } catch (e) {
            copied = false;
          }

          document.body.removeChild(textarea);
          return copied;
        });
    }

    return Promise.resolve((function () {
      var textarea = document.createElement("textarea");
      textarea.value = value;
      textarea.setAttribute("readonly", "readonly");
      textarea.style.position = "fixed";
      textarea.style.left = "-9999px";
      document.body.appendChild(textarea);
      textarea.focus();
      textarea.select();

      var copied = false;
      try {
        copied = !!document.execCommand("copy");
      } catch (e) {
        copied = false;
      }

      document.body.removeChild(textarea);
      return copied;
    })());
  }

  function choosePrimaryModelFile(files) {
    var supported = ["obj", "fbx", "stl", "dae", "gltf", "glb"];
    for (var i = 0; i < files.length; i += 1) {
      var ext = getFileExtension(files[i].name);
      if (supported.indexOf(ext) !== -1) {
        return files[i];
      }
    }

    return null;
  }

  function normalizeScene(THREE, scene, preserveTransform) {
    if (preserveTransform) {
      return;
    }

    scene.updateMatrixWorld(true);
    var box = new THREE.Box3().setFromObject(scene);
    if (box.isEmpty()) {
      return;
    }

    var size = new THREE.Vector3();
    var center = new THREE.Vector3();
    box.getSize(size);
    box.getCenter(center);

    var maxAxis = Math.max(size.x || 1, size.y || 1, size.z || 1);
    var scale = maxAxis > 0 ? 1 / maxAxis : 1;

    scene.position.sub(center);
    scene.scale.setScalar(scale);
    scene.updateMatrixWorld(true);
  }

  function convertFilesToGlb(files, options, onProgress) {
    return Promise.reject(new Error("Client-side conversion is not available in this version. Please upload GLB files directly, or convert your models to GLB using Babylon.js (https://sandbox.babylonjs.com), Blender, or similar tools."));
  }

  function UploadStep(props) {
    var state = props.state;
    var setState = props.setState;

    function onModelUrlInputChange(event) {
      setState({ ...state, modelUrlInput: String((event.target && event.target.value) || "") });
    }

    function onAddModelByUrl() {
      var url = String(state.modelUrlInput || "").trim();
      var traceId = createTraceId("url");

      if (!url) {
        setState({ ...state, error: "Please paste a model URL." });
        return;
      }

      setState({ ...state, isUploading: true, error: "", lastTraceId: traceId });

      requestJsonWithFallback("models/url", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-WP-Nonce": window.modeloraAdminData.nonce,
          "X-MODELORA-Trace": traceId,
        },
        credentials: "same-origin",
        body: JSON.stringify({ modelUrl: url }),
      })
        .then(function (result) {
          var data = result.data || {};
          var serverTraceId = data.trace_id || traceId;

          if (!data.success) {
            throw new Error(data.message || "Failed to add model URL.");
          }

          setState({
            ...state,
            file: { name: data.url ? data.url.split("/").pop() : "remote-model" },
            error: "",
            step: 2,
            modelUrl: data.url,
            modelId: data.model_id,
            isUploading: false,
            lastTraceId: serverTraceId,
          });
        })
        .catch(function (err) {
          setState({
            ...state,
            isUploading: false,
            error: err && err.message ? err.message : "Failed to add model URL.",
          });
        });
    }

    function onConvertFilesChange(event) {
      var files = Array.prototype.slice.call((event.target && event.target.files) || []);
      var primary = choosePrimaryModelFile(files);
      var outputName = state.convertOutputName;

      if (primary) {
        outputName = sanitizeOutputFileName(primary.name);
      }

      setState({
        ...state,
        convertFiles: files,
        convertError: "",
        convertStatus: primary ? "Ready to convert " + primary.name : "Select a source model file.",
        convertProgress: 0,
        convertWarnings: [],
        convertedPath: "",
        convertOutputName: outputName,
      });
    }

    function onConvertToGlb() {
      var files = Array.isArray(state.convertFiles) ? state.convertFiles : [];
      var primary = choosePrimaryModelFile(files);
      var traceId = createTraceId("convert");

      if (!primary) {
        setState({ ...state, convertError: "Select a source model file (OBJ, FBX, STL, DAE, GLTF, or GLB)." });
        return;
      }

      setState({
        ...state,
        isConverting: true,
        convertError: "",
        convertStatus: "Starting conversion...",
        convertProgress: 2,
        convertedPath: "",
        convertWarnings: [],
        lastTraceId: traceId,
      });

      convertFilesToGlb(
        files,
        {
          compression: state.convertCompression || "none",
          embedResources: !!state.convertEmbedResources,
          preserveTransform: !!state.convertPreserveTransform,
        },
        function (progress, message) {
          setState(function (prev) {
            return {
              ...prev,
              isConverting: true,
              convertProgress: Math.max(0, Math.min(100, progress || 0)),
              convertStatus: message || prev.convertStatus,
              convertError: "",
            };
          });
        }
      )
        .then(function (result) {
          var outputName = sanitizeOutputFileName(state.convertOutputName || primary.name);
          var convertedFile = new File([result.blob], outputName, { type: "model/gltf-binary" });
          var formData = new FormData();
          formData.append("file", convertedFile);
          formData.append("outputDir", String(state.convertOutputDir || "modelora-converted"));
          formData.append("sourceExtension", result.sourceExtension || "");
          formData.append("compression", String(state.convertCompression || "none"));
          formData.append("embedResources", state.convertEmbedResources ? "1" : "0");
          formData.append("preserveTransform", state.convertPreserveTransform ? "1" : "0");
          formData.append("conversionWarnings", JSON.stringify(result.warnings || []));

          setState(function (prev) {
            return {
              ...prev,
              isConverting: true,
              convertProgress: 97,
              convertStatus: "Uploading converted GLB...",
              convertWarnings: result.warnings || [],
            };
          });

          return requestJsonWithFallback("models/upload", {
            method: "POST",
            headers: {
              "X-WP-Nonce": window.modeloraAdminData.nonce,
              "X-MODELORA-Trace": traceId,
            },
            credentials: "same-origin",
            body: formData,
          }).then(function (response) {
            return {
              status: response.status,
              data: response.data,
              endpoint: response.endpoint,
              localWarnings: result.warnings || [],
            };
          });
        })
        .then(function (result) {
          var data = result.data || {};
          var warnings = (data.warnings || []).concat(result.localWarnings || []);
          var combinedWarnings = [];
          warnings.forEach(function (warning) {
            if (combinedWarnings.indexOf(warning) === -1) {
              combinedWarnings.push(warning);
            }
          });

          if (!data.success) {
            throw new Error(data.message || "Converted GLB upload failed.");
          }

          setState(function (prev) {
            return {
              ...prev,
              file: { name: data.url ? data.url.split("/").pop() : "converted.glb" },
              error: "",
              step: 2,
              modelUrl: data.url,
              modelId: data.model_id,
              isUploading: false,
              isConverting: false,
              convertProgress: 100,
              convertStatus: "Conversion complete.",
              convertWarnings: combinedWarnings,
              convertedPath: data.file_path || "",
              lastTraceId: data.trace_id || prev.lastTraceId,
            };
          });

          logInfo("convert_success", {
            traceId: data.trace_id || traceId,
            modelId: data.model_id || 0,
            filePath: data.file_path || "",
            endpoint: result.endpoint || "",
            warnings: combinedWarnings,
            issueCode: combinedWarnings.length ? "CONVERT_OK_WITH_WARNINGS" : "OK",
          });
        })
        .catch(function (err) {
          var message = err && err.message ? err.message : "Conversion failed.";
          logError("convert_failed", {
            traceId: traceId,
            message: message,
            issueCode: "CONVERT_RUNTIME_ERROR",
          });

          setState(function (prev) {
            return {
              ...prev,
              isConverting: false,
              convertError: message,
              convertStatus: "Conversion failed.",
            };
          });
        });
    }

    function onFileChange(event) {
      var file = event.target.files && event.target.files[0];
      if (!file) {
        return;
      }

      var traceId = createTraceId("upload");

      var ext = file.name.split(".").pop().toLowerCase();
      var maxMb = Number(window.modeloraAdminData.maxUploadMb || 50);
      var sizeMb = file.size / 1024 / 1024;
      logInfo("upload_file_selected", {
        traceId: traceId,
        fileName: file.name,
        extension: ext,
        sizeBytes: file.size,
        sizeMb: Number(sizeMb.toFixed(2)),
        maxMb: maxMb,
      });

      if (["glb", "gltf", "usdz"].indexOf(ext) === -1) {
        logWarn("upload_invalid_extension", {
          traceId: traceId,
          extension: ext,
          issueCode: "UPLOAD_EXT_UNSUPPORTED",
        });
        setState({ ...state, error: "Please upload a GLB, GLTF, or USDZ file. Other formats are not previewable in this build." });
        return;
      }

      if (sizeMb > maxMb) {
        logWarn("upload_file_too_large", {
          traceId: traceId,
          sizeMb: Number(sizeMb.toFixed(2)),
          maxMb: maxMb,
          issueCode: "UPLOAD_SIZE_LIMIT",
        });
        setState({ ...state, error: window.modeloraAdminData.i18n.largeFile });
        return;
      }

      setState({ ...state, isUploading: true, error: "", lastTraceId: traceId });
      var uploadCandidates = buildRestEndpointCandidates("models/upload");
      logInfo("upload_request_start", {
        traceId: traceId,
        endpoint: uploadCandidates[0] || "",
        endpointCandidates: uploadCandidates,
      });

      var formData = new FormData();
      formData.append("file", file);

      requestJsonWithFallback("models/upload", {
        method: "POST",
        headers: {
          "X-WP-Nonce": window.modeloraAdminData.nonce,
          "X-MODELORA-Trace": traceId,
        },
        credentials: "same-origin",
        body: formData,
      })
        .then(function (result) {
          var data = result.data || {};
          var serverTraceId = data.trace_id || traceId;
          logInfo("upload_response_received", {
            traceId: serverTraceId,
            httpStatus: result.status,
            endpoint: result.endpoint || "",
            success: !!data.success,
            modelId: data.model_id || 0,
            url: data.url || "",
            issueCode: data.success ? "OK" : "UPLOAD_API_FAIL",
          });

          if (data.success) {
            setState({
              ...state,
              file: file,
              error: "",
              step: 2,
              modelUrl: data.url,
              modelId: data.model_id,
              isUploading: false,
              lastTraceId: serverTraceId,
            });
          } else {
            logWarn("upload_failed", {
              traceId: serverTraceId,
              message: data.message || "Upload failed.",
              issueCode: "UPLOAD_RESPONSE_ERROR",
            });
            setState({ ...state, error: data.message || "Upload failed.", isUploading: false });
          }
        })
        .catch(function (err) {
          logError("upload_network_error", {
            traceId: traceId,
            message: err && err.message ? err.message : "Network error during upload.",
            issueCode: "UPLOAD_NETWORK_ERROR",
          });
          setState({ ...state, error: "Network error during upload.", isUploading: false });
        });
    }

    function importDemo() {
      var traceId = createTraceId("demo");
      setState({ ...state, isUploading: true, error: "", lastTraceId: traceId });
      var demoCandidates = buildRestEndpointCandidates("models/demo");
      logInfo("demo_request_start", {
        traceId: traceId,
        endpoint: demoCandidates[0] || "",
        endpointCandidates: demoCandidates,
      });

      requestJsonWithFallback("models/demo", {
        method: "POST",
        headers: {
          "X-WP-Nonce": window.modeloraAdminData.nonce,
          "X-MODELORA-Trace": traceId,
        },
        credentials: "same-origin",
      })
        .then(function (result) {
          var data = result.data || {};
          var serverTraceId = data.trace_id || traceId;
          logInfo("demo_response_received", {
            traceId: serverTraceId,
            httpStatus: result.status,
            endpoint: result.endpoint || "",
            success: !!data.success,
            modelId: data.model_id || 0,
            url: data.url || "",
            issueCode: data.success ? "OK" : "DEMO_API_FAIL",
          });

          if (data.success) {
            setState({
              ...state,
              file: { name: "demo-model.glb" },
              error: "",
              step: 2,
              modelUrl: data.url,
              modelId: data.model_id,
              isUploading: false,
              lastTraceId: serverTraceId,
            });
          } else {
            logWarn("demo_failed", {
              traceId: serverTraceId,
              message: data.message || "Failed to load demo.",
              issueCode: "DEMO_RESPONSE_ERROR",
            });
            setState({ ...state, error: data.message || "Failed to load demo.", isUploading: false });
          }
        })
        .catch(function (err) {
          logError("demo_network_error", {
            traceId: traceId,
            message: err && err.message ? err.message : "Network error importing demo.",
            issueCode: "DEMO_NETWORK_ERROR",
          });
          setState({ ...state, error: "Network error importing demo.", isUploading: false });
        });
    }

    return el(
      "section",
      { className: "modelora-step-card" },
      el(
        "div",
        { className: "modelora-section-head" },
        el("h2", null, "Upload"),
        el("p", null, "Start with a GLB, GLTF, or USDZ model file.")
      ),
      el(
        "div",
        { className: "modelora-upload-panels" },
        el(
          "div",
          { className: "modelora-convert-panel modelora-panel-upload" },
          el("span", { className: "modelora-panel-kicker" }, "Quick Start"),
          el("h3", null, "Upload from Device"),
          el("p", { className: "modelora-note" }, "Upload a previewable model file directly from your computer."),
          el(
            "label",
            { className: "modelora-switch" },
            el("input", {
              type: "checkbox",
              checked: true,
              disabled: true,
            }),
            "Enable object background"
          ),
          el("input", {
            type: "file",
            accept: ".glb,.gltf,.obj,.fbx,.stl,.dae,.usdz",
            onChange: onFileChange,
            disabled: state.isUploading || state.isConverting,
          })
        ),
        null /* Add Model by URL section disabled on request */, 
        null /* Convert to GLB section disabled on request */
      ),
      el("div", { className: "modelora-demo-box" },
        el("p", { className: "modelora-note" }, "Don't have a model yet? Try the astronaut demo."),
        el("button", { className: "button", onClick: importDemo, disabled: state.isUploading || state.isConverting }, "Load Demo Model")
      ),
      state.isUploading ? el("p", { className: "modelora-note", style: { marginTop: "10px" } }, "Uploading... please wait.") : null,
      state.error ? el("p", { className: "modelora-error" }, state.error) : null
    );
  }

  function PreviewStep(props) {
    var state = props.state;
    var setState = props.setState;
    var modelExtension = getLoadedModelExtension(state);
    var canPreview = !!state.modelUrl && isPreviewableModelExtension(modelExtension);
    var pluginUrl = window.modeloraAdminData && window.modeloraAdminData.pluginUrl ? String(window.modeloraAdminData.pluginUrl) : '/wp-content/plugins/modelora-3d-product-viewer/';
    if (pluginUrl.slice(-1) !== '/') {
      pluginUrl += '/';
    }
    var viewerAvailableTuple = useState(false);
    var viewerAvailable = viewerAvailableTuple[0];
    var setViewerAvailable = viewerAvailableTuple[1];

    useEffect(function () {
      if (!canPreview) {
        setViewerAvailable(false);
        return;
      }

      var attempts = 0;
      var maxAttempts = 600; // ~60s

      var checkAndInitialize = function () {
        attempts += 1;
        var viewer = document.getElementById("modelora-admin-preview-viewer");
        if (!viewer) {
          if (attempts < maxAttempts) {
            window.setTimeout(checkAndInitialize, 100);
          } else {
            setViewerAvailable(false);
          }
          return;
        }

        // Check if model-viewer custom element is defined
        if (window.customElements && window.customElements.get && window.customElements.get("model-viewer")) {
          setViewerAvailable(true);
        } else {
          if (attempts < maxAttempts) {
            window.setTimeout(checkAndInitialize, 100);
            return;
          }
          setViewerAvailable(false);
        }

        // Set src and load when available
        if (state.modelUrl && viewer.getAttribute("src") !== state.modelUrl) {
          try {
            viewer.setAttribute("src", state.modelUrl);
            if (typeof viewer.load === "function") {
              viewer.load();
            }
          } catch (e) {
            if (window.console && typeof window.console.error === "function") {
              window.console.error('[MODELORA] error setting viewer src', e);
            }
          }
        }
      };

      window.setTimeout(checkAndInitialize, 10);
    }, [canPreview, state.modelUrl]);

    return el(
      "section",
      { className: "modelora-step-card" },
      el(
        "div",
        { className: "modelora-section-head" },
        el("h2", null, "Preview"),
        el("p", null, "Inspect the model before finalizing settings.")
      ),
      el(
        "div",
        { className: "modelora-preview-box" },
        canPreview
          ? el(
              "div",
              { style: { position: 'relative' } },
              el("model-viewer", {
                key: state.modelUrl,
                id: "modelora-admin-preview-viewer",
                src: state.modelUrl,
                "camera-controls": true,
                "touch-action": "pan-y",
                style: {
                  width: "100%",
                  height: "360px",
                  borderRadius: "14px",
                  background: getPreviewBackgroundStyle(state),
                },
              }),
              !viewerAvailable
                ? el(
                    "div",
                    { style: { marginTop: '12px' } },
                    el(
                      "div",
                      { className: "modelora-error" },
                      "Viewer failed to load. Check browser Console and Network for 'model-viewer' module errors (404/CORS/MIME)."
                    )
                  )
                : null
            )
          : el(
              "div",
              {
                className: "modelora-preview-empty",
                style: {
                  minHeight: "360px",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  textAlign: "center",
                  borderRadius: "14px",
                  background: getPreviewBackgroundStyle(state),
                  border: "1px dashed #bfd0e4",
                  padding: "24px",
                },
              },
              el(
                "div",
                { style: { maxWidth: "360px" } },
                el("strong", { style: { display: "block", marginBottom: "8px" } },
                  state.modelUrl
                    ? "This file type cannot be previewed here"
                    : "No model loaded yet"
                ),
                el(
                  "p",
                  { className: "modelora-note", style: { margin: 0 } },
                  state.modelUrl
                    ? "The admin preview only renders GLB, GLTF, and USDZ files. Upload a supported file or use the frontend viewer after conversion."
                    : "Upload a model in the previous step to preview it here."
                ),
                (window.modeloraAdminData && window.modeloraAdminData.debug)
                  ? el(
                      "div",
                      { className: "modelora-debug-panel", style: { marginTop: "12px", textAlign: "left", fontSize: "13px", color: "#475569" } },
                      el("div", null, "DEBUG: modelUrl: ", el("code", null, state.modelUrl || "(empty)")),
                      el("div", null, "DEBUG: modelId: ", el("code", null, String(state.modelId || "0")))
                    )
                  : null
              )
            )
      ),
      el(
        "div",
        { className: "modelora-row" },
        el(
          "button",
          {
            className: "button button-primary",
            disabled: !canPreview,
            onClick: function () {
              setState({ ...state, step: 4 });
            },
          },
          "Continue"
        )
      )
    );
  }

  function CustomizeStep(props) {
    var state = props.state;
    var setState = props.setState;

    function onSelectBackgroundImage() {
      openWpImagePicker(function (url) {
        setState({ ...state, backgroundEnabled: true, backgroundImageUrl: url });
      });
    }

    function onRemoveBackgroundImage() {
      setState({ ...state, backgroundImageUrl: "" });
    }

    function saveDefaultAngle() {
      var viewer = document.getElementById("modelora-admin-preview-viewer");
      if (!viewer || !state.modelId) return;

      var traceId = state.lastTraceId || createTraceId("angle");

      var orbit = viewer.getCameraOrbit();
      var target = viewer.getCameraTarget();

      var orbitStr = orbit.theta + "rad " + orbit.phi + "rad " + orbit.radius + "m";
      var targetStr = target.x + "m " + target.y + "m " + target.z + "m";

      requestJsonWithFallback("models/" + state.modelId + "/view-angle", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          "X-WP-Nonce": window.modeloraAdminData.nonce,
          "X-MODELORA-Trace": traceId,
        },
        credentials: "same-origin",
        body: JSON.stringify({ cameraOrbit: orbitStr, cameraTarget: targetStr }),
      })
        .then(function (result) {
          var data = result.data || {};
          var serverTraceId = data.trace_id || traceId;
          logInfo("view_angle_saved", {
            traceId: serverTraceId,
            modelId: state.modelId,
            httpStatus: result.status,
            success: !!data.success,
            orbit: orbitStr,
            target: targetStr,
            issueCode: data.success ? "OK" : "ANGLE_SAVE_FAIL",
          });
          alert("Default angle saved!");
        })
        .catch(function (err) {
          logError("view_angle_network_error", {
            traceId: traceId,
            modelId: state.modelId,
            message: err && err.message ? err.message : "Failed to save default angle.",
            issueCode: "ANGLE_SAVE_NETWORK_ERROR",
          });
          alert("Could not save default angle. Check console logs.");
        });
    }

    return el(
      "section",
      { className: "modelora-step-card" },
      el(
        "div",
        { className: "modelora-section-head" },
        el("h2", null, "Customize"),
        el("p", null, "Set environment and save a default camera angle.")
      ),
      el(
        "div",
        { style: { display: "flex", gap: "16px", flexDirection: "column" } },
        el(
          "label",
          { className: "modelora-field" },
          "Environment preset ",
          el(
            "select",
            {
              value: state.preset,
              onChange: function (event) {
                setState({ ...state, preset: event.target.value });
              },
            },
            (window.modeloraAdminData.presets || []).map(function (preset) {
              return el("option", { key: preset.key, value: preset.key }, preset.label);
            })
          )
        ),
        el(
          "div",
          null,
          el("p", { className: "modelora-note", style: { marginBottom: "8px" } }, "Background settings for client view:"),
          el(
            "label",
            { className: "modelora-switch", style: { marginBottom: "8px" } },
            el("input", {
              type: "checkbox",
              checked: !!state.backgroundEnabled,
              onChange: function (event) {
                setState({ ...state, backgroundEnabled: !!event.target.checked });
              },
            }),
            "Enable viewer background"
          ),
          el(
            "label",
            { className: "modelora-field", style: { maxWidth: "240px" } },
            "Background color",
            el("input", {
              type: "color",
              value: state.backgroundColor || "#eaf2fb",
              onChange: function (event) {
                setState({ ...state, backgroundEnabled: true, backgroundColor: event.target.value || "#eaf2fb" });
              },
              disabled: !state.backgroundEnabled,
            })
          ),
          el(
            "div",
            { className: "modelora-row", style: { marginTop: "10px" } },
            el(
              "button",
              {
                className: "button",
                type: "button",
                onClick: onSelectBackgroundImage,
                disabled: !state.backgroundEnabled,
              },
              state.backgroundImageUrl ? "Change background image" : "Select background image"
            ),
            state.backgroundImageUrl
              ? el(
                  "button",
                  {
                    className: "button",
                    type: "button",
                    onClick: onRemoveBackgroundImage,
                  },
                  "Remove image"
                )
              : null
          ),
          state.backgroundImageUrl
            ? el("p", { className: "modelora-note", style: { wordBreak: "break-all" } }, "Image: " + state.backgroundImageUrl)
            : el("p", { className: "modelora-note" }, "No background image selected. Color will be used.")
        ),
        el(
          "div",
          null,
          el("p", { className: "modelora-note" }, "Rotate the model in Preview, then save that camera angle as default."),
          el(
            "button",
            { className: "button", onClick: saveDefaultAngle },
            "Save Current Angle as Default"
          )
        )
      ),
      el(
        "div",
        { className: "modelora-row" },
        el(
          "button",
          {
            className: "button button-secondary",
            onClick: function () {
              setState({ ...state, step: 2 });
            },
          },
          "Back"
        ),
        el(
          "button",
          {
            className: "button button-primary",
            onClick: function () {
              setState({ ...state, step: 4 });
            },
          },
          "Continue"
        )
      )
    );
  }

  function EmbedStep(props) {
    var state = props.state;
    var copiedTuple = useState(false);
    var copied = copiedTuple[0];
    var setCopied = copiedTuple[1];
    var shortcode = '[modelora_viewer id="' + (state.modelId || "123") + '" preset="' + state.preset + '" background="' + (state.backgroundEnabled ? "1" : "0") + '"';

    if (state.backgroundEnabled && state.backgroundColor) {
      shortcode += ' bg_color="' + escapeShortcodeAttribute(state.backgroundColor) + '"';
    }

    if (state.backgroundEnabled && state.backgroundImageUrl) {
      shortcode += ' bg_image="' + escapeShortcodeAttribute(state.backgroundImageUrl) + '"';
    }

    shortcode += ']';

    function copyShortcode() {
      copyTextToClipboard(shortcode).then(function (didCopy) {
        if (didCopy) {
          setCopied(true);
          setTimeout(function () {
            setCopied(false);
          }, 1800);
        } else {
          setCopied(false);
          alert("Could not copy automatically. Please copy manually:\n\n" + shortcode);
        }
      });
    }

    return el(
      "section",
      { className: "modelora-step-card" },
      el(
        "div",
        { className: "modelora-section-head" },
        el("h2", null, "Embed"),
        el("p", null, "Use Gutenberg block, Elementor widget, or copy this shortcode.")
      ),
      el("textarea", { readOnly: true, value: shortcode, rows: 3 }),
      el(
        "div",
        { className: "modelora-embed-actions" },
        el(
          "button",
          {
            className: "button button-primary",
            onClick: copyShortcode,
            type: "button",
          },
          "Copy shortcode"
        )
      ),
      copied ? el("div", { className: "modelora-inline-success" }, "Shortcode copied.") : null,
      el("p", { className: "modelora-note" }, "Tip: In WooCommerce products, use the Modelora meta box for one-click linking.")
    );
  }

  function App() {
    var stateTuple = useState({
      step: 1,
      file: null,
      modelUrl: "",
      preset: "studio",
      backgroundEnabled: true,
      backgroundColor: "#eaf2fb",
      backgroundImageUrl: "",
      isConverting: false,
      convertFiles: [],
      convertCompression: "none",
      convertEmbedResources: true,
      convertPreserveTransform: true,
      convertOutputName: "converted-model.glb",
      convertOutputDir: "modelora-converted",
      convertProgress: 0,
      convertStatus: "",
      convertError: "",
      convertWarnings: [],
      convertedPath: "",
      modelUrlInput: "",
      error: "",
      lastTraceId: "",
    });
    var state = stateTuple[0];
    var setState = stateTuple[1];

    return el(
      "main",
      { className: "modelora-admin-app" },
      el("header", { className: "modelora-admin-header" },
        el("h1", null, "Modelora"),
        el("p", null, "A clean workflow to upload, fine-tune, and embed 3D models."),
        el(
          "ol",
          { className: "modelora-stepper" },
          ["Upload", "Preview", "Customize", "Embed"].map(function (label, idx) {
            var stepNumber = idx + 1;
            var className = stepNumber === state.step ? "is-active" : (stepNumber < state.step ? "is-complete" : "");
            return el("li", { key: "step-" + label, className: className }, stepNumber + ". " + label);
          })
        )
      ),
      state.step === 1 ? el(UploadStep, { state: state, setState: setState }) : null,
      state.step === 2 ? el(PreviewStep, { state: state, setState: setState }) : null,
      null /* Customize section disabled on request */,
      state.step === 4 ? el(EmbedStep, { state: state, setState: setState }) : null
    );
  }

  var root = document.getElementById("modelora-admin-root");
  if (root) {
    wp.element.render(el(App), root);
  }
})(window.wp);
