





(function () {
  'use strict';

  var DEG2RAD = Math.PI / 180;

  

  function Robot(desc, opts) {
    opts = opts || {};
    this.desc = desc;
    this.offsets = desc.joint_offsets_deg;
    this.root = new THREE.Group();
    this.root.name = 'robot:' + desc.name;
    this._joints = [];      
    this._linkGroups = {};
    this._meshesPending = 0;
    this._onReady = opts.onReady || null;

    this._build();
  }

  Robot.prototype._material = function (rgba) {
    return new THREE.MeshStandardMaterial({
      color: new THREE.Color(rgba[0], rgba[1], rgba[2]),
      roughness: 0.55, metalness: 0.35
    });
  };

  Robot.prototype._build = function () {
    var self = this;
    var desc = this.desc;

    desc.links.forEach(function (l) {
      var g = new THREE.Group();
      g.name = l.name;
      self._linkGroups[l.name] = g;
    });

    
    var children = {};
    desc.joints.forEach(function (j) { children[j.child] = true; });
    desc.links.forEach(function (l) {
      if (!children[l.name]) self.root.add(self._linkGroups[l.name]);
    });

    desc.joints.forEach(function (j) {
      
      var jointGroup = new THREE.Group();
      jointGroup.name = 'joint:' + j.name;
      jointGroup.position.set(j.origin.xyz[0], j.origin.xyz[1], j.origin.xyz[2]);
      
      jointGroup.quaternion.setFromEuler(
        new THREE.Euler(j.origin.rpy[0], j.origin.rpy[1], j.origin.rpy[2], 'ZYX'));
      self._linkGroups[j.parent].add(jointGroup);
      var child = self._linkGroups[j.child];
      jointGroup.add(child);
      self._joints.push({
        node: child,
        axis: new THREE.Vector3(j.axis[0], j.axis[1], j.axis[2]).normalize()
      });
    });

    
    var loader = new THREE.STLLoader();
    desc.links.forEach(function (l) {
      if (!l.mesh) return;
      self._meshesPending++;
      loader.load(l.mesh, function (geometry) {
        geometry.computeVertexNormals();
        var mesh = new THREE.Mesh(geometry, self._material(l.color));
        mesh.position.set(l.visual_origin.xyz[0], l.visual_origin.xyz[1], l.visual_origin.xyz[2]);
        mesh.quaternion.setFromEuler(new THREE.Euler(
          l.visual_origin.rpy[0], l.visual_origin.rpy[1], l.visual_origin.rpy[2], 'ZYX'));
        self._linkGroups[l.name].add(mesh);
        self._meshesPending--;
        if (self._meshesPending === 0 && self._onReady) self._onReady();
      }, undefined, function (err) {
        console.error('STL load failed: ' + l.mesh + ' ' + err);
        self._meshesPending--;
      });
    });

    
    
    
  };

  
  Robot.prototype._buildGripperPlaceholder = function () {
    var link6 = this._linkGroups['link6'];
    if (!link6) return;
    var mat = new THREE.MeshStandardMaterial({ color: 0x666c73, roughness: 0.5, metalness: 0.4 });

    this._sucker = new THREE.Mesh(new THREE.CylinderGeometry(0.008, 0.006, 0.03, 16), mat.clone());
    this._sucker.rotation.x = Math.PI / 2;   
    this._sucker.position.set(0, 0, 0.015);
    link6.add(this._sucker);

    this._fingers = [];
    for (var s = -1; s <= 1; s += 2) {
      var f = new THREE.Mesh(new THREE.BoxGeometry(0.006, 0.006, 0.028), mat.clone());
      f.position.set(0.014 * s, 0, 0.016);
      link6.add(f);
      this._fingers.push(f);
    }
  };

  
  Robot.prototype.setJointsDeg = function (degList) {
    for (var i = 0; i < this._joints.length && i < degList.length; i++) {
      var rad = (degList[i] - this.offsets[i]) * DEG2RAD;
      var j = this._joints[i];
      j.node.quaternion.setFromAxisAngle(j.axis, rad);
    }
  };

  Robot.prototype.setVacuum = function (on) {
    if (!this._sucker) return;
    this._sucker.material.color.setHex(on ? 0x2ecc71 : 0x666c73);
  };

  Robot.prototype.setServo = function (deg) {
    if (!this._fingers) return;
    
    var open = 0.020 - (deg / 110) * 0.014;
    this._fingers[0].position.x = -open;
    this._fingers[1].position.x = open;
  };

  

  Robot.prototype.setTint = function (color) {
    this.root.traverse(function (o) {
      if (!o.isMesh || !o.material || !o.material.color) return;
      if (!o.material.__origColor) o.material.__origColor = o.material.color.clone();
      if (color === null || color === undefined) o.material.color.copy(o.material.__origColor);
      else o.material.color.setHex(color);
    });
  };

  

  Robot.prototype.setLinkTint = function (names, color) {
    var want = {};
    (names || []).forEach(function (n) { want[n] = true; });
    var groups = this._linkGroups;
    Object.keys(groups).forEach(function (name) {
      groups[name].traverse(function (o) {
        if (!o.isMesh || !o.material || !o.material.color) return;
        if (!o.material.__origColor) o.material.__origColor = o.material.color.clone();
        if (want[name] && color != null) o.material.color.setHex(color);
        else o.material.color.copy(o.material.__origColor);
      });
    });
  };

  
  Robot.prototype.flangeGroup = function () {
    return this._linkGroups['link6'] || null;
  };

  
  Robot.prototype.flangeWorldPos = function () {
    this.root.updateMatrixWorld(true);
    var v = new THREE.Vector3();
    this._linkGroups['link6'].getWorldPosition(v);
    return [v.x, v.y, v.z];
  };

  window.Robot = Robot;
})();
