









(function () {
  'use strict';

  var DEG2RAD = Math.PI / 180;

  
  
  var SNAP_DEG = 90;

  var RADIUS = 0.055;          
  var BAND = 0.010;            
  var MINOR_STEP_DEG = 15;     
  var SETTLE_MS = 220;         
  

  var COL_RING = 0x8ab4f8;
  var COL_MINOR = 0x4a5560;
  var COL_MAJOR = 0x8ab4f8;
  var COL_ACTIVE = 0xffcc44;   
  var COL_REF = 0x6ed49a;      

  function MountDial(vizScene, opts) {
    opts = opts || {};
    this.scene = vizScene;
    
    
    this.onPreview = opts.onPreview || function () {};
    this.onChange = opts.onChange || function () {};
    this.yawDeg = 0;             
    this._visualYaw = 0;         
    this._anim = null;           
    this.enabled = false;        
    this.active = false;         
    this.dragging = false;
    this._opacity = 0;           
    this._visibleUntil = 0;      
    this._materials = [];

    this.root = new THREE.Group();
    this.root.name = 'mount-dial';
    this.root.visible = false;

    this._build();

    this._registerClaim();       
    this._wire();
  }

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

  MountDial.prototype._build = function () {
    var i, a;

    
    [RADIUS - BAND / 2, RADIUS + BAND / 2].forEach(function (r) {
      var pts = [];
      for (i = 0; i <= 96; i++) {
        a = (i / 96) * Math.PI * 2;
        pts.push(Math.cos(a) * r, Math.sin(a) * r, 0);
      }
      var geom = new THREE.BufferGeometry();
      geom.setAttribute('position', new THREE.Float32BufferAttribute(pts, 3));
      this.root.add(new THREE.Line(geom, this._mat(COL_RING, 0.75)));
    }, this);

    
    
    var minor = [];
    for (a = 0; a < 360; a += MINOR_STEP_DEG) {
      if (a % SNAP_DEG === 0) continue;             
      var r0 = RADIUS - BAND / 2, r1 = RADIUS - BAND / 2 + BAND * 0.45;
      var c = Math.cos(a * DEG2RAD), s = Math.sin(a * DEG2RAD);
      minor.push(c * r0, s * r0, 0, c * r1, s * r1, 0);
    }
    var mg = new THREE.BufferGeometry();
    mg.setAttribute('position', new THREE.Float32BufferAttribute(minor, 3));
    this.root.add(new THREE.LineSegments(mg, this._mat(COL_MINOR, 0.6)));

    
    var major = [];
    this._labels = [];
    for (a = 0; a < 360; a += SNAP_DEG) {
      var cc = Math.cos(a * DEG2RAD), ss = Math.sin(a * DEG2RAD);
      major.push(cc * (RADIUS - BAND), ss * (RADIUS - BAND), 0,
                 cc * (RADIUS + BAND), ss * (RADIUS + BAND), 0);
      var sprite = this._makeLabel(String(a));
      sprite.position.set(cc * (RADIUS + BAND * 2.6), ss * (RADIUS + BAND * 2.6), 0);
      sprite.userData.deg = a;
      this.root.add(sprite);
      this._labels.push(sprite);
    }
    var jg = new THREE.BufferGeometry();
    jg.setAttribute('position', new THREE.Float32BufferAttribute(major, 3));
    this.root.add(new THREE.LineSegments(jg, this._mat(COL_MAJOR, 0.9)));

    
    var ig = new THREE.BufferGeometry();
    ig.setAttribute('position', new THREE.Float32BufferAttribute(
      [0, 0, 0, RADIUS + BAND, 0, 0], 3));
    this._indicator = new THREE.Line(ig, this._mat(COL_ACTIVE, 1.0));
    this.root.add(this._indicator);

    
    var rg = new THREE.BufferGeometry();
    rg.setAttribute('position', new THREE.Float32BufferAttribute(
      [RADIUS + BAND * 1.2, 0, 0, RADIUS + BAND * 2.0, 0, 0], 3));
    this.root.add(new THREE.Line(rg, this._mat(COL_REF, 0.9)));
  };

  MountDial.prototype._makeLabel = function (text) {
    var sp = DialSupport.makeLabel(text, 0.022);
    this._materials.push(sp.material);
    return sp;
  };

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

  


  MountDial.prototype.setActive = function (on) {
    this.active = !!on;
    this._visibleUntil = this.active ? Infinity : 0;
    if (!this.active) this.dragging = false;
  };
  MountDial.prototype.isActive = function () { return !!this.active; };

  MountDial.prototype.setEnabled = function (on) {
    this.enabled = !!on;
    if (!this.enabled) {
      this.dragging = false;
      
      
      if (this._anim) {
        this._anim = null;
        this._applyVisual(this.yawDeg);
      }
      this._opacity = 0;
      this.root.visible = false;
    }
  };

  
  MountDial.prototype.getYawDeg = function () { return this.yawDeg; };
  MountDial.prototype.getVisualYawDeg = function () { return this._visualYaw; };

  
  MountDial.prototype.setYawDeg = function (deg) {
    this._anim = null;
    this.yawDeg = MountDial.snap(deg);
    this._applyVisual(this.yawDeg);
  };

  

  MountDial.prototype._applyVisual = function (deg) {
    this._visualYaw = deg;
    if (this._indicator) this._indicator.rotation.z = deg * DEG2RAD;
    var nearest = MountDial.snap(deg);      
    this._labels.forEach(function (sp) {
      var on = (sp.userData.deg === nearest);
      sp.material.__baseOpacity = on ? 1.0 : 0.55;
      sp.scale.set(on ? 0.028 : 0.022, on ? 0.028 : 0.022, 1);
    });
    this.onPreview(deg);
  };

  
  MountDial.shortestDelta = function (from, to) {
    var d = (to - from) % 360;
    if (d > 180) d -= 360;
    if (d < -180) d += 360;
    return d;
  };

  
  MountDial.snap = function (deg) {
    var v = Math.round(deg / SNAP_DEG) * SNAP_DEG;
    v = v % 360;
    if (v < 0) v += 360;
    return v;
  };

  

  MountDial.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.6) return null;
    return this._planeHitDistance(raycaster);
  };

  MountDial.prototype._wire = function () {
    var self = this;
    var dom = this.scene.renderer.domElement;
    var raycaster = new THREE.Raycaster();
    var startAz = 0, startYaw = 0;

    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;
      var az = self._azimuthAt(raycaster);
      if (!az) return;
      self.dragging = true;
      self._anim = null;                      
      startAz = az.deg;
      startYaw = self._visualYaw;             
      self.scene.controls.enabled = false;    
      e.stopPropagation();
    });

    dom.addEventListener('pointermove', function (e) {
      
      
      
      
      if (!self.dragging) return;
      raycaster.setFromCamera(ndcOf(e), self.scene.camera);
      var az = self._azimuthAt(raycaster);
      if (!az) return;
      
      self._applyVisual(startYaw + (az.deg - startAz));
      self._visibleUntil = Infinity;          
    });

    dom.addEventListener('pointerup', function () {
      if (!self.dragging) return;
      self.dragging = false;
      self.scene.controls.enabled = true;
      if (self.active) self._visibleUntil = Infinity;   
      
      
      var target = MountDial.snap(self._visualYaw);
      self.yawDeg = target;
      var delta = MountDial.shortestDelta(self._visualYaw, target);
      if (Math.abs(delta) < 0.01) {
        self._applyVisual(target);
      } else {
        self._anim = { from: self._visualYaw, delta: delta, start: performance.now() };
      }
      self.onChange(target);
    });
  };

  
  MountDial.prototype.update = function (nowMs) {
    if (!this.enabled) return;
    var t = (typeof nowMs === 'number') ? nowMs : performance.now();

    
    if (this._anim) {
      var k = (t - this._anim.start) / SETTLE_MS;
      if (k >= 1) {
        this._applyVisual(this.yawDeg);
        this._anim = null;
      } else {
        var eased = 1 - Math.pow(1 - Math.max(0, k), 3);
        this._applyVisual(this._anim.from + this._anim.delta * eased);
      }
    }

    this._stepFade(t);
  };

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

  DialSupport.install(MountDial.prototype);   
  MountDial.SNAP_DEG = SNAP_DEG;
  window.MountDial = MountDial;
})();
