/**
 * DemoArrows — SVG overlay for animated arrows between DOM nodes or as rays.
 * No Beast dependencies. Pointer-events: none on the overlay (clicks pass through).
 *
 * API:
 *   DemoArrows.init([container]) — append fixed SVG layer (default: document.body)
 *   DemoArrows.clear() — remove all arrows and labels
 *   DemoArrows.drawBetween(fromEl, toEl, options?)
 *   DemoArrows.drawBetweenSelectors(fromSel, toSel, options?)
 *   DemoArrows.drawRay(fromEl, options) — from element center in direction (angleDeg, lengthPx)
 *   DemoArrows.redraw() — repeat last draws after resize/scroll (call from demo steps)
 *
 * options: { color, strokeWidth, durationMs, curved, delayMs, className, arrowHeadSize }
 */
(function (global) {
  'use strict';

  var svgNS = 'http://www.w3.org/2000/svg';
  var layer = null;
  var mainGroup = null;
  var resizeBound = false;
  var lastOps = [];
  var preferredContainer = null;

  function ensureLayer(container) {
    if (layer && layer.parentNode) return;
    container = container || preferredContainer || document.body;
    layer = document.createElementNS(svgNS, 'svg');
    layer.setAttribute('id', 'demo-arrows-svg-layer');
    layer.style.cssText =
      'position:fixed;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:10050;overflow:visible;';
    var defs = document.createElementNS(svgNS, 'defs');
    var marker = document.createElementNS(svgNS, 'marker');
    marker.setAttribute('id', 'demo-arrow-head');
    marker.setAttribute('markerWidth', '10');
    marker.setAttribute('markerHeight', '10');
    marker.setAttribute('refX', '9');
    marker.setAttribute('refY', '3');
    marker.setAttribute('orient', 'auto');
    marker.setAttribute('markerUnits', 'strokeWidth');
    var pathHead = document.createElementNS(svgNS, 'path');
    pathHead.setAttribute('d', 'M0,0 L0,6 L9,3 z');
    pathHead.setAttribute('fill', 'currentColor');
    marker.appendChild(pathHead);
    defs.appendChild(marker);
    layer.appendChild(defs);
    mainGroup = document.createElementNS(svgNS, 'g');
    mainGroup.setAttribute('class', 'demo-arrows-group');
    layer.appendChild(mainGroup);
    container.appendChild(layer);
    syncViewBox();
    if (!resizeBound) {
      resizeBound = true;
      global.addEventListener('resize', function () {
        syncViewBox();
        redraw();
      });
      global.addEventListener('scroll', function () {
        redraw();
      }, true);
    }
  }

  function syncViewBox() {
    if (!layer) return;
    var w = global.innerWidth || document.documentElement.clientWidth;
    var h = global.innerHeight || document.documentElement.clientHeight;
    layer.setAttribute('width', String(w));
    layer.setAttribute('height', String(h));
    layer.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
  }

  function animatePath(pathEl, durationMs, delayMs) {
    durationMs = durationMs == null ? 900 : durationMs;
    delayMs = delayMs || 0;
    try {
      var len = pathEl.getTotalLength();
      pathEl.style.strokeDasharray = String(len);
      pathEl.style.strokeDashoffset = String(len);
      pathEl.style.transition = 'none';
      pathEl.getBoundingClientRect();
      global.setTimeout(function () {
        pathEl.style.transition = 'stroke-dashoffset ' + durationMs + 'ms ease-out';
        pathEl.style.strokeDashoffset = '0';
      }, delayMs);
    } catch (e) {
      pathEl.style.strokeDasharray = 'none';
    }
  }

  function edgeToward(rFrom, rTo) {
    var cx1 = rFrom.left + rFrom.width / 2;
    var cy1 = rFrom.top + rFrom.height / 2;
    var cx2 = rTo.left + rTo.width / 2;
    var cy2 = rTo.top + rTo.height / 2;
    var dx = cx2 - cx1;
    var dy = cy2 - cy1;
    var len = Math.sqrt(dx * dx + dy * dy) || 1;
    var ux = dx / len;
    var uy = dy / len;
    var halfW = rFrom.width / 2;
    var halfH = rFrom.height / 2;
    var t = Infinity;
    if (Math.abs(ux) > 1e-6) t = Math.min(t, halfW / Math.abs(ux));
    if (Math.abs(uy) > 1e-6) t = Math.min(t, halfH / Math.abs(uy));
    if (!isFinite(t) || t <= 0) t = Math.min(halfW, halfH);
    return { x: cx1 + ux * t, y: cy1 + uy * t };
  }

  function edgeEntry(rTo, rFrom) {
    return edgeToward(rTo, rFrom);
  }

  function drawBetweenImpl(fromEl, toEl, opts) {
    ensureLayer(opts && opts.container ? opts.container : null);
    syncViewBox();
    opts = opts || {};
    var color = opts.color || '#c62828';
    var sw = opts.strokeWidth != null ? opts.strokeWidth : 2.5;
    var curved = opts.curved !== false;
    var durationMs = opts.durationMs;
    var delayMs = opts.delayMs || 0;
    var effectiveDurationMs = durationMs == null ? 900 : durationMs;

    var r1 = fromEl.getBoundingClientRect();
    var r2 = toEl.getBoundingClientRect();
    var p1 = edgeToward(r1, r2);
    var p2 = edgeEntry(r2, r1);

    var d;
    if (curved) {
      var mx = (p1.x + p2.x) / 2;
      var my = (p1.y + p2.y) / 2 - Math.min(80, Math.abs(p2.y - p1.y) * 0.35);
      d = 'M ' + p1.x + ' ' + p1.y + ' Q ' + mx + ' ' + my + ' ' + p2.x + ' ' + p2.y;
    } else {
      d = 'M ' + p1.x + ' ' + p1.y + ' L ' + p2.x + ' ' + p2.y;
    }

    var path = document.createElementNS(svgNS, 'path');
    path.setAttribute('d', d);
    path.setAttribute('fill', 'none');
    path.setAttribute('stroke', color);
    path.setAttribute('stroke-width', String(sw));
    path.setAttribute('stroke-linecap', 'round');
    path.style.color = color;
    // Включаем кончик стрелки только после завершения анимации линии.
    // Иначе marker-end рисуется сразу, хотя линия еще скрыта dashoffset.
    path.setAttribute('marker-end', 'none');
    if (opts.className) path.setAttribute('class', opts.className);
    mainGroup.appendChild(path);
    animatePath(path, durationMs, delayMs);
    global.setTimeout(function () {
      // Если path уже удалён — ничего не делаем.
      if (!path || !path.parentNode) return;
      path.setAttribute('marker-end', 'url(#demo-arrow-head)');
    }, delayMs + effectiveDurationMs + 20);
    return path;
  }

  function drawRayImpl(fromEl, opts) {
    ensureLayer(opts && opts.container ? opts.container : null);
    syncViewBox();
    opts = opts || {};
    var angleDeg = opts.angleDeg != null ? opts.angleDeg : 0;
    var len = opts.lengthPx != null ? opts.lengthPx : 120;
    var color = opts.color || '#1565C0';
    var sw = opts.strokeWidth != null ? opts.strokeWidth : 2.5;
    var delayMs = opts.delayMs || 0;
    var durationMs = opts.durationMs;
    var effectiveDurationMs = durationMs == null ? 900 : durationMs;

    var r = fromEl.getBoundingClientRect();
    var cx = r.left + r.width / 2;
    var cy = r.top + r.height / 2;
    var rad = (angleDeg * Math.PI) / 180;
    var x2 = cx + Math.cos(rad) * len;
    var y2 = cy + Math.sin(rad) * len;

    var path = document.createElementNS(svgNS, 'path');
    path.setAttribute('d', 'M ' + cx + ' ' + cy + ' L ' + x2 + ' ' + y2);
    path.setAttribute('fill', 'none');
    path.setAttribute('stroke', color);
    path.setAttribute('stroke-width', String(sw));
    path.style.color = color;
    // Аналогично drawBetweenImpl: кончик появится после завершения анимации.
    path.setAttribute('marker-end', 'none');
    mainGroup.appendChild(path);
    animatePath(path, durationMs, delayMs);
    global.setTimeout(function () {
      if (!path || !path.parentNode) return;
      path.setAttribute('marker-end', 'url(#demo-arrow-head)');
    }, delayMs + effectiveDurationMs + 20);
    return path;
  }

  function redraw() {
    var ops = lastOps.slice();
    clearKeepHistory(false);
    for (var i = 0; i < ops.length; i++) {
      var op = ops[i];
      if (op.type === 'between' && op.a && op.b && op.a.parentNode && op.b.parentNode) {
        drawBetweenImpl(op.a, op.b, op.opts);
      } else if (op.type === 'ray' && op.el && op.el.parentNode) {
        drawRayImpl(op.el, op.opts);
      }
    }
  }

  function clearKeepHistory(keep) {
    if (mainGroup) {
      while (mainGroup.firstChild) mainGroup.removeChild(mainGroup.firstChild);
    }
    if (!keep) lastOps = [];
  }

  var DemoArrows = {
    init: function (container) {
      if (container) preferredContainer = container;
      ensureLayer(container || preferredContainer);
      syncViewBox();
    },

    clear: function () {
      clearKeepHistory(false);
    },

    drawBetween: function (fromEl, toEl, opts) {
      if (!fromEl || !toEl) return null;
      var o = opts || {};
      lastOps.push({ type: 'between', a: fromEl, b: toEl, opts: o });
      return drawBetweenImpl(fromEl, toEl, o);
    },

    drawBetweenSelectors: function (fromSel, toSel, opts) {
      var a = document.querySelector(fromSel);
      var b = document.querySelector(toSel);
      if (!a || !b) return null;
      return DemoArrows.drawBetween(a, b, opts);
    },

    drawRay: function (fromEl, opts) {
      if (!fromEl) return null;
      var o = opts || {};
      lastOps.push({ type: 'ray', el: fromEl, opts: o });
      return drawRayImpl(fromEl, o);
    },

    redraw: function () {
      redraw();
    },

    removeLayer: function () {
      if (layer && layer.parentNode) layer.parentNode.removeChild(layer);
      layer = null;
      mainGroup = null;
      lastOps = [];
    }
  };

  global.DemoArrows = DemoArrows;
})(window);
