import {
  NumericRangeInput,
  niceNumLabels,
  violinRenderer
} from "./chunk-DD4R5P6W.js";
import {
  convertUnits
} from "./chunk-JNITUVXP.js";
import {
  addBrushes,
  addNewBrush
} from "./chunk-ESEQBXTM.js";
import {
  select_default
} from "./chunk-I6Y4O3RR.js";

// filter/tvs.numeric.js
var handler = {
  type: "numeric",
  term_name_gen,
  get_pill_label,
  getSelectRemovePos,
  fillMenu,
  setTvsDefaults
};
function term_name_gen(d) {
  const name = d.term.name;
  return name.length < 26 ? name : '<label title="' + name + '">' + name.substring(0, 24) + "...</label>";
}
function get_pill_label(tvs) {
  if (tvs.ranges.length == 1) {
    const v = tvs.ranges[0];
    if ("value" in v) {
      if (v.label) return { txt: v.label };
      if (tvs.term.values && tvs.term.values[v.value] && tvs.term.values[v.value].label)
        return { txt: tvs.term.values[v.value].label };
      console.error(`key "${v.value}" not found in values{} of ${tvs.term.name}`);
      return { txt: v.value };
    }
    return { txt: format_val_text(v, tvs.term) };
  }
  return { txt: tvs.ranges.length + " intervals" };
}
function format_val_text(range, term) {
  const x = '<span style="font-family:Times;font-style:italic;font-size:1em; vertical-align:top">x</span>';
  if (range.startunbounded && range.stopunbounded) {
    const inf = (sign = "") => `<span style='vertical-align: middle; font-size:1.1em; line-height: 0.9em'>${sign}\u221E</span>`;
    const lt = `<span style='vertical-align: top; font-size: 0.9em'>&lt;</span>`;
    return `<span>${inf("\uFE63")} ${lt} ${x} ${lt} ${inf("\uFE62")}</span>`;
  }
  const vc = term.valueConversion;
  if (range.startunbounded)
    return `${x} ${range.stopinclusive ? "&le;" : "&lt;"} ${vc ? convertUnits(range.stop, vc.fromUnit, vc.toUnit, vc.scaleFactor) : range.stop}`;
  if (range.stopunbounded)
    return `${x} ${range.startinclusive ? "&ge;" : "&gt;"} ${vc ? convertUnits(range.start, vc.fromUnit, vc.toUnit, vc.scaleFactor) : range.start}`;
  let startName, stopName;
  if (vc) {
    startName = convertUnits(range.start, vc.fromUnit, vc.toUnit, vc.scaleFactor);
    stopName = convertUnits(range.stop, vc.fromUnit, vc.toUnit, vc.scaleFactor);
  } else {
    ;
    [startName, stopName] = niceNumLabels([range.start, range.stop]);
  }
  return `${startName} 
			${range.startinclusive ? "&le;" : "&lt;"}
			${x}
			${range.stopinclusive ? "&le;" : "&lt;"}
			${stopName}`;
}
function getSelectRemovePos(j, tvs) {
  return j - tvs.ranges.slice(0, j).filter((a) => a.start || a.stop).length;
}
async function fillMenu(self, div, tvs) {
  const num_parent_div = div.append("div");
  self.num_obj = {};
  self.num_obj.num_div = num_parent_div.append("div").attr("class", "num_div").style("padding", "5px").style("color", "#000");
  self.num_obj.plot_size = {
    width: 450,
    height: 100,
    xpad: 10,
    ypad: 20,
    radius: 8
  };
  if (typeof self.opts.vocabApi.getViolinBox == "function") {
    try {
      const data = await self.opts.vocabApi.getViolinBox(
        {
          plotType: "violin",
          tw: { term: tvs.term, q: { mode: "continuous" } },
          // must supply this filter and override state filter in function, so the density plot will not be limited to range defined by current numeric tvs but will show whole range
          filter: self.filter,
          svgw: self.num_obj.plot_size.width,
          radius: self.num_obj.plot_size.radius
        },
        self.opts.getCategoriesArguments
      );
      if (data.error) throw data.error;
      if (data.max === null && data.min === null) {
        throw `No data available under current filter conditions.`;
      }
      self.num_obj.density_data = data;
    } catch (err) {
      throw err;
    }
  } else {
    self.num_obj.density_data = {};
  }
  if (self.num_obj.density_data.error) throw self.num_obj.density_data.error;
  if (!self.num_obj.density_data.charts?.[""]?.plots?.[0]?.density?.bins) {
    addRangeTableNoDensity(self, tvs);
    return;
  }
  self.vr = new violinRenderer({
    holder: self.num_obj.num_div,
    rd: self.num_obj.density_data,
    width: self.num_obj.plot_size.width,
    radius: self.num_obj.plot_size.radius,
    height: self.num_obj.plot_size.height
  });
  self.vr.render();
  self.num_obj.svg = self.vr.svg;
  self.num_obj.range_table = self.num_obj.num_div.append("table").style("table-layout", "fixed").style("border-collapse", "collapse");
  const ranges = [];
  for (const [index, range] of tvs.ranges.entries()) {
    range.index = index;
    ranges.push(range);
  }
  self.num_obj.brush_g = self.vr.brushG.attr("class", "brush_g");
  self.num_obj.xscale = self.vr.axisScale;
  self.num_obj.ranges = ranges;
  self.num_obj.brushes = [];
  addBrushes(self);
  addRangeTable(self);
  if (!ranges.length) {
    const callback = () => addRangeTable(self);
    addNewBrush(self, "center", callback);
  }
  self.num_obj.brushes.forEach((brush) => brush.init());
  await showCheckList_numeric(self, tvs, div);
}
function setTvsDefaults(tvs) {
  if (!tvs.ranges) tvs.ranges = [];
}
function addRangeTableNoDensity(self, tvs) {
  const termrange = tvs.term.range || {};
  const range = tvs.ranges && tvs.ranges[0] ? tvs.ranges[0] : termrange;
  range.min = "min" in tvs.term ? tvs.term.min : null;
  range.max = "max" in tvs.term ? tvs.term.max : null;
  const num_div = self.num_obj.num_div;
  num_div.selectAll("*").remove();
  const brush = {};
  const table = num_div.append("table").style("padding-left", "5px");
  const rangeTr = table.append("tr");
  const rangeLabelTd = rangeTr.append("td");
  brush.equation_td = rangeTr.append("td");
  brush.rangeInput = new NumericRangeInput(brush.equation_td, range, () => {
  }, { width: "125px" });
  if (self.opts.isMafFilter) {
    rangeLabelTd.text("MAF Range (0-1)");
    const depthTr = table.append("tr");
    depthTr.append("td").text("Minimum Total Depth");
    brush.depthInput = depthTr.append("td").append("input").attr("type", "number").attr("min", 1).attr("step", 1).style("width", "125px").property("value", tvs.minAllelicDepth);
    const applyTr = table.append("tr");
    brush.apply_btn = applyTr.append("td").attr("class", "sja_filter_tag_btn sjpp_apply_btn").style("border-radius", "13px").style("margin-top", "10px").style("font-size", ".8em").style("text-transform", "uppercase").text("apply").on("click", clickApply);
  } else {
    rangeLabelTd.text("Range");
    brush.apply_btn = rangeTr.append("td").attr("class", "sja_filter_tag_btn sjpp_apply_btn").style("border-radius", "13px").style("margin", "5px").style("margin-left", "10px").style("text-align", "center").style("font-size", ".8em").style("text-transform", "uppercase").text("apply").on("click", clickApply);
  }
  function clickApply() {
    const r = brush.rangeInput.getRange();
    if (!Number.isFinite(r.start) && !Number.isFinite(r.stop)) {
      window.alert("Invalid range.");
      return;
    }
    const new_tvs = { term: tvs.term, ranges: [r] };
    if (brush.depthInput) {
      const minAllelicDepth = Number(brush.depthInput.property("value"));
      if (!Number.isFinite(minAllelicDepth)) {
        window.alert("Minimum allelic depth must be a numeric value.");
        return;
      }
      new_tvs.minAllelicDepth = Math.max(minAllelicDepth, 1);
    }
    self.dom.tip.hide();
    self.opts.callback(new_tvs);
  }
}
function addRangeTable(self) {
  const brushes = self.num_obj.brushes;
  const range_divs = self.num_obj.range_table.selectAll(".range_div").data(brushes);
  range_divs.exit().each(function() {
    select_default(this).style("opacity", 1).transition().duration(100).style("opacity", 0).remove();
  });
  range_divs.enter().append("tr").attr("class", "range_div").style("white-space", "nowrap").style("padding", "2px").transition().duration(200).each(function(brush, i) {
    enterRange(self, this, brush, i);
  });
}
function enterRange(self, tr, brush, i) {
  if (!brush.range_tr) brush.range_tr = select_default(tr);
  const range_tr = brush.range_tr;
  const xscale = self.num_obj.xscale;
  range_tr.append("td").append("td").style("display", "inline-block").style("margin-left", "10px").style("padding", "3px 10px").style("font-size", ".9em").text("Range " + (i + 1) + ": ");
  brush.equation_td = range_tr.append("td").style("width", "150px");
  brush.rangeInput = new NumericRangeInput(brush.equation_td, brush.range, apply);
  makeRangeButtons(self, brush);
  if (i == 0) {
    self.num_obj.range_table.append("tr").attr("class", "note_tr").append("td").attr("colspan", "3").append("div").style("font-size", ".8em").style("margin-left", "20px").style("font-style", "italic").style("color", "#888").html("Option 1: Drag the rectangle to select a range. Overlapping ranges will be merged.");
  }
  if (i == 0) {
    self.num_obj.range_table.append("tr").attr("class", "note_tr").append("td").attr("colspan", "3").append("div").style("font-size", ".8em").style("margin-left", "20px").style("font-style", "italic").style("color", "#888").html(
      `Option 2: Type in values to select a range.${self.tvs.term.valueConversion ? ` Values are in the unit of ${self.tvs.term.valueConversion.fromUnit}.` : ""}`
    );
  }
  async function apply(new_range) {
    try {
      brush.range = new_range;
      const minvalue = self.num_obj.density_data.min;
      const maxvalue = self.num_obj.density_data.max;
      const start = new_range.value != void 0 ? new_range.value : new_range.start != void 0 ? new_range.start : minvalue;
      const stop = new_range.value != void 0 ? new_range.value : new_range.stop != void 0 ? new_range.stop : maxvalue;
      brush.elem.call(brush.d3brush).call(brush.d3brush.move, [start, stop].map(xscale));
    } catch (e) {
      window.alert(e);
    }
  }
  function makeRangeButtons(self2, brush2) {
    const buttons_td = brush2.range_tr.append("td");
    const range = brush2.range;
    brush2.apply_btn = buttons_td.append("td").attr("class", "sja_filter_tag_btn sjpp_apply_btn").style("border-radius", "13px").style("margin", "5px").style("margin-left", "10px").style("text-align", "center").style("font-size", ".8em").style("text-transform", "uppercase").text("apply").on("click", async () => {
      self2.dom.tip.hide();
      const new_range = brush2.rangeInput.parseRange();
      const new_tvs = JSON.parse(JSON.stringify(self2.tvs));
      delete new_tvs.groupset_label;
      if (self2.num_obj.ranges.length > 1) new_tvs.ranges = mergeOverlapRanges(self2, new_range);
      else new_tvs.ranges[range.index] = new_range;
      try {
        validateNumericTvs(new_tvs);
        self2.opts.callback(new_tvs);
      } catch (ex) {
        alert(ex);
      }
    });
    buttons_td.append("td").attr("class", "sja_filter_tag_btn sjpp_delete_btn").style("border-radius", "13px").style("display", self2.num_obj.ranges.length > 1 ? "inline-block" : "none").style("margin", "5px").style("margin-left", "10px").style("text-align", "center").style("font-size", ".8em").style("text-transform", "uppercase").text("Delete").on("click", async () => {
      const new_tvs = JSON.parse(JSON.stringify(self2.tvs));
      new_tvs.ranges.splice(range.index, 1);
      self2.num_obj.ranges.pop();
      self2.num_obj.brushes.pop();
      self2.num_obj.num_div.select(".note_tr").remove();
      addBrushes(self2);
      addRangeTable(self2);
      if (new_tvs.ranges.length) self2.opts.callback(new_tvs);
    });
  }
}
function mergeOverlapRanges(self, new_range) {
  let ranges = JSON.parse(JSON.stringify(self.tvs.ranges));
  let merged_flag = false;
  for (const [i, range] of ranges.entries()) {
    if (!range.value && new_range.index != i) {
      if (new_range.start <= range.start && new_range.stop >= range.stop) {
        range.start = new_range.start;
        range.stop = new_range.stop;
        merged_flag = true;
      } else if (new_range.start <= range.stop && new_range.stop >= range.stop) {
        range.stop = new_range.stop;
        merged_flag = true;
      } else if (new_range.stop >= range.start && new_range.start <= range.start) {
        range.start = new_range.start;
        merged_flag = true;
      } else if (new_range.start >= range.start && new_range.stop <= range.stop) {
        merged_flag = true;
      } else if (new_range.startunbounded) {
        if (new_range.stop > range.stop) {
          range.stop = new_range.stop;
        }
        delete range.start;
        range.startunbounded = true;
        merged_flag = true;
      } else if (new_range.stopunbounded) {
        if (new_range.start < range.start) {
          range.start = new_range.start;
        }
        delete range.stop;
        range.stopunbounded = true;
        merged_flag = true;
      }
    }
  }
  if (merged_flag) {
    if (new_range.index <= ranges.length - 1) ranges.splice(new_range.index, 1);
  } else {
    ranges = JSON.parse(JSON.stringify(self.tvs.ranges));
    if (new_range.index) ranges[new_range.index] = new_range;
    else ranges.push(new_range);
  }
  return ranges;
}
async function showCheckList_numeric(self, tvs, div) {
  if (!tvs.term.values) {
    return;
  }
  const values = await self.opts.vocabApi.getNumericUncomputableCategories(tvs.term, self.filter);
  const unannotated_cats = [];
  const lst = values?.lst || values;
  for (const cat of lst) {
    const key = "key" in cat ? cat.key : cat.value;
    if (!("key" in cat)) cat.key = key;
    if (!("value" in cat)) cat.value = key;
    if (key in tvs.term.values) {
      cat.label = tvs.term.values[key].label;
      unannotated_cats.push(cat);
    }
  }
  const sortedVals = unannotated_cats.sort((a, b) => {
    return b.samplecount - a.samplecount;
  });
  const unanno_div = div.append("div").style("padding", "5px").style("color", "#000");
  const callback = (indexes) => {
    try {
      const new_tvs = JSON.parse(JSON.stringify(tvs));
      delete new_tvs.groupset_label;
      new_tvs.ranges = [
        ...new_tvs.ranges.filter((r) => !("value" in r)),
        ...indexes.map((i) => ({ value: sortedVals[i].value, label: sortedVals[i].label }))
      ];
      self.dom.tip.hide();
      if (new_tvs.ranges.length == 0) throw "select at least one range or category";
      if (JSON.parse(JSON.stringify(tvs) != new_tvs)) {
        try {
          validateNumericTvs(new_tvs);
        } catch (e) {
          window.alert(e);
          return;
        }
        self.opts.callback(new_tvs);
      }
    } catch (e) {
      window.alert(e);
    }
  };
  const values_table = self.makeValueTable(unanno_div, tvs, sortedVals, callback).node();
}
function validateNumericTvs(tvs) {
  if (!tvs.term) throw "tvs.term is not defined";
  if (!tvs.ranges) throw `.values[] missing for a term ${tvs.term.name}`;
  if (!Array.isArray(tvs.ranges)) throw `.values[] is not an array for a term ${tvs.term.name}`;
  if (!tvs.ranges.length) throw `no categories selected for ${tvs.term.name}`;
  for (const range of tvs.ranges) {
    if (range.value != void 0) {
      if (!range.label) throw `.label missing for special category for a term ${tvs.term.name}`;
    } else {
      if (range.startunbounded) {
        if (range.stopunbounded) throw `both start & stop are unbounded for a term ${tvs.term.name}`;
        if (!Number.isFinite(range.stop)) throw `.stop undefined when start is unbounded for a term ${tvs.term.name}`;
      } else if (range.stopunbounded) {
        if (!Number.isFinite(range.start)) throw `.start undefined when stop is unbounded for a term ${tvs.term.name}`;
      } else {
        if (!Number.isFinite(range.start))
          throw `.start undefined when start is not unbounded for a term ${tvs.term.name}`;
        if (!Number.isFinite(range.stop)) throw `.stop undefined when stop is not unbounded for a term ${tvs.term.name}`;
        if (range.start >= range.stop) throw `.start is not lower than stop for a term ${tvs.term.name}`;
      }
    }
  }
}

export {
  handler,
  format_val_text
};
//# sourceMappingURL=chunk-7XFGYYRV.js.map
