import {
  hierCluster_renderers_exports
} from "./chunk-SHWLROJG.js";
import {
  hierCluster_interactivity_exports
} from "./chunk-NSXZPWRP.js";
import {
  Matrix
} from "./chunk-6A3IPDE2.js";
import {
  filterJoin,
  getNormalRoot
} from "./chunk-SA7APTJR.js";
import {
  clusterMethodLst,
  distanceMethodLst,
  dofetch3
} from "./chunk-7EMWHCVW.js";
import {
  deepEqual,
  getCompInit
} from "./chunk-M3J4MINX.js";
import {
  NUMERIC_DICTIONARY_TERM,
  TermTypes2Dt
} from "./chunk-NOEAT6CX.js";
import {
  colorScaleMap
} from "./chunk-4UWS5Y3N.js";
import {
  extent,
  linear
} from "./chunk-OAWQ6LOO.js";

// plots/matrix/hierCluster.js
var HierCluster = class _HierCluster extends Matrix {
  static type = "hierCluster";
  constructor(opts) {
    super(opts);
    this.type = _HierCluster.type;
    this.chartType = _HierCluster.type;
  }
  async init(appState) {
    await super.init(appState);
    this.maySetSandboxHeader(appState);
    this.hcClipId = this.seriesClipId + "-hc";
    this.dom.hcClipRect = this.dom.svg.select("defs").append("clipPath").attr("id", this.hcClipId).attr("clipPathUnits", "userSpaceOnUse").append("rect").attr("display", "block");
    this.dom.topDendrogram = this.dom.svg.insert("g", "g").attr("clip-path", `url(#${this.hcClipId})`).append("g").attr("class", "sjpp-matrix-dendrogram").attr("data-testid", "hierCluster_top_dendrogram").on("click", (event) => {
      const clickedClusterId = this.getClusterFromTopDendrogram(event);
      if (clickedClusterId) {
        this.clickedClusterIds = this.getAllChildrenClusterIds(clickedClusterId);
        this.clickedClusterIds.push(clickedClusterId);
        const clickedCluster = this.hierClusterData.clustering.col.mergedClusters.get(clickedClusterId);
        const clickedClusterSampleNames = clickedCluster.children.map((c) => c.name);
        this.addSelectedSamplesOptions(clickedClusterSampleNames, event);
      } else {
        delete this.clickedClusterIds;
      }
      if (this.clickedLeftClusterIds) {
        delete this.clickedLeftClusterIds;
        this.plotDendrogramHclust();
      } else this.plotDendrogramHclust("top");
    });
    this.dom.leftDendrogram = this.dom.svg.insert("g", "g").attr("class", "sjpp-matrix-dendrogram").attr("data-testid", "hierCluster_left_dendrogram").on("click", (event) => {
      const clickedLeftClusterId = this.getClusterFromLeftDendrogram(event);
      if (clickedLeftClusterId) {
        this.clickedLeftClusterIds = this.getAllChildrenClusterIds(clickedLeftClusterId, true);
        this.clickedLeftClusterIds.push(clickedLeftClusterId);
        const clickedLeftCluster = this.hierClusterData.clustering.row.mergedClusters.get(clickedLeftClusterId);
        const clickedLeftClusterRowsNames = clickedLeftCluster.children.map((c) => c.name);
        this.addSelectedRowsOptions(clickedLeftClusterRowsNames, event);
      } else {
        delete this.clickedLeftClusterIds;
      }
      if (this.clickedClusterIds) {
        delete this.clickedClusterIds;
        this.plotDendrogramHclust();
      } else this.plotDendrogramHclust("left");
    });
  }
  async setHierClusterData(_data = {}) {
    this.prevServerData = this.currServerData;
    const [d, twlst] = await this.requestData({});
    if (d.error) throw d.error;
    this.currServerData = structuredClone(d);
    if (!deepEqual(this.prevServerData, this.currServerData)) {
      delete this.clickedClusterIds;
      delete this.clickedLeftClusterIds;
    }
    const s = this.settings.hierCluster;
    if (!d.clustering) {
      if (d.gene) {
        throw `Cannot do clustering: data is only available for 1 gene (${d.gene}). Try again by adding more genes.`;
      }
    }
    this.hierClusterData = d;
    const c = this.hierClusterData.clustering;
    this.setHierColorScale(c);
    const samples = {};
    for (const [i, column] of c.col.order.entries()) {
      samples[column.name] = { sample: column.name };
      for (const [j, row] of c.row.order.entries()) {
        const tw = twlst.find((tw2) => tw2.$id === row.name || tw2.id === row.name);
        const value = c.matrix[j][i];
        samples[column.name][tw.$id] = {
          key: tw.term.name,
          values: [
            {
              sample: column.name,
              dt: TermTypes2Dt[this.state.config.dataType],
              label: s.termGroupName,
              // gene: tw.term.name,
              // chr: tw.term.chr,
              // pos: `${tw.term.start}-${tw.term.stop}`,
              value
              // the color will be computed in matrix.cells, so that
              // it can get updated even when there are no nonsetting state diff
            }
          ]
        };
      }
    }
    this.hcTermNameOrder = this.settings.hierCluster.sortClusterRows == "asListed" ? twlst.map((t) => t.term.name) : this.settings.hierCluster.sortClusterRows == "byName" ? twlst.map((t) => t.term.name).sort() : this.config.dataType === NUMERIC_DICTIONARY_TERM ? c.row.order.map((row) => twlst.find((t) => t.$id == row.name || t.term.id == row.name)?.term.name) : c.row.order.map((row) => twlst.find((t) => t.$id == row.name)?.term.name);
    if (this.hcTermNameOrder.includes(void 0)) throw `unable to map row.name to term.name`;
    this.hcTermSorter = (a, b) => {
      const i = this.hcTermNameOrder.indexOf(a.tw.term.name);
      const j = this.hcTermNameOrder.indexOf(b.tw.term.name);
      if (i == -1 && j == -1) return 0;
      if (i == -1) return 1;
      if (j == -1) return -1;
      return i - j;
    };
    this.hcSampleNameOrder = c.col.order.map((col) => col.name);
    this.hcSampleSorter = (a, b) => {
      const i = this.hcSampleNameOrder.indexOf(a.sample);
      const j = this.hcSampleNameOrder.indexOf(b.sample);
      if (i == -1 && j == -1) return 0;
      if (i == -1) return 1;
      if (j == -1) return -1;
      return i - j;
    };
    const byTermId = {};
    for (const tw of twlst) {
      if (d.byTermId?.[tw.term.name]) byTermId[tw.$id] = d.byTermId[tw.term.name];
    }
    this.hierClusterSamples = {
      refs: { byTermId, bySampleId: d.bySampleId },
      lst: c.col.order.map((c2) => samples[c2.name]),
      samples,
      removedHierClusterTerms: d.removedHierClusterTerms
    };
  }
  async requestData() {
    const body = this.currRequestOpts?.hierCluster || this.getHCRequestBody(this.state);
    const twlst = this.hcTermGroup.lst;
    const data = await dofetch3("termdb/cluster", { body, signal: this.api.getAbortSignal?.() });
    return [data, twlst];
  }
  getHCRequestBody(state) {
    this.hcTermGroup = this.config.termgroups.find((grp) => grp.type == "hierCluster") || this.termOrder?.find((t) => t.grp.type == "hierCluster")?.grp;
    const s = state.config.settings.hierCluster;
    const dictionaryLegendFilter = {
      type: "tvslst",
      in: true,
      join: "and",
      lst: state.config.legendValueFilter.lst.filter((f) => !f.tvs.legendFilterType)
    };
    const terms = this.getClusterRowTermsAsParameter();
    if (!terms.length) throw "no data";
    if (!clusterMethodLst.find((i) => i.value == s.clusterMethod)) throw "Invalid cluster method";
    if (!distanceMethodLst.find((i) => i.value == s.distanceMethod)) throw "Invalid distance method";
    const body = {
      genome: state.vocab.genome,
      dslabel: state.vocab.dslabel,
      dataType: state.config.dataType,
      clusterMethod: s.clusterMethod,
      distanceMethod: s.distanceMethod,
      zScoreTransformation: s.zScoreTransformation,
      terms,
      filter: getNormalRoot(filterJoin([state.filter, dictionaryLegendFilter])),
      filter0: state.filter0
    };
    if (state.config.dataType == "proteomeAbundance") {
      body.proteomeDetails = {
        organism: state.config.proteomeDetails?.organism,
        assay: state.config.proteomeDetails?.assay,
        cohort: state.config.proteomeDetails?.cohort
      };
    }
    return body;
  }
  combineData() {
    if (!this.hierClusterSamples) return;
    const d = this.data;
    const removedHierClusterTerms = this.hierClusterSamples.removedHierClusterTerms;
    const samples = {};
    const lst = [];
    for (const sampleId in this.hierClusterSamples.samples) {
      const s = this.hierClusterSamples.samples[sampleId];
      samples[sampleId] = s;
      lst.push(s);
      if (sampleId in d.samples) Object.assign(s, d.samples[sampleId]);
      const _ref_ = this.hierClusterSamples.refs.bySampleId[sampleId] || {};
      if (!s._ref_) s._ref_ = _ref_;
      else Object.assign(s._ref_, _ref_);
    }
    const t = this.hierClusterSamples.refs.byTermId;
    for (const $id of Object.keys(t)) {
      d.refs.byTermId[$id] = Object.assign({}, d.refs.byTermId[$id] || {}, t[$id]);
    }
    this.data = { samples, lst, refs: d.refs, removedHierClusterTerms };
  }
  setHierColorScale(c) {
    const hc = this.settings.hierCluster;
    const scale = linear(colorScaleMap[hc.colorScale].domain, colorScaleMap[hc.colorScale].range).clamp(true);
    const globalMinMaxes = [];
    for (const row of c.matrix) {
      globalMinMaxes.push(...extent(row));
    }
    const absMax = Math.min(hc.zScoreCap, Math.max(...extent(globalMinMaxes).map(Math.abs)));
    const [min, max] = hc.zScoreTransformation ? [-absMax, absMax] : [Math.min(...globalMinMaxes), Math.max(...globalMinMaxes)];
    this.hierClusterValues = { scale, min, max };
  }
  getValueColor(value) {
    const hc = this.settings.hierCluster;
    if (hc.zScoreTransformation) {
      const zScoreCap = this.settings.hierCluster.zScoreCap;
      return this.hierClusterValues.scale((value - -zScoreCap) / (zScoreCap * 2));
    } else {
      return this.hierClusterValues.scale(value / this.hierClusterValues.max);
    }
  }
  /* returns list of gene terms as request parameter, e.g. {gene,chr,start,stop}
  	request parameter only need term but not tw, as it will simply fetch continuous sample values on terms without transform
  
  	use of this function is unfortunate because:
  		the incomplete migration of {name} to {gene} for gene-based term
  		geneset edit ui is hardcoded to return {name}
  		existing plot states contain {name}
  
  	!!! migration instruction !!!
  	- term.name is for display only, if a term is gene-based, it has term.gene=str
  	- a geneVariant term can be based on a genomic range (and not a gene), in that case it won't have term.gene and cannot be used where gene is expected, e.g. gene-based clustering analysis
  
  	*/
  getClusterRowTermsAsParameter() {
    const lst = this.hcTermGroup.lst.map(this.opts.app.vocabApi.getTwMinCopy);
    lst.sort((a, b) => a.term.name < b.term.name ? -1 : 1);
    return lst;
  }
};
for (const methods of [hierCluster_renderers_exports, hierCluster_interactivity_exports]) {
  for (const methodName in methods) HierCluster.prototype[methodName] = methods[methodName];
}
var hierClusterInit = getCompInit(HierCluster);
var componentInit = hierClusterInit;

export {
  HierCluster,
  hierClusterInit,
  componentInit
};
//# sourceMappingURL=chunk-J4SOF2ZH.js.map
