text: /* globals */
  import * as THREE from 'three';
  import { registerDragEvents } from './dragAndDrop.js';
  import { STLLoader } from 'three/examples/jsm/loaders/STLLoader.js';
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
  import { ColladaLoader } from 'three/examples/jsm/loaders/ColladaLoader.js';
  import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
  import URDFManipulator from '../../src/urdf-manipulator-element.js';
  
  customElements.define('urdf-viewer', URDFManipulator);
  
  // declare these globally for the sake of the example.
  // Hack to make the build work with webpack for now.
  // TODO: Remove this once modules or parcel is being used
  const viewer = document.querySelector('urdf-viewer');
  
  const limitsToggle = document.getElementById('ignore-joint-limits');
  const upSelect = document.getElementById('up-select');
  const sliderList = document.querySelector('#controls ul');
  const controlsel = document.getElementById('controls');
  const controlsToggle = document.getElementById('toggle-controls');
  const animToggle = document.getElementById('do-animate');
  const DEG2RAD = Math.PI / 180;
  const RAD2DEG = 1 / DEG2RAD;
  let sliders = {};
  
  // Global Functions
  const setColor = color => {
  
      document.body.style.backgroundColor = color;
      viewer.highlightColor = '#' + (new THREE.Color(0xffffff)).lerp(new THREE.Color(color), 0.35).getHexString();
  
  };
  
  // Events
  // toggle checkbox
  limitsToggle.addEventListener('click', () => {
      limitsToggle.classList.toggle('checked');
      viewer.ignoreLimits = limitsToggle.classList.contains('checked');
  });
  
  upSelect.addEventListener('change', () => viewer.up = upSelect.value);
  
  controlsToggle.addEventListener('click', () => controlsel.classList.toggle('hidden'));
  
  // watch for urdf changes
  viewer.addEventListener('urdf-change', () => {
  
      Object
          .values(sliders)
          .forEach(sl => sl.remove());
      sliders = {};
  
  });
  
  viewer.addEventListener('ignore-limits-change', () => {
  
      Object
          .values(sliders)
          .forEach(sl => sl.update());
  
  });
  
  viewer.addEventListener('angle-change', e => {
  
      if (sliders[e.detail]) sliders[e.detail].update();
  
  });
  
  viewer.addEventListener('joint-mouseover', e => {
  
      const j = document.querySelector(`li[joint-name="${ e.detail }"]`);
      if (j) j.setAttribute('robot-hovered', true);
  
  });
  
  viewer.addEventListener('joint-mouseout', e => {
  
      const j = document.querySelector(`li[joint-name="${ e.detail }"]`);
      if (j) j.removeAttribute('robot-hovered');
  
  });
  
  let originalNoAutoRecenter;
  viewer.addEventListener('manipulate-start', e => {
  
      const j = document.querySelector(`li[joint-name="${ e.detail }"]`);
      if (j) {
          j.scrollIntoView({ block: 'nearest' });
          window.scrollTo(0, 0);
      }
  
      originalNoAutoRecenter = viewer.noAutoRecenter;
      viewer.noAutoRecenter = true;
  
  });
  
  viewer.addEventListener('manipulate-end', e => {
  
      viewer.noAutoRecenter = originalNoAutoRecenter;
  
  });
  
  // create the sliders
  viewer.addEventListener('urdf-processed', () => {
  
      const r = viewer.robot;
      Object
          .keys(r.joints)
          .sort((a, b) => {
  
              const da = a.split(/[^\d]+/g).filter(v => !!v).pop();
              const db = b.split(/[^\d]+/g).filter(v => !!v).pop();
  
              if (da !== undefined && db !== undefined) {
                  const delta = parseFloat(da) - parseFloat(db);
                  if (delta !== 0) return delta;
              }
  
              if (a > b) return 1;
              if (b > a) return -1;
              return 0;
  
          })
          .map(key => r.joints[key])
          .forEach(joint => {
  
              const li = document.createElement('li');
              li.innerHTML =
              `
              <span title="${ joint.name }">${ joint.name }</span>
              <input type="range" value="0" step="0.0001"/>
              <input type="number" step="0.0001" />
              `;
              li.setAttribute('joint-type', joint.jointType);
              li.setAttribute('joint-name', joint.name);
  
              sliderList.appendChild(li);
  
              // update the joint display
              const slider = li.querySelector('input[type="range"]');
              const input = li.querySelector('input[type="number"]');
              li.update = () => {
                  let degVal = joint.angle;
  
                  if (joint.jointType === 'revolute' || joint.jointType === 'continuous') {
                      degVal *= RAD2DEG;
                  }
  
                  if (Math.abs(degVal) > 1) {
                      degVal = degVal.toFixed(1);
                  } else {
                      degVal = degVal.toPrecision(2);
                  }
  
                  input.value = parseFloat(degVal);
  
                  // directly input the value
                  slider.value = joint.angle;
  
                  if (viewer.ignoreLimits || joint.jointType === 'continuous') {
                      slider.min = -6.28;
                      slider.max = 6.28;
  
                      input.min = -6.28 * RAD2DEG;
                      input.max = 6.28 * RAD2DEG;
                  } else {
                      slider.min = joint.limit.lower;
                      slider.max = joint.limit.upper;
  
                      input.min = joint.limit.lower * RAD2DEG;
                      input.max = joint.limit.upper * RAD2DEG;
                  }
              };
  
              switch (joint.jointType) {
  
                  case 'continuous':
                  case 'prismatic':
                  case 'revolute':
                      break;
                  default:
                      li.update = () => {};
                      input.remove();
                      slider.remove();
  
              }
  
              slider.addEventListener('input', () => {
                  viewer.setJointValue(joint.name, slider.value);
                  li.update();
              });
  
              input.addEventListener('change', () => {
                  viewer.setJointValue(joint.name, input.value * DEG2RAD);
                  li.update();
              });
  
              li.update();
  
              sliders[joint.name] = li;
  
          });
  
  });
  
  document.addEventListener('WebComponentsReady', () => {
  
      viewer.loadMeshFunc = (path, manager, done) => {
  
          const ext = path.split(/\./g).pop().toLowerCase();
          switch (ext) {
  
              case 'gltf':
              case 'glb':
                  new GLTFLoader(manager).load(
                      path,
                      result => done(result.scene),
                      null,
                      err => done(null, err),
                  );
                  break;
              case 'obj':
                  new OBJLoader(manager).load(
                      path,
                      result => done(result),
                      null,
                      err => done(null, err),
                  );
                  break;
              case 'dae':
                  new ColladaLoader(manager).load(
                      path,
                      result => done(result.scene),
                      null,
                      err => done(null, err),
                  );
                  break;
              case 'stl':
                  new STLLoader(manager).load(
                      path,
                      result => {
                          const material = new THREE.MeshPhongMaterial();
                          const mesh = new THREE.Mesh(result, material);
                          done(mesh);
                      },
                      null,
                      err => done(null, err),
                  );
                  break;
  
          }
  
      };
  
      document.querySelector('li[urdf]').dispatchEvent(new Event('click'));
  
      if (/javascript\/example\/bundle/i.test(window.location)) {
          viewer.package = '../../../urdf';
      }
  
      registerDragEvents(viewer, () => {
          setColor('#263238');
          animToggle.classList.remove('checked');
      });
  
  });
  
  // init 2D UI and animation
  const updateAngles = () => {
  
      if (!viewer.setJointValue) return;
  
      // reset everything to 0 first
      const resetJointValues = viewer.angles;
      for (const name in resetJointValues) resetJointValues[name] = 0;
      viewer.setJointValues(resetJointValues);
  
      // animate the legs
      const time = Date.now() / 3e2;
      for (let i = 1; i <= 6; i++) {
  
          const offset = i * Math.PI / 3;
          const ratio = Math.max(0, Math.sin(time + offset));
  
          viewer.setJointValue(`HP${ i }`, THREE.MathUtils.lerp(30, 0, ratio) * DEG2RAD);
          viewer.setJointValue(`KP${ i }`, THREE.MathUtils.lerp(90, 150, ratio) * DEG2RAD);
          viewer.setJointValue(`AP${ i }`, THREE.MathUtils.lerp(-30, -60, ratio) * DEG2RAD);
  
          viewer.setJointValue(`TC${ i }A`, THREE.MathUtils.lerp(0, 0.065, ratio));
          viewer.setJointValue(`TC${ i }B`, THREE.MathUtils.lerp(0, 0.065, ratio));
  
          viewer.setJointValue(`W${ i }`, window.performance.now() * 0.001);
  
      }
  
  };
  
  const updateLoop = () => {
  
      if (animToggle.classList.contains('checked')) {
          updateAngles();
      }
  
      requestAnimationFrame(updateLoop);
  
  };
  
  document.querySelectorAll('#urdf-options li[urdf]').forEach(el => {
  
      el.addEventListener('click', e => {
  
          const urdf = e.target.getAttribute('urdf');
          const color = e.target.getAttribute('color');
  
          viewer.up = '-Z';
          document.getElementById('up-select').value = viewer.up;
          viewer.urdf = urdf;
          animToggle.classList.add('checked');
          setColor(color);
  
      });
  
  });
  
  document.addEventListener('WebComponentsReady', () => {
  
      animToggle.addEventListener('click', () => animToggle.classList.toggle('checked'));
  
      // stop the animation if user tried to manipulate the model
      viewer.addEventListener('manipulate-start', e => animToggle.classList.remove('checked'));
      viewer.addEventListener('urdf-processed', e => updateAngles());
      updateLoop();
      viewer.camera.position.set(-5.5, 3.5, 5.5);
  
  });
  