;(function () {
  var READY = "mkt-editor:ready"
  var HOVER = "mkt-editor:hover"
  var HOVER_CLEAR = "mkt-editor:hover-clear"
  var SELECTION = "mkt-editor:selection"
  var HOTSPOT_ACTION = "mkt-editor:hotspot-action"
  var FOCUS_SELECTOR = "mkt-editor:focus-selector"
  var INSPECT_SELECTOR = "mkt-editor:inspect-selector"
  var START_INLINE_EDIT = "mkt-editor:start-inline-edit"
  var MOVE_BLOCK = "mkt-editor:move-block"
  var TOGGLE_BLOCK_VISIBILITY = "mkt-editor:toggle-block-visibility"
  var DUPLICATE_BLOCK = "mkt-editor:duplicate-block"
  var DELETE_BLOCK = "mkt-editor:delete-block"
  var INSERT_BLOCK = "mkt-editor:insert-block"
  var RESTORE_CANVAS_STATE = "mkt-editor:restore-canvas-state"
  var INLINE_TEXT_CHANGE = "mkt-editor:inline-text-change"
  var INLINE_ATTRIBUTE_CHANGE = "mkt-editor:inline-attribute-change"
  var INLINE_STYLE_CHANGE = "mkt-editor:inline-style-change"
  var BLOCK_VISIBILITY_CHANGE = "mkt-editor:block-visibility-change"
  var BLOCK_STRUCTURE_CHANGE = "mkt-editor:block-structure-change"
  var STATUS_SUMMARY = "mkt-editor:status-summary"
  var HIGHLIGHT_ID = "__mkt_editor_highlight__"
  var HOVER_HIGHLIGHT_ID = "__mkt_editor_hover_highlight__"
  var DROP_INDICATOR_ID = "__mkt_editor_drop_indicator__"
  var DROP_BADGE_ID = "__mkt_editor_drop_badge__"
  var FEEDBACK_BADGE_ID = "__mkt_editor_feedback_badge__"
  var TOOLTIP_ID = "__mkt_editor_tooltip__"
  var PANEL_ID = "__mkt_editor_panel__"
  var HOTSPOT_ID = "__mkt_editor_hotspot__"
  var INLINE_BADGE_ID = "__mkt_editor_inline_badge__"
  var SHARED_INLINE_PANEL_POSITION_ID = "__mkt_editor_shared_inline_panel__"
  var INLINE_TEXT_PANEL_ID = "__mkt_editor_inline_text_panel__"
  var INLINE_ATTRIBUTE_PANEL_ID = "__mkt_editor_inline_attribute_panel__"
  var previewOriginalState = new Map()
  var latestPreviewOperations = []
  var currentFocusedElement = null
  var currentHoveredElement = null
  var currentInlineEditableElement = null
  var currentInlineAttributeElement = null
  var currentInlineTextOriginalValue = null
  var currentInlineAttributeOriginalValue = null
  var currentDragMoveState = null
  var floatingPanelPositions = {}
  var currentFloatingPanelDragState = null
  var runtimeSelectorSeed = Date.now()
  var lastHoveredSelector = ""
  var latestStatusSummary = null
  var PANEL_THEME = {
    shellBackground: "#f8fafc",
    shellBorder: "1px solid rgba(148,163,184,0.24)",
    shellShadow: "0 18px 36px rgba(15, 23, 42, 0.12)",
    headerBackground: "rgba(248,250,252,0.96)",
    headerBorder: "1px solid rgba(148,163,184,0.14)",
    dragHandleBackground: "#e8f0ff",
    dragHandleBorder: "1px solid rgba(96,165,250,0.24)",
    dragHandleText: "#35507a",
    closeButtonText: "#475569",
    sectionTitleText: "#334155",
    iconText: "#64748b",
    inputBackground: "#ffffff",
    inputBorder: "1px solid rgba(100,116,139,0.30)",
    inputText: "#0f172a",
    divider: "1px solid rgba(148,163,184,0.14)",
    pickerBackground: "#ffffff",
    pickerBorder: "1px solid rgba(100,116,139,0.30)",
  }

  function refreshVisualState() {
    updateHighlight(currentFocusedElement, "focus")
    updateHoverHighlight(currentHoveredElement)
  }

  function rememberOriginalState(element) {
    if (!element || previewOriginalState.has(element)) {
      return
    }

    previewOriginalState.set(element, {
      textContent: element.textContent,
      innerHTML: element.innerHTML,
      attributes: Array.prototype.slice.call(element.attributes || []).reduce(function (summary, attr) {
        summary[attr.name] = attr.value
        return summary
      }, {}),
      style: element.getAttribute("style") || "",
    })
  }

  function resetPreviewState() {
    previewOriginalState.forEach(function (originalState, element) {
      if (!originalState || !element) {
        return
      }

      element.innerHTML = originalState.innerHTML
      element.textContent = originalState.textContent

      Array.prototype.slice.call(element.attributes || []).forEach(function (attr) {
        if (!(attr.name in originalState.attributes)) {
          element.removeAttribute(attr.name)
        }
      })

      Object.keys(originalState.attributes).forEach(function (name) {
        element.setAttribute(name, originalState.attributes[name])
      })

      if (originalState.style) {
        element.setAttribute("style", originalState.style)
      } else {
        element.removeAttribute("style")
      }
    })
  }

  function applyPreviewOperations(operations) {
    resetPreviewState()
    latestPreviewOperations = Array.isArray(operations) ? operations : []

    if (!Array.isArray(operations) || operations.length === 0) {
      refreshVisualState()
      return
    }

    operations.forEach(function (operation) {
      var selector = operation.selector_value || operation.selector || ""
      if (!selector) {
        return
      }

      var element = document.querySelector(selector)
      if (!element) {
        return
      }

      rememberOriginalState(element)

      var changeType = operation.change_type || operation.action || ""
      var payload = operation.payload || {}

      if (changeType === "replace_text" && typeof payload.text === "string") {
        element.textContent = payload.text
        return
      }

      if (changeType === "set_html" && typeof payload.html === "string") {
        element.innerHTML = payload.html
        return
      }

      if (changeType === "set_attribute" && payload.name) {
        element.setAttribute(payload.name, payload.value || "")
        return
      }

      if (changeType === "set_style" && payload.property) {
        element.style[payload.property] = payload.value || ""
      }
    })

    refreshVisualState()
  }

  function getPreviewSummaryForSelector(selector) {
    if (!selector) {
      return {
        total: latestPreviewOperations.length,
        matched: 0,
        types: [],
      }
    }

    var matchedOperations = latestPreviewOperations.filter(function (operation) {
      return (operation.selector_value || operation.selector || "") === selector
    })

    return {
      total: latestPreviewOperations.length,
      matched: matchedOperations.length,
      types: matchedOperations
        .map(function (operation) {
          return operation.change_type || operation.action || ""
        })
        .filter(Boolean),
    }
  }

  function getPreviewTypeLabel(type) {
    if (type === "replace_text") {
      return "文字"
    }

    if (type === "set_html") {
      return "內容"
    }

    if (type === "set_attribute") {
      return "屬性"
    }

    if (type === "set_style") {
      return "樣式"
    }

    return "修改"
  }

  function getModeTone(mode, status) {
    if (status === "success" || mode === "preview-ready") {
      return {
        badge: "rgba(14,116,144,0.10);color:#0f766e;",
        panel: "rgba(20,184,166,0.06)",
        panelBorder: "rgba(94,234,212,0.24)",
        border: "#0f766e",
        shadow: "0 0 0 9999px rgba(15, 118, 110, 0.06)",
      }
    }

    if (mode === "inspect") {
      return {
        badge: "rgba(180,83,9,0.10);color:#9a3412;",
        panel: "rgba(245,158,11,0.06)",
        panelBorder: "rgba(251,191,36,0.24)",
        border: "#b45309",
        shadow: "0 0 0 9999px rgba(180, 83, 9, 0.06)",
      }
    }

    if (mode === "hover" || mode === "compare" || status === "warning") {
      return {
        badge: "rgba(30,64,175,0.10);color:#1d4ed8;",
        panel: "rgba(59,130,246,0.06)",
        panelBorder: "rgba(96,165,250,0.24)",
        border: "#2563eb",
        shadow: "0 0 0 9999px rgba(37, 99, 235, 0.05)",
      }
    }

    return {
      badge: "rgba(30,64,175,0.10);color:#1d4ed8;",
      panel: "rgba(59,130,246,0.06)",
      panelBorder: "rgba(96,165,250,0.24)",
      border: "#2563eb",
      shadow: "0 0 0 9999px rgba(37, 99, 235, 0.06)",
    }
  }

  function postToParent(payload) {
    if (window.parent && window.parent !== window) {
      window.parent.postMessage(payload, "*")
    }
  }

  function stripEditorArtifacts(root) {
    if (!root || typeof root.querySelectorAll !== "function") {
      return
    }

    ;[
      HIGHLIGHT_ID,
      HOVER_HIGHLIGHT_ID,
      DROP_INDICATOR_ID,
      DROP_BADGE_ID,
      FEEDBACK_BADGE_ID,
      TOOLTIP_ID,
      PANEL_ID,
      HOTSPOT_ID,
      INLINE_BADGE_ID,
      INLINE_TEXT_PANEL_ID,
      INLINE_ATTRIBUTE_PANEL_ID,
    ].forEach(function (id) {
      var node = root.querySelector("#" + id)
      if (node && node.parentNode) {
        node.parentNode.removeChild(node)
      }
    })

    Array.prototype.forEach.call(root.querySelectorAll("[data-mkt-inline-edit-original]"), function (node) {
      node.removeAttribute("data-mkt-inline-edit-original")
      node.removeAttribute("contenteditable")
    })

    Array.prototype.forEach.call(root.querySelectorAll("[style]"), function (node) {
      if (!(node instanceof Element)) {
        return
      }

      node.style.outline = ""
      node.style.outlineOffset = ""
      if (!node.getAttribute("style")) {
        node.removeAttribute("style")
      }
    })
  }

  function ensureEditorTooltip() {
    var tooltip = document.getElementById(TOOLTIP_ID)
    if (tooltip) {
      return tooltip
    }

    tooltip = document.createElement("div")
    tooltip.id = TOOLTIP_ID
    tooltip.style.position = "fixed"
    tooltip.style.zIndex = "2147483647"
    tooltip.style.display = "none"
    tooltip.style.pointerEvents = "none"
    tooltip.style.maxWidth = "180px"
    tooltip.style.padding = "6px 8px"
    tooltip.style.borderRadius = "8px"
    tooltip.style.background = "rgba(15,23,42,0.94)"
    tooltip.style.color = "#fff"
    tooltip.style.fontSize = "12px"
    tooltip.style.lineHeight = "1.4"
    tooltip.style.boxShadow = "0 8px 20px rgba(15,23,42,0.22)"
    tooltip.style.fontFamily =
      '"Noto Sans TC","PingFang TC","Microsoft JhengHei",system-ui,sans-serif'
    document.body.appendChild(tooltip)
    return tooltip
  }

  function hideEditorTooltip() {
    var tooltip = document.getElementById(TOOLTIP_ID)
    if (!tooltip) {
      return
    }

    tooltip.style.display = "none"
  }

  function showEditorTooltip(label, event) {
    if (!label || !event) {
      return
    }

    var tooltip = ensureEditorTooltip()
    tooltip.textContent = label
    tooltip.style.display = "block"
    var offsetX = 12
    var offsetY = 14
    var left = event.clientX + offsetX
    var top = event.clientY + offsetY
    var maxLeft = window.innerWidth - tooltip.offsetWidth - 12
    var maxTop = window.innerHeight - tooltip.offsetHeight - 12
    tooltip.style.left = Math.max(8, Math.min(left, maxLeft)) + "px"
    tooltip.style.top = Math.max(8, Math.min(top, maxTop)) + "px"
  }

  function decoratePanelIconTooltips(panel) {
    if (!panel || typeof panel.querySelectorAll !== "function") {
      return
    }

    Array.prototype.forEach.call(panel.querySelectorAll("[title]"), function (node) {
      if (!(node instanceof Element)) {
        return
      }

      var label = node.getAttribute("title") || ""
      if (!label) {
        return
      }

      node.removeAttribute("title")
      node.style.cursor = node.getAttribute("data-mkt-panel-drag-handle")
        ? "grab"
        : node.style.cursor || "help"

      node.onmouseenter = function (event) {
        showEditorTooltip(label, event)
      }
      node.onmousemove = function (event) {
        showEditorTooltip(label, event)
      }
      node.onmouseleave = function () {
        hideEditorTooltip()
      }

      Array.prototype.forEach.call(node.querySelectorAll("*"), function (child) {
        if (!(child instanceof Element)) {
          return
        }

        child.removeAttribute("title")
        child.style.pointerEvents = "none"
      })
    })
  }

  function createPageHtmlSnapshot() {
    if (!document.body) {
      return ""
    }

    var clone = document.body.cloneNode(true)
    stripEditorArtifacts(clone)
    return clone.innerHTML
  }

  function resolveActionTargetElement() {
    if (
      latestStatusSummary &&
      (latestStatusSummary.mode === "hover" || latestStatusSummary.mode === "compare")
    ) {
      return currentHoveredElement
    }

    return currentFocusedElement || currentHoveredElement
  }

  function dispatchActionFromTarget(action, targetElement) {
    if (!targetElement) {
      return
    }

    var meta = extractElementMeta(targetElement)
    var selector = meta.selector
    var actionIntent = resolveActionIntent(action, meta)

    if (action === "select-current") {
      handleSelection(targetElement, "canvas-hotspot")
      clearHoverState()
    }

    if (action === "inspect-current") {
      handleInspectSelector(selector)
    }

    if (action === "edit-current") {
      handleSelection(targetElement, "canvas-hotspot")
    }

    if (action === "confirm-current") {
      handleFocusSelector(selector)
    }

    postToParent({
      type: HOTSPOT_ACTION,
      action: action,
      selector: selector,
      tag: meta.tag,
      text: meta.text,
      href: meta.href,
      src: meta.src,
      intent_change_type: actionIntent.changeType,
      intent_field: actionIntent.field,
      intent_attribute_name: actionIntent.attributeName,
      intent_attribute_value: actionIntent.attributeValue,
      source: "canvas-hotspot",
    })
  }

  function resolveEditAction(targetElement) {
    if (!targetElement) {
      return "edit-current"
    }

    var tag = targetElement.tagName ? targetElement.tagName.toLowerCase() : ""
    var href = targetElement.getAttribute && targetElement.getAttribute("href")
    var src = targetElement.getAttribute && targetElement.getAttribute("src")
    var text = ((targetElement.innerText || targetElement.textContent) || "").trim()
    var className = typeof targetElement.className === "string" ? targetElement.className : ""

    if (tag === "img" || src) {
      return "edit-image-current"
    }

    if (
      (tag === "a" || tag === "button") &&
      text &&
      (tag === "button" ||
        targetElement.getAttribute("role") === "button" ||
        /button|btn|cta/i.test(className))
    ) {
      return "edit-text-current"
    }

    if (tag === "a" || href) {
      return "edit-link-current"
    }

    if (["h1", "h2", "h3", "h4", "h5", "h6", "p", "span", "button", "li"].indexOf(tag) >= 0) {
      return "edit-text-current"
    }

    return "edit-style-current"
  }

  function resolveActionIntent(action, meta) {
    if (action === "edit-text-current") {
      return {
        changeType: "replace_text",
        field: "content",
        attributeName: "",
        attributeValue: "",
      }
    }

    if (action === "edit-link-current") {
      return {
        changeType: "set_attribute",
        field: "attribute-value",
        attributeName: "href",
        attributeValue: meta.href || "",
      }
    }

    if (action === "edit-image-current") {
      return {
        changeType: "set_attribute",
        field: "attribute-value",
        attributeName: "src",
        attributeValue: meta.src || "",
      }
    }

    if (action === "edit-style-current") {
      return {
        changeType: "set_style",
        field: "style-property",
        attributeName: "",
        attributeValue: "",
      }
    }

    return {
      changeType: "",
      field: "",
      attributeName: "",
      attributeValue: "",
    }
  }

  function getActionLabel(action) {
    if (action === "edit-text-current") {
      return "改文字"
    }

    if (action === "edit-link-current") {
      return "改連結"
    }

    if (action === "edit-image-current") {
      return "換圖片"
    }

    if (action === "edit-style-current") {
      return "改樣式"
    }

    if (action === "select-current") {
      return "設為檢查目標"
    }

    if (action === "inspect-current") {
      return "先檢查"
    }

    if (action === "confirm-current") {
      return "回畫面確認"
    }

    return "開始修改"
  }

  function resolveQuickEditActions(targetElement) {
    if (!targetElement) {
      return []
    }

    var primaryEditAction = resolveEditAction(targetElement)
    var quickActions = [primaryEditAction]

    if (primaryEditAction !== "edit-style-current") {
      quickActions.push("edit-style-current")
    }

    return quickActions.slice(0, 2)
  }

  function ensureHighlightBox() {
    var box = document.getElementById(HIGHLIGHT_ID)
    if (box) {
      return box
    }

    box = document.createElement("div")
    box.id = HIGHLIGHT_ID
    box.style.position = "fixed"
    box.style.zIndex = "2147483647"
    box.style.pointerEvents = "none"
    box.style.border = "2px solid #1d4ed8"
    box.style.borderRadius = "10px"
    box.style.boxShadow = "none"
    box.style.transition = "all 120ms ease"
    box.style.display = "none"
    document.body.appendChild(box)
    return box
  }

  function ensureHoverHighlightBox() {
    var box = document.getElementById(HOVER_HIGHLIGHT_ID)
    if (box) {
      return box
    }

    box = document.createElement("div")
    box.id = HOVER_HIGHLIGHT_ID
    box.style.position = "fixed"
    box.style.zIndex = "2147483646"
    box.style.pointerEvents = "none"
    box.style.border = "2px dashed #2563eb"
    box.style.borderRadius = "10px"
    box.style.boxShadow = "none"
    box.style.transition = "all 120ms ease"
    box.style.display = "none"
    document.body.appendChild(box)
    return box
  }

  function ensureDropIndicator() {
    var indicator = document.getElementById(DROP_INDICATOR_ID)
    if (indicator) {
      return indicator
    }

    indicator = document.createElement("div")
    indicator.id = DROP_INDICATOR_ID
    indicator.style.position = "fixed"
    indicator.style.zIndex = "2147483647"
    indicator.style.pointerEvents = "none"
    indicator.style.height = "4px"
    indicator.style.borderRadius = "999px"
    indicator.style.background = "#1d4ed8"
    indicator.style.boxShadow = "0 0 0 2px rgba(255,255,255,0.85)"
    indicator.style.display = "none"
    document.body.appendChild(indicator)
    return indicator
  }

  function ensureDropBadge() {
    var badge = document.getElementById(DROP_BADGE_ID)
    if (badge) {
      return badge
    }

    badge = document.createElement("div")
    badge.id = DROP_BADGE_ID
    badge.style.position = "fixed"
    badge.style.zIndex = "2147483647"
    badge.style.pointerEvents = "none"
    badge.style.display = "none"
    badge.style.padding = "6px 10px"
    badge.style.borderRadius = "999px"
    badge.style.background = "rgba(30,41,59,0.92)"
    badge.style.color = "#fff"
    badge.style.fontSize = "12px"
    badge.style.fontWeight = "700"
    badge.style.fontFamily =
      '"Noto Sans TC","PingFang TC","Microsoft JhengHei",system-ui,sans-serif'
    badge.style.boxShadow = "0 8px 18px rgba(15, 23, 42, 0.18)"
    badge.textContent = "放到這裡"
    document.body.appendChild(badge)
    return badge
  }

  function ensureFeedbackBadge() {
    var badge = document.getElementById(FEEDBACK_BADGE_ID)
    if (badge) {
      return badge
    }

    badge = document.createElement("div")
    badge.id = FEEDBACK_BADGE_ID
    badge.style.position = "fixed"
    badge.style.zIndex = "2147483647"
    badge.style.pointerEvents = "none"
    badge.style.opacity = "0"
    badge.style.padding = "7px 14px"
    badge.style.borderRadius = "999px"
    badge.style.background = "rgba(15,23,42,0.88)"
    badge.style.color = "#f1f5f9"
    badge.style.fontSize = "12px"
    badge.style.fontWeight = "600"
    badge.style.letterSpacing = "0.01em"
    badge.style.fontFamily =
      '"Noto Sans TC","PingFang TC","Microsoft JhengHei",system-ui,sans-serif'
    badge.style.boxShadow = "0 8px 20px rgba(15, 23, 42, 0.22), 0 0 0 1px rgba(255,255,255,0.06) inset"
    badge.style.backdropFilter = "blur(8px)"
    badge.style.transition = "opacity 0.18s ease"
    document.body.appendChild(badge)
    return badge
  }

  function showFeedbackBadge(message, element) {
    if (!message) {
      return
    }

    var badge = ensureFeedbackBadge()
    badge.textContent = message

    if (element) {
      var rect = element.getBoundingClientRect()
      var approxWidth = message.length * 8 + 28
      badge.style.left = Math.max(12, Math.min(window.innerWidth - approxWidth - 12, rect.left + rect.width / 2 - approxWidth / 2)) + "px"
      badge.style.top = Math.max(12, rect.top - 38) + "px"
      badge.style.transform = ""
    } else {
      badge.style.left = "50%"
      badge.style.top = "16px"
      badge.style.transform = "translateX(-50%)"
    }

    badge.style.opacity = "1"

    window.clearTimeout(showFeedbackBadge._timer)
    showFeedbackBadge._timer = window.setTimeout(function () {
      badge.style.opacity = "0"
    }, 1400)
  }

  function ensureInspectPanel() {
    return null
  }

  function ensureActionHotspot() {
    var hotspot = document.getElementById(HOTSPOT_ID)
    if (hotspot) {
      return hotspot
    }

    hotspot = document.createElement("div")
    hotspot.id = HOTSPOT_ID
    hotspot.style.position = "fixed"
    hotspot.style.zIndex = "2147483647"
    hotspot.style.display = "none"
    hotspot.style.pointerEvents = "auto"
    hotspot.style.padding = "4px"
    hotspot.style.borderRadius = "999px"
    hotspot.style.background = "rgba(30,41,59,0.84)"
    hotspot.style.backdropFilter = "blur(10px)"
    hotspot.style.boxShadow = "0 10px 24px rgba(15, 23, 42, 0.16)"
    hotspot.style.color = "#fff"
    hotspot.style.fontFamily =
      'ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
    hotspot.innerHTML =
      '<div style="display:flex;align-items:center;gap:4px;">' +
      '<button id="__mkt_editor_hotspot_move_up__" type="button" title="上移" style="width:32px;height:32px;border:0;border-radius:999px;background:rgba(255,255,255,0.10);color:#fff;font-size:14px;font-weight:700;cursor:pointer;">↑</button>' +
      '<button id="__mkt_editor_hotspot_drag__" type="button" title="拖移" style="width:32px;height:32px;border:0;border-radius:999px;background:rgba(255,255,255,0.10);color:#fff;font-size:13px;font-weight:700;cursor:grab;">⋮⋮</button>' +
      '<button id="__mkt_editor_hotspot_move_down__" type="button" title="下移" style="width:32px;height:32px;border:0;border-radius:999px;background:rgba(255,255,255,0.10);color:#fff;font-size:14px;font-weight:700;cursor:pointer;">↓</button>' +
      "</div>"

    document.body.appendChild(hotspot)

    hotspot
      .querySelector("#__mkt_editor_hotspot_move_up__")
      .addEventListener("click", function (event) {
        event.preventDefault()
        event.stopPropagation()

        var targetElement = resolveActionTargetElement()

        if (!targetElement) {
          return
        }

        handleMoveBlock({
          selector: buildSelector(targetElement),
          direction: "up",
        })
      })

    hotspot
      .querySelector("#__mkt_editor_hotspot_move_down__")
      .addEventListener("click", function (event) {
        event.preventDefault()
        event.stopPropagation()

        var targetElement = resolveActionTargetElement()

        if (!targetElement) {
          return
        }

        handleMoveBlock({
          selector: buildSelector(targetElement),
          direction: "down",
        })
      })

    hotspot
      .querySelector("#__mkt_editor_hotspot_drag__")
      .addEventListener("pointerdown", function (event) {
        event.preventDefault()
        event.stopPropagation()

        var targetElement = resolveActionTargetElement()
        if (!targetElement) {
          return
        }

        startDragMove(event, targetElement)
      })

    return hotspot
  }

  function ensureInlineEditBadge() {
    return null
  }

  function buildPanelInputStyle(options) {
    var minHeight = (options && options.minHeight) || "42px"
    var fontSize = (options && options.fontSize) || "13px"
    var padding = (options && options.padding) || "10px 12px"
    var extra = (options && options.extra) || ""

    return (
      "width:100%;" +
      "min-height:" +
      minHeight +
      ";" +
      "border:" +
      PANEL_THEME.inputBorder +
      ";" +
      "border-radius:12px;" +
      "padding:" +
      padding +
      ";" +
      "font-size:" +
      fontSize +
      ";" +
      "color:" +
      PANEL_THEME.inputText +
      ";" +
      "background:" +
      PANEL_THEME.inputBackground +
      ";" +
      "box-shadow:inset 0 1px 0 rgba(255,255,255,0.7);" +
      extra
    )
  }

  function applyInlineFloatingPanelStyles(panel, options) {
    if (!panel) {
      return
    }

    var width = (options && options.width) || "324px"
    var left = (options && options.left) || "calc(100vw - 356px)"
    var top = (options && options.top) || "88px"
    var maxHeight = (options && options.maxHeight) || "min(520px, calc(100vh - 96px))"
    var padding = (options && options.padding) || "10px 12px 12px"
    var radius = (options && options.radius) || "16px"

    panel.style.position = "fixed"
    panel.style.left = left
    panel.style.top = top
    panel.style.zIndex = "2147483647"
    panel.style.display = "none"
    panel.style.width = width
    panel.style.maxHeight = maxHeight
    panel.style.overflowY = "auto"
    panel.style.padding = padding
    panel.style.borderRadius = radius
    panel.style.background = PANEL_THEME.shellBackground
    panel.style.border = PANEL_THEME.shellBorder
    panel.style.boxShadow = PANEL_THEME.shellShadow
    panel.style.fontFamily =
      '"Noto Sans TC","PingFang TC","Microsoft JhengHei",system-ui,sans-serif'
  }

  function buildInlineFloatingPanelHeader(closeButtonId, titleId) {
    var gripIcon =
      '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true"><circle cx="4" cy="4" r="1.2" fill="currentColor"/><circle cx="4" cy="7" r="1.2" fill="currentColor"/><circle cx="4" cy="10" r="1.2" fill="currentColor"/><circle cx="10" cy="4" r="1.2" fill="currentColor"/><circle cx="10" cy="7" r="1.2" fill="currentColor"/><circle cx="10" cy="10" r="1.2" fill="currentColor"/></svg>'

    return (
      '<div data-mkt-panel-drag-handle="true" style="position:sticky;top:0;z-index:4;margin:0 0 10px;padding:9px 8px 10px;display:flex;align-items:center;gap:8px;cursor:grab;user-select:none;background:' +
      PANEL_THEME.headerBackground +
      ';backdrop-filter:blur(10px);border-bottom:' +
      PANEL_THEME.headerBorder +
      ';">' +
      '<div title="拖曳編輯框" style="flex-shrink:0;width:26px;height:26px;border-radius:8px;background:' +
      PANEL_THEME.dragHandleBackground +
      ';display:flex;align-items:center;justify-content:center;color:' +
      PANEL_THEME.dragHandleText +
      ';cursor:grab;border:' +
      PANEL_THEME.dragHandleBorder +
      ';">' +
      gripIcon +
      "</div>" +
      '<span id="' +
      (titleId || "__mkt_panel_title__") +
      '" style="flex:1;font-size:12px;font-weight:700;color:#0f172a;letter-spacing:0.02em;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">編輯</span>' +
      '<button id="' +
      closeButtonId +
      '" type="button" title="關閉" style="flex-shrink:0;width:26px;height:26px;border:0;border-radius:8px;background:rgba(148,163,184,0.1);color:' +
      PANEL_THEME.closeButtonText +
      ';font-size:16px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;">×</button>' +
      "</div>"
    )
  }

  function buildPanelSelectOptions(options) {
    return (options || [])
      .map(function (option) {
        return '<option value="' + option.value + '">' + option.label + "</option>"
      })
      .join("")
  }

  function buildPanelControlRow(config) {
    var colorControl =
      config.controlType === "color"
        ? '<input id="' +
          config.controlId +
          '" type="color" style="width:44px;height:42px;border:' +
          PANEL_THEME.pickerBorder +
          ';border-radius:10px;padding:4px;background:' +
          PANEL_THEME.pickerBackground +
          ';" />'
        : '<select id="' +
          config.controlId +
          '" style="' +
          config.selectStyle +
          '"><option value=""></option>' +
          buildPanelSelectOptions(config.options) +
          "</select>"

    return (
      '<div style="' +
      config.controlShellStyle +
      '">' +
      '<div title="' +
      config.iconLabel +
      '" style="' +
      config.iconStyle +
      '">' +
      config.iconSvg +
      "</div>" +
      '<input id="' +
      config.inputId +
      '" type="text" placeholder="' +
      config.placeholder +
      '" style="' +
      config.inputStyle +
      '" />' +
      colorControl +
      "</div>"
    )
  }

  function buildPanelSectionTitle(label, marginTop) {
    return (
      '<div style="margin-top:' +
      (marginTop || "14px") +
      ';font-size:12px;font-weight:700;color:' +
      PANEL_THEME.sectionTitleText +
      ';letter-spacing:0.02em;">' +
      label +
      "</div>"
    )
  }

  function buildPanelRowStyles() {
    return {
      controlShellStyle:
        "display:grid;grid-template-columns:34px minmax(0,1fr) 52px;gap:10px;align-items:center;",
      iconStyle:
        "width:34px;height:34px;display:flex;align-items:center;justify-content:center;color:" +
        PANEL_THEME.iconText +
        ";opacity:0.92;",
      inputStyle: buildPanelInputStyle(),
      selectStyle: buildPanelInputStyle({ padding: "10px 8px", fontSize: "12px" }),
    }
  }

  function buildBlockStyleRows(prefix, styles) {
    return (
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "寬度",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M3 10 6 7v2h8V7l3 3-3 3v-2H6v2l-3-3Z" fill="currentColor"/></svg>',
        inputId: prefix + "_width__",
        placeholder: "寬度，例如 400px",
        controlId: prefix + "_width_preset__",
        options: [
          { value: "100%", label: "滿" },
          { value: "400px", label: "4" },
          { value: "600px", label: "6" },
          { value: "800px", label: "8" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "圓角",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M5 6a1 1 0 0 1 1-1h4a5 5 0 0 1 5 5v4a1 1 0 1 1-2 0v-4a3 3 0 0 0-3-3H6a1 1 0 0 1-1-1Z" fill="currentColor"/></svg>',
        inputId: prefix + "_radius__",
        placeholder: "圓角，例如 16px",
        controlId: prefix + "_radius_preset__",
        options: [
          { value: "0px", label: "0" },
          { value: "8px", label: "8" },
          { value: "12px", label: "12" },
          { value: "16px", label: "16" },
          { value: "24px", label: "24" },
          { value: "999px", label: "∞" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "陰影",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="4" y="4" width="8" height="8" rx="2" fill="currentColor" opacity=".35"/><rect x="8" y="8" width="8" height="8" rx="2" stroke="currentColor" stroke-width="1.5"/></svg>',
        inputId: prefix + "_shadow__",
        placeholder: "陰影",
        controlId: prefix + "_shadow_preset__",
        options: [
          { value: "none", label: "無" },
          { value: "0 6px 16px rgba(15,23,42,0.08)", label: "輕" },
          { value: "0 12px 28px rgba(15,23,42,0.14)", label: "中" },
          { value: "0 18px 40px rgba(15,23,42,0.18)", label: "深" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "外距",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="6" y="6" width="8" height="8" stroke="currentColor" stroke-width="1.5" stroke-dasharray="2 2"/><path d="M10 2v3M10 15v3M2 10h3M15 10h3" stroke="currentColor" stroke-width="1.5"/></svg>',
        inputId: prefix + "_margin__",
        placeholder: "外距",
        controlId: prefix + "_margin_preset__",
        options: [
          { value: "0px", label: "0" },
          { value: "0px 0px 16px", label: "16" },
          { value: "0px 0px 24px", label: "24" },
          { value: "24px auto", label: "中" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "內距",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="5" y="5" width="10" height="10" stroke="currentColor" stroke-width="1.5"/><rect x="8" y="8" width="4" height="4" fill="currentColor"/></svg>',
        inputId: prefix + "_padding__",
        placeholder: "內距",
        controlId: prefix + "_padding_preset__",
        options: [
          { value: "0px", label: "0" },
          { value: "8px", label: "8" },
          { value: "12px", label: "12" },
          { value: "16px", label: "16" },
          { value: "24px", label: "24" },
          { value: "32px", label: "32" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "背景色",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M6 13.5 11.5 8 15 11.5 9.5 17H6v-3.5Z" fill="currentColor"/><path d="M4 17h12" stroke="currentColor" stroke-width="1.5"/></svg>',
        inputId: prefix + "_background__",
        placeholder: "背景色",
        controlId: prefix + "_background_picker__",
        controlType: "color",
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "邊框寬度",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="4" y="4" width="12" height="12" stroke="currentColor" stroke-width="2"/></svg>',
        inputId: prefix + "_border_width__",
        placeholder: "框線寬",
        controlId: prefix + "_border_width_preset__",
        options: [
          { value: "0px", label: "0" },
          { value: "1px", label: "1" },
          { value: "2px", label: "2" },
          { value: "4px", label: "4" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "邊框樣式",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="4" y="4" width="12" height="12" stroke="currentColor" stroke-width="1.5" stroke-dasharray="2 2"/></svg>',
        inputId: prefix + "_border_style__",
        placeholder: "框線樣式",
        controlId: prefix + "_border_style_preset__",
        options: [
          { value: "none", label: "無" },
          { value: "solid", label: "實" },
          { value: "dashed", label: "虛" },
          { value: "dotted", label: "點" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "邊框顏色",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><rect x="4" y="4" width="12" height="12" stroke="currentColor" stroke-width="1.5"/><path d="M4 15h12" stroke="#2563eb" stroke-width="2"/></svg>',
        inputId: prefix + "_border_color__",
        placeholder: "框線色",
        controlId: prefix + "_border_color_picker__",
        controlType: "color",
      })
    )
  }

  function buildTextStyleRows(prefix, styles) {
    return (
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "字級",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M4 15L8 5h1.6l4 10h-1.9l-.9-2.4H6.6L5.7 15H4Zm3.1-3.9h3.1L8.65 7 7.1 11.1Z" fill="currentColor"/></svg>',
        inputId: prefix + "_font_size__",
        placeholder: "字級，例如 16px",
        controlId: prefix + "_font_size_preset__",
        options: [
          { value: "12px", label: "12" },
          { value: "14px", label: "14" },
          { value: "16px", label: "16" },
          { value: "18px", label: "18" },
          { value: "20px", label: "20" },
          { value: "24px", label: "24" },
          { value: "32px", label: "32" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "粗細",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M6 4h4.8c2.2 0 3.7 1.2 3.7 3.1 0 1.2-.6 2.1-1.7 2.6 1.4.4 2.2 1.5 2.2 3 0 2.1-1.6 3.3-4.1 3.3H6V4Zm1.8 4.9h2.7c1.3 0 2.1-.6 2.1-1.6s-.8-1.6-2.1-1.6H7.8v3.2Zm0 5.5h3c1.5 0 2.4-.6 2.4-1.8s-.9-1.8-2.4-1.8h-3v3.6Z" fill="currentColor"/></svg>',
        inputId: prefix + "_font_weight__",
        placeholder: "粗細，例如 400",
        controlId: prefix + "_font_weight_preset__",
        options: [
          { value: "300", label: "300" },
          { value: "400", label: "400" },
          { value: "500", label: "500" },
          { value: "600", label: "600" },
          { value: "700", label: "700" },
          { value: "800", label: "800" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "字型",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M4 5h12v1.8h-5V16H9V6.8H4V5Z" fill="currentColor"/></svg>',
        inputId: prefix + "_font_family__",
        placeholder: "字型，例如 Noto Sans TC",
        controlId: prefix + "_font_family_preset__",
        options: [
          { value: '"Noto Sans TC", sans-serif', label: "黑" },
          { value: '"PingFang TC", sans-serif', label: "蘋" },
          { value: '"Microsoft JhengHei", sans-serif', label: "微" },
          { value: "Georgia, serif", label: "G" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "對齊",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M4 5h12v2H4V5Zm2 4h8v2H6V9Zm-2 4h12v2H4v-2Z" fill="currentColor"/></svg>',
        inputId: prefix + "_align__",
        placeholder: "對齊，例如 center",
        controlId: prefix + "_align_preset__",
        options: [
          { value: "left", label: "左" },
          { value: "center", label: "中" },
          { value: "right", label: "右" },
          { value: "justify", label: "齊" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "文字顏色",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10.9 4 15 15h-2.1l-.8-2.3H7.8L7 15H5L9.1 4h1.8Zm.5 6.9L10 6.8 8.6 10.9h2.8ZM5 16h10v1.5H5V16Z" fill="currentColor"/></svg>',
        inputId: prefix + "_color__",
        placeholder: "顏色，例如 #111827",
        controlId: prefix + "_color_picker__",
        controlType: "color",
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "文字裝飾",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M6 4v4a4 4 0 1 0 8 0V4h-2v4a2 2 0 1 1-4 0V4H6Zm-1 11h10v1.5H5V15Z" fill="currentColor"/></svg>',
        inputId: prefix + "_decoration__",
        placeholder: "裝飾，例如 underline",
        controlId: prefix + "_decoration_preset__",
        options: [
          { value: "none", label: "無" },
          { value: "underline", label: "底" },
          { value: "line-through", label: "刪" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "行高",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3 7 6h2v8H7l3 3 3-3h-2V6h2l-3-3ZM4 7h2v6H4V7Zm10 0h2v6h-2V7Z" fill="currentColor"/></svg>',
        inputId: prefix + "_line_height__",
        placeholder: "行高，例如 1.6",
        controlId: prefix + "_line_height_preset__",
        options: [
          { value: "1.2", label: "1.2" },
          { value: "1.4", label: "1.4" },
          { value: "1.6", label: "1.6" },
          { value: "1.8", label: "1.8" },
          { value: "2", label: "2" },
        ],
      }) +
      buildPanelControlRow({
        controlShellStyle: styles.controlShellStyle,
        iconStyle: styles.iconStyle,
        inputStyle: styles.inputStyle,
        selectStyle: styles.selectStyle,
        iconLabel: "字距",
        iconSvg:
          '<svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M4 10 7 7v2h6V7l3 3-3 3v-2H7v2l-3-3ZM2 5h1.5v10H2V5Zm14.5 0H18v10h-1.5V5Z" fill="currentColor"/></svg>',
        inputId: prefix + "_letter_spacing__",
        placeholder: "字距，例如 1px",
        controlId: prefix + "_letter_spacing_preset__",
        options: [
          { value: "normal", label: "N" },
          { value: "0px", label: "0" },
          { value: "0.5px", label: "0.5" },
          { value: "1px", label: "1" },
          { value: "2px", label: "2" },
        ],
      })
    )
  }

  function ensureInlineTextPanel() {
    var panel = document.getElementById(INLINE_TEXT_PANEL_ID)
    if (panel) {
      return panel
    }

    panel = document.createElement("div")
    panel.id = INLINE_TEXT_PANEL_ID
    applyInlineFloatingPanelStyles(panel, {
      width: "324px",
      left: "calc(100vw - 356px)",
    })
    var panelRowStyles = buildPanelRowStyles()
    var textStyleRows = buildTextStyleRows("__mkt_editor_inline_text", panelRowStyles)
    var blockStyleRows = buildBlockStyleRows("__mkt_editor_inline_text_block", panelRowStyles)
    panel.innerHTML =
      buildInlineFloatingPanelHeader("__mkt_editor_inline_text_close__", "__mkt_editor_inline_text_title__") +
      '<textarea id="__mkt_editor_inline_text_area__" style="margin-top:2px;' +
      buildPanelInputStyle({
        minHeight: "88px",
        fontSize: "14px",
        padding: "12px 14px",
        extra: "line-height:1.7;resize:vertical;",
      }) +
      '"></textarea>' +
      '<div id="__mkt_editor_inline_text_link_group__" style="display:none;margin-top:8px;">' +
      '<div style="font-size:12px;font-weight:700;color:#0f172a;margin-bottom:6px;">按鈕 / 連結網址</div>' +
      '<input id="__mkt_editor_inline_text_link_input__" type="text" placeholder="https://example.com" style="' +
      buildPanelInputStyle({}) +
      '" />' +
      "</div>" +
      '<div style="margin-top:14px;padding-top:12px;border-top:' +
      PANEL_THEME.divider +
      ';">' +
      buildPanelSectionTitle("文字樣式", "0px") +
      '<div style="margin-top:8px;display:grid;gap:10px;">' +
      textStyleRows +
      buildPanelSectionTitle("區塊樣式") +
      '<div style="margin-top:8px;display:grid;gap:10px;">' +
      blockStyleRows +
      "</div>"

    document.body.appendChild(panel)
    bindFloatingPanelDrag(panel, INLINE_TEXT_PANEL_ID)
    decoratePanelIconTooltips(panel)
    var closeButton = panel.querySelector("#__mkt_editor_inline_text_close__")
    if (closeButton) {
      closeButton.onclick = function (event) {
        event.preventDefault()
        event.stopPropagation()
        clearInlineEdit()
      }
    }
    return panel
  }

  function ensureInlineAttributePanel() {
    var panel = document.getElementById(INLINE_ATTRIBUTE_PANEL_ID)
    if (panel) {
      return panel
    }

    panel = document.createElement("div")
    panel.id = INLINE_ATTRIBUTE_PANEL_ID
    applyInlineFloatingPanelStyles(panel, {
      width: "324px",
      left: "calc(100vw - 356px)",
    })
    var panelRowStyles = buildPanelRowStyles()
    panel.innerHTML =
      buildInlineFloatingPanelHeader("__mkt_editor_inline_attr_close__", "__mkt_editor_inline_attr_title__") +
      '<div id="__mkt_editor_inline_attr_label__" style="font-size:11px;font-weight:700;color:#64748b;letter-spacing:0.05em;text-transform:uppercase;margin:2px 0 5px;"></div>' +
      '<input id="__mkt_editor_inline_attr_input__" type="text" style="' +
      buildPanelInputStyle({ fontSize: "14px", padding: "12px 14px" }) +
      '" />' +
      '<div style="margin-top:14px;padding-top:12px;border-top:' +
      PANEL_THEME.divider +
      ';">' +
      buildPanelSectionTitle("區塊樣式", "0px") +
      '<div style="margin-top:8px;display:grid;gap:10px;">' +
      buildBlockStyleRows("__mkt_editor_inline_attr", panelRowStyles) +
      "</div>"

    document.body.appendChild(panel)
    bindFloatingPanelDrag(panel, INLINE_ATTRIBUTE_PANEL_ID)
    decoratePanelIconTooltips(panel)
    var closeButton = panel.querySelector("#__mkt_editor_inline_attr_close__")
    if (closeButton) {
      closeButton.onclick = function (event) {
        event.preventDefault()
        event.stopPropagation()
        clearInlineEdit()
      }
    }
    return panel
  }

  function updateInlineEditBadge(element) {
    var badge = ensureInlineEditBadge()
    if (!badge || !element) {
      return
    }
  }

  function applyInlineStyleValue(element, property, value, source) {
    if (!element || !property) {
      return
    }

    element.style[property] = value || ""
    postToParent({
      type: INLINE_STYLE_CHANGE,
      selector: buildSelector(element),
      style_property: property,
      style_value: value || "",
      page_html: createPageHtmlSnapshot(),
      source: source || "canvas-inline-edit",
    })
  }

  function bindImmediateStyleControl(element, inputNode, property, source) {
    if (!element || !inputNode || !property) {
      return
    }

    inputNode.oninput = function () {
      applyInlineStyleValue(element, property, inputNode.value || "", source || "canvas-inline-edit-live")
    }
  }

  function rgbStringToHex(value) {
    if (!value || typeof value !== "string") {
      return ""
    }

    if (value[0] === "#") {
      return value
    }

    var match = value.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i)
    if (!match) {
      return ""
    }

    var hex = [match[1], match[2], match[3]]
      .map(function (part) {
        var number = Number(part)
        var normalized = Number.isNaN(number) ? 0 : Math.max(0, Math.min(255, number))
        return normalized.toString(16).padStart(2, "0")
      })
      .join("")

    return "#" + hex
  }

  function bindPresetSelectToInput(selectNode, inputNode) {
    if (!selectNode || !inputNode) {
      return
    }

    selectNode.onchange = function () {
      if (!selectNode.value) {
        return
      }

      inputNode.value = selectNode.value
      if (typeof inputNode.oninput === "function") {
        inputNode.oninput()
      }
      selectNode.value = ""
    }
  }

  function bindColorPickerToInput(colorPickerNode, inputNode, fallbackHex) {
    if (!colorPickerNode || !inputNode) {
      return
    }

    colorPickerNode.value = rgbStringToHex(inputNode.value) || fallbackHex || "#000000"
    colorPickerNode.oninput = function () {
      inputNode.value = colorPickerNode.value || ""
      if (typeof inputNode.oninput === "function") {
        inputNode.oninput()
      }
    }
  }

  function initializeBlockStyleControls(targetElement, controls) {
    if (!targetElement || !controls) {
      return
    }

    if (controls.widthNode) {
      controls.widthNode.value = getComputedStyleValue(targetElement, "width") || ""
    }
    bindImmediateStyleControl(targetElement, controls.widthNode, "width")
    bindPresetSelectToInput(controls.widthPresetNode, controls.widthNode)

    if (controls.radiusNode) {
      controls.radiusNode.value = getComputedStyleValue(targetElement, "borderRadius") || "0px"
    }
    bindImmediateStyleControl(targetElement, controls.radiusNode, "borderRadius")
    bindPresetSelectToInput(controls.radiusPresetNode, controls.radiusNode)

    if (controls.shadowNode) {
      controls.shadowNode.value = getComputedStyleValue(targetElement, "boxShadow") || "none"
    }
    bindImmediateStyleControl(targetElement, controls.shadowNode, "boxShadow")
    bindPresetSelectToInput(controls.shadowPresetNode, controls.shadowNode)

    if (controls.marginNode) {
      controls.marginNode.value = getComputedStyleValue(targetElement, "margin") || "0px"
    }
    bindImmediateStyleControl(targetElement, controls.marginNode, "margin")
    bindPresetSelectToInput(controls.marginPresetNode, controls.marginNode)

    if (controls.paddingNode) {
      controls.paddingNode.value = getComputedStyleValue(targetElement, "padding") || "0px"
    }
    bindImmediateStyleControl(targetElement, controls.paddingNode, "padding")
    bindPresetSelectToInput(controls.paddingPresetNode, controls.paddingNode)

    if (controls.backgroundNode) {
      var backgroundValue = getComputedStyleValue(targetElement, "backgroundColor")
      controls.backgroundNode.value =
        rgbStringToHex(backgroundValue) || backgroundValue || "#ffffff"
    }
    bindImmediateStyleControl(targetElement, controls.backgroundNode, "backgroundColor")
    bindColorPickerToInput(controls.backgroundPickerNode, controls.backgroundNode, "#ffffff")

    if (controls.borderWidthNode) {
      controls.borderWidthNode.value = getComputedStyleValue(targetElement, "borderWidth") || "0px"
    }
    bindImmediateStyleControl(targetElement, controls.borderWidthNode, "borderWidth")
    bindPresetSelectToInput(controls.borderWidthPresetNode, controls.borderWidthNode)

    if (controls.borderStyleNode) {
      controls.borderStyleNode.value = getComputedStyleValue(targetElement, "borderStyle") || "none"
    }
    bindImmediateStyleControl(targetElement, controls.borderStyleNode, "borderStyle")
    bindPresetSelectToInput(controls.borderStylePresetNode, controls.borderStyleNode)

    if (controls.borderColorNode) {
      var borderColorValue = getComputedStyleValue(targetElement, "borderColor")
      controls.borderColorNode.value =
        rgbStringToHex(borderColorValue) || borderColorValue || "#cbd5e1"
    }
    bindImmediateStyleControl(targetElement, controls.borderColorNode, "borderColor")
    bindColorPickerToInput(controls.borderColorPickerNode, controls.borderColorNode, "#cbd5e1")
  }

  function getComputedStyleValue(element, property) {
    if (!element || !property || typeof window.getComputedStyle !== "function") {
      return ""
    }

    try {
      return window.getComputedStyle(element)[property] || ""
    } catch (error) {
      return ""
    }
  }

  function queryBlockStyleControlNodes(panel, prefix) {
    return {
      widthNode: panel.querySelector("#" + prefix + "_width__"),
      widthPresetNode: panel.querySelector("#" + prefix + "_width_preset__"),
      radiusNode: panel.querySelector("#" + prefix + "_radius__"),
      radiusPresetNode: panel.querySelector("#" + prefix + "_radius_preset__"),
      shadowNode: panel.querySelector("#" + prefix + "_shadow__"),
      shadowPresetNode: panel.querySelector("#" + prefix + "_shadow_preset__"),
      marginNode: panel.querySelector("#" + prefix + "_margin__"),
      marginPresetNode: panel.querySelector("#" + prefix + "_margin_preset__"),
      paddingNode: panel.querySelector("#" + prefix + "_padding__"),
      paddingPresetNode: panel.querySelector("#" + prefix + "_padding_preset__"),
      backgroundNode: panel.querySelector("#" + prefix + "_background__"),
      backgroundPickerNode: panel.querySelector("#" + prefix + "_background_picker__"),
      borderWidthNode: panel.querySelector("#" + prefix + "_border_width__"),
      borderWidthPresetNode: panel.querySelector("#" + prefix + "_border_width_preset__"),
      borderStyleNode: panel.querySelector("#" + prefix + "_border_style__"),
      borderStylePresetNode: panel.querySelector("#" + prefix + "_border_style_preset__"),
      borderColorNode: panel.querySelector("#" + prefix + "_border_color__"),
      borderColorPickerNode: panel.querySelector("#" + prefix + "_border_color_picker__"),
    }
  }

  function queryTextStyleControlNodes(panel, prefix) {
    return {
      fontSizeNode: panel.querySelector("#" + prefix + "_font_size__"),
      fontSizePresetNode: panel.querySelector("#" + prefix + "_font_size_preset__"),
      fontWeightNode: panel.querySelector("#" + prefix + "_font_weight__"),
      fontWeightPresetNode: panel.querySelector("#" + prefix + "_font_weight_preset__"),
      fontFamilyNode: panel.querySelector("#" + prefix + "_font_family__"),
      fontFamilyPresetNode: panel.querySelector("#" + prefix + "_font_family_preset__"),
      textAlignNode: panel.querySelector("#" + prefix + "_align__"),
      textAlignPresetNode: panel.querySelector("#" + prefix + "_align_preset__"),
      textColorNode: panel.querySelector("#" + prefix + "_color__"),
      textColorPickerNode: panel.querySelector("#" + prefix + "_color_picker__"),
      textDecorationNode: panel.querySelector("#" + prefix + "_decoration__"),
      textDecorationPresetNode: panel.querySelector("#" + prefix + "_decoration_preset__"),
      textLineHeightNode: panel.querySelector("#" + prefix + "_line_height__"),
      textLineHeightPresetNode: panel.querySelector("#" + prefix + "_line_height_preset__"),
      textLetterSpacingNode: panel.querySelector("#" + prefix + "_letter_spacing__"),
      textLetterSpacingPresetNode: panel.querySelector("#" + prefix + "_letter_spacing_preset__"),
    }
  }

  function applyFloatingPanelPosition(panel, panelId) {
    if (!panel) {
      return
    }

    var savedPosition = floatingPanelPositions[panelId]
    if (savedPosition) {
      panel.style.left = savedPosition.left + "px"
      panel.style.top = savedPosition.top + "px"
      panel.style.right = "auto"
      panel.style.bottom = "auto"
      return
    }

    var panelWidth = panel.offsetWidth || 300
    var viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0
    var left = Math.max(16, viewportWidth - panelWidth - 24)
    var top = 88

    panel.style.left = left + "px"
    panel.style.top = top + "px"
    panel.style.right = "auto"
    panel.style.bottom = "auto"
  }

  function showFloatingPanel(panel, panelId) {
    if (!panel) {
      return
    }

    panel.style.display = "block"
    applyFloatingPanelPosition(panel, panelId)
  }

  function bindFloatingPanelDrag(panel, panelId) {
    var handle = panel && panel.querySelector("[data-mkt-panel-drag-handle='true']")
    if (!handle) {
      return
    }

    handle.addEventListener("pointerdown", function (event) {
      event.preventDefault()
      event.stopPropagation()

      var rect = panel.getBoundingClientRect()
      currentFloatingPanelDragState = {
        panel: panel,
        panelId: SHARED_INLINE_PANEL_POSITION_ID,
        offsetX: event.clientX - rect.left,
        offsetY: event.clientY - rect.top,
      }

      handle.style.cursor = "grabbing"
    })
  }

  function updateFloatingPanelDrag(event) {
    if (!currentFloatingPanelDragState) {
      return
    }

    var panel = currentFloatingPanelDragState.panel
    var panelWidth = panel.offsetWidth || 300
    var panelHeight = panel.offsetHeight || 240
    var viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0
    var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0

    var left = event.clientX - currentFloatingPanelDragState.offsetX
    var top = event.clientY - currentFloatingPanelDragState.offsetY

    left = Math.min(Math.max(12, left), Math.max(12, viewportWidth - panelWidth - 12))
    top = Math.min(Math.max(72, top), Math.max(12, viewportHeight - panelHeight - 12))

    panel.style.left = left + "px"
    panel.style.top = top + "px"
    panel.style.right = "auto"
    panel.style.bottom = "auto"
    floatingPanelPositions[currentFloatingPanelDragState.panelId] = { left: left, top: top }
  }

  function endFloatingPanelDrag() {
    if (!currentFloatingPanelDragState) {
      return
    }

    var handle = currentFloatingPanelDragState.panel.querySelector("[data-mkt-panel-drag-handle='true']")
    if (handle) {
      handle.style.cursor = "grab"
    }

    currentFloatingPanelDragState = null
  }

  function bindLiveAttributeInput(inputNode, targetElement, attributeName) {
    if (!inputNode || !targetElement || !attributeName) {
      return
    }

    inputNode.oninput = function () {
      targetElement.setAttribute(attributeName, inputNode.value || "")
      postToParent({
        type: INLINE_ATTRIBUTE_CHANGE,
        selector: buildSelector(targetElement),
        attribute_name: attributeName,
        attribute_value: inputNode.value || "",
        page_html: createPageHtmlSnapshot(),
        source: "canvas-inline-edit-live",
      })
    }

    inputNode.onkeydown = function (event) {
      if (event.key === "Escape") {
        event.preventDefault()
        clearInlineEdit()
      }
    }
  }

  function focusAndSelectInput(inputNode) {
    if (!inputNode) {
      return
    }

    window.setTimeout(function () {
      inputNode.focus()
      if (typeof inputNode.select === "function") {
        inputNode.select()
      }
    }, 32)
  }

  function bindLiveTextInput(textAreaNode, targetElement) {
    if (!textAreaNode || !targetElement) {
      return
    }

    textAreaNode.value = ((targetElement.innerText || targetElement.textContent) || "").trim()
    currentInlineTextOriginalValue = textAreaNode.value
    focusAndSelectInput(textAreaNode)
    textAreaNode.oninput = function () {
      targetElement.textContent = textAreaNode.value || ""
      updateInlineEditBadge(targetElement)
      postToParent({
        type: INLINE_TEXT_CHANGE,
        selector: buildSelector(targetElement),
        text: ((targetElement.innerText || targetElement.textContent) || "")
          .trim()
          .slice(0, 500),
        page_html: createPageHtmlSnapshot(),
        source: "canvas-inline-edit-live",
      })
    }
    textAreaNode.onkeydown = function (event) {
      if (event.key === "Escape") {
        event.preventDefault()
        clearInlineEdit()
      }
    }
  }

  function bindLinkedAttributeInput(groupNode, inputNode, targetElement, attributeName) {
    if (!groupNode || !inputNode) {
      return
    }

    var editableLinkElement =
      targetElement && targetElement.tagName && targetElement.tagName.toLowerCase() === "a"
        ? targetElement
        : targetElement && targetElement.closest
        ? targetElement.closest("a")
        : null

    if (!editableLinkElement || !attributeName) {
      groupNode.style.display = "none"
      inputNode.value = ""
      inputNode.oninput = null
      inputNode.onkeydown = null
      return
    }

    groupNode.style.display = "block"
    inputNode.value = editableLinkElement.getAttribute(attributeName) || ""
    bindLiveAttributeInput(inputNode, editableLinkElement, attributeName)
  }

  function initializeTextStyleControls(targetElement, controls) {
    if (!targetElement || !controls) {
      return
    }

    if (controls.fontSizeNode) {
      controls.fontSizeNode.value = getComputedStyleValue(targetElement, "fontSize") || "16px"
    }
    bindImmediateStyleControl(targetElement, controls.fontSizeNode, "fontSize")
    bindPresetSelectToInput(controls.fontSizePresetNode, controls.fontSizeNode)

    if (controls.fontWeightNode) {
      controls.fontWeightNode.value = getComputedStyleValue(targetElement, "fontWeight") || "400"
    }
    bindImmediateStyleControl(targetElement, controls.fontWeightNode, "fontWeight")
    bindPresetSelectToInput(controls.fontWeightPresetNode, controls.fontWeightNode)

    if (controls.fontFamilyNode) {
      controls.fontFamilyNode.value = getComputedStyleValue(targetElement, "fontFamily") || ""
    }
    bindImmediateStyleControl(targetElement, controls.fontFamilyNode, "fontFamily")
    bindPresetSelectToInput(controls.fontFamilyPresetNode, controls.fontFamilyNode)

    if (controls.textAlignNode) {
      controls.textAlignNode.value = getComputedStyleValue(targetElement, "textAlign") || "left"
    }
    bindImmediateStyleControl(targetElement, controls.textAlignNode, "textAlign")
    bindPresetSelectToInput(controls.textAlignPresetNode, controls.textAlignNode)

    if (controls.textColorNode) {
      var textColorValue = getComputedStyleValue(targetElement, "color")
      controls.textColorNode.value = rgbStringToHex(textColorValue) || textColorValue || "#000000"
    }
    bindImmediateStyleControl(targetElement, controls.textColorNode, "color")
    bindColorPickerToInput(controls.textColorPickerNode, controls.textColorNode, "#000000")

    if (controls.textDecorationNode) {
      controls.textDecorationNode.value = getComputedStyleValue(targetElement, "textDecoration") || "none"
    }
    bindImmediateStyleControl(targetElement, controls.textDecorationNode, "textDecoration")
    bindPresetSelectToInput(controls.textDecorationPresetNode, controls.textDecorationNode)

    if (controls.textLineHeightNode) {
      var textLineHeightValue = getComputedStyleValue(targetElement, "lineHeight")
      controls.textLineHeightNode.value =
        textLineHeightValue && textLineHeightValue !== "normal" ? textLineHeightValue : "1.6"
    }
    bindImmediateStyleControl(targetElement, controls.textLineHeightNode, "lineHeight")
    bindPresetSelectToInput(controls.textLineHeightPresetNode, controls.textLineHeightNode)

    if (controls.textLetterSpacingNode) {
      controls.textLetterSpacingNode.value =
        getComputedStyleValue(targetElement, "letterSpacing") || "normal"
    }
    bindImmediateStyleControl(targetElement, controls.textLetterSpacingNode, "letterSpacing")
    bindPresetSelectToInput(controls.textLetterSpacingPresetNode, controls.textLetterSpacingNode)
  }

  function updateInspectPanel(mode, selector, element) {
    var panel = ensureInspectPanel()
    if (!panel) {
      return
    }
  }

  function updateHighlight(element, mode) {
    var box = ensureHighlightBox()
    var hotspot = ensureActionHotspot()
    if (!element) {
      box.style.display = "none"
      hotspot.style.display = "none"
      return
    }

    var rect = element.getBoundingClientRect()
    var tone = getModeTone(mode, latestStatusSummary && latestStatusSummary.status)

    var computedRadius = window.getComputedStyle(element).borderRadius
    box.style.display = "block"
    box.style.top = rect.top + "px"
    box.style.left = rect.left + "px"
    box.style.width = rect.width + "px"
    box.style.height = rect.height + "px"
    box.style.borderRadius = computedRadius || "10px"
    box.style.border = mode === "hover" ? "2px dashed " + tone.border : "2px solid " + tone.border
    box.style.boxShadow = "none"

    if (mode === "focus" || mode === "inspect") {
      hotspot.style.display = "block"
      hotspot.style.top = Math.max(12, rect.top - 42) + "px"
      hotspot.style.left = Math.max(12, rect.left + rect.width / 2 - 36) + "px"
    } else {
      hotspot.style.display = "none"
    }
  }

  function updateHoverHighlight(element) {
    var box = ensureHoverHighlightBox()
    if (!element) {
      box.style.display = "none"
      return
    }

    var rect = element.getBoundingClientRect()
    var hoverRadius = window.getComputedStyle(element).borderRadius
    box.style.display = "block"
    box.style.top = rect.top + "px"
    box.style.left = rect.left + "px"
    box.style.width = rect.width + "px"
    box.style.height = rect.height + "px"
    box.style.borderRadius = hoverRadius || "10px"
  }

  function hideDropIndicator() {
    var indicator = ensureDropIndicator()
    indicator.style.display = "none"
    ensureDropBadge().style.display = "none"
  }

  function clearDragTargetMarker() {
    if (!currentDragMoveState || !currentDragMoveState.target) {
      return
    }

    currentDragMoveState.target.style.outline = ""
    currentDragMoveState.target.style.outlineOffset = ""
    currentDragMoveState.target.style.backgroundImage = currentDragMoveState.targetOriginalBackground || ""
  }

  function updateDropIndicator(targetElement, position) {
    var indicator = ensureDropIndicator()
    var badge = ensureDropBadge()
    if (!targetElement) {
      indicator.style.display = "none"
      badge.style.display = "none"
      return
    }

    var rect = targetElement.getBoundingClientRect()
    indicator.style.display = "block"
    indicator.style.left = rect.left + "px"
    indicator.style.width = rect.width + "px"
    indicator.style.top =
      (position === "before" ? rect.top : rect.bottom) - 2 + "px"

    badge.style.display = "block"
    badge.textContent = position === "before" ? "插入到這塊前面" : "插入到這塊後面"
    badge.style.left = Math.max(12, rect.left + rect.width / 2 - 58) + "px"
    badge.style.top = Math.max(12, (position === "before" ? rect.top : rect.bottom) - 34) + "px"
  }

  function maybeAutoScrollDuringDrag(event) {
    if (!currentDragMoveState) {
      return
    }

    var viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0
    var threshold = 96
    var delta = 0

    if (event.clientY < threshold) {
      delta = -18
    } else if (event.clientY > viewportHeight - threshold) {
      delta = 18
    }

    if (!delta) {
      return
    }

    window.scrollBy({
      top: delta,
      left: 0,
      behavior: "auto",
    })
  }

  function buildSelector(element) {
    if (!element || !(element instanceof Element)) {
      return ""
    }

    if (element.id) {
      var escapedId = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(element.id) : element.id
      return "#" + escapedId
    }

    if (element.dataset && element.dataset.mktSelector) {
      return '[data-mkt-selector="' + element.dataset.mktSelector.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"]'
    }

    return assignRuntimeSelector(element)
  }

  function assignRuntimeSelector(element, force) {
    if (!element || !(element instanceof Element)) {
      return ""
    }

    if (!force && element.dataset && element.dataset.mktSelector) {
      return '[data-mkt-selector="' + element.dataset.mktSelector.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"]'
    }

    runtimeSelectorSeed += 1
    var token = "mkt-" + runtimeSelectorSeed
    element.setAttribute("data-mkt-selector", token)
    return '[data-mkt-selector="' + token + '"]'
  }

  function seedRuntimeSelectors(root, force) {
    if (!root || !(root instanceof Element)) {
      return
    }

    Array.prototype.forEach.call(
      root.querySelectorAll(
        "a,button,img,h1,h2,h3,h4,h5,h6,p,span,li,[role='button'],section,article,aside,nav,header,footer,main,div"
      ),
      function (node) {
        assignRuntimeSelector(node, force)
      }
    )
  }

  function assignRuntimeSelectorsInSubtree(root, force) {
    if (!root || !(root instanceof Element)) {
      return ""
    }

    var rootSelector = assignRuntimeSelector(root, force)
    seedRuntimeSelectors(root, force)
    return rootSelector
  }

  function isBridgeUiElement(element) {
    return Boolean(
      element &&
        typeof element.closest === "function" &&
        element.closest(
          "#" +
            PANEL_ID +
            ",#" +
            HOTSPOT_ID +
            ",#" +
            HIGHLIGHT_ID +
            ",#" +
            HOVER_HIGHLIGHT_ID +
            ",#" +
            INLINE_TEXT_PANEL_ID +
            ",#" +
            INLINE_ATTRIBUTE_PANEL_ID
        )
    )
  }

  function resolveInspectableElement(element) {
    if (!element || !(element instanceof Element) || isBridgeUiElement(element)) {
      return null
    }

    var candidate = element.closest(
      "[data-mkt-selector],a,button,img,h1,h2,h3,h4,h5,h6,p,span,li,[role='button'],section,article,aside,nav,header,footer,main,div"
    )

    if (!candidate || candidate === document.body || candidate === document.documentElement) {
      return element
    }

    return candidate
  }

  function resolveStructureTargetElement(element) {
    if (!element || !(element instanceof Element) || isBridgeUiElement(element)) {
      return null
    }

    var preferred = element.closest(
      "[data-mkt-block],section,article,aside,header,footer,main,nav,.card,.feature-card,.hero,.offer-section,.testimonial-section"
    )

    if (!preferred || preferred === document.body || preferred === document.documentElement) {
      return element
    }

    return preferred
  }

  function extractElementMeta(element) {
    return {
      selector: buildSelector(element),
      tag: element && element.tagName ? element.tagName.toLowerCase() : "",
      text: ((element && (element.innerText || element.textContent)) || "").trim().slice(0, 120),
      href: (element && element.getAttribute && element.getAttribute("href")) || "",
      src: (element && element.getAttribute && element.getAttribute("src")) || "",
      hidden: Boolean(element && element.getAttribute && element.getAttribute("data-mkt-hidden") === "true"),
    }
  }

  function handleSelection(element, source) {
    var inspectableElement = resolveInspectableElement(element)
    if (!inspectableElement) {
      return
    }

    var meta = extractElementMeta(inspectableElement)
    currentFocusedElement = inspectableElement
    refreshVisualState()
    postToParent({
      type: SELECTION,
      selector: meta.selector,
      tag: meta.tag,
      text: meta.text,
      href: meta.href,
      src: meta.src,
      source: source || "canvas-bridge",
    })
  }

  function handleHover(element) {
    var inspectableElement = resolveInspectableElement(element)
    if (!inspectableElement) {
      return
    }

    var meta = extractElementMeta(inspectableElement)
    var selector = meta.selector
    if (!selector || selector === lastHoveredSelector) {
      return
    }

    lastHoveredSelector = selector
    currentHoveredElement = inspectableElement
    refreshVisualState()
  }

  function clearHoverState() {
    if (!lastHoveredSelector) {
      return
    }

    lastHoveredSelector = ""
    currentHoveredElement = null
    refreshVisualState()
  }

  function handleFocusSelector(selector) {
    if (!selector) {
      return
    }

    var element = document.querySelector(selector)
    if (!element) {
      return
    }

    currentFocusedElement = element
    refreshVisualState()
  }

  function handleInspectSelector(selector) {
    handleFocusSelector(selector)
  }

  function postStructureChange(action, selector, nextSelector, extra) {
    postToParent(Object.assign({
      type: BLOCK_STRUCTURE_CHANGE,
      action: action,
      selector: selector,
      next_selector: nextSelector,
      page_html: createPageHtmlSnapshot(),
      source: "canvas-bridge",
    }, extra || {}))
  }

  function handleMoveBlock(payload) {
    var selector = typeof payload === "string" ? payload : payload && payload.selector
    var direction = (payload && payload.direction) || "down"
    if (!selector) {
      return
    }

    clearInlineEdit()
    var element = resolveInspectableElement(document.querySelector(selector))
    if (!element || !element.parentElement) {
      return
    }

    if (direction === "up") {
      var previous = element.previousElementSibling
      if (previous) {
        element.parentElement.insertBefore(element, previous)
      }
    } else {
      var next = element.nextElementSibling
      if (next) {
        element.parentElement.insertBefore(next, element)
      }
    }

    currentFocusedElement = element
    refreshVisualState()
    var meta = extractElementMeta(element)
    postStructureChange("move", selector, meta.selector, { tag: meta.tag, text: meta.text, href: meta.href, src: meta.src, hidden: meta.hidden })
  }

  function handleToggleBlockVisibility(payload) {
    var selector = typeof payload === "string" ? payload : payload && payload.selector
    if (!selector) {
      return
    }

    clearInlineEdit()
    var element = resolveInspectableElement(document.querySelector(selector))
    if (!element) {
      return
    }

    var isHidden = element.getAttribute("data-mkt-hidden") === "true"

    if (isHidden) {
      var originalDisplay = element.getAttribute("data-mkt-hidden-display") || ""
      element.style.display = originalDisplay
      element.removeAttribute("data-mkt-hidden")
      element.removeAttribute("data-mkt-hidden-display")
      showFeedbackBadge("已顯示區塊", element)
    } else {
      element.setAttribute("data-mkt-hidden-display", element.style.display || "")
      element.setAttribute("data-mkt-hidden", "true")
      element.style.display = "none"
      showFeedbackBadge("已隱藏區塊")
    }

    postToParent({
      type: BLOCK_VISIBILITY_CHANGE,
      selector: selector,
      hidden: !isHidden,
      page_html: createPageHtmlSnapshot(),
      source: "canvas-bridge",
    })

    refreshVisualState()
  }

  function handleDuplicateBlock(payload) {
    var selector = typeof payload === "string" ? payload : payload && payload.selector
    if (!selector) {
      return
    }

    clearInlineEdit()
    var element = resolveInspectableElement(document.querySelector(selector))
    if (!element || !element.parentElement) {
      return
    }

    var clone = element.cloneNode(true)
    clone.removeAttribute("id")
    Array.prototype.forEach.call(clone.querySelectorAll("[id]"), function (node) {
      node.removeAttribute("id")
    })

    var nextSelector = assignRuntimeSelectorsInSubtree(clone, true)
    element.parentElement.insertBefore(clone, element.nextElementSibling)
    currentFocusedElement = clone
    refreshVisualState()
    showFeedbackBadge("已複製區塊", clone)

    var meta = extractElementMeta(clone)
    postStructureChange("duplicate", selector, nextSelector || meta.selector, { tag: meta.tag, text: meta.text, href: meta.href, src: meta.src, hidden: false })
  }

  function handleDeleteBlock(payload) {
    var selector = typeof payload === "string" ? payload : payload && payload.selector
    if (!selector) {
      return
    }

    clearInlineEdit()
    var element = resolveInspectableElement(document.querySelector(selector))
    if (!element || !element.parentElement) {
      return
    }

    var nextElement = element.nextElementSibling || element.previousElementSibling
    var nextSelector = nextElement ? buildSelector(nextElement) : ""

    if (currentFocusedElement === element) {
      currentFocusedElement = nextElement || null
    }

    element.remove()
    refreshVisualState()
    showFeedbackBadge("已刪除區塊", nextElement || null)

    postStructureChange("delete", selector, nextSelector)
  }

  function buildInsertedBlock(type) {
    var blockType = type || "content-card"
    var element

    if (blockType === "text-block") {
      element = document.createElement("section")
      element.className = "card feature-card"
      element.style.padding = "24px"
      element.style.marginTop = "18px"
      element.innerHTML =
        '<span class="hero-kicker" data-mkt-selector="inserted-kicker">新段落</span>' +
        '<h3 data-mkt-selector="inserted-title">新的文字標題</h3>' +
        '<p data-mkt-selector="inserted-copy">在這裡輸入新的段落內容，之後可以直接改文字與樣式。</p>'
    } else if (blockType === "button-block") {
      element = document.createElement("section")
      element.className = "card feature-card"
      element.style.padding = "24px"
      element.style.marginTop = "18px"
      element.innerHTML =
        '<h3 data-mkt-selector="inserted-title">新的行動按鈕</h3>' +
        '<p data-mkt-selector="inserted-copy">你可以直接替換按鈕文案與連結，快速建立新的 CTA 區塊。</p>' +
        '<div class="hero-actions"><a href="https://ose.tw" class="cta-button" data-mkt-selector="inserted-button">立即了解</a></div>'
    } else if (blockType === "image-block") {
      element = document.createElement("section")
      element.className = "card feature-card"
      element.style.padding = "24px"
      element.style.marginTop = "18px"
      element.innerHTML =
        '<img src="https://images.unsplash.com/photo-1516321318423-f06f85e504b3?auto=format&fit=crop&w=1200&q=80" alt="新的圖片區塊" style="width: 100%; border-radius: 20px;" data-mkt-selector="inserted-image">' +
        '<p data-mkt-selector="inserted-caption" style="margin: 12px 0 0; color: #475569;">新的圖片說明，可直接替換成活動主視覺描述。</p>'
    } else {
      element = document.createElement("section")
      element.className = "card feature-card"
      element.style.padding = "24px"
      element.style.marginTop = "18px"
      element.innerHTML =
        '<h3 data-mkt-selector="inserted-title">新的內容卡片</h3>' +
        '<p data-mkt-selector="inserted-copy">在這裡輸入新的區塊內容，之後可以直接改文字、連結、圖片或樣式。</p>' +
        '<a href="https://ose.tw" class="ghost-button" data-mkt-selector="inserted-link">延伸了解</a>'
    }

    assignRuntimeSelectorsInSubtree(element, true)
    return element
  }

  function handleInsertBlock(payload) {
    var selector = typeof payload === "string" ? payload : payload && payload.selector
    var position = (payload && payload.position) || "after"
    if (!selector) {
      return
    }

    clearInlineEdit()
    var target = resolveInspectableElement(document.querySelector(selector))
    if (!target || !target.parentElement) {
      return
    }

    var newBlock = buildInsertedBlock(payload && payload.blockType)

    if (position === "before") {
      target.parentElement.insertBefore(newBlock, target)
    } else if (target.nextElementSibling) {
      target.parentElement.insertBefore(newBlock, target.nextElementSibling)
    } else {
      target.parentElement.appendChild(newBlock)
    }

    currentFocusedElement = newBlock
    refreshVisualState()
    showFeedbackBadge(position === "before" ? "已在前面插入區塊" : "已在後面插入區塊", newBlock)

    var meta = extractElementMeta(newBlock)
    postStructureChange("insert", selector, buildSelector(newBlock), {
      block_type: payload && payload.blockType ? payload.blockType : "content-card",
      tag: meta.tag, text: meta.text, href: meta.href, src: meta.src, hidden: false,
    })
  }

  function startDragMove(event, element) {
    if (!element || !element.parentElement) {
      return
    }

    currentDragMoveState = {
      element: element,
      parent: element.parentElement,
      pointerId: event.pointerId,
      target: null,
      position: "after",
    }

    element.style.opacity = "0.72"
    element.style.cursor = "grabbing"
    document.body.style.userSelect = "none"
  }

  function updateDragMove(event) {
    if (!currentDragMoveState) {
      return
    }

    maybeAutoScrollDuringDrag(event)

    var hovered = document.elementFromPoint(event.clientX, event.clientY)
    var target = resolveInspectableElement(hovered)
    if (!target || target === currentDragMoveState.element) {
      return
    }

    if (!target.parentElement || target.parentElement !== currentDragMoveState.parent) {
      return
    }

    var rect = target.getBoundingClientRect()
    var position = event.clientY < rect.top + rect.height / 2 ? "before" : "after"

    if (
      currentDragMoveState.target === target &&
      currentDragMoveState.position === position
    ) {
      updateDropIndicator(target, position)
      return
    }

    clearDragTargetMarker()
    currentDragMoveState.target = target
    currentDragMoveState.targetOriginalBackground = target.style.backgroundImage || ""
    currentDragMoveState.position = position
    currentDragMoveState.target.style.outline = "2px dashed rgba(37, 99, 235, 0.45)"
    currentDragMoveState.target.style.outlineOffset = "4px"
    currentDragMoveState.target.style.backgroundImage =
      position === "before"
        ? "linear-gradient(to bottom, rgba(37,99,235,0.08), rgba(37,99,235,0.08) 18px, transparent 18px)"
        : "linear-gradient(to top, rgba(37,99,235,0.08), rgba(37,99,235,0.08) 18px, transparent 18px)"
    updateDropIndicator(target, position)
  }

  function endDragMove() {
    if (!currentDragMoveState) {
      hideDropIndicator()
      return
    }

    var source = currentDragMoveState.element
    var target = currentDragMoveState.target
    var parent = currentDragMoveState.parent
    var position = currentDragMoveState.position

    source.style.opacity = ""
    source.style.cursor = ""
    document.body.style.userSelect = ""
    clearDragTargetMarker()

    var moved = false

    if (source && target && parent && target !== source) {
      if (position === "before") {
        parent.insertBefore(source, target)
        moved = true
      } else if (target.nextElementSibling) {
        parent.insertBefore(source, target.nextElementSibling)
        moved = true
      } else {
        parent.appendChild(source)
        moved = true
      }
    }

    currentFocusedElement = source || currentFocusedElement
    currentDragMoveState = null
    hideDropIndicator()
    refreshVisualState()

    if (moved && source) {
      showFeedbackBadge("已移動區塊", source)
      var movedMeta = extractElementMeta(source)
      postToParent({
        type: BLOCK_STRUCTURE_CHANGE,
        action: "move",
        selector: buildSelector(source),
        next_selector: movedMeta.selector,
        tag: movedMeta.tag,
        text: movedMeta.text,
        href: movedMeta.href,
        src: movedMeta.src,
        hidden: movedMeta.hidden,
        page_html: createPageHtmlSnapshot(),
        source: "canvas-bridge",
      })
    }
  }

  function clearInlineEdit() {
    if (!currentInlineEditableElement) {
      updateInlineEditBadge(null)
      return
    }

    var originalEditableValue = currentInlineEditableElement.getAttribute(
      "data-mkt-inline-edit-original"
    )

    if (originalEditableValue == null || originalEditableValue === "none") {
      currentInlineEditableElement.removeAttribute("contenteditable")
    } else {
      currentInlineEditableElement.setAttribute(
        "contenteditable",
        originalEditableValue || "false"
      )
    }

    currentInlineEditableElement.removeAttribute("data-mkt-inline-edit-original")
    currentInlineEditableElement.oninput = null
    currentInlineEditableElement = null
    currentInlineTextOriginalValue = null
    updateInlineEditBadge(null)
    ensureInlineTextPanel().style.display = "none"
    currentInlineAttributeElement = null
    currentInlineAttributeOriginalValue = null
    ensureInlineAttributePanel().style.display = "none"
  }

  function focusEditableText(element) {
    if (!element) {
      return
    }

    if (typeof element.focus === "function") {
      element.focus({ preventScroll: true })
    }

    if (typeof window.getSelection !== "function" || typeof document.createRange !== "function") {
      return
    }

    var range = document.createRange()
    range.selectNodeContents(element)
    range.collapse(false)
    var selection = window.getSelection()
    selection.removeAllRanges()
    selection.addRange(range)
  }

  function handleStartInlineEdit(payload) {
    var selector = typeof payload === "string" ? payload : payload && payload.selector
    if (!selector) {
      return
    }

    var element = document.querySelector(selector)
    if (!element) {
      return
    }

    var inspectableElement = resolveInspectableElement(element)
    if (!inspectableElement) {
      return
    }

    clearInlineEdit()
    currentFocusedElement = inspectableElement
    refreshVisualState()

    currentInlineEditableElement = inspectableElement
    updateInlineEditBadge(inspectableElement)

    var elemTag = inspectableElement.tagName ? inspectableElement.tagName.toLowerCase() : ""
    var textPanel = ensureInlineTextPanel()

    if (payload && payload.editKind === "replace_text") {
      var textTitle = textPanel.querySelector("#__mkt_editor_inline_text_title__")
      if (textTitle) {
        textTitle.textContent = "文字編輯" + (elemTag ? " · " + elemTag : "")
      }
      showFloatingPanel(textPanel, SHARED_INLINE_PANEL_POSITION_ID)
      bindLiveTextInput(textPanel.querySelector("#__mkt_editor_inline_text_area__"), inspectableElement)
      bindLinkedAttributeInput(
        textPanel.querySelector("#__mkt_editor_inline_text_link_group__"),
        textPanel.querySelector("#__mkt_editor_inline_text_link_input__"),
        inspectableElement,
        "href"
      )
      initializeTextStyleControls(inspectableElement, queryTextStyleControlNodes(textPanel, "__mkt_editor_inline_text"))
      initializeBlockStyleControls(inspectableElement, queryBlockStyleControlNodes(textPanel, "__mkt_editor_inline_text_block"))
    }

    inspectableElement.oninput = null

    var isAttr = payload && payload.editKind === "set_attribute" &&
      (payload.attributeName === "href" || payload.attributeName === "src")
    var isStyle = payload && payload.editKind === "set_style"

    if (isAttr || isStyle) {
      currentInlineAttributeElement = inspectableElement
      var attrPanel = ensureInlineAttributePanel()
      var attrTitle = attrPanel.querySelector("#__mkt_editor_inline_attr_title__")
      var attrLabel = attrPanel.querySelector("#__mkt_editor_inline_attr_label__")
      var inputNode = attrPanel.querySelector("#__mkt_editor_inline_attr_input__")

      if (isAttr) {
        var isHref = payload.attributeName === "href"
        if (attrTitle) {
          attrTitle.textContent = (isHref ? "連結網址" : "圖片路徑") + (elemTag ? " · " + elemTag : "")
        }
        if (attrLabel) {
          attrLabel.textContent = isHref ? "連結 URL (href)" : "圖片來源 (src)"
          attrLabel.style.display = ""
        }
      } else {
        if (attrTitle) {
          attrTitle.textContent = "樣式調整" + (elemTag ? " · " + elemTag : "")
        }
        if (attrLabel) {
          attrLabel.style.display = "none"
        }
      }

      showFloatingPanel(attrPanel, SHARED_INLINE_PANEL_POSITION_ID)
      if (isAttr) {
        if (inputNode) {
          inputNode.style.display = ""
          inputNode.value = payload.attributeValue || ""
          currentInlineAttributeOriginalValue = payload.attributeValue || ""
          focusAndSelectInput(inputNode)
        }
        bindLiveAttributeInput(inputNode, inspectableElement, payload.attributeName)
      } else {
        if (inputNode) {
          inputNode.style.display = "none"
        }
      }
      initializeBlockStyleControls(inspectableElement, queryBlockStyleControlNodes(attrPanel, "__mkt_editor_inline_attr"))
    }
  }

  function handleClick(event) {
    var element = resolveInspectableElement(event.target)
    if (!element) {
      return
    }

    event.preventDefault()
    event.stopPropagation()
    handleSelection(element, "canvas-click")
    dispatchActionFromTarget(resolveEditAction(element), element)
  }

  function handleMouseOver(event) {
    var element = resolveInspectableElement(event.target)
    if (!element) {
      return
    }

    handleHover(element)
  }

  function handleMouseLeave() {
    clearHoverState()
  }

  function shouldIgnoreEditorKeydown(event) {
    var target = event && event.target
    if (!target || !(target instanceof Element)) {
      return false
    }

    if (isBridgeUiElement(target)) {
      return true
    }

    var tagName = target.tagName ? target.tagName.toLowerCase() : ""
    if (tagName === "input" || tagName === "textarea" || tagName === "select") {
      return true
    }

    if (target.closest("[contenteditable='true']")) {
      return true
    }

    return false
  }

  function handleKeyDown(event) {
    if (!currentFocusedElement || shouldIgnoreEditorKeydown(event)) {
      return
    }

    if (!event.altKey) {
      return
    }

    if (event.key === "ArrowUp") {
      event.preventDefault()
      handleMoveBlock({
        selector: buildSelector(currentFocusedElement),
        direction: "up",
      })
      return
    }

    if (event.key === "ArrowDown") {
      event.preventDefault()
      handleMoveBlock({
        selector: buildSelector(currentFocusedElement),
        direction: "down",
      })
    }
  }

  function handlePointerMove(event) {
    updateFloatingPanelDrag(event)
    updateDragMove(event)
  }

  function handlePointerUp() {
    endFloatingPanelDrag()
    endDragMove()
  }

  function handleMessage(event) {
    // Only accept messages from the direct parent frame
    if (!event.source || event.source !== window.parent) {
      return
    }
    var payload = event.data || {}
    if (payload.type === FOCUS_SELECTOR) {
      handleFocusSelector(payload.selector)
      return
    }

    if (payload.type === INSPECT_SELECTOR) {
      handleInspectSelector(payload.selector)
      return
    }

    if (payload.type === START_INLINE_EDIT) {
      handleStartInlineEdit(payload)
      return
    }

    if (payload.type === MOVE_BLOCK) {
      handleMoveBlock(payload)
      return
    }

    if (payload.type === TOGGLE_BLOCK_VISIBILITY) {
      handleToggleBlockVisibility(payload)
      return
    }

    if (payload.type === DUPLICATE_BLOCK) {
      handleDuplicateBlock(payload)
      return
    }

    if (payload.type === DELETE_BLOCK) {
      handleDeleteBlock(payload)
      return
    }

    if (payload.type === INSERT_BLOCK) {
      handleInsertBlock(payload)
      return
    }

    if (payload.type === RESTORE_CANVAS_STATE) {
      if (!payload.pageHtml || !document.body) {
        return
      }

      currentInlineEditableElement = null
      currentInlineAttributeElement = null
      currentInlineTextOriginalValue = null
      currentInlineAttributeOriginalValue = null
      currentFloatingPanelDragState = null
      if (currentDragMoveState) {
        document.body.style.userSelect = ""
        currentDragMoveState = null
      }

      document.body.innerHTML = payload.pageHtml
      currentHoveredElement = null
      currentFocusedElement = payload.selector ? document.querySelector(payload.selector) : null
      lastHoveredSelector = ""
      refreshVisualState()
      return
    }

    if (payload.type === STATUS_SUMMARY) {
      latestStatusSummary = {
        mode: payload.mode || "idle",
        modeLabel: payload.modeLabel || "",
        title: payload.title || "",
        selector: payload.selector || "",
        detail: payload.detail || "",
        helper: payload.helper || "",
        primaryActionLabel: payload.primaryActionLabel || "",
        secondaryActionLabel: payload.secondaryActionLabel || "",
        status: payload.status || "info",
      }
      refreshVisualState()
      return
    }

    if (payload.type === "mkt-editor:preview-operations") {
      applyPreviewOperations(payload.operations || [])
    }
  }

  function init() {
    if (document.body) {
      seedRuntimeSelectors(document.body)
    }

    document.addEventListener("click", handleClick, true)
    document.addEventListener("mouseover", handleMouseOver, true)
    document.addEventListener("mouseleave", handleMouseLeave, true)
    document.addEventListener("keydown", handleKeyDown, true)
    document.addEventListener("pointermove", handlePointerMove, true)
    document.addEventListener("pointerup", handlePointerUp, true)
    document.addEventListener("pointercancel", handlePointerUp, true)
    window.addEventListener("message", handleMessage)
    window.addEventListener("resize", function () {
      refreshVisualState()
      if (currentInlineEditableElement) {
        applyFloatingPanelPosition(ensureInlineTextPanel(), SHARED_INLINE_PANEL_POSITION_ID)
      }
      if (currentInlineAttributeElement) {
        applyFloatingPanelPosition(ensureInlineAttributePanel(), SHARED_INLINE_PANEL_POSITION_ID)
      }
    })
    window.addEventListener("scroll", function () {
      refreshVisualState()
    }, true)

    postToParent({
      type: READY,
      page_url: window.location.href,
      page_html: createPageHtmlSnapshot(),
    })
    refreshVisualState()
  }

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", init)
  } else {
    init()
  }
})()
