










(function () {
  'use strict';

  var DEG2RAD = Math.PI / 180;

  var RADIUS = 0.050;          
  var BAND = 0.009;            
  
  
  
  var ARC_Z = 0.115;
  
  
  var START_DEG = 0;
  var SWEEP_SIGN = -1;         

  var COL_ARC = 0x8ab4f8;
  var COL_TRACK = 0x4a5560;
  var COL_HANDLE = 0xffcc44;

  function OpenArc(vizScene, opts) {
    opts = opts || {};
    this.scene = vizScene;
    this.minDeg = opts.minDeg != null ? opts.minDeg : 0;
    this.maxDeg = opts.maxDeg != null ? opts.maxDeg : 110;
    this.onPreview = opts.onPreview || function () {};   
    this.onChange = opts.onChange || function () {};     
    this.angleDeg = this.minDeg;
    this.enabled = false;
    this.dragging = false;
    this._opacity = 0;
    this._visibleUntil = 0;
    this._materials = [];

    this.root = new THREE.Group();
    this.root.name = 'open-arc';
    this.root.visible = false;
    
    this.root.position.set(0, 0, ARC_Z);
    this.root.quaternion.setFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0, 'ZYX'));

    this._build();
    this._registerClaim();
    this._wire();
    this.setAngleDeg(this.angleDeg);
  }

  OpenArc.prototype._mat = function (color, opacity) {
    var m = DialSupport.lineMaterial(color, opacity);
    this._materials.push(m);
    return m;
  };

  
  OpenArc.prototype._azOf = function (deg) {
    return START_DEG + SWEEP_SIGN * (deg - this.minDeg);
  };

  
  OpenArc.prototype._degOf = function (az) {
    var d = this.minDeg + SWEEP_SIGN * (az - START_DEG);
    
    var span = this.maxDeg - this.minDeg;
    while (d < this.minDeg - 180) d += 360;
    while (d >= this.minDeg + 180) d -= 360;
    return Math.max(this.minDeg, Math.min(this.minDeg + span, d));
  };

  OpenArc.prototype._build = function () {
    var i, az;
    var steps = 48;

    
    var pts = [];
    for (i = 0; i <= steps; i++) {
      az = this._azOf(this.minDeg + (this.maxDeg - this.minDeg) * (i / steps)) * DEG2RAD;
      pts.push(Math.cos(az) * RADIUS, Math.sin(az) * RADIUS, 0);
    }
    var g = new THREE.BufferGeometry();
    g.setAttribute('position', new THREE.Float32BufferAttribute(pts, 3));
    this.root.add(new THREE.Line(g, this._mat(COL_TRACK, 0.7)));

    
    this._fillGeom = new THREE.BufferGeometry();
    this._fillGeom.setAttribute('position',
      new THREE.Float32BufferAttribute(new Float32Array((steps + 1) * 3), 3));
    this._fillSteps = steps;
    this.root.add(new THREE.Line(this._fillGeom, this._mat(COL_ARC, 0.95)));

    
    var ends = [this.minDeg, this.maxDeg];
    this._labels = [];
    var ticks = [];
    for (i = 0; i < ends.length; i++) {
      az = this._azOf(ends[i]) * DEG2RAD;
      var c = Math.cos(az), s = Math.sin(az);
      ticks.push(c * (RADIUS - BAND), s * (RADIUS - BAND), 0,
                 c * (RADIUS + BAND), s * (RADIUS + BAND), 0);
      var sp = DialSupport.makeLabel(String(Math.round(ends[i])), 0.020);
      sp.position.set(c * (RADIUS + BAND * 2.8), s * (RADIUS + BAND * 2.8), 0);
      this._materials.push(sp.material);
      this.root.add(sp);
      this._labels.push(sp);
    }
    var tg = new THREE.BufferGeometry();
    tg.setAttribute('position', new THREE.Float32BufferAttribute(ticks, 3));
    this.root.add(new THREE.LineSegments(tg, this._mat(COL_ARC, 0.9)));

    
    var hg = new THREE.BufferGeometry();
    hg.setAttribute('position', new THREE.Float32BufferAttribute(
      [RADIUS - BAND * 1.5, 0, 0, RADIUS + BAND * 1.5, 0, 0], 3));
    this._handle = new THREE.Line(hg, this._mat(COL_HANDLE, 1.0));
    this.root.add(this._handle);
  };

  OpenArc.prototype.getAngleDeg = function () { return this.angleDeg; };

  OpenArc.prototype.setAngleDeg = function (deg) {
    deg = Math.max(this.minDeg, Math.min(this.maxDeg, deg));
    this.angleDeg = deg;
    if (this._handle) this._handle.rotation.z = this._azOf(deg) * DEG2RAD;
    
    if (this._fillGeom) {
      var n = this._fillSteps;
      var arr = this._fillGeom.attributes.position.array;
      for (var i = 0; i <= n; i++) {
        var a = this._azOf(this.minDeg + (deg - this.minDeg) * (i / n)) * DEG2RAD;
        arr[i * 3] = Math.cos(a) * RADIUS;
        arr[i * 3 + 1] = Math.sin(a) * RADIUS;
        arr[i * 3 + 2] = 0;
      }
      this._fillGeom.attributes.position.needsUpdate = true;
    }
    var self = this;
    this._labels.forEach(function (sp, i) {
      var end = (i === 0) ? self.minDeg : self.maxDeg;
      var on = Math.abs(deg - end) < 0.5;      
      sp.material.__baseOpacity = on ? 1.0 : 0.6;
    });
  };

  OpenArc.prototype.attachTo = function (parentGroup) {
    if (parentGroup) parentGroup.add(this.root);
  };

  OpenArc.prototype.setEnabled = function (on) {
    this.enabled = !!on;
    if (!this.enabled) {
      this.dragging = false;
      this._opacity = 0;
      this.root.visible = false;
    }
  };

  
  OpenArc.prototype._hitDistance = function (raycaster) {
    if (!this.enabled || this._opacity < 0.15) return null;
    var az = this._azimuthAt(raycaster);
    if (!az) return null;
    if (Math.abs(az.radius - RADIUS) > BAND * 1.8) return null;
    
    var deg = this._degOf(az.deg);
    var back = this._azOf(deg);
    var diff = Math.abs(((back - az.deg + 180) % 360) - 180);
    if (diff > 8) return null;
    return this._planeHitDistance(raycaster);
  };

  OpenArc.prototype._wire = function () {
    var self = this;
    var dom = this.scene.renderer.domElement;
    var raycaster = new THREE.Raycaster();

    function ndcOf(e) {
      var rect = dom.getBoundingClientRect();
      return new THREE.Vector2(
        ((e.clientX - rect.left) / rect.width) * 2 - 1,
        -((e.clientY - rect.top) / rect.height) * 2 + 1);
    }

    dom.addEventListener('pointerdown', function (e) {
      if (!self.enabled) return;
      
      
      if (!self._claimOwned()) return;
      raycaster.setFromCamera(ndcOf(e), self.scene.camera);
      if (self._hitDistance(raycaster) == null) return;
      self.dragging = true;
      self.scene.controls.enabled = false;
      e.stopPropagation();
    });

    dom.addEventListener('pointermove', function (e) {
      
      
      var gizmoBusy = window.Gizmo && Gizmo.Arbiter && Gizmo.Arbiter.anyDragging();
      if (self.enabled && !self.dragging && !gizmoBusy) {
        var rect = dom.getBoundingClientRect();
        self._updateProximity(e.clientX - rect.left, e.clientY - rect.top,
                              rect.width, rect.height);
      }
      if (!self.dragging) return;
      raycaster.setFromCamera(ndcOf(e), self.scene.camera);
      var az = self._azimuthAt(raycaster);
      if (!az) return;
      
      self.setAngleDeg(self._degOf(az.deg));
      self._visibleUntil = Infinity;
      self.onPreview(self.angleDeg);
    });

    dom.addEventListener('pointerup', function () {
      if (!self.dragging) return;
      self.dragging = false;
      self.scene.controls.enabled = true;
      self.reveal();
      self.onChange(self.angleDeg);
    });
  };

  OpenArc.prototype.update = function (nowMs) {
    if (!this.enabled) return;
    this._stepFade((typeof nowMs === 'number') ? nowMs : performance.now());
  };

  OpenArc.prototype.dispose = function () { this._disposeRoot(); };

  DialSupport.install(OpenArc.prototype);
  window.OpenArc = OpenArc;
})();
