(function (global) {
  global.ARQ = global.ARQ || {};

  var store = {};

  function sheetKey(sheet) {
    return sheet && sheet.id ? sheet.id : '_default';
  }

  function allocMask(w, h) {
    return new Uint8Array(Math.max(0, w * h));
  }

  function clamp(v, a, b) {
    return Math.max(a, Math.min(b, v));
  }

  function rectNormalize(x0, y0, x1, y1) {
    return {
      x: Math.min(x0, x1),
      y: Math.min(y0, y1),
      w: Math.abs(x1 - x0),
      h: Math.abs(y1 - y0),
    };
  }

  function computeBounds(mask, w, h) {
    if (!mask || !w || !h) return null;
    var minX = w;
    var minY = h;
    var maxX = -1;
    var maxY = -1;
    var i;
    var x;
    var y;
    for (i = 0; i < mask.length; i++) {
      if (!mask[i]) continue;
      x = i % w;
      y = (i / w) | 0;
      if (x < minX) minX = x;
      if (y < minY) minY = y;
      if (x > maxX) maxX = x;
      if (y > maxY) maxY = y;
    }
    if (maxX < 0) return null;
    return { x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1 };
  }

  function applyRectToMask(mask, w, h, rx, ry, rw, rh, op) {
    var x0 = clamp(Math.floor(rx), 0, w - 1);
    var y0 = clamp(Math.floor(ry), 0, h - 1);
    var x1 = clamp(Math.ceil(rx + rw) - 1, 0, w - 1);
    var y1 = clamp(Math.ceil(ry + rh) - 1, 0, h - 1);
    var x;
    var y;
    var idx;
    for (y = y0; y <= y1; y++) {
      for (x = x0; x <= x1; x++) {
        idx = y * w + x;
        if (op === 'add') mask[idx] = 1;
        else if (op === 'subtract') mask[idx] = 0;
        else mask[idx] = 1;
      }
    }
  }

  ARQ.selection = {
    init: function () {},

    getState: function (sheet) {
      if (!sheet) return null;
      var k = sheetKey(sheet);
      var w = sheet.ancho || 1920;
      var h = sheet.alto || 1080;
      var st = store[k];
      if (!st || st.w !== w || st.h !== h) {
        st = {
          w: w,
          h: h,
          mask: null,
          bounds: null,
          outline: null,
          draft: null,
          cropDraft: null,
        };
        store[k] = st;
      }
      return st;
    },

    clear: function (sheet) {
      var st = this.getState(sheet);
      if (!st) return;
      st.mask = null;
      st.bounds = null;
      st.outline = null;
      st.draft = null;
      st.cropDraft = null;
      ARQ.bus.emit(ARQ.EVENTS.SELECTION_CHANGED);
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    hasMask: function (sheet) {
      var st = this.getState(sheet);
      if (!st || !st.mask) return false;
      var i;
      for (i = 0; i < st.mask.length; i++) {
        if (st.mask[i]) return true;
      }
      return false;
    },

    getBounds: function (sheet) {
      var st = this.getState(sheet);
      if (!st || !st.mask) return null;
      return st.bounds;
    },

    getMask: function (sheet) {
      var st = this.getState(sheet);
      return st && st.mask ? st.mask : null;
    },

    setDraftRect: function (sheet, x0, y0, x1, y1) {
      var st = this.getState(sheet);
      if (!st) return;
      st.draft = { kind: 'rect', x0: x0, y0: y0, x1: x1, y1: y1 };
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    setDraftLasso: function (sheet, points) {
      var st = this.getState(sheet);
      if (!st) return;
      st.draft = { kind: 'lasso', points: points ? points.slice() : [] };
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    clearDraft: function (sheet) {
      var st = this.getState(sheet);
      if (!st || !st.draft) return;
      st.draft = null;
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    combineMode: function (shiftKey, altKey) {
      if (altKey) return 'subtract';
      if (shiftKey) return 'add';
      return 'replace';
    },

    applyRect: function (sheet, x0, y0, x1, y1, combine) {
      var st = this.getState(sheet);
      if (!st) return;
      var w = st.w;
      var h = st.h;
      var r = rectNormalize(x0, y0, x1, y1);
      if (r.w < 1 && r.h < 1) return;
      if (!st.mask) st.mask = allocMask(w, h);
      if (combine === 'replace') {
        st.mask.fill(0);
        applyRectToMask(st.mask, w, h, r.x, r.y, r.w, r.h, 'replace');
      } else if (combine === 'add') {
        applyRectToMask(st.mask, w, h, r.x, r.y, r.w, r.h, 'add');
      } else {
        applyRectToMask(st.mask, w, h, r.x, r.y, r.w, r.h, 'subtract');
      }
      st.bounds = computeBounds(st.mask, w, h);
      st.outline = null;
      st.draft = null;
      ARQ.bus.emit(ARQ.EVENTS.SELECTION_CHANGED);
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    /**
     * Rellena máscara desde polígono cerrado (coords hoja).
     */
    applyPolygon: function (sheet, points, combine) {
      if (!points || points.length < 3) return;
      var st = this.getState(sheet);
      if (!st) return;
      var w = st.w;
      var h = st.h;
      var c = global.document.createElement('canvas');
      c.width = w;
      c.height = h;
      var x = c.getContext('2d');
      if (!x) return;
      x.beginPath();
      x.moveTo(points[0].x, points[0].y);
      var i;
      for (i = 1; i < points.length; i++) x.lineTo(points[i].x, points[i].y);
      x.closePath();
      x.fillStyle = '#fff';
      x.fill();
      var id = x.getImageData(0, 0, w, h);
      var d = id.data;
      var polyMask = allocMask(w, h);
      var pi;
      for (pi = 0; pi < w * h; pi++) {
        if (d[pi * 4 + 3] > 10) polyMask[pi] = 1;
      }
      if (!st.mask || combine === 'replace') {
        st.mask = allocMask(w, h);
      }
      if (combine === 'replace') {
        st.mask.set(polyMask);
      } else if (combine === 'add') {
        for (i = 0; i < st.mask.length; i++) {
          if (polyMask[i]) st.mask[i] = 1;
        }
      } else {
        for (i = 0; i < st.mask.length; i++) {
          if (polyMask[i]) st.mask[i] = 0;
        }
      }
      st.bounds = computeBounds(st.mask, w, h);
      st.outline = points.map(function (p) { return { x: p.x, y: p.y }; });
      st.draft = null;
      ARQ.bus.emit(ARQ.EVENTS.SELECTION_CHANGED);
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    /**
     * Combina máscara booleana (Uint8Array 0/1 misma dimensión) con la selección actual.
     */
    applyBinaryMask: function (sheet, binaryMask, combine) {
      var st = this.getState(sheet);
      if (!st || !binaryMask || binaryMask.length !== st.w * st.h) return;
      var w = st.w;
      var h = st.h;
      var i;
      if (!st.mask) st.mask = allocMask(w, h);
      if (combine === 'replace') {
        st.mask.set(binaryMask);
      } else if (combine === 'add') {
        for (i = 0; i < binaryMask.length; i++) {
          if (binaryMask[i]) st.mask[i] = 1;
        }
      } else {
        for (i = 0; i < binaryMask.length; i++) {
          if (binaryMask[i]) st.mask[i] = 0;
        }
      }
      st.bounds = computeBounds(st.mask, w, h);
      st.outline = null;
      ARQ.bus.emit(ARQ.EVENTS.SELECTION_CHANGED);
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    marchingOffset: function () {
      return ((global.performance && performance.now()) || Date.now()) / 40;
    },

    setCropDraft: function (sheet, x0, y0, x1, y1) {
      var st = this.getState(sheet);
      if (!st) return;
      st.cropDraft = { x0: x0, y0: y0, x1: x1, y1: y1 };
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    clearCropDraft: function (sheet) {
      var st = this.getState(sheet);
      if (!st || !st.cropDraft) return;
      st.cropDraft = null;
      if (ARQ.canvasEngine && ARQ.canvasEngine.requestRedraw) ARQ.canvasEngine.requestRedraw();
    },

    getCropDraft: function (sheet) {
      var st = this.getState(sheet);
      return st && st.cropDraft ? st.cropDraft : null;
    },

    shouldAnimate: function (sheet) {
      var st = this.getState(sheet);
      return !!(st && (st.draft || st.cropDraft || (st.mask && st.bounds)));
    },

    drawOverlay: function (ctx, sheet) {
      var st = this.getState(sheet);
      if (!st) return;
      var off = this.marchingOffset() % 20;
      ctx.save();
      ctx.setLineDash([5, 5]);
      ctx.lineDashOffset = -off;
      ctx.lineWidth = 1;
      ctx.strokeStyle = '#ffffff';
      if (st.draft && st.draft.kind === 'rect') {
        var d = st.draft;
        var r = rectNormalize(d.x0, d.y0, d.x1, d.y1);
        if (r.w > 0 || r.h > 0) {
          ctx.strokeRect(r.x + 0.5, r.y + 0.5, r.w, r.h);
        }
      }
      if (st.draft && st.draft.kind === 'lasso' && st.draft.points && st.draft.points.length > 1) {
        var pts = st.draft.points;
        ctx.beginPath();
        ctx.moveTo(pts[0].x, pts[0].y);
        for (var j = 1; j < pts.length; j++) ctx.lineTo(pts[j].x, pts[j].y);
        ctx.stroke();
      }
      if (st.cropDraft) {
        var cr = rectNormalize(st.cropDraft.x0, st.cropDraft.y0, st.cropDraft.x1, st.cropDraft.y1);
        ctx.strokeStyle = '#22c55e';
        ctx.setLineDash([6, 4]);
        ctx.lineDashOffset = -off;
        if (cr.w > 0 || cr.h > 0) ctx.strokeRect(cr.x + 0.5, cr.y + 0.5, cr.w, cr.h);
      }
      ctx.restore();

      var hasSel = st.mask && st.bounds;
      var pathPts = st.outline && st.outline.length >= 3 ? st.outline : null;
      if (hasSel && st.bounds) {
        ctx.save();
        ctx.setLineDash([4, 4]);
        ctx.lineDashOffset = -off;
        ctx.lineWidth = 1;
        ctx.strokeStyle = '#000000';
        if (pathPts) {
          ctx.beginPath();
          ctx.moveTo(pathPts[0].x, pathPts[0].y);
          var k;
          for (k = 1; k < pathPts.length; k++) ctx.lineTo(pathPts[k].x, pathPts[k].y);
          ctx.closePath();
          ctx.stroke();
        } else {
          var b = st.bounds;
          ctx.strokeRect(b.x + 0.5, b.y + 0.5, b.w, b.h);
        }
        ctx.strokeStyle = '#ffffff';
        ctx.lineDashOffset = -off + 4;
        if (pathPts) {
          ctx.beginPath();
          ctx.moveTo(pathPts[0].x, pathPts[0].y);
          for (k = 1; k < pathPts.length; k++) ctx.lineTo(pathPts[k].x, pathPts[k].y);
          ctx.closePath();
          ctx.stroke();
        } else {
          ctx.strokeRect(b.x + 0.5, b.y + 0.5, b.w, b.h);
        }
        ctx.restore();
      }
    },

    /**
     * Borra (transparente) píxeles bajo la máscara en la capa rasterizada.
     */
    eraseMaskedPixels: function (sheet, layer) {
      var mask = this.getMask(sheet);
      if (!mask || !layer || !ARQ.guards.canDrawOnLayer(layer)) return Promise.resolve(false);
      var w = sheet.ancho || 1920;
      var h = sheet.alto || 1080;
      if (mask.length !== w * h) return Promise.resolve(false);
      var self = this;
      return ARQ.canvasEngine.preloadLayerImages(sheet, [layer]).then(function () {
        var c = global.document.createElement('canvas');
        c.width = w;
        c.height = h;
        var ctx = c.getContext('2d');
        if (!ctx) return false;
        ctx.clearRect(0, 0, w, h);
        ctx.save();
        ctx.globalAlpha = layer.opacidad != null ? layer.opacidad : 1;
        ARQ.canvasEngine.renderLayersToContext(ctx, sheet, [layer], { includeBackground: false });
        ctx.restore();
        var id = ctx.getImageData(0, 0, w, h);
        var d = id.data;
        var i;
        for (i = 0; i < mask.length; i++) {
          if (!mask[i]) continue;
          var oi = i * 4;
          d[oi] = 0;
          d[oi + 1] = 0;
          d[oi + 2] = 0;
          d[oi + 3] = 0;
        }
        ctx.putImageData(id, 0, 0);
        var dataUrl = c.toDataURL('image/png');
        layer.contenido = layer.contenido || {};
        layer.contenido.strokes = [];
        layer.contenido.shapes = [];
        layer.contenido.texts = [];
        layer.contenido.imageSrc = dataUrl;
        layer.contenido.imageTransform = {
          x: 0,
          y: 0,
          w: w,
          h: h,
          rotation: 0,
          naturalW: w,
          naturalH: h,
        };
        delete layer.contenido.imageTransform.corners;
        layer.tipo = 'imagen';
        self.clear(sheet);
        ARQ.store.setDirty(true);
        ARQ.canvasEngine.requestRedraw();
        return true;
      }).catch(function () { return false; });
    },

    /**
     * Mueve píxeles seleccionados dentro de la capa (raster). Requiere máscara activa.
     */
    moveMaskedPixels: function (sheet, layer, dx, dy) {
      var mask = this.getMask(sheet);
      if (!mask || !layer || !ARQ.guards.canDrawOnLayer(layer)) return Promise.resolve(false);
      var w = sheet.ancho || 1920;
      var h = sheet.alto || 1080;
      if (mask.length !== w * h) return Promise.resolve(false);
      var self = this;
      return ARQ.canvasEngine.preloadLayerImages(sheet, [layer]).then(function () {
        var c = global.document.createElement('canvas');
        c.width = w;
        c.height = h;
        var ctx = c.getContext('2d');
        if (!ctx) return false;
        ctx.clearRect(0, 0, w, h);
        ctx.save();
        ctx.globalAlpha = layer.opacidad != null ? layer.opacidad : 1;
        ARQ.canvasEngine.renderLayersToContext(ctx, sheet, [layer], { includeBackground: false });
        ctx.restore();
        var id = ctx.getImageData(0, 0, w, h);
        var d = id.data;
        var src = new Uint8ClampedArray(d);
        var i;
        var ix;
        var iy;
        var nx;
        var ny;
        var oi;
        var ni;
        for (i = 0; i < mask.length; i++) {
          if (!mask[i]) continue;
          oi = i * 4;
          d[oi] = 0;
          d[oi + 1] = 0;
          d[oi + 2] = 0;
          d[oi + 3] = 0;
        }
        for (i = 0; i < mask.length; i++) {
          if (!mask[i]) continue;
          ix = i % w;
          iy = (i / w) | 0;
          nx = Math.round(ix + dx);
          ny = Math.round(iy + dy);
          if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
          ni = (ny * w + nx) * 4;
          oi = i * 4;
          d[ni] = src[oi];
          d[ni + 1] = src[oi + 1];
          d[ni + 2] = src[oi + 2];
          d[ni + 3] = src[oi + 3];
        }
        ctx.putImageData(id, 0, 0);
        var dataUrl = c.toDataURL('image/png');
        layer.contenido = layer.contenido || {};
        layer.contenido.strokes = [];
        layer.contenido.shapes = [];
        layer.contenido.texts = [];
        layer.contenido.imageSrc = dataUrl;
        layer.contenido.imageTransform = {
          x: 0,
          y: 0,
          w: w,
          h: h,
          rotation: 0,
          naturalW: w,
          naturalH: h,
        };
        delete layer.contenido.imageTransform.corners;
        layer.tipo = 'imagen';
        var st = self.getState(sheet);
        if (st && st.mask) {
          var nm = allocMask(w, h);
          for (i = 0; i < mask.length; i++) {
            if (!mask[i]) continue;
            ix = i % w;
            iy = (i / w) | 0;
            nx = Math.round(ix + dx);
            ny = Math.round(iy + dy);
            if (nx >= 0 && ny >= 0 && nx < w && ny < h) nm[ny * w + nx] = 1;
          }
          st.mask = nm;
          st.bounds = computeBounds(nm, w, h);
        }
        ARQ.store.setDirty(true);
        ARQ.canvasEngine.requestRedraw();
        return true;
      }).catch(function () { return false; });
    },
  };
})(typeof window !== 'undefined' ? window : this);
