import {
  PlotBase,
  addGeneSearchbox,
  appInit,
  filterInit,
  filterJoin,
  first_genetrack_tolist,
  getNormalRoot,
  make_one_checkbox,
  renderTable,
  sayerror
} from "./chunk-SA7APTJR.js";
import "./chunk-HJ6L54YS.js";
import "./chunk-LSEFWW72.js";
import "./chunk-SKREEF3H.js";
import {
  Menu
} from "./chunk-HYOEWQ5P.js";
import {
  Tabs
} from "./chunk-HBW42TDT.js";
import "./chunk-G6O3URDN.js";
import "./chunk-FN5XPUPH.js";
import "./chunk-IIT367QZ.js";
import "./chunk-RZGEKL77.js";
import "./chunk-UI7OCM2A.js";
import "./chunk-YZK37YCC.js";
import {
  dofetch3
} from "./chunk-7EMWHCVW.js";
import "./chunk-7IYJZZQI.js";
import {
  getCompInit
} from "./chunk-M3J4MINX.js";
import "./chunk-PF4DSFDR.js";
import "./chunk-QH6W3NVF.js";
import "./chunk-NOEAT6CX.js";
import "./chunk-4UWS5Y3N.js";
import "./chunk-DD4R5P6W.js";
import "./chunk-JNITUVXP.js";
import "./chunk-KSGA62R2.js";
import "./chunk-LOZEKOES.js";
import "./chunk-TOU7EVFQ.js";
import "./chunk-OAWQ6LOO.js";
import "./chunk-TLT4YIG3.js";
import "./chunk-KYBIQBXE.js";
import "./chunk-I6Y4O3RR.js";
import "./chunk-OMR2DT66.js";
import "./chunk-DQC5FFGV.js";
import "./chunk-HFNDKYVF.js";

// plots/gb/model/Model.ts
var Model = class {
  constructor(state, app) {
    this.state = state;
    this.app = app;
  }
  async preComputeData() {
    if (!this.state.config.geneSearchResult || !this.state.config.snvindel?.shown || !this.state.config.snvindel?.details)
      return;
    const body = {
      genome: this.app.opts.state.vocab.genome,
      dslabel: this.app.opts.state.vocab.dslabel,
      for: "mds3variantData",
      chr: this.state.config.geneSearchResult.chr,
      start: this.state.config.geneSearchResult.start,
      stop: this.state.config.geneSearchResult.stop,
      details: this.state.config.snvindel.details,
      filter: this.state.filter,
      filter0: this.state.filter0,
      variantFilter: this.state.config.variantFilter?.filter
    };
    const data = await dofetch3("termdb", { body });
    if (data.error) throw data.error;
    return data;
  }
};

// plots/gb/interactions/Interactions.ts
var Interactions = class {
  constructor(app, dom, id) {
    // using arrow function to bind "this" to the Interactions class
    // otherwise "this" can refer to the Block class
    this.onCoordinateChange = (rglst) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: { geneSearchResult: { chr: rglst[0].chr, start: rglst[0].start, stop: rglst[0].stop } }
      });
    };
    this.onGeneSearch = (result, blockIsProteinMode) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: {
          geneSearchResult: result,
          blockIsProteinMode
        }
      });
    };
    this.maySaveTrackUpdatesToState = (blockInstance) => {
      if (!blockInstance) return;
      const state = this.getState(this.app.getState());
      const config = structuredClone(state.config);
      config.subMds3Tks = [];
      for (const t of blockInstance.tklst) {
        if (t.type == "mds3" && t.filterObj) {
          const mclassHiddenValues = t.legend?.mclass?.hiddenvalues;
          if (!t.subtk) {
            if (mclassHiddenValues) {
              config.mclassHiddenValues = [...mclassHiddenValues];
            }
            continue;
          } else {
            const subtk = { filterObj: t.filterObj };
            if (mclassHiddenValues) {
              subtk.mclassHiddenValues = [...mclassHiddenValues];
            }
            config.subMds3Tks.push(subtk);
          }
        }
      }
      if (config.trackLst?.activeTracks) {
        const newLst = config.trackLst.activeTracks.filter((n) => blockInstance.tklst.find((i) => i.name == n));
        config.trackLst.activeTracks = newLst;
      }
      this.app.save({
        type: "plot_edit",
        id: this.id,
        config
      });
    };
    this.launchFacet = (config) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config
      });
    };
    this.launchVariantTrack = (toDisplay) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: { snvindel: { shown: toDisplay } }
      });
    };
    this.launchLdTrack = (tracks) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: { ld: { tracks } }
      });
    };
    this.launchGroupsFilter = (groups) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: { snvindel: { details: { groups } } }
      });
    };
    this.launchSnvIndelDetails = (details) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: { snvindel: { details } }
      });
    };
    this.launchVariantFilter = (filter) => {
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: { variantFilter: { filter } }
      });
    };
    this.app = app;
    this.dom = dom;
    this.id = id;
  }
  // copied from "GB.ts", should try to inherit it instead
  getState(appState) {
    const config = appState.plots.find((p) => p.id === this.id);
    if (!config) throw `No plot with id='${this.id}' found`;
    return {
      config,
      filter: getNormalRoot(appState.termfilter.filter),
      vocab: appState.vocab
    };
  }
};
function mayUpdateGroupTestMethodsIdx(state, d) {
  if (d.groups.length != 2) return;
  const [g1, g2] = d.groups;
  if (g1.type == "info" || g2.type == "info" || g1.type == "population" && g2.type == "population") {
    const i = state.config.snvindel.details.groupTestMethods.findIndex((i2) => i2.name == "Allele frequency difference");
    if (i == -1) throw "Allele frequency difference not found";
    d.groupTestMethodsIdx = i;
  } else {
  }
}

// plots/gb/view/TabsRenderer.ts
var TabsRenderer = class {
  constructor(state, dom, opts, interactions) {
    this.state = state;
    this.dom = dom;
    this.opts = opts;
    this.interactions = interactions;
    this.filterUI = {};
  }
  async main() {
    let previousActiveTabLabel = null;
    const existingActiveButton = this.dom.tabsDiv.select("button.sjpp-active");
    if (!existingActiveButton.empty()) {
      previousActiveTabLabel = existingActiveButton.datum()?.label ?? null;
    }
    this.dom.tabsDiv.selectAll("*").remove();
    this.getTabs();
    if (previousActiveTabLabel) {
      const matchingTab = this.tabs.find((tab) => tab.label === previousActiveTabLabel);
      if (matchingTab) {
        this.tabs.forEach((tab) => tab.active = false);
        matchingTab.active = true;
      }
    }
    await this.mayRenderTabs();
  }
  getTabs() {
    const tabs = [];
    if (this.state.config.trackLst?.facets) {
      const activeTracks = this.state.config.trackLst.activeTracks;
      for (const facet of this.state.config.trackLst.facets) {
        const shown = facet.tracks.some((t) => activeTracks.includes(t.name));
        tabs.push({ label: facet.name || "Facet Table", active: shown });
      }
    }
    if (this.state.config.snvindel) {
      const shown = this.state.config.snvindel.shown;
      if (this.state.config.snvindel.details) {
        tabs.push({ label: "Variant Values", active: shown });
        if (this.state.config.variantFilter) {
          tabs.push({ label: "Variant Filter" });
        }
      } else {
        if (this.state.config.trackLst) {
          tabs.push({ label: "Variants", active: shown });
        } else {
        }
      }
    }
    if (this.state.config.ld) {
      const shown = this.state.config.ld.tracks.some((t) => t.shown);
      tabs.push({ label: "LD Map", active: shown });
    }
    this.tabs = tabs;
  }
  async mayRenderTabs() {
    const tabs = this.tabs;
    if (!tabs?.length) return;
    const toggles = new Tabs({
      holder: this.dom.tabsDiv.append("div").style("border-bottom", "solid 1px #ccc").style("padding-bottom", "20px"),
      tabs,
      hideOnDblClick: true
    });
    toggles.main();
    let tabsIdx = 0;
    if (this.state.config.trackLst?.facets) {
      for (const facet of this.state.config.trackLst.facets) {
        const div = tabs[tabsIdx++].contentHolder.append("div");
        this.renderFacetTable(facet, div);
      }
    }
    if (this.state.config.snvindel) {
      if (this.state.config.snvindel.details) {
        const div = tabs[tabsIdx++].contentHolder.append("div");
        this.dom.group1div = div.append("div").attr("class", "sjpp-gb-group1");
        this.dom.group2div = div.append("div").attr("class", "sjpp-gb-group2");
        this.dom.testMethodDiv = div.append("div").style("margin-top", "3px");
        await this.mayRenderGroups();
        if (this.state.config.variantFilter) {
          this.dom.variantFilterHolder = tabs[tabsIdx++].contentHolder.append("div").style("white-space", "normal");
          this.mayRenderVariantFilter();
        }
      } else {
        if (this.state.config.trackLst) {
          const div = tabs[tabsIdx++].contentHolder.append("div");
          make_one_checkbox({
            labeltext: "Show variant track",
            checked: this.state.config.snvindel.shown,
            holder: div,
            callback: this.interactions.launchVariantTrack
          });
        }
      }
    }
    if (this.state.config.ld) {
      const tracks = structuredClone(this.state.config.ld.tracks);
      const div = tabs[tabsIdx++].contentHolder.append("div");
      div.append("div").text("Show/hide linkage disequilibrium map from an ancestry:").style("opacity", 0.5);
      for (const [i, t] of tracks.entries()) {
        make_one_checkbox({
          labeltext: t.name,
          checked: t.shown,
          holder: div,
          callback: (checked) => {
            tracks[i].shown = checked;
            this.interactions.launchLdTrack(tracks);
          }
        });
      }
    }
  }
  renderFacetTable(facet, div) {
    const assayset = /* @__PURE__ */ new Set(), sampleset = /* @__PURE__ */ new Set();
    for (const t of facet.tracks) {
      if (t.assay) assayset.add(t.assay);
      if (t.sample) sampleset.add(t.sample);
    }
    const sampleLst = [...sampleset];
    const assayLst = [...assayset];
    const columns = [{ label: "Sample" }];
    for (const assay of assayLst) {
      columns.push({
        label: assay,
        fillCell: (td, si) => {
          const tklst = facet.tracks.filter((i) => i.assay == assay && i.sample == sampleLst[si]);
          if (tklst.length == 0) return;
          const shownCount = tklst.filter((tk) => this.state.config.trackLst.activeTracks.includes(tk.name)).length;
          const displayText = shownCount ? `${shownCount}/${tklst.length}` : `${tklst.length}`;
          td.append("div").attr("class", "sja_clbtext").style("text-align", "center").text(displayText).on("click", (event) => {
            this.clickFacetCell(event, tklst);
          });
        }
      });
    }
    const rows = [];
    for (const sample of sampleLst) {
      const row = [{ value: sample }];
      for (let i = 0; i < assayLst.length; i++) {
        row.push({});
      }
      rows.push(row);
    }
    renderTable({
      columns,
      rows,
      div
    });
  }
  clickFacetCell(event, tklst) {
    this.dom.tip.clear().showunder(event.target);
    const activeTracks = this.state.config.trackLst.activeTracks;
    const table = this.dom.tip.d.append("table").style("margin", "5px 5px 5px 2px");
    for (const tk of tklst) {
      const tr = table.append("tr");
      const td1 = tr.append("td").style("font-size", ".8em").text(activeTracks.includes(tk.name) ? "SHOWN" : "");
      tr.append("td").attr("class", "sja_menuoption").text(tk.name).on("click", () => {
        this.dom.tip.hide();
        let newActiveTracks, newRemoveTracks;
        if (activeTracks.includes(tk.name)) {
          td1.text("");
          newActiveTracks = structuredClone(activeTracks).filter((n) => n != tk.name);
          newRemoveTracks = [tk.name];
        } else {
          td1.text("SHOWN");
          newActiveTracks = structuredClone(activeTracks);
          newActiveTracks.push(tk.name);
        }
        const config = {
          trackLst: {
            activeTracks: newActiveTracks,
            removeTracks: newRemoveTracks
          }
        };
        this.interactions.launchFacet(config);
      });
    }
  }
  async mayRenderGroups() {
    const groups = this.state.config.snvindel.details.groups;
    if (!groups) return;
    await this.render1group(0);
    await this.render1group(1);
    if (this.state.config.snvindel.details.groupTestMethods) {
      this.renderTestMethod();
    }
  }
  mayRenderVariantFilter() {
    if (this.state.config.variantFilter) {
      this.dom.variantFilterHolder.selectAll("*").remove();
      filterInit({
        joinWith: this.state.config.variantFilter.opts.joinWith,
        emptyLabel: "+Add Filter",
        holder: this.dom.variantFilterHolder,
        vocab: { terms: this.state.config.variantFilter.terms },
        callback: (filter) => this.interactions.launchVariantFilter(filter)
      }).main(this.state.config.variantFilter.filter);
    }
  }
  /*
  	render ui contents of one group, both arguments are provided to be convenient for ad-hoc update
  
  	groupIdx: array index of self.state.config.snvindel.details.groups[]
  		determines which <div> to render to at self.dom.group1/2div
  	group{}: element of same array
  	*/
  async render1group(groupIdx) {
    const group = this.state.config.snvindel.details.groups[groupIdx];
    const div = groupIdx == 0 ? this.dom.group1div : this.dom.group2div;
    let canReuse = false;
    if (group?.type == "filter" && this.filterUI[groupIdx]) {
      canReuse = true;
    } else {
      delete this.filterUI[groupIdx];
      div.selectAll("*").remove();
    }
    if (!group) {
      this.makePrompt2addNewGroup(groupIdx, div);
      return;
    }
    if (!canReuse) this.makeGroupHeaderButton(groupIdx, div);
    if (group.type == "info") return this.render1group_info(groupIdx, group, div);
    if (group.type == "population") return this.render1group_population(groupIdx, group, div);
    if (group.type == "filter") return await this.render1group_filter(groupIdx, group, div);
    throw "render1group: unknown group type";
  }
  makePrompt2addNewGroup(groupIdx, div) {
    div.append("div").style("display", "inline-block").text("Create Group 2").attr("class", "sja_clbtext").style("margin", "10px").on("click", (event) => {
      this.dom.tip.showunder(event.target).clear();
      this.launchMenu_createGroup(groupIdx, this.dom.tip.d);
    });
  }
  makeGroupHeaderButton(groupIdx, div) {
    div.append("div").style("display", "inline-block").text("Group " + (groupIdx + 1)).attr("class", "sja_menuoption").style("margin-right", "10px").on("click", (event) => {
      this.dom.tip.showunder(event.target).clear();
      if (groupIdx == 0) {
        this.launchMenu_createGroup(0, this.dom.tip.d);
        return;
      }
      this.dom.tip.d.append("div").text("Change").attr("class", "sja_menuoption").style("border-radius", "0px").on("click", () => {
        this.launchMenu_createGroup(1, this.dom.tip.clear().d);
      });
      this.dom.tip.d.append("div").text("Delete").attr("class", "sja_menuoption").style("border-radius", "0px").on("click", () => {
        this.dom.tip.hide();
        const groups = [this.state.config.snvindel.details.groups[0]];
        this.interactions.launchGroupsFilter(groups);
      });
    });
  }
  render1group_info(groupIdx, group, div) {
    let name = group.infoKey;
    if (this.state.config.variantFilter?.terms) {
      const f = this.state.config.variantFilter.terms.find((i) => i.id == group.infoKey);
      if (f && f.name) name = f.name;
    }
    div.append("span").text(name).attr("class", "sja_menuoption").on("click", (event) => {
      if (this.state.config.variantFilter.terms.length <= 1) {
        return;
      }
      this.dom.tip.clear().showunder(event.target).d.append("div").text("Replace with:").style("margin", "10px").style("font-size", ".8em");
      for (const f of this.state.config.variantFilter.terms) {
        if (f.type != "integer" && f.type != "float") continue;
        if (f.id == group.infoKey) continue;
        this.dom.tip.d.append("div").text(f.name).attr("class", "sja_menuoption").on("click", () => {
          this.dom.tip.hide();
          const groups = structuredClone(this.state.config.snvindel.details.groups);
          groups[groupIdx].infoKey = f.id;
          groups[groupIdx].type = "info";
          this.interactions.launchGroupsFilter(groups);
        });
      }
    });
    div.append("span").text("PER-VARIANT NUMERICAL VALUES").style("font-size", ".7em").style("opacity", 0.6).style("margin-left", "10px");
  }
  render1group_population(groupIdx, group, div) {
    div.append("span").text(group.label).attr("class", "sja_menuoption").on("click", (event) => {
      if (this.state.config.snvindel.populations.length <= 1) {
        return;
      }
      this.dom.tip.clear().showunder(event.target).d.append("div").text("Replace with:").style("margin", "10px").style("font-size", ".8em");
      for (const p of this.state.config.snvindel.populations) {
        if (p.key == group.key) continue;
        this.dom.tip.d.append("div").text(p.label).attr("class", "sja_menuoption").on("click", () => {
          this.dom.tip.hide();
          const groups = structuredClone(this.state.config.snvindel.details.groups);
          groups[groupIdx] = structuredClone(p);
          groups[groupIdx].type = "population";
          this.interactions.launchGroupsFilter(groups);
        });
      }
    });
    div.append("span").text(`POPULATION${group.adjust_race ? ", RACE ADJUSTED" : ""}`).style("font-size", ".7em").style("margin-left", "10px").style("opacity", 0.6);
    if (this.state.config.snvindel.details.groups[groupIdx == 1 ? 0 : 1]?.type == "filter") {
      div.append("span").attr("class", "sja_clbtext").attr("id", "sjpp-gb-pop2avg").style("display", "none").style("margin-left", "20px").text(`Group ${groupIdx == 1 ? 1 : 2} average admixture:`).on("click", (event) => {
        this.dom.tip.clear().showunder(event.target).d.append("div").style("margin", "10px").style("width", "500px").html(`These are average admixture coefficients based on current Group ${groupIdx == 1 ? 1 : 2} samples.
					They are used to adjust variant allele counts of matching ancestries from <span class=sja_menuoption style="padding:2px 5px">${group.label}</span>,
					so that the adjusted allele counts can be compared against Group ${groupIdx == 1 ? 1 : 2} allele counts.
					This allows to account for ancestry composition difference between the two groups.
					`);
      }).append("span").attr("id", "sjpp-gb-pop2avg-values");
    }
  }
  async render1group_filter(groupIdx, group, div) {
    if (!this.filterUI[groupIdx]) {
      this.filterUI[groupIdx] = await filterInit({
        holder: div,
        vocab: this.state.vocab,
        emptyLabel: "Entire cohort",
        termdbConfig: this.opts.vocabApi.termdbConfig,
        callback: (f) => {
          const groups = JSON.parse(JSON.stringify(this.state.config.snvindel.details.groups));
          groups[groupIdx].filter = f;
          this.interactions.launchGroupsFilter(groups);
        }
      });
    }
    this.filterUI[groupIdx].main(this.getJoinedFilter(group));
    div.append("span").attr("class", "sjpp-gb-filter-count").style("display", "none").style("margin-left", "10px").style("opacity", 0.5).style("font-size", ".9em");
  }
  getJoinedFilter(group) {
    const joinedFilter = structuredClone(this.state.filter || { type: "tvslst", in: true, join: "", lst: [] });
    const gf = structuredClone(group.filter);
    gf.tag = "filterUiRoot";
    joinedFilter.lst.push(gf);
    joinedFilter.join = "and";
    return joinedFilter;
  }
  // show vertical toggle options: filter/population/info
  // when any is selected, create a new group object and set to snvindel.details.groups[groupIdx]
  launchMenu_createGroup(groupIdx, div) {
    const opt = {
      holder: div.append("div").style("margin", "5px"),
      tabs: this.state.config.snvindel.details.groupTypes.map((i) => {
        return { label: i.name };
      }),
      tabsPosition: "vertical",
      linePosition: "right"
    };
    new Tabs(opt).main();
    for (const [idx, groupType] of this.state.config.snvindel.details.groupTypes.entries()) {
      const tab = opt.tabs[idx];
      tab.contentHolder.style("margin", "10px");
      if (groupType.type == "info") {
        if (!this.state.config.variantFilter?.terms)
          throw "looking for snvindel info fields but self.state.config.variantFilter.terms[] missing";
        for (const f of this.state.config.variantFilter.terms) {
          if (f.type != "integer" && f.type != "float") continue;
          tab.contentHolder.append("div").text(f.name).attr("class", "sja_menuoption").on("click", () => {
            this.dom.tip.hide();
            const newGroup = {
              type: "info",
              infoKey: f.id
            };
            const details = this.makeNewDetail(newGroup, groupIdx);
            mayUpdateGroupTestMethodsIdx(this.state, details);
            this.interactions.launchSnvIndelDetails(details);
          });
        }
        continue;
      }
      if (groupType.type == "population") {
        if (!this.state.config.snvindel.populations) throw "state.config.snvindel.populations missing";
        for (const p of this.state.config.snvindel.populations) {
          tab.contentHolder.append("div").text(p.label).attr("class", "sja_menuoption").on("click", () => {
            this.dom.tip.hide();
            const newGroup = {
              type: "population",
              key: p.key,
              label: p.label,
              allowto_adjust_race: p.allowto_adjust_race,
              adjust_race: p.adjust_race
            };
            const details = this.makeNewDetail(newGroup, groupIdx);
            mayUpdateGroupTestMethodsIdx(this.state, details);
            this.interactions.launchSnvIndelDetails(details);
          });
        }
        continue;
      }
      if (groupType.type == "filter") {
        const arg = {
          holder: tab.contentHolder,
          vocabApi: this.opts.vocabApi,
          state: {
            activeCohort: this.state.activeCohort,
            termfilter: { filter: this.state.filter }
          },
          tree: {
            click_term2select_tvs: (tvs) => {
              this.dom.tip.hide();
              const newGroup = {
                type: "filter",
                filter: {
                  in: true,
                  join: "",
                  type: "tvslst",
                  lst: [{ type: "tvs", tvs }]
                }
              };
              const details = this.makeNewDetail(newGroup, groupIdx);
              mayUpdateGroupTestMethodsIdx(this.state, details);
              this.interactions.launchSnvIndelDetails(details);
            }
          }
        };
        appInit(arg);
        continue;
      }
      throw "unknown group type";
    }
  }
  makeNewDetail(newGroup, groupIdx) {
    const newDetail = {
      groups: JSON.parse(JSON.stringify(this.state.config.snvindel.details.groups))
    };
    newDetail.groups[groupIdx] = newGroup;
    return newDetail;
  }
  renderTestMethod() {
    const div = this.dom.testMethodDiv;
    div.selectAll("*").remove();
    const [g1, g2] = this.state.config.snvindel.details.groups;
    if (!g2) {
      return;
    }
    div.append("span").text("TEST METHOD").style("font-size", ".8em").style("opacity", 0.6);
    if (g1.type != "filter" && g2.type != "filter") {
      div.append("span").style("padding-left", "10px").text("Value difference").style("opacity", 0.6);
      return;
    }
    const select = div.append("select").style("margin-left", "10px").on("change", () => {
      const details = { groupTestMethodsIdx: select.property("selectedIndex") };
      this.interactions.launchSnvIndelDetails(details);
    });
    for (const m of this.state.config.snvindel.details.groupTestMethods) {
      select.append("option").text(m.name);
    }
    select.property("selectedIndex", this.state.config.snvindel.details.groupTestMethodsIdx);
  }
};

// plots/gb/view/GeneSearchRenderer.ts
var GeneSearchRenderer = class {
  constructor(state, holder, opts, interactions) {
    this.state = state;
    this.holder = holder;
    this.opts = opts;
    this.interactions = interactions;
  }
  main() {
    this.renderGeneSearch();
  }
  renderGeneSearch() {
    const gbRestrictMode = this.opts.vocabApi.termdbConfig.queries.gbRestrictMode;
    this.holder.selectAll("*").remove();
    const row = this.holder.append("div");
    row.append("span").html("Search");
    const arg = {
      tip: new Menu({ padding: "0px" }),
      genome: this.opts.genome,
      row,
      callback: async () => {
        if (result.geneSymbol && !gbRestrictMode) {
          this.holder.select(".sjpp_gbmodebtndiv").remove();
          const btndiv = this.holder.append("div").attr("class", "sjpp_gbmodebtndiv").style("margin-top", "10px");
          btndiv.append("button").style("margin-right", "10px").text("Protein view of " + result.geneSymbol).on("click", async () => {
            await this.interactions.onGeneSearch(result, true);
          });
          btndiv.append("button").text("Genomic view of " + result.geneSymbol).on("click", async () => {
            await this.interactions.onGeneSearch(result, false);
          });
          return;
        }
        await this.interactions.onGeneSearch(result, gbRestrictMode == "protein");
      }
    };
    switch (gbRestrictMode) {
      case void 0:
        break;
      case "genomic":
        arg.defaultCoord = this.opts.vocabApi.termdbConfig.queries.defaultCoord;
        break;
      case "protein":
        arg.searchOnly = "gene";
        break;
      default:
        throw "unknown gbRestrictMode";
    }
    const result = addGeneSearchbox(arg);
  }
};

// plots/gb/view/View.ts
var View = class {
  constructor(state, blockInstance, data, dom, opts, interactions) {
    this.state = state;
    this.blockInstance = blockInstance;
    this.data = data;
    this.dom = dom;
    this.opts = opts;
    this.interactions = interactions;
  }
  async main() {
    const tabs = new TabsRenderer(this.state, this.dom, this.opts, this.interactions);
    await tabs.main();
    const geneSearch = new GeneSearchRenderer(this.state, this.dom.geneSearchDiv, this.opts, this.interactions);
    geneSearch.main();
    if (this.state.config.geneSearchResult) {
      this.dom.geneSearchDiv.style("display", "none");
      const tklst = await this.generateTracks();
      await this.launchBlockWithTracks(tklst);
    }
  }
  async generateTracks() {
    const tklst = [];
    if (this.state.config.snvindel?.shown) {
      if (this.data) {
        if (this.blockInstance) {
          this.updateCustomMds3tk();
        } else {
          const tk = await this.launchCustomMds3tk();
          if (tk) {
            tklst.push(tk);
          }
        }
      } else {
        const tk = {
          type: "mds3",
          dslabel: this.state.vocab.dslabel,
          allow2selectSamples: this.opts.allow2selectSamples,
          onClose: () => {
            this.interactions.maySaveTrackUpdatesToState(this.blockInstance);
          },
          callbackOnRender: () => {
            this.interactions.maySaveTrackUpdatesToState(this.blockInstance);
          },
          // for showing disco etc as ad-hoc sandbox, persistently in the mass plotDiv, rather than a menu
          newChartHolder: this.opts.plotDiv
        };
        {
          const lst = [];
          if (this.state.filter?.lst?.length) lst.push(this.state.filter);
          if (this.state.config.snvindel.filter) lst.push(this.state.config.snvindel.filter);
          if (lst.length == 1) {
            tk.filterObj = structuredClone(lst[0]);
          } else if (lst.length > 1) {
            tk.filterObj = filterJoin(lst);
          }
        }
        if (this.state.filter0) tk.filter0 = this.state.filter0;
        if (this.state.config.mclassHiddenValues) {
          tk.legend = { mclass: { hiddenvalues: new Set(this.state.config.mclassHiddenValues) } };
        }
        tklst.push(tk);
        if (this.state.config?.subMds3Tks) {
          for (const subtk of this.state.config.subMds3Tks) {
            const t2 = {
              type: "mds3",
              subtk: true,
              dslabel: this.state.vocab.dslabel,
              onClose: () => {
                this.interactions.maySaveTrackUpdatesToState(this.blockInstance);
              },
              callbackOnRender: () => {
                this.interactions.maySaveTrackUpdatesToState(this.blockInstance);
              },
              // for showing disco etc as ad-hoc sandbox, persistently in the mass plotDiv, rather than a menu
              newChartHolder: this.opts.plotDiv
            };
            if (this.state.filter?.lst?.length) {
              t2.filterObj = filterJoin([this.state.filter, subtk.filterObj]);
            } else {
              t2.filterObj = structuredClone(subtk.filterObj);
            }
            if (this.state.filter0) t2.filter0 = this.state.filter0;
            if (subtk.mclassHiddenValues) {
              t2.legend = { mclass: { hiddenvalues: new Set(subtk.mclassHiddenValues) } };
            }
            tklst.push(t2);
          }
        }
      }
    }
    if (this.state.config?.trackLst?.activeTracks?.length) {
      for (const n of this.state.config.trackLst.activeTracks) {
        for (const f of this.state.config.trackLst.facets) {
          for (const t of f.tracks) {
            if (t.name == n) tklst.push(t);
          }
        }
      }
    }
    if (this.state.config.ld?.tracks) {
      for (const t of this.state.config.ld.tracks) {
        if (t.shown) tklst.push(t);
      }
    }
    return tklst;
  }
  async launchCustomMds3tk() {
    this.mayGetSampleCounts();
    const nm = {
      // numeric mode object; to fill in based on snvindel.details{}
      type: "numeric",
      inuse: true,
      byAttribute: "nm_axis_value"
    };
    furbishViewModeWithSnvindelComputeDetails(this, nm);
    const tk = {
      type: "mds3",
      // despite having custom data, still provide dslabel for the mds3 tk to function as an official dataset
      dslabel: this.state.vocab.dslabel,
      name: "Variants",
      custom_variants: this.data.mlst,
      skewerModes: [nm],
      filter0: this.state.filter0
    };
    return tk;
  }
  updateCustomMds3tk() {
    if (!this.blockInstance) throw "blockInstance is missing";
    this.mayGetSampleCounts();
    const t2 = this.blockInstance.tklst.find((i) => i.type == "mds3");
    t2.custom_variants = this.data.mlst;
    furbishViewModeWithSnvindelComputeDetails(
      this,
      t2.skewer.viewModes.find((i) => i.type == "numeric")
    );
    t2.load();
  }
  mayGetSampleCounts() {
    const data = this.data;
    this.mayRenderSampleCount("group1", data.totalSampleCount_group1);
    this.mayRenderSampleCount("group2", data.totalSampleCount_group2);
    this.mayRenderPop2Avg(data.pop2average);
  }
  mayRenderSampleCount(group, count) {
    if (!Number.isInteger(count)) return;
    this.dom.tabsDiv.select(`.sjpp-gb-${group}`).select(".sjpp-gb-filter-count").style("display", "inline").text("n=" + count);
  }
  mayRenderPop2Avg(pop2average) {
    if (!pop2average) return;
    const span = this.dom.tabsDiv.select("#sjpp-gb-pop2avg");
    const lst = [];
    for (const k in pop2average) {
      const value = pop2average[k];
      if (!Number.isFinite(value)) continue;
      lst.push(`${k}=${value.toFixed(2)}`);
    }
    if (lst.length) {
      span.style("display", "inline");
      span.select("#sjpp-gb-pop2avg-values").text(` ${lst.join(", ")}`);
    } else {
      span.style("display", "none");
    }
  }
  /* tricky logic */
  async launchBlockWithTracks(tklst) {
    if (!["GDC", "MMRF"].includes(this.state?.vocab?.dslabel) && this.blockInstance) {
      for (const tk of tklst) {
        let tki;
        if (tk.dslabel) {
          tki = this.blockInstance.tklst.findIndex((i) => i.dslabel == tk.dslabel);
        } else if (tk.name) {
          tki = this.blockInstance.tklst.findIndex((i) => i.name == tk.name);
        } else {
          throw "tk missing dslabel & name";
        }
        if (tki == -1) {
          const t = this.blockInstance.block_addtk_template(tk);
          this.blockInstance.tk_load(t);
        }
      }
      if (this.state.config.trackLst?.removeTracks) {
        for (const n of this.state.config.trackLst.removeTracks) {
          const i = this.blockInstance.tklst.findIndex((i2) => i2.name == n);
          if (i != -1) this.blockInstance.tk_remove(i);
        }
      }
      if (this.state.config.ld?.tracks) {
        for (const t of this.state.config.ld.tracks) {
          const i = this.blockInstance.tklst.findIndex((i2) => i2.name == t.name);
          if (!t.shown && i != -1) this.blockInstance.tk_remove(i);
        }
      }
      if (this.state.config.snvindel && !this.state.config.snvindel.shown) {
        let found = true;
        while (found) {
          found = false;
          for (const [i, tk] of this.blockInstance.tklst.entries()) {
            if (tk.type == "mds3") {
              this.blockInstance.tk_remove(i);
              found = true;
              break;
            }
          }
        }
      }
      return;
    }
    this.dom.blockHolder.selectAll("*").remove();
    const arg = {
      holder: this.dom.blockHolder,
      genome: this.opts.genome,
      // genome obj
      nobox: true,
      tklst,
      debugmode: this.opts.debug,
      onAddRemoveTk: () => {
        this.interactions.maySaveTrackUpdatesToState(this.blockInstance);
      },
      filter0: this.state.filter0
    };
    if (this.data) {
      arg.onCoordinateChange = this.interactions.onCoordinateChange;
    }
    if (this.state.config.blockIsProteinMode) {
      if (!this.state.config.geneSearchResult.geneSymbol) throw "blockIsProteinMode=true but geneSymbol missing";
      arg.query = this.state.config.geneSearchResult.geneSymbol;
      const _2 = await import("./block.init-RRHHCNSR.js");
      await _2.default(arg);
      this.blockInstance = arg.__blockInstance;
      if (this.opts.header) this.opts.header.text(arg.query);
      return;
    }
    if (!this.state.config.geneSearchResult.chr) throw "blockIsProteinMode=false but chr missing";
    arg.chr = this.state.config.geneSearchResult.chr;
    arg.start = this.state.config.geneSearchResult.start;
    arg.stop = this.state.config.geneSearchResult.stop;
    first_genetrack_tolist(this.opts.genome, arg.tklst);
    const _ = await import("./block-TTN2IQAH.js");
    this.blockInstance = new _.Block(arg);
  }
};
function furbishViewModeWithSnvindelComputeDetails(self, viewmode) {
  delete viewmode.tooltipPrintValue;
  const [g1, g2] = self.state.config.snvindel.details.groups;
  if (g1 && g2) {
    if (g1.type == "info" || g2.type == "info") {
      viewmode.label = "Value difference";
      return;
    }
    const testMethod = self.state.config.snvindel.details.groupTestMethods[self.state.config.snvindel.details.groupTestMethodsIdx];
    viewmode.label = testMethod.axisLabel || testMethod.name;
    if (testMethod.name == "Allele frequency difference") {
      viewmode.tooltipPrintValue = (m) => [{ k: "AF diff", v: m.nm_axis_value }];
    } else if (testMethod.name == "Fisher's exact test") {
      viewmode.tooltipPrintValue = (m) => [{ k: "p-value", v: m.p_value }];
    }
    return;
  }
  if (g1.type == "info") {
    const f = self.state.config.variantFilter?.terms?.find((i) => i.id == g1.infoKey);
    viewmode.label = f?.name || g1.infoKey;
    viewmode.tooltipPrintValue = (m) => [{ k: viewmode.label, v: m.info[g1.infoKey] }];
    return;
  }
  if (g1.type == "filter") {
    viewmode.label = "Allele frequency";
    viewmode.tooltipPrintValue = (m) => [{ k: "Allele frequency", v: m.nm_axis_value }];
    return;
  }
  if (g1.type == "population") {
    viewmode.label = "Allele frequency";
    viewmode.tooltipPrintValue = (m) => [{ k: "Allele frequency", v: m.nm_axis_value }];
    return;
  }
  throw "unknown type of the only group";
}

// plots/gb/GB.ts
var TdbGenomeBrowser = class _TdbGenomeBrowser extends PlotBase {
  constructor(opts, api) {
    super(opts, api);
    this.components = {};
    this.type = _TdbGenomeBrowser.type;
    this.dom = this.getDom();
  }
  static {
    this.type = "genomeBrowser";
  }
  getDom() {
    const holder = this.opts.holder.append("div");
    if (this.opts.header) {
      this.opts.header.append("div").style("opacity", 0.6).style("padding-left", "10px").style("font-size", "0.75em").text("GENOME BROWSER");
    }
    const loadingDiv = holder.append("div").style("display", "none").style("margin-left", "25px").text("Loading...");
    const errDiv = holder.append("div").style("display", "none").style("margin", "10px");
    const controlsDiv = holder.append("div").style("margin", "15px 0px 0px 25px");
    const dom = {
      tip: new Menu(),
      holder,
      loadingDiv,
      errDiv,
      tabsDiv: controlsDiv.append("div"),
      geneSearchDiv: controlsDiv.append("div").style("margin", "20px 0px"),
      blockHolder: holder.append("div")
    };
    return dom;
  }
  async init() {
    this.interactions = new Interactions(this.app, this.dom, this.id);
  }
  getState(appState) {
    const config = appState.plots.find((p) => p.id === this.id);
    if (!config) throw `No plot with id='${this.id}' found`;
    return {
      config,
      filter: getNormalRoot(appState.termfilter.filter),
      filter0: appState.termfilter.filter0,
      vocab: appState.vocab
    };
  }
  async main() {
    this.dom.loadingDiv.style("display", "block");
    this.dom.errDiv.style("display", "none");
    const state = this.getState(this.app.getState());
    if (state.config.chartType != this.type) return;
    const opts = this.getOpts();
    try {
      const model = new Model(state, this.app);
      const data = await model.preComputeData();
      const view = new View(state, this.blockInstance, data, this.dom, opts, this.interactions);
      await view.main();
      this.blockInstance = view.blockInstance;
    } catch (e) {
      this.dom.errDiv.style("display", "block");
      sayerror(this.dom.errDiv, "Error: " + (e.message || e));
      if (e.stack) console.log(e.stack);
    }
    this.dom.loadingDiv.style("display", "none");
  }
  // get options for view instance
  getOpts() {
    const opts = {
      genome: this.app.opts.genome,
      vocabApi: this.app.vocabApi,
      debug: this.app.opts.debug,
      plotDiv: this.opts.plotDiv,
      header: this.opts.header,
      allow2selectSamples: this.opts.allow2selectSamples
    };
    return opts;
  }
};
var genomeBrowserInit = getCompInit(TdbGenomeBrowser);
var componentInit = genomeBrowserInit;
async function getPlotConfig(opts, app, activeCohort) {
  try {
    const c = await getDefaultConfig(app.vocabApi, opts, activeCohort);
    return c;
  } catch (e) {
    throw `${e} [genomeBrowser getPlotConfig()]`;
  }
}
async function getDefaultConfig(vocabApi, override, activeCohort) {
  const config = Object.assign(
    // clone for modifying
    structuredClone({
      snvindel: vocabApi.termdbConfig.queries.snvindel,
      trackLst: vocabApi.termdbConfig.queries.trackLst,
      ld: vocabApi.termdbConfig.queries.ld
    }),
    override || {}
  );
  computeBlockModeFlag(config, vocabApi);
  if (config.snvindel) {
    if (!config.variantFilter) {
      const vf = await vocabApi.get_variantFilter();
      if (vf?.filter) {
        config.variantFilter = vf;
      }
    }
    if (config.snvindel.details) {
      mayUpdateGroupTestMethodsIdx({ config }, config.snvindel.details);
      const gf = config.snvindel.details.groups.find((i) => i.type == "filter");
      if (gf?.filterByCohort) {
        if (!Number.isInteger(activeCohort)) throw "filterByCohort but activeCohort not integer";
        gf.filter = gf.filterByCohort[vocabApi.termdbConfig.selectCohort.values[activeCohort].keys.join(",")];
        if (!gf.filter) throw "unknown filter by current cohort name";
        delete gf.filterByCohort;
      }
    }
    if (typeof config.snvindel.shown != "boolean") {
      if (config.trackLst) {
        config.snvindel.shown = false;
      } else {
        config.snvindel.shown = true;
      }
    }
  }
  if (config.trackLst) {
    if (!config.trackLst.facets) throw "trackLst.facets[] missing";
    if (!config.trackLst.activeTracks) config.trackLst.activeTracks = [];
  }
  return config;
}
function computeBlockModeFlag(config, vocabApi) {
  if (typeof config.blockIsProteinMode == "boolean") {
    return;
  }
  switch (vocabApi.termdbConfig.queries.gbRestrictMode) {
    case void 0:
      if (config.geneSearchResult?.geneSymbol) {
        config.blockIsProteinMode = true;
      } else {
        config.blockIsProteinMode = false;
      }
      break;
    case "protein":
      config.blockIsProteinMode = true;
      break;
    case "genomic":
      config.blockIsProteinMode = false;
      break;
    default:
      throw "unknown gbRestrictMode";
  }
}
function makeChartBtnMenu(_holder, chartsInstance) {
  const chart = { config: { chartType: "genomeBrowser" } };
  chartsInstance.prepPlot(chart);
}
export {
  componentInit,
  computeBlockModeFlag,
  genomeBrowserInit,
  getPlotConfig,
  makeChartBtnMenu
};
//# sourceMappingURL=GB-H6JETPRC.js.map
