/**
 * Token Layer Interactions (Drag, Resize, Duplicate, Undo/Redo, Precision Dialog)
 * 
 * COORDINATE SYSTEM (v5.2.1+):
 * - Layer positions (layerObj.x/y) are CONTAINER-RELATIVE (y=0 is top of container)
 * - DOM positions (el.style.left/top) are CANVAS-ABSOLUTE (y=480 for lower third layer at y=0)
 * - We get container offset from layout._originalPosition or layout.position
 * - During drag: work in container-relative, then add offset for DOM
 * 
 * Enhancements:
 *  - Drag with snapping
 *  - Resize handles (4 corners + mid edges) maintaining aspect for images by default (Shift breaks)
 *  - Double-click opens precision dialog to edit x,y,width,height + template/artwork token
 *  - Keyboard: Delete removes layer, Ctrl/Cmd + D duplicates, Ctrl/Cmd + Z undo, Ctrl/Cmd + Shift + Z / Y redo
 *  - Duplication clones layer with slight offset
 *  - Undo stack maintained on editor (_pushTokenUndoState())
 */
import { ensureBaselineGuides as guidesEnsureBaseline, beginSnapSession as guidesBeginSession, endSnapSession as guidesEndSession, applyGuideSnapping as guidesApplySnap } from './guides-snapping.js';
import { getRegistry as getTokenRegistry, listAllTokens } from './token-registry.js';

/**
 * Get available tokens for a specific layer type.
 * @param {'image'|'text'} type - 'image' for token-image layers, 'text' for token-text layers
 * @returns {Array<{token: string, description: string}>}
 */
function getTokensForLayerType(type) {
    const registry = getTokenRegistry();
    const tokens = [];
    for (const category of Object.keys(registry)) {
        for (const field of Object.keys(registry[category])) {
            const meta = registry[category][field];
            if (meta.type === type) {
                tokens.push({
                    token: `${category}.${field}`,
                    description: meta.description || `${category}.${field}`
                });
            }
        }
    }
    return tokens;
}

/**
 * Get container offset for coordinate conversion.
 * Container-relative to canvas-absolute: add offset
 * Canvas-absolute to container-relative: subtract offset
 * 
 * Uses layout.position first (updated by Intended Container changes)
 * then falls back to _originalPosition (set during migration).
 */
function getContainerOffset(editor) {
    const layout = editor?.currentConfig?.layout;
    if (!layout) return { x: 0, y: 0 };
    
    // Use layout.position first (updated by preset changes)
    if (layout.position) {
        return {
            x: layout.position.x || 0,
            y: layout.position.y || 0
        };
    }
    
    // Fall back to _originalPosition (set during migration)
    if (layout._originalPosition) {
        return {
            x: layout._originalPosition.x || 0,
            y: layout._originalPosition.y || 0
        };
    }
    
    return { x: 0, y: 0 };
}

export function attachTokenLayerInteractions(editor){
  const stage = document.getElementById('block-stage');
  if(!stage) return;
  if(stage.__ccveTokenDragBound) return; stage.__ccveTokenDragBound=true;

  // Ensure undo helpers exist on editor
  if(!editor._tokenUndo){
    editor._tokenUndo = { stack: [], index: -1 };
    editor._pushTokenUndoState = function(label='change'){
      try {
  // Track ALL layer types for undo (deep clone each layer)
  const layers = (editor.currentConfig?.layers||[]).map(l => JSON.parse(JSON.stringify(l)));
        // Truncate forward states if new branch
        if(editor._tokenUndo.index < editor._tokenUndo.stack.length-1){
          editor._tokenUndo.stack = editor._tokenUndo.stack.slice(0, editor._tokenUndo.index+1);
        }
        editor._tokenUndo.stack.push({ label, layers });
        // Cap history
        if(editor._tokenUndo.stack.length > 40) editor._tokenUndo.stack.shift();
        editor._tokenUndo.index = editor._tokenUndo.stack.length-1;
      } catch(_){}
    };
    editor._restoreTokenUndoState = function(delta){
      const u = editor._tokenUndo; if(!u) return;
      const next = u.index + delta;
      if(next < 0 || next >= u.stack.length) return;
      u.index = next;
      const snapshot = u.stack[u.index];
      // Restore ALL layers from snapshot (complete replacement)
      if(!editor.currentConfig) editor.currentConfig = editor.getDefaultConfig();
      editor.currentConfig.layers = snapshot.layers.map(l => JSON.parse(JSON.stringify(l)));
      _rerenderAllLayers();
  editor.unsavedChanges = true;
  try { editor.updateSaveButton && editor.updateSaveButton(); } catch(_) {}
      try { editor.buildLayersPanel(); } catch(_) {}
    };
  }

  // Add explicit edit buttons on layers for intuitive access (no dblclick reliance)
  function _ensureEditButtons(){
    try {
      stage.querySelectorAll('.unified-layer').forEach(node=>{
        if(!node.querySelector('.ccve-layer-edit-btn')){
          const btn = document.createElement('button');
          btn.type = 'button';
          btn.className = 'ccve-layer-edit-btn';
          btn.title = 'Edit layer…';
          btn.innerHTML = '✎';
          btn.addEventListener('click', (e)=>{
            e.stopPropagation(); e.preventDefault();
            const id = node.getAttribute('data-layer-id');
            const layerObj = (editor.currentConfig?.layers||[]).find(l=>l.id===id);
            if(layerObj){ _ensureHandlesForSelection(node); _openPrecisionDialog(layerObj); }
          });
          node.appendChild(btn);
        }
      });
    } catch(_){}
  }

  function _createResizeHandles(el){
    if(el.querySelector('.ccve-layer-handle')) return; // already
    const dirs = ['nw','n','ne','e','se','s','sw','w'];
    dirs.forEach(dir=>{
      const h = document.createElement('div');
      h.className = 'ccve-layer-handle ccve-layer-h-'+dir;
      h.dataset.dir = dir;
      el.appendChild(h);
    });
  }

  function _ensureHandlesForSelection(target){
    const targetId = target?.getAttribute('data-layer-id');
    const targetKind = target?.getAttribute('data-layer-kind');
    stage.querySelectorAll('.unified-layer').forEach(n=>{
      if(n === target) {
        n.classList.add('unified-layer--selected');
        _createResizeHandles(n);
      } else {
        n.classList.remove('unified-layer--selected');
      }
    });
    // Store last selected layer ID on editor for Typography tab to use as fallback
    // This persists even if DOM selection is lost when clicking outside stage
    if (targetId && (targetKind === 'token-text' || targetKind === 'static-text')) {
        editor._lastSelectedTextLayerId = targetId;
        console.log('[CCVE][Selection] Stored last selected text layer:', targetId);
    }
    // Sync Typography tab with selected layer's values
    if (targetId && editor.populateTypographyFromLayer) {
      try {
        editor.populateTypographyFromLayer(targetId);
        console.log('[CCVE][Selection] Populated Typography from layer:', targetId);
      } catch(e) {
        console.warn('[CCVE][Selection] Failed to populate Typography:', e);
      }
    }
  }

  function _rerenderAllLayers(){
    try {
      // Use the unified renderer for ALL layer types
      if (editor._renderUnifiedLayers) {
        editor._renderUnifiedLayers();
        _ensureEditButtons();
        return;
      }
      // Legacy fallback for token-text and token-image only
      const layers = (editor.currentConfig?.layers||[]).filter(l=>l && (l.kind==='token-text'||l.kind==='token-image'));
      const bounds = (editor && editor.canvasBounds) ? editor.canvasBounds : { w:1280, h:720 };
      const containerOffset = getContainerOffset(editor);
      layers.forEach(layer=>{
        const el = stage.querySelector(`[data-layer-id="${layer.id}"]`);
        if(!el) return;
        // Snap OOB layers back into view (non-destructive clamp) - in container-relative coords
        let x = layer.x||0, y = layer.y||0, w = layer.width||0, h = layer.height||0;
        if (x < 0) { w += x; x = 0; }
        if (y < 0) { h += y; y = 0; }
        if (x + w > bounds.w) { x = Math.max(0, bounds.w - w); }
        if (y + h > bounds.h) { y = Math.max(0, bounds.h - h); }
        // Apply to DOM using canvas-absolute coordinates
        el.style.left = (x + containerOffset.x) + 'px';
        el.style.top = (y + containerOffset.y) + 'px';
        el.style.width = w + 'px';
        el.style.height = h + 'px';
        if(layer.kind==='token-text'){
          // Use unified naming: .layer-text-content or legacy fallback
          const txt = el.querySelector('.layer-text-content') || el.querySelector('.ccve-layer-text');
          if(txt) txt.textContent = layer.renderedText || layer.templateText || '';
        } else if(layer.kind==='token-image') {
          const img = el.querySelector('img[data-token-image]');
          if(img && layer.renderedUrl) img.src = layer.renderedUrl;
        }
      });
      _ensureEditButtons();
    } catch(_){}
  }

  let drag=null; // { id,startX,startY,origX,origY,origW,origH,mode:'move'|'resize',dir,el,layerObj }
  const snap = (v)=>{ try { const snapSize = editor.canvasSnap || 8; return Math.round(v / snapSize) * snapSize; } catch(_) { return v; } };
  let group=null; // for group-drag of stacked text layers

  const onPointerDown=(e)=>{
    const handle = e.target.closest('.ccve-layer-handle');
    const layerNode = e.target.closest('.unified-layer');
    
    // DEBUG: Log every mousedown on stage to verify event listener is working
    console.log('[CCVE][PointerDown] Event fired', {
        target: e.target.className,
        layerNode: !!layerNode,
        layerKind: layerNode?.getAttribute('data-layer-kind'),
        layerId: layerNode?.getAttribute('data-layer-id')
    });
    
    if(!layerNode) return;
    
    // IMPORTANT: Allow text editing for static-text layers
    // If user clicks on contentEditable element inside a static-text layer, don't start drag
    const layerKind = layerNode.getAttribute('data-layer-kind');
    if (layerKind === 'static-text' && !handle) {
        const editableContent = e.target.closest('[contenteditable="true"]');
        if (editableContent) {
            // Let the browser handle the text editing - don't start drag
            console.log('[CCVE][PointerDown] Allowing text editing for static-text');
            return;
        }
    }
    
    const id = layerNode.getAttribute('data-layer-id');
    const layerObj = (editor.currentConfig?.layers||[]).find(l=>l.id===id);
    if(!layerObj) {
        console.warn('[CCVE][PointerDown] Layer not found in config:', id);
        return;
    }
    console.log('[CCVE][PointerDown] Selecting layer:', id, layerKind);
    
    // Notify LayerGroupingManager of the selection
    // This will auto-select all group members if layer is in a group, and show badge
    const isShiftClick = e.shiftKey;
    try {
        editor._layerGrouping?.selectLayer(id, isShiftClick);
    } catch (err) {
        console.warn('[CCVE][PointerDown] LayerGrouping selectLayer error:', err);
    }
    
    _ensureHandlesForSelection(layerNode);
    const isHandle = !!handle;
    
    // Get container offset to convert DOM positions to container-relative
    const containerOffset = getContainerOffset(editor);
    
    // Resolve reliable starting rect from DOM as fallback when config lacks width/height
    const domX = parseInt(layerNode.style.left, 10);
    const domY = parseInt(layerNode.style.top, 10);
    const rectX = Number.isFinite(domX) ? domX : (layerNode.offsetLeft || 0);
    const rectY = Number.isFinite(domY) ? domY : (layerNode.offsetTop || 0);
    const domW = parseInt(layerNode.style.width, 10);
    const domH = parseInt(layerNode.style.height, 10);
    const rectW = (Number.isFinite(domW) && domW>0) ? domW : (layerNode.offsetWidth || layerObj.width || 40);
    const rectH = (Number.isFinite(domH) && domH>0) ? domH : (layerNode.offsetHeight || layerObj.height || 40);
    
    // Convert DOM positions (canvas-absolute) to container-relative for storage
    const containerRelX = rectX - containerOffset.x;
    const containerRelY = rectY - containerOffset.y;
    
    // If layerObj lacks geometry, seed from DOM now to avoid 0-size clamp bugs
    if(!layerObj.width || layerObj.width<=0) layerObj.width = rectW;
    if(!layerObj.height || layerObj.height<=0) layerObj.height = rectH;
    // Store container-relative coordinates
    if(!Number.isFinite(layerObj.x)) layerObj.x = containerRelX;
    if(!Number.isFinite(layerObj.y)) layerObj.y = containerRelY;
    drag = {
      id,
      startX: e.clientX,
      startY: e.clientY,
      // origX/origY should be container-relative
      origX: Number.isFinite(layerObj.x) ? layerObj.x : containerRelX,
      origY: Number.isFinite(layerObj.y) ? layerObj.y : containerRelY,
      origW: (layerObj.width && layerObj.width>0) ? layerObj.width : rectW,
      origH: (layerObj.height && layerObj.height>0) ? layerObj.height : rectH,
      mode: isHandle ? 'resize' : 'move',
      dir: handle ? handle.dataset.dir : null,
      el: layerNode,
      layerObj
    };
    // Debug: log initial positions
    console.debug('[CCVE][drag] START', {
        id,
        layerObj: { x: layerObj.x, y: layerObj.y, w: layerObj.width, h: layerObj.height },
        dom: { left: rectX, top: rectY, w: rectW, h: rectH },
        orig: { x: drag.origX, y: drag.origY }
    });
    
    // Layer group-based grouping: if this layer is in a group, include all group members
    // This takes precedence over the legacy text-stack grouping
    group = null;
    let isLayerGroupDrag = false;
    try {
      const groupId = layerObj.groupId;
      if (groupId && editor.currentConfig?.layerGroups?.[groupId]) {
        const layerGroup = editor.currentConfig.layerGroups[groupId];
        const memberIds = layerGroup.memberIds || [];
        if (memberIds.length > 1) {
          isLayerGroupDrag = true;
          // Build group offsets relative to the dragged layer
          group = memberIds.map(memberId => {
            if (memberId === id) return { id: memberId, dx: 0, dy: 0 };
            const node = stage.querySelector(`[data-layer-id="${memberId}"]`);
            const obj = (editor.currentConfig?.layers || []).find(l => l.id === memberId);
            if (!node || !obj) return null;
            // Use container-relative coordinates for offset calculation
            const memberX = Number.isFinite(obj.x) ? obj.x : (parseInt(node.style.left, 10) || 0) - containerOffset.x;
            const memberY = Number.isFinite(obj.y) ? obj.y : (parseInt(node.style.top, 10) || 0) - containerOffset.y;
            return { 
              id: memberId, 
              dx: memberX - drag.origX,
              dy: memberY - drag.origY 
            };
          }).filter(g => g !== null);
          console.log('[CCVE][drag] Layer group detected:', groupId, 'with', group.length, 'members');
        }
      }
    } catch (e) {
      console.warn('[CCVE][drag] Error checking layer group:', e);
    }
    
    // Detect legacy text-stack group: if dragging a text layer that is the top-most among a left-aligned stack
    // Only use this fallback if not already in a layer group
    try {
      if (!isLayerGroupDrag && !handle && (layerObj.kind === 'token-text' || layerObj.kind === 'static-text')) {
        const allText = Array.from(stage.querySelectorAll('.unified-layer[data-layer-kind="token-text"], .unified-layer[data-layer-kind="static-text"]'));
        // Compute left x for each, group by rounded left (tolerance 2px)
        const tol = 2;
        const items = allText.map(n=>({
          node:n,
          id:n.getAttribute('data-layer-id'),
          x:(parseInt(n.style.left,10) || n.offsetLeft || 0),
          y:(parseInt(n.style.top,10) || n.offsetTop || 0),
          h:(parseInt(n.style.height,10) || n.offsetHeight || 0)
        }));
        const keyX = (v)=>Math.round(v/ tol)*tol;
        const leftKey = keyX(items.find(i=>i.id===id)?.x || 0);
        const col = items.filter(i=> keyX(i.x) === leftKey).sort((a,b)=>a.y - b.y);
        // If the active node is the top-most in that column, define group offsets
        if (col.length>=2 && col[0].id === id) {
          group = col.map(i=>({ id:i.id, dy:i.y - (parseInt(drag.el.style.top,10) || drag.el.offsetTop || 0) }));
        }
      }
    } catch(_) {}
    // Prepare snapping session - pass token mode for container-relative bounds
  try { guidesEnsureBaseline(editor, { layerType: 'token' }); if (editor._smartSnappingEnabled) guidesBeginSession(editor, id, { layerType: 'token' }); } catch(_) {}
    document.addEventListener('mousemove', onPointerMove);
    document.addEventListener('mouseup', onPointerUp, { once:true });
    e.preventDefault();
  };

  function _applyMoveResize(clientX, clientY, shiftKey){
    if(!drag) return;
    // Get the viewport scale to convert screen pixels to authoring pixels
    const scale = editor?._stageViewport?.currentScale || editor?._currentScale || 0.75;
    // Mouse delta in screen pixels, convert to authoring space
    const dx = (clientX - drag.startX) / scale;
    const dy = (clientY - drag.startY) / scale;
    // Use container bounds from currentConfig.layout for token layers
    const layout = editor?.currentConfig?.layout;
    const bounds = (layout && layout.width && layout.height)
        ? { w: layout.width, h: layout.height }
        : (editor && editor.canvasBounds) ? editor.canvasBounds : { w: 1280, h: 720 };
    // Log bounds once per drag session for debugging
    if (!drag._boundsLogged) {
        console.debug('[CCVE][drag] Using container bounds:', bounds.w, 'x', bounds.h, layout ? '(from layout)' : '(fallback)');
        drag._boundsLogged = true;
    }
    const snapEdgeThreshold = 16; // px threshold to auto-snap to left/right/top/bottom edges
    const hysteresis = editor._snapHysteresis || 6; // px to pull away from a snapped guide before releasing
  if(drag.mode==='move'){
      // Prefer live DOM sizes when config is missing/zero
      const liveW = parseInt(drag.el.style.width,10) || drag.el.offsetWidth || 0;
      const liveH = parseInt(drag.el.style.height,10) || drag.el.offsetHeight || 0;
      const curW = Math.max(1, drag.layerObj.width || drag.origW || liveW || 1);
      const curH = Math.max(1, drag.layerObj.height || drag.origH || liveH || 1);
      let nx = snap(drag.origX + dx);
      let ny = snap(drag.origY + dy);
      // Clamp to stage bounds
      nx = Math.max(0, Math.min(bounds.w - curW, nx));
      ny = Math.max(0, Math.min(bounds.h - curH, ny));
      // Edge snap
      if (Math.abs(nx) <= snapEdgeThreshold) nx = 0;
      if (Math.abs((bounds.w) - (nx + curW)) <= snapEdgeThreshold) nx = bounds.w - curW;
      if (Math.abs(ny) <= snapEdgeThreshold) ny = 0;
      if (Math.abs((bounds.h) - (ny + curH)) <= snapEdgeThreshold) ny = bounds.h - curH;
      // Smart snapping (guides) for move
      if (editor._smartSnappingEnabled) {
        try {
          // If grouping, compute the bounding box for the column to snap its center
          let gw = curW, gh = curH, gyTop = ny, gxLeft = nx;
          if (group && group.length) {
            const ys = group.map(g=> ny + g.dy).concat([ny]);
            const yMin = Math.min(...ys);
            const yMax = Math.max(...ys);
            gyTop = yMin;
            gh = (yMax - yMin) + (parseInt(drag.el.style.height,10) || drag.el.offsetHeight || curH);
            gxLeft = nx; gw = curW; // left aligned column, width from leader
          }
          const snapped = guidesApplySnap(editor, { x: gxLeft, y: gyTop, w: gw, h: gh });
          // Map snapped group box back to leader's position
          nx = snapped.x; ny = snapped.y;
          // Hysteresis: keep axis snapped until movement exceeds threshold away from guide
          if (editor._activeGuide && editor._activeGuide.axis === 'v') {
            const gx = editor._activeGuide.pos;
            const dist = Math.abs((snapped.x) - gx);
            if (dist <= hysteresis) nx = gx; else nx = snapped.x;
            ny = snapped.y;
          } else if (editor._activeGuide && editor._activeGuide.axis === 'h') {
            const gy = editor._activeGuide.pos;
            const dist = Math.abs((snapped.y) - gy);
            if (dist <= hysteresis) ny = gy; else ny = snapped.y;
            nx = snapped.x;
          } else { nx = snapped.x; ny = snapped.y; }
          // Crosshair + highlight coordination with small hysteresis
          try {
            const cx = editor._centerSnap && editor._centerSnap.x;
            const cy = editor._centerSnap && editor._centerSnap.y;
            const both = Number.isFinite(cx) && Number.isFinite(cy);
            const hyster = Number.isFinite(editor._centerHysteresis) ? editor._centerHysteresis : (editor._snapHysteresis || 6);
            let showCross = both;
            let crossPt = both ? { x: cx, y: cy } : null;
            if (!both && editor._centerSnapLast && Number.isFinite(hyster)) {
              const dx = Math.abs((snapped.x + Math.round(snapped.w/2)) - editor._centerSnapLast.x);
              const dy = Math.abs((snapped.y + Math.round(snapped.h/2)) - editor._centerSnapLast.y);
              if (dx <= hyster && dy <= hyster) { showCross = true; crossPt = { ...editor._centerSnapLast }; }
            }
            if (showCross) {
              if (typeof editor._updateCenterCrosshair === 'function') editor._updateCenterCrosshair(crossPt);
              if (both) editor._centerSnapLast = { x: cx, y: cy };
              // Suppress single-axis highlight when full crosshair is shown
              if (typeof editor._updateActiveGuideHighlight === 'function') editor._updateActiveGuideHighlight(null);
            } else {
              if (typeof editor._updateCenterCrosshair === 'function') editor._updateCenterCrosshair(null);
              if (typeof editor._updateActiveGuideHighlight === 'function') editor._updateActiveGuideHighlight(editor._activeGuide);
            }
          } catch(_) {}
        } catch(_) {}
      }
      // Apply to primary layer (store container-relative, position canvas-absolute)
      drag.layerObj.x = nx; drag.layerObj.y = ny;
      const containerOffset = getContainerOffset(editor);
      drag.el.style.left = (nx + containerOffset.x) + 'px';
      drag.el.style.top = (ny + containerOffset.y) + 'px';
      // Group-drag: move other layers in the group, preserving relative offsets
      if (group && group.length) {
        group.forEach(g=>{
          if (g.id === drag.id) return;
          const node = stage.querySelector(`[data-layer-id="${g.id}"]`);
          const obj = (editor.currentConfig?.layers||[]).find(l=>l.id===g.id);
          if (!node || !obj) return;
          // Support both dx (layer groups) and dy-only (legacy text stacks)
          const gx = Number.isFinite(g.dx) ? (nx + g.dx) : nx;
          const gy = Number.isFinite(g.dy) ? (ny + g.dy) : ny;
          obj.x = gx; obj.y = gy;
          node.style.left = (gx + containerOffset.x) + 'px';
          node.style.top = (gy + containerOffset.y) + 'px';
        });
      }
      // Measurements: show gutter to nearest image and inter-line spacing within group
      // Note: Measurements use container-relative coords since that's what we're working with
      try {
        const imgs = Array.from(stage.querySelectorAll('.unified-layer[data-layer-kind="token-image"]'));
        let measure = null;
        if (imgs.length) {
          const img = imgs[0];
          // Convert DOM position to container-relative
          const imgDomX = (parseInt(img.style.left,10) || img.offsetLeft || 0);
          const ix = imgDomX - containerOffset.x;
          const iw = (parseInt(img.style.width,10) || img.offsetWidth || 0);
          const right = ix + iw;
          const gutter = Math.max(0, nx - right);
          // Store in container-relative for now, overlays will convert if needed
          measure = { gutter: { fromX: right, toX: nx, y: ny, value: gutter }, interlines: [] };
        } else {
          measure = { interlines: [] };
        }
        const inter = [];
        if (group && group.length>=2) {
          // Compute contiguous spacing between neighbors (all in container-relative)
          const sorted = [{ id: drag.id, y: ny }].concat(group.filter(g=>g.id!==drag.id).map(g=>({ id:g.id, y: ny + g.dy }))).sort((a,b)=>a.y-b.y);
          for (let i=0;i<sorted.length-1;i++) {
            const a = sorted[i], b = sorted[i+1];
            const an = stage.querySelector(`[data-layer-id="${a.id}"]`);
            const bn = stage.querySelector(`[data-layer-id="${b.id}"]`);
            const ah = parseInt(an?.style.height,10) || an?.offsetHeight || 0;
            const gap = (b.y) - (a.y + ah);
            inter.push({ leftX: nx, y1: a.y + ah, y2: b.y, value: Math.max(0, gap) });
          }
        }
        measure.interlines = inter;
        if (typeof editor._updateMeasurementOverlays === 'function') editor._updateMeasurementOverlays(measure);
      } catch(_) {}
      return;
    }
    // Resize
    let { origX, origY, origW, origH } = drag;
    let newX = origX, newY = origY, newW = origW, newH = origH;
    const dir = drag.dir || '';
    const aspect = origW && origH ? origW / origH : 1;
    if(/e/.test(dir)) newW = origW + dx;
    if(/s/.test(dir)) newH = origH + dy;
    if(/w/.test(dir)) { newW = origW - dx; newX = origX + dx; }
    if(/n/.test(dir)) { newH = origH - dy; newY = origY + dy; }
    // Constrain minimums
    newW = Math.max(20, newW);
    newH = Math.max(20, newH);
    // Maintain aspect for images unless Shift pressed
    if(drag.layerObj.kind==='token-image' && !shiftKey){
      if(/n|s/.test(dir) && !/e|w/.test(dir)) { newW = Math.round(newH * aspect); }
      else if(/e|w/.test(dir) && !/n|s/.test(dir)) { newH = Math.round(newW / aspect); }
      else { newH = Math.round(newW / aspect); }
      if(/w/.test(dir)) newX = origX + (origW - newW);
      if(/n/.test(dir)) newY = origY + (origH - newH);
    }
    // Clamp to stage bounds
    if (newX < 0) { newW += newX; newX = 0; }
    if (newY < 0) { newH += newY; newY = 0; }
    if (newX + newW > bounds.w) { newW = bounds.w - newX; }
    if (newY + newH > bounds.h) { newH = bounds.h - newY; }
    // Edge snapping post-calc
    if (Math.abs(newX) <= snapEdgeThreshold) { newW += newX; newX = 0; }
    if (Math.abs(bounds.w - (newX + newW)) <= snapEdgeThreshold) { newW = bounds.w - newX; }
    if (Math.abs(newY) <= snapEdgeThreshold) { newH += newY; newY = 0; }
    if (Math.abs(bounds.h - (newY + newH)) <= snapEdgeThreshold) { newH = bounds.h - newY; }
    // Smart snapping (guides) for resize first, then grid snap fallback
    if (editor._smartSnappingEnabled) {
      try {
        const snapped = guidesApplySnap(editor, { x: newX, y: newY, w: newW, h: newH });
        // Hysteresis on resize: stick to the snapped edge until pull-away exceeds threshold
        if (editor._activeGuide && editor._activeGuide.axis === 'v') {
          const gx = editor._activeGuide.pos;
          // If left edge snapped, keep newX; if right edge snapped, keep newW via gx - newX
          const leftDist = Math.abs(snapped.x - gx);
          const rightDist = Math.abs((snapped.x + snapped.w) - gx);
          if (leftDist <= hysteresis) { newX = gx; newW = Math.max(20, snapped.w + (snapped.x - newX)); }
          else if (rightDist <= hysteresis) { newW = Math.max(20, gx - snapped.x); newX = snapped.x; }
          else { newX = snapped.x; newW = snapped.w; }
          newY = snapped.y; newH = snapped.h;
        } else if (editor._activeGuide && editor._activeGuide.axis === 'h') {
          const gy = editor._activeGuide.pos;
          const topDist = Math.abs(snapped.y - gy);
          const botDist = Math.abs((snapped.y + snapped.h) - gy);
          if (topDist <= hysteresis) { newY = gy; newH = Math.max(20, snapped.h + (snapped.y - newY)); }
          else if (botDist <= hysteresis) { newH = Math.max(20, gy - snapped.y); newY = snapped.y; }
          else { newY = snapped.y; newH = snapped.h; }
          newX = snapped.x; newW = snapped.w;
        } else { newX = snapped.x; newY = snapped.y; newW = snapped.w; newH = snapped.h; }
        if (typeof editor._updateActiveGuideHighlight === 'function') editor._updateActiveGuideHighlight(editor._activeGuide);
      } catch(_) {}
    }
    // Final grid snap
    newX = snap(newX); newY = snap(newY); newW = snap(newW); newH = snap(newH);
    drag.layerObj.x = newX; drag.layerObj.y = newY; drag.layerObj.width = newW; drag.layerObj.height = newH;
    // Apply to DOM using canvas-absolute coordinates
    const containerOffset = getContainerOffset(editor);
    drag.el.style.left = (newX + containerOffset.x) + 'px';
    drag.el.style.top = (newY + containerOffset.y) + 'px';
    drag.el.style.width = newW + 'px';
    drag.el.style.height = newH + 'px';
    
    // GROUP PROPORTIONAL RESIZE
    // When resizing a layer that's part of a layer group, scale all members proportionally
    if (group && group.length > 1 && drag.layerObj.groupId) {
      const scaleX = newW / origW;
      const scaleY = newH / origH;
      
      // Calculate anchor point based on resize direction
      // Anchor is the corner/edge that stays fixed during resize
      let anchorX = origX, anchorY = origY;
      if (/e/.test(dir)) anchorX = origX; // East: left edge fixed
      else if (/w/.test(dir)) anchorX = origX + origW; // West: right edge fixed
      if (/s/.test(dir)) anchorY = origY; // South: top edge fixed
      else if (/n/.test(dir)) anchorY = origY + origH; // North: bottom edge fixed
      // Corner: use appropriate corner as anchor
      if (/se/.test(dir)) { anchorX = origX; anchorY = origY; }
      if (/sw/.test(dir)) { anchorX = origX + origW; anchorY = origY; }
      if (/ne/.test(dir)) { anchorX = origX; anchorY = origY + origH; }
      if (/nw/.test(dir)) { anchorX = origX + origW; anchorY = origY + origH; }
      
      group.forEach(g => {
        if (g.id === drag.id) return; // Skip the layer being resized directly
        
        const node = stage.querySelector(`[data-layer-id="${g.id}"]`);
        const obj = (editor.currentConfig?.layers || []).find(l => l.id === g.id);
        if (!node || !obj) return;
        
        // Get member's original position (stored at drag start)
        const memberOrigX = drag.origX + (g.dx || 0);
        const memberOrigY = drag.origY + (g.dy || 0);
        const memberOrigW = obj._groupOrigW || obj.width || parseInt(node.style.width, 10) || 40;
        const memberOrigH = obj._groupOrigH || obj.height || parseInt(node.style.height, 10) || 40;
        
        // Store original size on first resize frame
        if (!obj._groupOrigW) obj._groupOrigW = memberOrigW;
        if (!obj._groupOrigH) obj._groupOrigH = memberOrigH;
        
        // Calculate new position relative to anchor
        const relX = memberOrigX - anchorX;
        const relY = memberOrigY - anchorY;
        
        // Scale position and size
        const memberNewX = anchorX + (relX * scaleX);
        const memberNewY = anchorY + (relY * scaleY);
        const memberNewW = Math.max(20, Math.round(memberOrigW * scaleX));
        const memberNewH = Math.max(20, Math.round(memberOrigH * scaleY));
        
        // Update layer object
        obj.x = Math.round(memberNewX);
        obj.y = Math.round(memberNewY);
        obj.width = memberNewW;
        obj.height = memberNewH;
        
        // Scale font size for text layers
        if ((obj.kind === 'token-text' || obj.kind === 'static-text') && obj.style?.font_size) {
          const origFontSize = obj._groupOrigFontSize || obj.style.font_size;
          if (!obj._groupOrigFontSize) obj._groupOrigFontSize = origFontSize;
          const avgScale = (scaleX + scaleY) / 2;
          obj.style.font_size = Math.max(8, Math.round(origFontSize * avgScale));
          // Update font size on the text element
          const textEl = node.querySelector('.layer-text-content, .ccve-layer-text');
          if (textEl) textEl.style.fontSize = obj.style.font_size + 'px';
        }
        
        // Apply to DOM
        node.style.left = (obj.x + containerOffset.x) + 'px';
        node.style.top = (obj.y + containerOffset.y) + 'px';
        node.style.width = memberNewW + 'px';
        node.style.height = memberNewH + 'px';
      });
    }
  }

  const onPointerMove=(e)=>{ if(!drag) return; _applyMoveResize(e.clientX, e.clientY, e.shiftKey); };
  const onPointerUp=()=>{ 
    if(drag){ 
      try { 
        guidesEndSession(editor); 
        if (typeof editor._updateActiveGuideHighlight === 'function') editor._updateActiveGuideHighlight(null); 
        if (typeof editor._clearMeasurementOverlays === 'function') editor._clearMeasurementOverlays(); 
        if (typeof editor._clearCenterCrosshair === 'function') editor._clearCenterCrosshair(); 
        delete editor._centerSnapLast; 
      } catch(_) {} 
      
      // Clean up temporary group resize properties
      if (group && group.length > 1) {
        group.forEach(g => {
          const obj = (editor.currentConfig?.layers || []).find(l => l.id === g.id);
          if (obj) {
            delete obj._groupOrigW;
            delete obj._groupOrigH;
            delete obj._groupOrigFontSize;
          }
        });
      }
      
      editor.unsavedChanges = true; 
      try { editor.updateSaveButton && editor.updateSaveButton(); } catch(_) {}
      editor._pushTokenUndoState('move/resize'); 
      drag=null; 
      group=null; 
    } 
    document.removeEventListener('mousemove', onPointerMove); 
  };

  function _openPrecisionDialog(layerObj){
    const existing = document.getElementById('ccve-token-precision');
    if(existing) existing.remove();
    const dlg = document.createElement('div');
    dlg.id='ccve-token-precision';
    dlg.className='ccve-token-precision-dialog';
    
    const isTextLayer = ['token-text', 'static-text'].includes(layerObj.kind);
    const isImageLayer = ['token-image', 'static-image', 'slideshow-image', 'qr-image', 'background-image'].includes(layerObj.kind);
    
    // Get current style values with fallbacks
    const style = layerObj.style || {};
    const fontFamily = style.font_family || layerObj.font_family || 'system-ui,Arial,Helvetica,sans-serif';
    const fontSize = style.font_size || layerObj.font_size || 24;
    const fontWeight = style.font_weight || layerObj.font_weight || '400';
    const fontColor = style.color || layerObj.color || layerObj.font_color || '#ffffff';
    const textAlign = style.text_align || layerObj.text_align || 'left';
    const lineHeight = style.line_height || layerObj.line_height || 1.2;
    const textShadow = style.text_shadow || layerObj.text_shadow || { x: 0, y: 0, blur: 0, color: 'rgba(0,0,0,0.5)' };
    const shadowX = typeof textShadow === 'object' ? textShadow.x || 0 : 0;
    const shadowY = typeof textShadow === 'object' ? textShadow.y || 0 : 0;
    const shadowBlur = typeof textShadow === 'object' ? textShadow.blur || 0 : 0;
    const shadowColor = typeof textShadow === 'object' ? textShadow.color || '#000000' : '#000000';
    
    // Image properties
    const objectFit = layerObj.objectFit || layerObj.fit || layerObj.object_fit || 'cover';
    const borderRadius = layerObj.border_radius || 0;
    const opacity = layerObj.opacity !== undefined ? layerObj.opacity : 1;
    
    // Border properties (all layers)
    const borderWidth = layerObj.border_width || 0;
    const borderColor = layerObj.border_color || '#000000';
    const borderStyle = layerObj.border_style || 'solid';
    
    // Background properties (all layers)
    const bgColor = layerObj.background_color || layerObj.bg_color || '';
    
    // Slideshow properties
    const slideshowTransition = layerObj.transition || 'fade';
    
    // Animation properties (for all layer types)
    const animation = layerObj.animation || 'none';
    const animationSpeed = layerObj.animation_speed || 50;
    
    // Build kind-specific fields
    let kindSpecificFields = '';
    if(layerObj.kind==='token-text') {
        // Get available text tokens for dropdown hint
        const textTokens = getTokensForLayerType('text');
        const textTokenOptions = textTokens.map(t => `<option value="{{${t.token}}}">${t.token} - ${t.description}</option>`).join('');
        kindSpecificFields = `<label>Template <textarea id="ccve-tp-template" rows="3">${(layerObj.templateText||'').replace(/</g,'&lt;')}</textarea></label>
            <label>Insert Token <select id="ccve-tp-text-token-dropdown" style="margin-top: 4px;">
                <option value="">-- Select token to insert --</option>
                ${textTokenOptions}
            </select></label>
            <small style="color: #64748b; font-size: 11px;">Text tokens use curly braces: {{track.title}}</small>`;
    } else if(layerObj.kind==='token-image') {
        // Get available image tokens for dropdown
        const imageTokens = getTokensForLayerType('image');
        const currentToken = layerObj.token || '';
        const imageTokenOptions = imageTokens.map(t => 
            `<option value="${t.token}" ${currentToken === t.token ? 'selected' : ''}>${t.token} - ${t.description}</option>`
        ).join('');
        kindSpecificFields = `<label>Artwork Token <select id="ccve-tp-token">
                <option value="">-- Select token --</option>
                ${imageTokenOptions}
            </select></label>
            <small style="color: #64748b; font-size: 11px;">Image tokens do NOT use curly braces</small>
            <label>Fallback URL <input type="text" id="ccve-tp-fallback" value="${layerObj.fallback_url || ''}" /></label>`;
    } else if(layerObj.kind==='static-text') {
        kindSpecificFields = `<label>Text <textarea id="ccve-tp-text" rows="3" style="width: 100%; min-height: 80px;">${(layerObj.text||'').replace(/</g,'&lt;')}</textarea></label>`;
    } else if(layerObj.kind==='static-image') {
        kindSpecificFields = `<label>Image URL <input type="text" id="ccve-tp-image-url" value="${layerObj.url || ''}" /></label>
            <button type="button" id="ccve-tp-select-image" style="margin-top: 4px; padding: 6px 12px; background: #f3f4f6; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer;">📁 Media Library</button>`;
    } else if(layerObj.kind==='qr-image') {
        kindSpecificFields = `<label>URL <input type="text" id="ccve-tp-qr-url" value="${layerObj.sourceUrl || ''}" placeholder="https://example.com" /></label>`;
    } else if(layerObj.kind==='wordpress-post') {
        kindSpecificFields = `<label>Post ID <input type="number" id="ccve-tp-post-id" value="${layerObj.post_id || ''}" placeholder="Select a post..." /></label>
            <button type="button" id="ccve-tp-select-post" style="margin-top: 8px; padding: 8px 16px; background: #6366f1; color: white; border: none; border-radius: 6px; cursor: pointer;">📰 Select Post...</button>`;
    } else if(layerObj.kind==='slideshow-image') {
        const sources = layerObj.sources || layerObj.images || [];
        kindSpecificFields = `<label>Images: ${sources.length}</label>
            <label>Interval (sec) <input type="number" id="ccve-tp-interval" value="${layerObj.interval || 5}" min="1" max="60" /></label>
            <label>Transition <select id="ccve-tp-transition">
                <option value="fade" ${slideshowTransition==='fade'?'selected':''}>Fade</option>
                <option value="slide" ${slideshowTransition==='slide'?'selected':''}>Slide</option>
                <option value="none" ${slideshowTransition==='none'?'selected':''}>None (Instant)</option>
            </select></label>
            <button type="button" id="ccve-tp-add-slides" style="margin-top: 4px; padding: 6px 12px; background: #f3f4f6; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer;">📁 Add Images</button>`;
    } else if(layerObj.kind==='video') {
        const videoUrl = layerObj.url || layerObj.video_url || '';
        const autoplay = layerObj.autoplay !== false;
        const loop = layerObj.loop !== false;
        const muted = layerObj.muted !== false;
        kindSpecificFields = `<label>Video URL <input type="text" id="ccve-tp-video-url" value="${videoUrl}" placeholder="https://example.com/video.mp4" /></label>
            <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px; margin-top: 8px;">
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-video-autoplay" ${autoplay ? 'checked' : ''} /> Autoplay</label>
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-video-loop" ${loop ? 'checked' : ''} /> Loop</label>
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-video-muted" ${muted ? 'checked' : ''} /> Muted</label>
            </div>`;
    } else if(layerObj.kind==='feed-layer') {
        const feedUrl = layerObj.feed_url || layerObj.url || '';
        const layout = layerObj.layout || 'grid';
        const maxItems = layerObj.max_items || 300;
        const columns = layerObj.columns || 3;
        const thumbWidth = layerObj.thumbnail_width || 200;
        const thumbHeight = layerObj.thumbnail_height || 200;
        const itemSpacing = layerObj.item_spacing || 15;
        const titleFontSize = layerObj.title_font_size || 14;
        const titleColor = layerObj.title_font_color || '#ffffff';
        const descFontSize = layerObj.description_font_size || 12;
        const descColor = layerObj.description_font_color || '#cccccc';
        const selectionColor = layerObj.selection_color || '#FFD700';
        const cardBgColor = layerObj.card_background_color || '#333333';
        const focusScale = layerObj.focus_scale || 1.2;
        const showThumbnail = layerObj.show_thumbnail !== false;
        const showTitle = layerObj.show_title !== false;
        const showDescription = layerObj.show_description !== false;
        const showDuration = layerObj.show_duration === true;
        const showContainer = layerObj.show_container === true;
        const centerFocusMode = layerObj.center_focus_mode !== false;
        
        kindSpecificFields = `<label>Feed URL <input type="text" id="ccve-tp-feed-url" value="${feedUrl}" placeholder="https://soundcloud.com/user/sets/playlist" /></label>
            <label>Layout <select id="ccve-tp-feed-layout">
                <option value="grid" ${layout==='grid'?'selected':''}>Grid</option>
                <option value="list" ${layout==='list'?'selected':''}>List</option>
                <option value="carousel" ${layout==='carousel'?'selected':''}>Carousel</option>
                <option value="featured" ${layout==='featured'?'selected':''}>Featured + List</option>
            </select></label>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px;">
                <label>Max Items <input type="number" id="ccve-tp-feed-max" value="${maxItems}" min="1" max="100" /></label>
                <label>Columns <input type="number" id="ccve-tp-feed-columns" value="${columns}" min="1" max="6" /></label>
            </div>
            
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">📐 Card Sizing</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 8px;">
                <label>Thumb W <input type="number" id="ccve-tp-feed-thumb-w" value="${thumbWidth}" min="60" max="400" /></label>
                <label>Thumb H <input type="number" id="ccve-tp-feed-thumb-h" value="${thumbHeight}" min="60" max="400" /></label>
                <label>Spacing <input type="number" id="ccve-tp-feed-spacing" value="${itemSpacing}" min="0" max="50" /></label>
            </div>
            <small style="color: #64748b; margin-top: 4px; display: block;">Tip: Use equal W/H for square podcast artwork</small>
            
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">🎨 Typography</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label>Title Size <input type="number" id="ccve-tp-feed-title-size" value="${titleFontSize}" min="8" max="48" /></label>
                <label>Title Color <input type="color" id="ccve-tp-feed-title-color" value="${titleColor}" /></label>
                <label>Desc Size <input type="number" id="ccve-tp-feed-desc-size" value="${descFontSize}" min="8" max="36" /></label>
                <label>Desc Color <input type="color" id="ccve-tp-feed-desc-color" value="${descColor}" /></label>
            </div>
            
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">✨ Focus & Selection</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label>Highlight <input type="color" id="ccve-tp-feed-sel-color" value="${selectionColor}" /></label>
                <label>Card BG <input type="color" id="ccve-tp-feed-card-bg" value="${cardBgColor}" /></label>
                <label>Focus Scale <input type="number" id="ccve-tp-feed-focus-scale" value="${focusScale}" min="1.0" max="1.5" step="0.05" /></label>
            </div>
            <small style="color: #64748b; margin-top: 4px; display: block;">Highlight = focused item border. Card BG = default background behind title text.</small>
            
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">📺 Display Options</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-show-container" ${showContainer ? 'checked' : ''} /> Show Container Box</label>
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-center-focus" ${centerFocusMode ? 'checked' : ''} /> Center Focus (Carousel)</label>
            </div>
            <small style="color: #64748b; margin-top: 4px; display: block;">Uncheck "Show Container Box" to see just cards on your background. "Center Focus" keeps selected item centered (Netflix-style filmstrip).</small>
            
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">👁️ Show/Hide Elements</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-show-thumb" ${showThumbnail ? 'checked' : ''} /> Thumbnails</label>
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-show-title" ${showTitle ? 'checked' : ''} /> Titles</label>
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-show-desc" ${showDescription ? 'checked' : ''} /> Descriptions</label>
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-show-duration" ${showDuration ? 'checked' : ''} /> Duration</label>
            </div>
            
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">📄 Detail Overlay Styling</div>
            <small style="color: #64748b; margin-bottom: 8px; display: block;">Controls the appearance of the episode detail/play screen</small>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label>BG Color <input type="color" id="ccve-tp-feed-detail-bg" value="${layerObj.detail_bg_color || '#000000'}" /></label>
                <label>Opacity <input type="number" id="ccve-tp-feed-detail-opacity" value="${layerObj.detail_bg_opacity !== undefined ? layerObj.detail_bg_opacity : 0.85}" min="0" max="1" step="0.05" /></label>
                <label>Title Size <input type="number" id="ccve-tp-feed-detail-title-size" value="${layerObj.detail_title_size || 32}" min="16" max="64" /></label>
                <label>Title Color <input type="color" id="ccve-tp-feed-detail-title-color" value="${layerObj.detail_title_color || '#ffffff'}" /></label>
                <label>Desc Size <input type="number" id="ccve-tp-feed-detail-desc-size" value="${layerObj.detail_desc_size || 18}" min="12" max="36" /></label>
                <label>Desc Color <input type="color" id="ccve-tp-feed-detail-desc-color" value="${layerObj.detail_desc_color || '#cccccc'}" /></label>
            </div>
            <label style="margin-top: 8px;">BG Image URL <input type="text" id="ccve-tp-feed-detail-bg-image" value="${layerObj.detail_bg_image_url || ''}" placeholder="Optional background image" /></label>
            <button type="button" id="ccve-tp-feed-detail-bg-image-btn" style="margin-top: 4px; padding: 6px 12px; background: #f3f4f6; border: 1px solid #d1d5db; border-radius: 4px; cursor: pointer;">📁 Select BG Image</button>
            <small style="color: #64748b; margin-top: 4px; display: block;">Leave empty for solid color. Image adds a photo background behind the detail overlay.</small>
            
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">🎵 Audio Controls</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-show-progress" ${layerObj.show_progress_bar !== false ? 'checked' : ''} /> Show Progress Bar</label>
                <label style="display: flex; align-items: center; gap: 4px;"><input type="checkbox" id="ccve-tp-feed-enable-transport" ${layerObj.enable_transport_overlay !== false ? 'checked' : ''} /> Enable Transport Overlay</label>
            </div>
            <label style="margin-top: 8px;">Transport Trigger Key <select id="ccve-tp-feed-transport-trigger" style="width: 100%;">
                <option value="transport" ${(layerObj.transport_trigger_key || 'transport') === 'transport' ? 'selected' : ''}>Any Transport Button (play/pause/rewind/forward)</option>
                <option value="replay" ${layerObj.transport_trigger_key === 'replay' ? 'selected' : ''}>Instant Replay (⏪) Button Only</option>
                <option value="play" ${layerObj.transport_trigger_key === 'play' ? 'selected' : ''}>Play Button Only</option>
            </select></label>
            <small style="color: #64748b; margin-top: 4px; display: block;">Note: Star (✱) button is system-reserved. Transport buttons show overlay + execute command.</small>`;
    }
    
    // Typography fields (for text layers)
    let typographyFields = '';
    if(isTextLayer) {
        typographyFields = `
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">Typography</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label style="grid-column: 1 / -1;">Font Family <select id="ccve-tp-font-family" style="width: 100%;">
                    <option value="system-ui,Arial,Helvetica,sans-serif" ${fontFamily.includes('system-ui')?'selected':''}>Roku System Font</option>
                    <option value="Lexend" ${fontFamily==='Lexend'?'selected':''}>Lexend</option>
                    <option value="Oxygen" ${fontFamily==='Oxygen'?'selected':''}>Oxygen</option>
                    <option value="ShareTechMono" ${fontFamily==='ShareTechMono'?'selected':''}>Share Tech Mono</option>
                    <option value="SpaceGrotesk" ${fontFamily==='SpaceGrotesk'?'selected':''}>Space Grotesk</option>
                </select></label>
                <small style="grid-column: 1 / -1; color: #64748b; margin-top: -4px; margin-bottom: 4px;">Custom fonts bundled with Roku app</small>
                <label>Font Size <input type="number" id="ccve-tp-font-size" value="${fontSize}" min="8" max="120" /></label>
                <label>Weight <select id="ccve-tp-font-weight">
                    <option value="400" ${fontWeight==='400'?'selected':''}>Regular</option>
                    <option value="600" ${fontWeight==='600'?'selected':''}>Semi-Bold</option>
                    <option value="700" ${fontWeight==='700'?'selected':''}>Bold</option>
                </select></label>
                <label>Color <input type="color" id="ccve-tp-font-color" value="${fontColor.startsWith('#') ? fontColor : '#ffffff'}" /></label>
                <label>Align <select id="ccve-tp-text-align">
                    <option value="left" ${textAlign==='left'?'selected':''}>Left</option>
                    <option value="center" ${textAlign==='center'?'selected':''}>Center</option>
                    <option value="right" ${textAlign==='right'?'selected':''}>Right</option>
                </select></label>
                <label>Line Height <input type="number" id="ccve-tp-line-height" value="${lineHeight}" min="0.5" max="3" step="0.1" /></label>
            </div>
            <div style="font-weight: 500; margin: 12px 0 4px; font-size: 12px;">Scrolling Text Ticker</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: center;">
                <label style="display: flex; align-items: center; gap: 6px;"><input type="checkbox" id="ccve-tp-scroll-enabled" ${layerObj.scroll_enabled !== false ? 'checked' : ''} /> Enabled</label>
                <label>Speed <input type="number" id="ccve-tp-scroll-speed" value="${layerObj.scroll_speed || 30}" min="10" max="100" style="width:100%;" /></label>
            </div>
            <div style="font-weight: 500; margin: 8px 0 4px; font-size: 12px;">Text Shadow</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 6px;">
                <label style="font-size:11px;">X <input type="number" id="ccve-tp-shadow-x" value="${shadowX}" style="width:100%;" /></label>
                <label style="font-size:11px;">Y <input type="number" id="ccve-tp-shadow-y" value="${shadowY}" style="width:100%;" /></label>
                <label style="font-size:11px;">Blur <input type="number" id="ccve-tp-shadow-blur" value="${shadowBlur}" min="0" style="width:100%;" /></label>
                <label style="font-size:11px;">Color <input type="color" id="ccve-tp-shadow-color" value="${shadowColor.startsWith('#') ? shadowColor : '#000000'}" style="width:100%;height:28px;" /></label>
            </div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 8px;">
                <label>Opacity <input type="number" id="ccve-tp-opacity" value="${opacity}" min="0" max="1" step="0.1" /></label>
                <label>Background <input type="color" id="ccve-tp-bg-color" value="${bgColor || '#000000'}" ${!bgColor ? 'style="opacity:0.5;"' : ''} /></label>
            </div>
            <div style="font-weight: 500; margin: 8px 0 4px; font-size: 12px;">Border</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px;">
                <label style="font-size:11px;">Width <input type="number" id="ccve-tp-border-width" value="${borderWidth}" min="0" max="20" style="width:100%;" /></label>
                <label style="font-size:11px;">Style <select id="ccve-tp-border-style" style="width:100%;">
                    <option value="solid" ${borderStyle==='solid'?'selected':''}>Solid</option>
                    <option value="dashed" ${borderStyle==='dashed'?'selected':''}>Dashed</option>
                    <option value="dotted" ${borderStyle==='dotted'?'selected':''}>Dotted</option>
                </select></label>
                <label style="font-size:11px;">Color <input type="color" id="ccve-tp-border-color" value="${borderColor}" style="width:100%;height:28px;" /></label>
            </div>`;
    }
    
    // Appearance fields (for image layers)
    let appearanceFields = '';
    if(isImageLayer) {
        appearanceFields = `
            <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
            <div style="font-weight: 600; margin-bottom: 8px;">Appearance</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
                <label>Fit <select id="ccve-tp-object-fit">
                    <option value="cover" ${objectFit==='cover'?'selected':''}>Cover</option>
                    <option value="contain" ${objectFit==='contain'?'selected':''}>Contain</option>
                    <option value="fill" ${objectFit==='fill'?'selected':''}>Fill</option>
                    <option value="none" ${objectFit==='none'?'selected':''}>None</option>
                </select></label>
                <label>Border Radius <input type="number" id="ccve-tp-border-radius" value="${borderRadius}" min="0" max="100" /></label>
                <label>Opacity <input type="number" id="ccve-tp-opacity" value="${opacity}" min="0" max="1" step="0.1" /></label>
                <label>Background <input type="color" id="ccve-tp-bg-color" value="${bgColor || '#000000'}" ${!bgColor ? 'style="opacity:0.5;"' : ''} /></label>
            </div>
            <div style="font-weight: 500; margin: 8px 0 4px; font-size: 12px;">Border</div>
            <div style="display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 6px;">
                <label style="font-size:11px;">Width <input type="number" id="ccve-tp-border-width" value="${borderWidth}" min="0" max="20" style="width:100%;" /></label>
                <label style="font-size:11px;">Style <select id="ccve-tp-border-style" style="width:100%;">
                    <option value="solid" ${borderStyle==='solid'?'selected':''}>Solid</option>
                    <option value="dashed" ${borderStyle==='dashed'?'selected':''}>Dashed</option>
                    <option value="dotted" ${borderStyle==='dotted'?'selected':''}>Dotted</option>
                </select></label>
                <label style="font-size:11px;">Color <input type="color" id="ccve-tp-border-color" value="${borderColor}" style="width:100%;height:28px;" /></label>
            </div>`;
    }
    
    // Animation fields (for all animatable layers)
    const animationFields = `
        <hr style="margin: 12px 0; border: none; border-top: 1px solid #e5e7eb;" />
        <div style="font-weight: 600; margin-bottom: 8px;">Animation</div>
        <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
            <label style="grid-column: 1 / -1;">Type <select id="ccve-tp-animation">
                <option value="none" ${animation==='none'?'selected':''}>None</option>
                <option value="fade" ${animation==='fade'?'selected':''}>Fade In</option>
                <option value="float" ${animation==='float'?'selected':''}>Float</option>
                <option value="pulse" ${animation==='pulse'?'selected':''}>Pulse</option>
                <option value="float-pulse" ${animation==='float-pulse'?'selected':''}>Float + Pulse</option>
            </select></label>
            <label id="ccve-tp-animation-speed-row" style="grid-column: 1 / -1; ${animation==='none'?'display:none;':''}">
                Speed <input type="range" id="ccve-tp-animation-speed" value="${animationSpeed}" min="10" max="100" style="width: 100%;" />
                <span id="ccve-tp-speed-label" style="font-size: 11px; color: #64748b;">${animationSpeed <= 25 ? 'Slow' : animationSpeed <= 50 ? 'Medium' : animationSpeed <= 75 ? 'Fast' : 'Very Fast'}</span>
            </label>
        </div>`;
    
    dlg.innerHTML = `
      <div class="ccve-tp-inner">
        <h3>Layer Settings</h3>
        <div class="ccve-tp-geometry-grid">
          <label>X <input type="number" id="ccve-tp-x" value="${layerObj.x}" /></label>
          <label>Y <input type="number" id="ccve-tp-y" value="${layerObj.y}" /></label>
          <label>Width <input type="number" id="ccve-tp-w" value="${layerObj.width}" /></label>
          <label>Height <input type="number" id="ccve-tp-h" value="${layerObj.height}" /></label>
        </div>
        ${kindSpecificFields}
        ${typographyFields}
        ${appearanceFields}
        ${animationFields}
        <div class="ccve-tp-actions">
          <button type="button" id="ccve-tp-save">Apply</button>
          <button type="button" id="ccve-tp-cancel">Cancel</button>
        </div>
      </div>`;
    document.body.appendChild(dlg);
    const close=()=>{ dlg.remove(); };
    dlg.querySelector('#ccve-tp-cancel').addEventListener('click', close);
    
    // Animation type change handler - show/hide speed row
    const animSelect = dlg.querySelector('#ccve-tp-animation');
    const speedRow = dlg.querySelector('#ccve-tp-animation-speed-row');
    const speedSlider = dlg.querySelector('#ccve-tp-animation-speed');
    const speedLabel = dlg.querySelector('#ccve-tp-speed-label');
    if(animSelect && speedRow) {
        animSelect.addEventListener('change', () => {
            speedRow.style.display = animSelect.value === 'none' ? 'none' : '';
        });
    }
    if(speedSlider && speedLabel) {
        speedSlider.addEventListener('input', () => {
            const val = parseInt(speedSlider.value, 10);
            speedLabel.textContent = val <= 25 ? 'Slow' : val <= 50 ? 'Medium' : val <= 75 ? 'Fast' : 'Very Fast';
        });
    }
    
    // Text token dropdown - insert selected token into template textarea
    const textTokenDropdown = dlg.querySelector('#ccve-tp-text-token-dropdown');
    if(textTokenDropdown) {
        textTokenDropdown.addEventListener('change', () => {
            const val = textTokenDropdown.value;
            if (!val) return;
            const templateTextarea = dlg.querySelector('#ccve-tp-template');
            if (templateTextarea) {
                // Insert token at cursor position or append
                const start = templateTextarea.selectionStart || templateTextarea.value.length;
                const end = templateTextarea.selectionEnd || templateTextarea.value.length;
                const before = templateTextarea.value.substring(0, start);
                const after = templateTextarea.value.substring(end);
                templateTextarea.value = before + val + after;
                templateTextarea.focus();
                // Position cursor after inserted token
                const newPos = start + val.length;
                templateTextarea.setSelectionRange(newPos, newPos);
            }
            // Reset dropdown
            textTokenDropdown.value = '';
        });
    }
    
    // WordPress post selector button
    const selectPostBtn = dlg.querySelector('#ccve-tp-select-post');
    if(selectPostBtn) {
        selectPostBtn.addEventListener('click', ()=>{
            close();
            if(editor.openPostSelector) {
                editor.openPostSelector(layerObj);
            }
        });
    }
    
    // Media library button for static images
    const selectImageBtn = dlg.querySelector('#ccve-tp-select-image');
    if(selectImageBtn) {
        selectImageBtn.addEventListener('click', () => {
            if(!window.wp || !window.wp.media) { alert('WordPress media library not available'); return; }
            const frame = window.wp.media({ title: 'Select Image', button: { text: 'Select' }, multiple: false });
            frame.on('select', () => {
                const attachment = frame.state().get('selection').first().toJSON();
                dlg.querySelector('#ccve-tp-image-url').value = attachment.url;
            });
            frame.open();
        });
    }
    
    // Slideshow media library button
    const addSlidesBtn = dlg.querySelector('#ccve-tp-add-slides');
    if(addSlidesBtn) {
        addSlidesBtn.addEventListener('click', () => {
            if(!window.wp || !window.wp.media) { alert('WordPress media library not available'); return; }
            const frame = window.wp.media({ title: 'Select Images', button: { text: 'Add to Slideshow' }, multiple: true });
            frame.on('select', () => {
                const selection = frame.state().get('selection');
                const newImages = selection.map(att => ({ url: att.toJSON().url, duration: layerObj.interval || 5 }));
                layerObj.sources = [...(layerObj.sources || []), ...newImages];
                layerObj.images = layerObj.sources;
                _rerenderAllLayers();
                editor.unsavedChanges = true;
                try { editor.updateSaveButton && editor.updateSaveButton(); } catch(_) {}
                close();
            });
            frame.open();
        });
    }
    
    // Detail overlay background image media library button
    const detailBgImageBtn = dlg.querySelector('#ccve-tp-feed-detail-bg-image-btn');
    if(detailBgImageBtn) {
        detailBgImageBtn.addEventListener('click', () => {
            if(!window.wp || !window.wp.media) { alert('WordPress media library not available'); return; }
            const frame = window.wp.media({ title: 'Select Background Image', button: { text: 'Select' }, multiple: false });
            frame.on('select', () => {
                const attachment = frame.state().get('selection').first().toJSON();
                dlg.querySelector('#ccve-tp-feed-detail-bg-image').value = attachment.url;
            });
            frame.open();
        });
    }
    
    dlg.querySelector('#ccve-tp-save').addEventListener('click', ()=>{
      const x = parseInt(dlg.querySelector('#ccve-tp-x').value,10)||0;
      const y = parseInt(dlg.querySelector('#ccve-tp-y').value,10)||0;
      const w = Math.max(20, parseInt(dlg.querySelector('#ccve-tp-w').value,10)||layerObj.width);
      const h = Math.max(20, parseInt(dlg.querySelector('#ccve-tp-h').value,10)||layerObj.height);
      layerObj.x = x; layerObj.y = y; layerObj.width = w; layerObj.height = h;
      
      // Handle kind-specific fields
      if(layerObj.kind==='token-text'){
        const tpl = dlg.querySelector('#ccve-tp-template').value.trim();
        layerObj.templateText = tpl;
      } else if(layerObj.kind==='token-image') {
        const tk = dlg.querySelector('#ccve-tp-token').value.trim(); if(tk) layerObj.token = tk;
        const fb = dlg.querySelector('#ccve-tp-fallback')?.value.trim(); if(fb) layerObj.fallback_url = fb;
      } else if(layerObj.kind==='static-text') {
        const txt = dlg.querySelector('#ccve-tp-text').value;
        layerObj.text = txt;
      } else if(layerObj.kind==='static-image') {
        const url = dlg.querySelector('#ccve-tp-image-url')?.value.trim();
        if(url) layerObj.url = url;
      } else if(layerObj.kind==='qr-image') {
        const url = dlg.querySelector('#ccve-tp-qr-url').value.trim();
        if(url) layerObj.sourceUrl = url;
        layerObj._needsQrRegenerate = true;
      } else if(layerObj.kind==='wordpress-post') {
        const postId = parseInt(dlg.querySelector('#ccve-tp-post-id').value, 10);
        if(postId) layerObj.post_id = postId;
      } else if(layerObj.kind==='slideshow-image') {
        const interval = parseInt(dlg.querySelector('#ccve-tp-interval')?.value, 10);
        if(interval) layerObj.interval = interval;
        const transition = dlg.querySelector('#ccve-tp-transition')?.value;
        if(transition) layerObj.transition = transition;
      } else if(layerObj.kind==='video') {
        const videoUrl = dlg.querySelector('#ccve-tp-video-url')?.value.trim();
        if(videoUrl) { layerObj.url = videoUrl; layerObj.video_url = videoUrl; }
        layerObj.autoplay = dlg.querySelector('#ccve-tp-video-autoplay')?.checked ?? true;
        layerObj.loop = dlg.querySelector('#ccve-tp-video-loop')?.checked ?? true;
        layerObj.muted = dlg.querySelector('#ccve-tp-video-muted')?.checked ?? true;
      } else if(layerObj.kind==='feed-layer') {
        const feedUrl = dlg.querySelector('#ccve-tp-feed-url')?.value.trim();
        if(feedUrl) { layerObj.feed_url = feedUrl; layerObj.url = feedUrl; }
        const layout = dlg.querySelector('#ccve-tp-feed-layout')?.value;
        if(layout) layerObj.layout = layout;
        const maxItems = parseInt(dlg.querySelector('#ccve-tp-feed-max')?.value, 10);
        if(maxItems) layerObj.max_items = maxItems;
        const columns = parseInt(dlg.querySelector('#ccve-tp-feed-columns')?.value, 10);
        if(columns) layerObj.columns = columns;
        
        // Card Sizing
        const thumbW = parseInt(dlg.querySelector('#ccve-tp-feed-thumb-w')?.value, 10);
        if(thumbW) layerObj.thumbnail_width = thumbW;
        const thumbH = parseInt(dlg.querySelector('#ccve-tp-feed-thumb-h')?.value, 10);
        if(thumbH) layerObj.thumbnail_height = thumbH;
        const spacing = parseInt(dlg.querySelector('#ccve-tp-feed-spacing')?.value, 10);
        if(spacing !== undefined && !isNaN(spacing)) layerObj.item_spacing = spacing;
        
        // Typography
        const titleSize = parseInt(dlg.querySelector('#ccve-tp-feed-title-size')?.value, 10);
        if(titleSize) layerObj.title_font_size = titleSize;
        const titleClr = dlg.querySelector('#ccve-tp-feed-title-color')?.value;
        if(titleClr) layerObj.title_font_color = titleClr;
        const descSize = parseInt(dlg.querySelector('#ccve-tp-feed-desc-size')?.value, 10);
        if(descSize) layerObj.description_font_size = descSize;
        const descClr = dlg.querySelector('#ccve-tp-feed-desc-color')?.value;
        if(descClr) layerObj.description_font_color = descClr;
        
        // Focus & Selection
        const selClr = dlg.querySelector('#ccve-tp-feed-sel-color')?.value;
        if(selClr) layerObj.selection_color = selClr;
        const cardBgClr = dlg.querySelector('#ccve-tp-feed-card-bg')?.value;
        if(cardBgClr) layerObj.card_background_color = cardBgClr;
        const focusScale = parseFloat(dlg.querySelector('#ccve-tp-feed-focus-scale')?.value);
        if(focusScale && !isNaN(focusScale)) layerObj.focus_scale = focusScale;
        
        // Show/Hide Elements
        layerObj.show_thumbnail = dlg.querySelector('#ccve-tp-feed-show-thumb')?.checked ?? true;
        layerObj.show_title = dlg.querySelector('#ccve-tp-feed-show-title')?.checked ?? true;
        layerObj.show_description = dlg.querySelector('#ccve-tp-feed-show-desc')?.checked ?? false;
        layerObj.show_duration = dlg.querySelector('#ccve-tp-feed-show-duration')?.checked ?? true;
        
        // Display Options
        layerObj.show_container = dlg.querySelector('#ccve-tp-feed-show-container')?.checked ?? true;
        layerObj.center_focus_mode = dlg.querySelector('#ccve-tp-feed-center-focus')?.checked ?? false;
        
        // Detail Overlay Styling
        const detailBgClr = dlg.querySelector('#ccve-tp-feed-detail-bg')?.value;
        if(detailBgClr) layerObj.detail_bg_color = detailBgClr;
        const detailOpacity = parseFloat(dlg.querySelector('#ccve-tp-feed-detail-opacity')?.value);
        if(detailOpacity !== undefined && !isNaN(detailOpacity)) layerObj.detail_bg_opacity = detailOpacity;
        const detailBgImg = dlg.querySelector('#ccve-tp-feed-detail-bg-image')?.value?.trim();
        layerObj.detail_bg_image_url = detailBgImg || '';
        const detailTitleSize = parseInt(dlg.querySelector('#ccve-tp-feed-detail-title-size')?.value, 10);
        if(detailTitleSize) layerObj.detail_title_size = detailTitleSize;
        const detailTitleClr = dlg.querySelector('#ccve-tp-feed-detail-title-color')?.value;
        if(detailTitleClr) layerObj.detail_title_color = detailTitleClr;
        const detailDescSize = parseInt(dlg.querySelector('#ccve-tp-feed-detail-desc-size')?.value, 10);
        if(detailDescSize) layerObj.detail_desc_size = detailDescSize;
        const detailDescClr = dlg.querySelector('#ccve-tp-feed-detail-desc-color')?.value;
        if(detailDescClr) layerObj.detail_desc_color = detailDescClr;
        
        // Audio Controls
        layerObj.show_progress_bar = dlg.querySelector('#ccve-tp-feed-show-progress')?.checked ?? true;
        layerObj.enable_transport_overlay = dlg.querySelector('#ccve-tp-feed-enable-transport')?.checked ?? true;
        layerObj.transport_trigger_key = dlg.querySelector('#ccve-tp-feed-transport-trigger')?.value || 'transport';
        
        // Mark for re-fetch
        layerObj._needsFeedRefresh = true;
      }
      
      // Typography (text layers)
      if(isTextLayer) {
        const ff = dlg.querySelector('#ccve-tp-font-family')?.value;
        const fs = parseInt(dlg.querySelector('#ccve-tp-font-size')?.value, 10);
        const fw = dlg.querySelector('#ccve-tp-font-weight')?.value;
        const fc = dlg.querySelector('#ccve-tp-font-color')?.value;
        const ta = dlg.querySelector('#ccve-tp-text-align')?.value;
        const lh = parseFloat(dlg.querySelector('#ccve-tp-line-height')?.value);
        const sx = parseInt(dlg.querySelector('#ccve-tp-shadow-x')?.value, 10) || 0;
        const sy = parseInt(dlg.querySelector('#ccve-tp-shadow-y')?.value, 10) || 0;
        const sb = parseInt(dlg.querySelector('#ccve-tp-shadow-blur')?.value, 10) || 0;
        const sc = dlg.querySelector('#ccve-tp-shadow-color')?.value || '#000000';
        
        layerObj.style = layerObj.style || {};
        if(ff) { layerObj.style.font_family = ff; layerObj.font_family = ff; }
        if(fs) { layerObj.style.font_size = fs; layerObj.font_size = fs; }
        if(fw) { layerObj.style.font_weight = fw; layerObj.font_weight = fw; }
        if(fc) { layerObj.style.color = fc; layerObj.color = fc; }
        if(ta) { layerObj.style.text_align = ta; layerObj.text_align = ta; }
        if(lh) { layerObj.style.line_height = lh; layerObj.line_height = lh; }
        layerObj.style.text_shadow = { x: sx, y: sy, blur: sb, color: sc };
        layerObj.text_shadow = { x: sx, y: sy, blur: sb, color: sc };
        
        // Opacity and background for text layers
        const textOp = parseFloat(dlg.querySelector('#ccve-tp-opacity')?.value);
        const textBg = dlg.querySelector('#ccve-tp-bg-color')?.value;
        if(textOp !== undefined && !isNaN(textOp)) layerObj.opacity = textOp;
        if(textBg && textBg !== '#000000') layerObj.background_color = textBg;
        
        // Border for text layers
        const textBw = parseInt(dlg.querySelector('#ccve-tp-border-width')?.value, 10) || 0;
        const textBs = dlg.querySelector('#ccve-tp-border-style')?.value || 'solid';
        const textBc = dlg.querySelector('#ccve-tp-border-color')?.value || '#000000';
        if(textBw > 0) {
          layerObj.border_width = textBw;
          layerObj.border_style = textBs;
          layerObj.border_color = textBc;
        }
        
        // Scrolling text settings
        const scrollEnabled = dlg.querySelector('#ccve-tp-scroll-enabled')?.checked ?? true;
        const scrollSpeed = parseInt(dlg.querySelector('#ccve-tp-scroll-speed')?.value, 10) || 30;
        layerObj.scroll_enabled = scrollEnabled;
        layerObj.scroll_speed = scrollSpeed;
        
        // Sync Typography tab controls with layer values
        try {
          if(editor.setControlValue) {
            if(ff) editor.setControlValue('canvas-font-family', ff);
            if(fs) editor.setControlValue('canvas-font-size', fs);
            if(fw) editor.setControlValue('canvas-font-weight', fw);
            if(fc) editor.setControlValue('canvas-font-color', fc);
            if(ta) editor.setControlValue('canvas-text-align', ta);
            if(lh) editor.setControlValue('canvas-line-height', lh);
            editor.setControlValue('canvas-text-shadow-x', sx);
            editor.setControlValue('canvas-text-shadow-y', sy);
            editor.setControlValue('canvas-text-shadow-blur', sb);
            editor.setControlValue('canvas-text-shadow-color', sc);
          }
        } catch(_) { console.warn('[Precision] Could not sync Typography tab:', _); }
      }
      
      // Appearance (image layers)
      if(isImageLayer) {
        const fit = dlg.querySelector('#ccve-tp-object-fit')?.value;
        const br = parseInt(dlg.querySelector('#ccve-tp-border-radius')?.value, 10);
        const op = parseFloat(dlg.querySelector('#ccve-tp-opacity')?.value);
        
        if(fit) { layerObj.objectFit = fit; layerObj.fit = fit; layerObj.object_fit = fit; }
        if(br !== undefined && !isNaN(br)) layerObj.border_radius = br;
        if(op !== undefined && !isNaN(op)) layerObj.opacity = op;
        
        // Border for image layers
        const imgBw = parseInt(dlg.querySelector('#ccve-tp-border-width')?.value, 10) || 0;
        const imgBs = dlg.querySelector('#ccve-tp-border-style')?.value || 'solid';
        const imgBc = dlg.querySelector('#ccve-tp-border-color')?.value || '#000000';
        if(imgBw > 0) {
          layerObj.border_width = imgBw;
          layerObj.border_style = imgBs;
          layerObj.border_color = imgBc;
        }
      }
      
      // Animation (all layers)
      const animType = dlg.querySelector('#ccve-tp-animation')?.value || 'none';
      const animSpeed = parseInt(dlg.querySelector('#ccve-tp-animation-speed')?.value, 10) || 50;
      layerObj.animation = animType;
      layerObj.animation_speed = animSpeed;
      
      _rerenderAllLayers();
      try { editor.refreshLiveTokens && editor.refreshLiveTokens(); } catch(_) {}
      editor._pushTokenUndoState('precision');
      editor.unsavedChanges = true;
      try { editor.updateSaveButton && editor.updateSaveButton(); } catch(_) {}
      close();
    });
  }

  stage.addEventListener('mousedown', onPointerDown);
  console.log('[CCVE][Init] Token layer interactions attached to stage:', stage.id, {
      layersInStage: stage.querySelectorAll('.unified-layer').length,
      stageElement: stage
  });
  // Ensure edit buttons initially
  _ensureEditButtons();
  // Robust double-click: capture at document to avoid interference
  if(!document.__ccveTokenDblBound){
    document.addEventListener('dblclick', (e)=>{
      const inStage = !!e.target.closest('#block-stage');
      if(!inStage) return;
      const node = e.target.closest('.unified-layer');
      if(!node) return;
      const id = node.getAttribute('data-layer-id');
      const layerObj = (editor.currentConfig?.layers||[]).find(l=>l.id===id);
      if(!layerObj) return;
      _openPrecisionDialog(layerObj);
    }, true);
    document.__ccveTokenDblBound = true;
  }
  // Right-click: show context menu with Group options + Edit
  if(!stage.__ccveTokenCtxBound){
    stage.addEventListener('contextmenu', (e)=>{
      const node = e.target.closest('.unified-layer');
      if(!node) return;
      e.preventDefault();
      const id = node.getAttribute('data-layer-id');
      const layerObj = (editor.currentConfig?.layers||[]).find(l=>l.id===id);
      if(!layerObj) return;
      _ensureHandlesForSelection(node);
      
      // Build context menu
      const existingMenu = document.querySelector('.ccve-canvas-context-menu');
      if (existingMenu) existingMenu.remove();
      
      const groupingMgr = editor._layerGrouping;
      const selectedLayers = groupingMgr?.getSelectedLayers() || [];
      const hasMultiSelection = selectedLayers.length > 1;
      const isInGroup = layerObj.groupId;
      const groupLabel = isInGroup ? (groupingMgr?.getGroupLabel(layerObj.groupId) || 'Group') : '';
      
      let menuHTML = `
        <div class="ccve-canvas-context-menu" style="position:fixed; left:${e.clientX}px; top:${e.clientY}px; z-index:10000; background:#1e2328; border:1px solid #3d4450; border-radius:6px; padding:4px 0; min-width:180px; box-shadow:0 4px 12px rgba(0,0,0,0.4); font-size:12px; color:#e5e7eb;">
          <div class="ccve-ctx-item" data-action="edit" style="padding:8px 12px; cursor:pointer; display:flex; align-items:center; gap:8px;">
            <span>✏️</span> Edit Layer Properties
          </div>
          <div style="border-top:1px solid #3d4450; margin:4px 0;"></div>
      `;
      
      // Group/Ungroup options
      if (hasMultiSelection) {
        menuHTML += `<div class="ccve-ctx-item" data-action="group" style="padding:8px 12px; cursor:pointer; display:flex; align-items:center; gap:8px;">
          <span>📦</span> Group ${selectedLayers.length} Layers <span style="color:#6b7280; margin-left:auto; font-size:10px;">G</span>
        </div>`;
      }
      
      if (isInGroup) {
        menuHTML += `<div class="ccve-ctx-item" data-action="ungroup" style="padding:8px 12px; cursor:pointer; display:flex; align-items:center; gap:8px;">
          <span>📂</span> Ungroup "${groupLabel}" <span style="color:#6b7280; margin-left:auto; font-size:10px;">G</span>
        </div>`;
        menuHTML += `<div class="ccve-ctx-item" data-action="select-group" style="padding:8px 12px; cursor:pointer; display:flex; align-items:center; gap:8px;">
          <span>🎯</span> Select Entire Group
        </div>`;
      }
      
      if (!selectedLayers.some(l => l.id === id) && groupingMgr) {
        menuHTML += `<div class="ccve-ctx-item" data-action="add-to-selection" style="padding:8px 12px; cursor:pointer; display:flex; align-items:center; gap:8px;">
          <span>➕</span> Add to Selection <span style="color:#6b7280; margin-left:auto; font-size:10px;">Shift+Click</span>
        </div>`;
      }
      
      menuHTML += `
          <div style="border-top:1px solid #3d4450; margin:4px 0;"></div>
          <div class="ccve-ctx-item" data-action="duplicate" style="padding:8px 12px; cursor:pointer; display:flex; align-items:center; gap:8px;">
            <span>📋</span> Duplicate <span style="color:#6b7280; margin-left:auto; font-size:10px;">Ctrl+D</span>
          </div>
          <div class="ccve-ctx-item" data-action="delete" style="padding:8px 12px; cursor:pointer; display:flex; align-items:center; gap:8px; color:#ef4444;">
            <span>🗑️</span> Delete <span style="color:#6b7280; margin-left:auto; font-size:10px;">Del</span>
          </div>
        </div>
      `;
      
      document.body.insertAdjacentHTML('beforeend', menuHTML);
      const menu = document.querySelector('.ccve-canvas-context-menu');
      
      // Hover effects
      menu.querySelectorAll('.ccve-ctx-item').forEach(item => {
        item.addEventListener('mouseenter', () => {
          item.style.backgroundColor = item.style.color?.includes('ef4444') ? 'rgba(239, 68, 68, 0.1)' : '#2a313a';
        });
        item.addEventListener('mouseleave', () => item.style.backgroundColor = '');
      });
      
      // Handle clicks
      menu.addEventListener('click', (menuE) => {
        const item = menuE.target.closest('.ccve-ctx-item');
        if (!item) return;
        const action = item.getAttribute('data-action');
        
        switch(action) {
          case 'edit':
            _openPrecisionDialog(layerObj);
            break;
          case 'group':
            if (groupingMgr) groupingMgr.groupSelectedLayers();
            break;
          case 'ungroup':
            if (groupingMgr && layerObj.groupId) groupingMgr.ungroupLayers(layerObj.groupId);
            break;
          case 'select-group':
            if (groupingMgr && layerObj.groupId) groupingMgr.selectGroup(layerObj.groupId);
            break;
          case 'add-to-selection':
            if (groupingMgr) groupingMgr.selectLayer(id, true);
            break;
          case 'duplicate':
            _duplicateSelected();
            break;
          case 'delete':
            _deleteSelected();
            break;
        }
        menu.remove();
      });
      
      // Close on click outside
      const closeMenu = (closeE) => {
        if (!menu.contains(closeE.target)) {
          menu.remove();
          document.removeEventListener('click', closeMenu);
        }
      };
      setTimeout(() => document.addEventListener('click', closeMenu), 0);
      
      // Close on Escape
      const escHandler = (escE) => {
        if (escE.key === 'Escape') {
          menu.remove();
          document.removeEventListener('keydown', escHandler);
        }
      };
      document.addEventListener('keydown', escHandler);
    });
    stage.__ccveTokenCtxBound = true;
  }

  function _duplicateSelected(){
    const sel = stage.querySelector('.unified-layer--selected');
    if(!sel) return;
    const id = sel.getAttribute('data-layer-id');
    const layer = (editor.currentConfig?.layers||[]).find(l=>l.id===id);
    if(!layer) return; 
    const clone = { ...layer, id: layer.id + '_copy', x: layer.x + 24, y: layer.y + 24 };
    if(editor.currentConfig && Array.isArray(editor.currentConfig.layers)) editor.currentConfig.layers.push(clone);
    // Create DOM clone
    const node = sel.cloneNode(true);
    node.setAttribute('data-layer-id', clone.id);
    node.style.left = clone.x + 'px';
    node.style.top = clone.y + 'px';
    stage.appendChild(node);
    _ensureHandlesForSelection(node);
    _ensureEditButtons();
    editor._pushTokenUndoState('duplicate');
    editor.unsavedChanges = true;
    try { editor.updateSaveButton && editor.updateSaveButton(); } catch(_) {}
    try { editor.buildLayersPanel(); } catch(_) {}
  }

  function _deleteSelected(){
    const sel = stage.querySelector('.unified-layer--selected');
    if(!sel) return;
    const id = sel.getAttribute('data-layer-id');
    if(!id) return;
    const layers = (editor.currentConfig?.layers)||[];
    const idx = layers.findIndex(l=>l.id===id);
  if(idx!==-1){ layers.splice(idx,1); sel.remove(); editor._pushTokenUndoState('delete'); editor.unsavedChanges=true; try { editor.updateSaveButton && editor.updateSaveButton(); } catch(_) {} try { editor.buildLayersPanel(); } catch(_){} }
  }

  document.addEventListener('keydown', (e)=>{
    // Ignore form inputs and contentEditable elements (e.g., static-text inline editing)
    if(e.target && (['INPUT','TEXTAREA'].includes(e.target.tagName) || e.target.isContentEditable)) return;
    if((e.metaKey||e.ctrlKey) && e.key.toLowerCase()==='z'){ e.preventDefault(); if(e.shiftKey){ editor._restoreTokenUndoState(+1); } else { editor._restoreTokenUndoState(-1); } console.log('[CCVE][Undo] ' + (e.shiftKey ? 'Redo' : 'Undo') + ' triggered, stack index:', editor._tokenUndo?.index); }
    else if(((e.metaKey||e.ctrlKey) && (e.key.toLowerCase()==='y'))){ e.preventDefault(); editor._restoreTokenUndoState(+1); }
    else if((e.metaKey||e.ctrlKey) && e.key.toLowerCase()==='d'){ e.preventDefault(); _duplicateSelected(); }
    else if(e.key==='Delete'){ e.preventDefault(); _deleteSelected(); }
  });

  // Initial snapshot if layers already exist
  setTimeout(()=>{ try { editor._pushTokenUndoState('init'); } catch(_){} }, 100);
}

export default { attachTokenLayerInteractions };

/* CSS (injected once) */
(()=>{
  if(document.getElementById('unified-layer-style')) return;
  const s = document.createElement('style');
  s.id='unified-layer-style';
  s.textContent = `
    /* Ensure unified layers sit above the preview overlay and receive pointer events */
    #block-stage .unified-layer { z-index: 5; pointer-events: auto; cursor: move; }
    .unified-layer.unified-layer--selected { outline: 2px solid #4da3ff; }
    .unified-layer--handle { position:absolute; width:12px; height:12px; background:#4da3ff; border-radius:2px; transform:translate(-50%, -50%); cursor:pointer; z-index:10; }
    .unified-layer--handle-nw { left:0%; top:0%; cursor:nwse-resize; }
    .unified-layer--handle-n { left:50%; top:0%; cursor:ns-resize; }
    .unified-layer--handle-ne { left:100%; top:0%; cursor:nesw-resize; }
    .unified-layer--handle-e { left:100%; top:50%; cursor:ew-resize; }
    .unified-layer--handle-se { left:100%; top:100%; cursor:nwse-resize; }
    .unified-layer--handle-s { left:50%; top:100%; cursor:ns-resize; }
    .unified-layer--handle-sw { left:0%; top:100%; cursor:nesw-resize; }
    .unified-layer--handle-w { left:0%; top:50%; cursor:ew-resize; }
    /* Legacy handle support for backward compatibility */
    .ccve-layer-handle { position:absolute; width:12px; height:12px; background:#4da3ff; border-radius:2px; transform:translate(-50%, -50%); cursor:pointer; z-index:10; }
    .ccve-layer-h-nw { left:0%; top:0%; cursor:nwse-resize; }
    .ccve-layer-h-n { left:50%; top:0%; cursor:ns-resize; }
    .ccve-layer-h-ne { left:100%; top:0%; cursor:nesw-resize; }
    .ccve-layer-h-e { left:100%; top:50%; cursor:ew-resize; }
    .ccve-layer-h-se { left:100%; top:100%; cursor:nwse-resize; }
    .ccve-layer-h-s { left:50%; top:100%; cursor:ns-resize; }
    .ccve-layer-h-sw { left:0%; top:100%; cursor:nesw-resize; }
    .ccve-layer-h-w { left:0%; top:50%; cursor:ew-resize; }
    /* Precision dialog styling - Two-column layout for feature-rich modal */
    .unified-precision-dialog, .ccve-token-precision-dialog { 
        position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); 
        background:#1e1e1e; color:#fff; padding:0; border:1px solid #444; 
        border-radius:8px; z-index:4000; width:600px; max-width:95vw;
        font:13px system-ui,sans-serif; box-shadow: 0 20px 40px rgba(0,0,0,0.5);
    }
    .ccve-tp-inner { 
        max-height: 80vh; overflow-y: auto; padding: 20px 24px;
    }
    .ccve-tp-inner h3 { 
        margin:0 0 16px; font-size:16px; padding-bottom: 12px; 
        border-bottom: 1px solid #333; position: sticky; top: 0; 
        background: #1e1e1e; z-index: 1;
    }
    .ccve-tp-geometry-grid {
        display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; gap: 8px; margin-bottom: 12px;
    }
    .unified-precision-dialog h3, .ccve-token-precision-dialog h3 { margin:0 0 16px; font-size:16px; }
    .unified-precision-dialog label, .ccve-token-precision-dialog label { display:flex; flex-direction:column; gap:4px; margin:4px 0; font-weight:600; font-size: 12px; }
    .unified-precision-dialog input, .unified-precision-dialog textarea, .ccve-token-precision-dialog input, .ccve-token-precision-dialog textarea { width:100%; box-sizing:border-box; background:#111; border:1px solid #333; color:#fff; padding:6px 8px; font:12px monospace; border-radius: 4px; }
    .unified-precision-dialog select, .ccve-token-precision-dialog select { width:100%; box-sizing:border-box; background:#111; border:1px solid #333; color:#fff; padding:6px 8px; font:12px system-ui,sans-serif; border-radius: 4px; }
    .unified-precision-dialog textarea, .ccve-token-precision-dialog textarea { resize:vertical; min-height: 60px; }
    .unified-precision-dialog .ccve-tp-actions, .ccve-token-precision-dialog .ccve-tp-actions { 
        display:flex; justify-content:flex-end; gap:8px; margin-top:16px; padding-top: 16px;
        border-top: 1px solid #333; position: sticky; bottom: 0; background: #1e1e1e;
    }
    .unified-precision-dialog button, .ccve-token-precision-dialog button { background:#2d76d2; border:none; padding:8px 16px; color:#fff; cursor:pointer; border-radius:4px; font-weight:600; font-size: 13px; }
    .unified-precision-dialog button#ccve-tp-cancel, .ccve-token-precision-dialog button#ccve-tp-cancel { background:#555; }
    .unified-precision-dialog button:hover, .ccve-token-precision-dialog button:hover { filter: brightness(1.1); }
  `;
  document.head.appendChild(s);
})();
