



(function () {
  'use strict';

  var NPHI = 48;           
  var SEG_DEG = 7.5;       
  var SMOOTH_PASSES = 2;   
  var NR = 5;              

  var DEG2RAD = Math.PI / 180;

  function Workspace(vizScene) {
    this.scene = vizScene;
    this.group = null;
    this.loaded = false;
  }

  


  Workspace.prototype.clear = function () {
    if (!this.group) { this.loaded = false; return; }
    this.scene.scene.remove(this.group);
    this.group.traverse(function (o) {
      if (o.geometry) o.geometry.dispose();
      if (o.material) o.material.dispose();
    });
    this.group = null;
    this.loaded = false;
  };

  
  Workspace.prototype.setData = function (data) {
    this.clear();                  
    var prof = data.profile_mm;
    var i, n = prof.length, sz = 0;
    var pts = new Array(n);
    for (i = 0; i < n; i++) {
      pts[i] = [prof[i][0] / 1000, prof[i][1] / 1000];
      sz += pts[i][1];
    }
    var oz = sz / n;   

    
    var maxD = new Float32Array(NPHI);
    var minD = new Float32Array(NPHI);
    var valid = new Uint8Array(NPHI);
    for (i = 0; i < NPHI; i++) minD[i] = Infinity;
    for (i = 0; i < n; i++) {
      var dr = pts[i][0], dz = pts[i][1] - oz;
      var dist = Math.sqrt(dr * dr + dz * dz);
      if (dist < 1e-6) continue;
      var phi = Math.atan2(dz, dr);                  
      var pi_ = Math.min(NPHI - 1, Math.floor((phi + Math.PI) / (2 * Math.PI) * NPHI));
      valid[pi_] = 1;
      if (dist > maxD[pi_]) maxD[pi_] = dist;
      if (dist < minD[pi_]) minD[pi_] = dist;
    }
    this._fillHoles1D(maxD, minD, valid);
    for (i = 0; i < SMOOTH_PASSES; i++) {
      this._smooth1D(maxD, valid);
      this._smooth1D(minD, valid);
    }

    
    var azStart = data.az_start_deg * DEG2RAD;
    var azStep = data.az_dir * SEG_DEG * DEG2RAD;
    var nseg = Math.max(2, Math.round(data.az_span_deg / SEG_DEG));

    
    function node(pi_, D, az) {
      var phi = (pi_ + 0.5) / NPHI * 2 * Math.PI - Math.PI;
      var rho = D * Math.cos(phi);
      var z = oz + D * Math.sin(phi);
      if (rho < 0) rho = 0;                          
      return [rho * Math.cos(az), rho * Math.sin(az), z];
    }

    var group = new THREE.Group();
    group.add(this._revolve(maxD, valid, node, azStart, azStep, nseg, 0x5b9fd6, 0.45));
    group.add(this._revolve(minD, valid, node, azStart, azStep, nseg, 0xb07fd0, 0.5));
    
    group.add(this._cutFace(maxD, minD, valid, node, azStart, 0x5b9fd6, 0.45));
    group.add(this._cutFace(maxD, minD, valid, node, azStart + azStep * nseg, 0x5b9fd6, 0.45));
    group.visible = false;
    this.group = group;
    this.scene.scene.add(group);
    this.loaded = true;
  };

  
  Workspace.prototype.setPoints = Workspace.prototype.setData;

  Workspace.prototype._fillHoles1D = function (maxD, minD, valid) {
    for (var i = 0; i < NPHI; i++) {
      if (valid[i]) continue;
      var l = (i - 1 + NPHI) % NPHI, r = (i + 1) % NPHI;
      if (valid[l] && valid[r]) {
        maxD[i] = (maxD[l] + maxD[r]) / 2;
        minD[i] = (minD[l] + minD[r]) / 2;
        valid[i] = 1;
      }
    }
  };

  Workspace.prototype._smooth1D = function (D, valid) {
    var out = new Float32Array(NPHI);
    for (var i = 0; i < NPHI; i++) {
      if (!valid[i]) { out[i] = D[i]; continue; }
      var s = D[i], k = 1;
      var l = (i - 1 + NPHI) % NPHI, r = (i + 1) % NPHI;
      if (valid[l]) { s += D[l]; k++; }
      if (valid[r]) { s += D[r]; k++; }
      out[i] = s / k;
    }
    D.set(out);
  };

  
  Workspace.prototype._revolve = function (D, valid, node, azStart, azStep, nseg, color, opacity) {
    var verts = [];
    function seg(a, b) { if (a && b) verts.push(a[0], a[1], a[2], b[0], b[1], b[2]); }
    function nd(pi_, k) { return valid[pi_] ? node(pi_, D[pi_], azStart + azStep * k) : null; }
    for (var k = 0; k <= nseg; k++) {
      for (var pi_ = 0; pi_ < NPHI; pi_++) {
        var v = nd(pi_, k);
        if (pi_ < NPHI - 1) seg(v, nd(pi_ + 1, k));          
        if (k < nseg) seg(v, nd(pi_, k + 1));                
      }
    }
    var geom = new THREE.BufferGeometry();
    geom.setAttribute('position', new THREE.BufferAttribute(new Float32Array(verts), 3));
    return new THREE.LineSegments(geom,
      new THREE.LineBasicMaterial({ color: color, transparent: true, opacity: opacity }));
  };

  
  Workspace.prototype._cutFace = function (maxD, minD, valid, node, az, color, opacity) {
    var verts = [];
    function seg(a, b) { if (a && b) verts.push(a[0], a[1], a[2], b[0], b[1], b[2]); }
    function dAt(pi_, t) { return minD[pi_] + (maxD[pi_] - minD[pi_]) * t / NR; }
    for (var pi_ = 0; pi_ < NPHI; pi_++) {
      if (!valid[pi_]) continue;
      var nextValid = pi_ < NPHI - 1 && valid[pi_ + 1];
      for (var t = 0; t <= NR; t++) {
        if (t < NR) seg(node(pi_, dAt(pi_, t), az), node(pi_, dAt(pi_, t + 1), az));      
        if (nextValid) seg(node(pi_, dAt(pi_, t), az), node(pi_ + 1, dAt(pi_ + 1, t), az)); 
      }
    }
    var geom = new THREE.BufferGeometry();
    geom.setAttribute('position', new THREE.BufferAttribute(new Float32Array(verts), 3));
    return new THREE.LineSegments(geom,
      new THREE.LineBasicMaterial({ color: color, transparent: true, opacity: opacity }));
  };

  Workspace.prototype.setVisible = function (on) {
    if (this.group) this.group.visible = !!on;
  };

  window.Workspace = Workspace;
})();
