import {
  recoverInit
} from "./chunk-4KY4XKJV.js";
import {
  parentCorsMessage
} from "./chunk-PRZWSBMA.js";
import {
  chartsInit,
  filterRxCompInit,
  getFilterItemByTag,
  groupsInit,
  keyupEnter,
  renderTable,
  sayerror,
  to_textfile
} from "./chunk-SA7APTJR.js";
import {
  require_debounce
} from "./chunk-LSEFWW72.js";
import {
  importPlot
} from "./chunk-SKREEF3H.js";
import {
  Menu
} from "./chunk-HYOEWQ5P.js";
import {
  icons
} from "./chunk-IIT367QZ.js";
import {
  dofetch3
} from "./chunk-7EMWHCVW.js";
import {
  getCompInit,
  multiInit
} from "./chunk-M3J4MINX.js";
import {
  select_default
} from "./chunk-I6Y4O3RR.js";
import {
  __toESM
} from "./chunk-HFNDKYVF.js";

// mass/search.js
var import_debounce = __toESM(require_debounce(), 1);
var MassSearch = class _MassSearch {
  static type = "search";
  constructor(opts) {
    this.type = _MassSearch.type;
    setRenderers(this);
    setInteractivity(this);
  }
  async init(appState) {
    this.dom = {
      holder: this.opts.holder,
      holderNode: this.opts.holder.node(),
      tip: new Menu({ padding: "5px" })
    };
    this.initUI();
  }
  reactsTo(action) {
    return action.type.startsWith("search") || action.type.startsWith("cohort");
  }
  getState(appState) {
    return {
      cohortStr: appState.activeCohort == -1 || !appState.termdbConfig.selectCohort ? "" : appState.termdbConfig.selectCohort.values[appState.activeCohort].keys.slice().sort().join(","),
      search: appState.search,
      nav: appState.nav
    };
  }
  async main() {
    this.dom.holder.style("display", this.state.search.isVisible ? "inline-block" : "none");
  }
  async doSearch(str) {
    if (!str) {
      this.clear({ hide: true });
      this.bus.emit("postSearch", []);
      return;
    }
    const data = await this.app.vocabApi.findTerm(str, this.state.cohortStr, this.opts.usecase, this.opts.targetType);
    if (!data.lst || data.lst.length == 0) {
      this.noResult();
    } else {
      this.showTerms(data);
    }
    this.bus.emit("postSearch", data);
  }
};
var searchInit = getCompInit(MassSearch);
function setRenderers(self) {
  self.initUI = () => {
    self.dom.holder.style("display", self.search && self.search.isVisible == false ? "none" : "inline-block");
    self.dom.input = self.dom.holder.style("text-align", "center").append("input").attr("type", "search").attr("class", "tree_search").attr("placeholder", "Search").style("width", "180px").style("display", "block").on("input", (0, import_debounce.debounce)(self.onInput, 300));
    self.dom.resultDiv = self.dom.tip.d.style("border-left", self.opts.resultsHolder ? "" : "solid 1px rgb(133,182,225)").style("padding-left", "5px");
  };
  self.noResult = () => {
    self.clear();
    self.dom.resultDiv.append("div").text("No match").style("padding", "3px 3px 3px 0px").style("opacity", 0.5);
  };
  self.showTerms = (data) => {
    if (self.opts.disable_terms)
      data.lst.forEach((t) => {
        if (t.disabled) self.opts.disable_terms.push(t);
      });
    self.clear({ hide: !data.lst.length });
    if (data.lst.length) {
      self.dom.resultDiv.append("table").selectAll().data(data.lst).enter().append("tr").each(self.showTerm);
    }
  };
  self.showTerm = function(term) {
    const tr = select_default(this);
    const button = tr.append("td").text(term.name);
    if (term.type) {
      button.style("cursor", "pointer").attr("class", "sja_menuoption").attr("data-testid", `sjpp-mass-search-term-${term.id}`).on("click", async () => {
        if (self.state.nav?.activeTab == 0) {
          await self.app.dispatch({
            type: "tab_set",
            activeTab: 1
          });
        }
        self.app.dispatch({
          type: "plot_create",
          config: {
            chartType: term.type == "survival" ? "survival" : "summary",
            term: { term }
          }
        });
        self.clear({ hide: true });
      });
    } else {
      button.style("padding", "5px 10px").style("opacity", 0.5);
    }
    tr.append("td").text((term.__ancestorNames || []).join(" > ")).style("opacity", 0.5).style("font-size", ".7em");
  };
  self.clear = (opts = {}) => {
    self.dom.tip.clear();
    if (opts.hide) self.dom.tip.hide();
    else self.dom.tip.showunder(self.dom.holderNode);
  };
}
function setInteractivity(self) {
  self.onInput = async () => {
    const str = self.dom.input.property("value");
    try {
      await self.doSearch(str);
    } catch (e) {
      self.clear();
      sayerror(self.dom.resultDiv, "Error: " + (e.message || e));
      if (e.stack) console.log(e.stack);
    }
  };
}

// mass/sessionBtn.js
var MassSessionBtn = class _MassSessionBtn {
  static type = "sessionBtn";
  constructor() {
    this.type = _MassSessionBtn.type;
    this.route = "termdb";
    this.embedderOrigin = window.location.origin;
    this.hostURL = sessionStorage.getItem("hostURL") || this.embedderOrigin;
  }
  async init(appState) {
    const tip = new Menu({ padding: "0px" });
    const copytip = new Menu({ padding: "5px" });
    this.dom = {
      button: this.opts.button,
      tip,
      copytip
    };
    this.dom.button.on("click", (event2) => {
      event2.stopPropagation();
      this.dom.tip.clear();
      this.showMenu();
    });
    this.dslabel = appState.vocab.dslabel;
    this.savedSessions = JSON.parse(localStorage.getItem("savedMassSessions") || `{}`);
    this.requiredAuth = appState.termdbConfig?.requiredAuth?.find((a) => a.route == this.route && a.type == "jwt");
  }
  async showMenu() {
    this.dom.tip.clear().d.style("padding", 0);
    this.dom.tip.d.attr("id", "sjpp-session-menu");
    const gt = `<span style='margin-left: 24px; float: right'>&gt;</span>`;
    const options = [
      { label: `Open`, title: "Recover a saved session", callback: this.open },
      { label: `Save`, title: "Save the current view", callback: this.save },
      { label: `Share`, title: "Share the current view", callback: this.getSessionUrl }
    ];
    if (!this.serverCachedSessions) await this.setServerCachedSessions();
    if (Object.keys(this.savedSessions).length || Object.keys(this.serverCachedSessions).length) {
      options.push({ label: `Delete`, title: "Delete a saved session", callback: this.delete });
    }
    this.dom.tip.clear().d.selectAll(".sja_menuoption sja_sharp_border").data(options).enter().append("div").attr("class", "sja_menuoption sja_sharp_border").attr("data-testid", (d) => `sjpp-session-btn-${d.label.toLowerCase().replace(/\s/g, "-")}`).attr("aria-label", (d) => d.title).html((d) => d.label).on("click", (event2, d) => {
      this.dom.tip.clear().d.style("padding", "10px");
      event2.stopPropagation();
      this.showBackBtn();
      d.callback.call(this);
    });
    this.dom.tip.showunder(this.dom.button.node());
  }
  async open() {
    const radioName = `sjpp-session-open-radio-` + Math.random().toString().slice(-6);
    this.dom.tip.d.append("div").style("display", "none").style("padding", "3px 9px").html(`
			<b>Open in</b>
			<label>
				<input type='radio' name='${radioName}' value='new' style='margin-right: 0; vertical-align: bottom'/>
				<span>a new tab</span>
			</label>
			<label style='margin-left: 5px'>
				<input type='radio' name='${radioName}' value='current' checked=checked style='margin-right: 0; vertical-align: bottom'/>
				<span>current tab</span>
			</label>
		`);
    const t = await this.listSessions({
      trClickHandler: async (event2, d) => {
        const { loc, id: id2 } = d;
        if (!id2) return;
        if (loc.includes("browser")) {
          this.sessionName = id2;
          const state = structuredClone(this.savedSessions[id2]);
          await preprocessState(state, this.app);
          const targetWindow = this.dom.tip.d.node().querySelector(`[name="${radioName}"]:checked`).value;
          if (targetWindow == "current") {
            this.app.dispatch({ type: "app_refresh", state });
          } else if (window.location.origin == this.hostURL) {
            window.open(`/?mass-session-id=${id2}&src=browser`);
          } else {
            if (state.embedder) parentCorsMessage({ state });
            else {
              const { protocol, host, search, origin, href } = window.location;
              const embedder = { protocol, host, search, origin, href };
              parentCorsMessage({ state: Object.assign({ embedder }, state) });
            }
          }
          this.dom.tip.hide();
        } else if (loc == "server") {
          const headers = await this.app.vocabApi.mayGetAuthHeaders(this.route);
          const body = { id: id2, route: this.route, dslabel: this.dslabel, embedder: window.location.hostname };
          const res = await dofetch3(`/massSession?`, { headers, body });
          if (!res.state) throw res.error || "unable to get the cached session from the server";
          await preprocessState(res.state, this.app);
          this.savedSessions[id2] = res.state;
          const targetWindow = this.dom.tip.d.node().querySelector(`[name="${radioName}"]:checked`).value;
          if (targetWindow == "current") {
            this.app.dispatch({ type: "app_refresh", state: res.state });
          } else if (window.location.origin == this.hostURL) {
            window.open(`/?mass-session-id=${id2}&src=cred&dslabel=${this.dslabel}&route=${this.route}`);
          } else {
            parentCorsMessage(res);
          }
          this.dom.tip.hide();
        }
      }
    });
    t.headtr.select("th").html("Open from");
    const tr1 = t.tbody.insert("tr", "tr");
    tr1.append("td").style("text-align", "center").style("padding", "3px 9px").html("Local file");
    const label = tr1.append("td").style("text-align", "left").append("label");
    label.append("span").style("padding", "3px 9px").style("text-decoration", "underline").style("cursor", "pointer").html("Choose File");
    label.append("input").attr("type", "file").attr("placeholder", "file name").style("opacity", 0).style("width", "0.1px").style("height", "0.1px").style("position", "absolute").on("change", async () => {
      const file = event.target.files.item(0);
      const json = await file.text();
      let sessionName = file.name;
      if (this.savedSessions[sessionName]) {
        sessionName = prompt(
          `Leave as-is to overwrite a session with the same name, or enter a different session name.`,
          sessionName
        );
      }
      this.sessionName = sessionName;
      const state = JSON.parse(json);
      await preprocessState(state, this.app);
      this.savedSessions[sessionName] = state;
      localStorage.setItem("savedMassSessions", JSON.stringify(this.savedSessions));
      const targetWindow = this.dom.tip.d.node().querySelector(`[name="${radioName}"]:checked`).value;
      if (targetWindow == "current") {
        this.app.dispatch({ type: "app_refresh", state });
      } else if (window.location.origin == this.hostURL) {
        window.open(`/?mass-session-id=${sessionName}&src=browser`);
      } else {
        if (state.embedder) parentCorsMessage({ state });
        else {
          const { protocol, host, search, origin, href } = window.location;
          const embedder = { protocol, host, search, origin, href };
          parentCorsMessage({ state: Object.assign({ embedder }, state) });
        }
      }
      this.dom.tip.hide();
    });
  }
  async listSessions(opts = {}) {
    const table = this.dom.tip.d.append("table").attr("class", "sjpp-controls-table");
    const headtr = table.append("thead").append("tr");
    headtr.selectAll("th").data(["Cache Location", "Session ID"]).enter().append("th").style("text-align", (d, i) => i === 0 ? "center" : "left").style("padding", "3px 9px").html((d) => d);
    const sessionIds = Object.keys(this.savedSessions).map((id2) => ({ loc: "browser", id: id2 }));
    if (!this.serverCachedSessions) await this.setServerCachedSessions();
    sessionIds.push(...this.serverCachedSessions.map((id2) => ({ loc: "server", id: id2 })));
    const tbody = table.append("tbody");
    const trs = tbody.selectAll("tr").data(sessionIds).enter().append("tr").on("click", opts.trClickHandler || null);
    trs.selectAll("td").data((d) => [d, d]).enter().append("td").style("text-align", (d, i) => i === 0 ? "center" : "left").style("padding", "3px 9px").style("cursor", "pointer").html((d, i) => i === 0 ? d.loc : d.id);
    if (!this.serverCachedSessions.length && this.requiredAuth && !this.app.vocabApi.hasVerifiedToken()) {
      const tbody2 = table.append("tbody").append("tr").selectAll("td").data(["server", "requires sign-in"]).enter().append("td").style("text-align", (d, i) => i === 0 ? "center" : "left").style("padding", "3px 9px").html((d) => d);
    }
    return { table, headtr, tbody, trs };
  }
  async setServerCachedSessions() {
    const state = this.app.getState();
    this.requiredAuth = state.termdbConfig?.requiredAuth?.find((a) => a.route == this.route && a.type == "jwt");
    if (!this.requiredAuth) {
      this.serverCachedSessions = [];
      return;
    }
    const headers = this.app.vocabApi.mayGetAuthHeaders(this.route);
    const body = { route: this.route, dslabel: this.dslabel, embedder: window.location.hostname };
    const res = await dofetch3("/sessionIds", { headers, body });
    this.serverCachedSessions = res.sessionIds || [];
  }
  async save(d) {
    const div = this.dom.tip.d;
    const inputDiv = div.append("div");
    inputDiv.append("span").html("Save as");
    const sessionNames = Object.keys(this.savedSessions);
    if (!this.serverCachedSessions) await this.setServerCachedSessions();
    sessionNames.push(...this.serverCachedSessions.filter((d2) => !sessionNames.includes(d2)));
    const placeholder = this.sessionName || "unnamed-session";
    const input = inputDiv.append("input").attr("type", "text").attr("placeholder", placeholder).style("width", "220px").on("input", () => {
      searchResultDiv.selectAll("*").remove();
      const value = input.property("value");
      const exactMatch = sessionNames.filter((s) => s === value);
      const startsWith = sessionNames.filter((s) => s.startsWith(value));
      const includes = sessionNames.filter((s) => s.includes(value) && s !== value && !startsWith.includes(s));
      searchResultDiv.selectAll("div").data([...exactMatch, ...startsWith, ...includes]).enter().append("div").attr("class", "sja_menuoption").attr("data-testid", `sjpp-save-session-option`).html((d2) => d2).on("click", (event2, d2) => {
        input.property("value", d2);
        searchResultDiv.selectAll("*").remove();
      });
    });
    const searchResultDiv = div.append("div");
    const submitDiv = div.append("div");
    submitDiv.append("span").html("Save to&nbsp;");
    submitDiv.append("button").style("min-width", "80px").html("Browser").attr(
      "title",
      `Save the session in your current browser's cache. The session can be easily recovered, but not shared among your other devices`
    ).on("click", () => {
      this.sessionName = input.property("value") || placeholder;
      this.savedSessions[this.sessionName] = this.app.getState();
      localStorage.setItem("savedMassSessions", JSON.stringify(this.savedSessions));
      this.confirmAction(`Cached '<b>${this.sessionName}</b>' in browser`);
    });
    submitDiv.append("button").style("min-width", "80px").html("File").attr(
      "title",
      `Save the session into a local file. The session can be easily recoved using the 'Open from local file' option.`
    ).on("click", () => {
      const name = input.property("value") || placeholder;
      this.savedSessions[name] = this.app.getState();
      this.download(name);
      this.confirmAction(`Downloaded '<b>${name}</b>'`);
    });
    if (this.requiredAuth) {
      const requiresSignIn = this.app.vocabApi.hasVerifiedToken() ? "" : "Requires sign-in. ";
      submitDiv.append("button").style("min-width", "80px").html("Server").attr(
        "title",
        `${requiresSignIn}Save the session into a remote server. The session can be easily shared across your different devices and recovered using the 'Open from server' option.`
      ).property("disabled", !this.app.vocabApi.hasVerifiedToken()).on("click", async () => {
        if (!this.app.vocabApi.hasVerifiedToken()) {
          alert("Requires sign-in");
          return;
        }
        const name = input.property("value") || placeholder;
        this.savedSessions[name] = this.app.getState();
        const res = await this.getSessionUrl(name);
        if (res.id != name) throw `error saving ${name}`;
        this.confirmAction(`Saved '<b>${name}</b>' on the server`);
      });
    }
  }
  download(name = "") {
    const sessionName = name || this.sessionName;
    const ext = sessionName?.endsWith(".txt") ? "" : ".txt";
    const filename = `${sessionName}${ext}`;
    to_textfile(filename, JSON.stringify(this.savedSessions[sessionName]));
  }
  async getSessionUrl(filename = "") {
    const headers = await this.app.vocabApi.mayGetAuthHeaders("termdb");
    const state = structuredClone(this.app.getState());
    const { protocol, host, search, origin, href } = window.location;
    state.embedder = { protocol, host, search, origin, href };
    if (filename) {
      state.__sessionFor__ = {
        route: this.route,
        filename,
        dslabel: this.dslabel,
        embedder: window.location.hostname
      };
    }
    const res = await dofetch3("/massSession", {
      headers,
      method: "POST",
      body: JSON.stringify(state)
    });
    if (filename) {
      return res;
    } else {
      const url = `${this.hostURL}/?mass-session-id=${res.id}&noheader=1`;
      this.dom.tip.showunder(this.dom.button.node());
      const linkDiv = this.dom.tip.d.append("div").style("margin", "10px");
      linkDiv.append("div").style("display", "flex").style("margin-bottom", "12px").html(`Open or copy the session link.`);
      const a = linkDiv.append("a").style("display", "none").attr("href", url).attr("target", "_blank").html(res.id);
      linkDiv.append("button").style("cursor", "pointer").text("Open link").on("click", () => {
        a.node().click();
      });
      linkDiv.append("button").style("cursor", "pointer").style("margin-left", "10px").text("Copy link").on("click", async (event2) => {
        await navigator.clipboard.writeText(url);
        this.dom.copytip.clear().showunder(event2.target);
        this.dom.copytip.d.append("div").html("&#10003;");
        setTimeout(() => {
          this.dom.copytip.hide();
        }, 1e3);
      });
      if (this.hostURL != window.location.origin) {
        a.on("click", (event2) => {
          event2.preventDefault();
          parentCorsMessage({ state }, "noredirect");
          return false;
        });
      }
      linkDiv.append("div").html(`
					<br>
					<div style="max-width: 400px; font-size: 1em; opacity:.6">
					<span>NOTES</span>
					<ul>
					<li>A recovered session may hide data or views to users that are not authorized to access the saved datasets or features.</li>
					<li>This session will be saved for ${this.opts.massSessionDuration} days.</li>
					</ul>
					</div>`);
      setTimeout(() => {
        this.dom.button.property("disabled", false);
      }, 1e3);
    }
  }
  async delete() {
    const t = await this.listSessions({
      trClickHandler: function(event2) {
        const input = this.lastChild.querySelector("input");
        const checked = event2.target == input ? input.checked : !input.checked;
        select_default(this).style("text-decoration", checked ? "line-through" : "");
        if (event2.target != input) input.checked = checked;
        const checkedRows = t.table.node().querySelectorAll("input:checked");
        submitBtn.property("disabled", checkedRows.length ? false : true);
      }
    });
    t.headtr.append("th").html("Delete");
    t.trs.each(function(d) {
      select_default(this).append("td").style("text-align", "center").append("input").attr("type", "checkbox").attr("value", d.id);
    });
    const submitBtn = this.dom.tip.d.append("div").style("text-align", "center").append("button").html("Delete selected sessions").property("disabled", true).on("click", async () => {
      const inputs = t.table.node().querySelectorAll("input");
      const sessionIdsDeletedFromServer = [];
      for (const input of inputs) {
        if (select_default(input).property("checked")) {
          const d = input.parentNode.parentNode.__data__;
          if (d.loc == "browser") {
            delete this.savedSessions[input.value];
          } else if (d.loc == "server") {
            delete this.serverCachedSessions[input.value];
            sessionIdsDeletedFromServer.push(input.value);
          } else throw `unknown cache location=${d.loc}`;
        }
      }
      localStorage.setItem("savedMassSessions", JSON.stringify(this.savedSessions));
      try {
        const headers = await this.app.vocabApi.mayGetAuthHeaders("termdb");
        const body = {
          ids: sessionIdsDeletedFromServer,
          route: this.route,
          dslabel: this.dslabel,
          embedder: window.location.hostname
        };
        const res = dofetch3(`/massSession?`, { method: "DELETE", headers, body });
      } catch (e) {
        throw e;
      }
      this.dom.tip.hide();
    });
  }
  showBackBtn() {
    this.dom.tip.d.append("div").attr("class", "sja_clbtext2").style("margin-bottom", "10px").style("cursor", "pointer").html(`&lt; Session Menu`).on("click", () => this.showMenu());
  }
  confirmAction(html) {
    this.dom.tip.clear().d.append("div").html(html).transition().delay(3e3).duration(1e3).style("opacity", 0);
    setTimeout(() => {
      this.dom.tip.hide();
    }, 3500);
  }
};
async function preprocessState(state, app) {
  delete state.termdbConfig;
  if (state.plots) {
    const promises = [];
    for (const plot of state.plots) {
      promises.push(
        (async () => {
          const _ = await importPlot(plot.chartType);
          return await _.getPlotConfig(plot, app);
        })()
      );
    }
    try {
      await Promise.all(promises);
    } catch (e) {
      console.log(e);
      app.printError(e);
    }
  }
}
var sessionBtnInit = getCompInit(MassSessionBtn);

// mass/about.ts
var cohortTableActiveColor = "yellow";
var MassAbout = class _MassAbout {
  constructor(opts) {
    this.initCohort = (appState) => {
      if (this.selectCohort == null) return;
      if (!this.selectCohort.values) return;
      const instanceNum2 = this.instanceNum;
      const activeCohort = appState.activeCohort;
      const app = this.app;
      this.dom.cohortOpts.append("table").attr("data-testid", "sjpp-about-cohort-options-table").selectAll("tr").data(this.selectCohort.values).enter().append("tr").each(function(d, i, nodes) {
        const tr = select_default(nodes[i]);
        const td0 = tr.append("td");
        const radioName = "sja-termdb-cohort-" + instanceNum2;
        const radioId = radioName + "-" + i;
        td0.append("input").style("scale", "1.2").attr("type", "radio").attr("name", radioName).attr("id", radioId).attr("value", i).property("checked", i === activeCohort).style("margin-right", "5px").style("margin-left", "0px").on("click", async (event2) => {
          const state = app.getState();
          const clearOnChange = state.termdbConfig.selectCohort.clearOnChange;
          if (clearOnChange) {
            const subactions = [];
            const toBeCleared = [];
            const plots = state.plots;
            const filter = state.termfilter.filter.lst.find((f) => f.tag == "filterUiRoot")?.lst;
            const groups = state.groups;
            if (clearOnChange.plots && plots?.length) toBeCleared.push("plots");
            for (const plot of plots) {
              subactions.push({
                type: "plot_delete",
                id: plot.id
              });
            }
            if (clearOnChange.filter && filter?.length) toBeCleared.push("filters");
            subactions.push({
              type: "filter_replace",
              filter: {
                type: "tvslst",
                in: true,
                join: "",
                tag: "filterUiRoot",
                lst: []
              }
            });
            if (clearOnChange.groups && groups?.length) {
              toBeCleared.push("groups");
              for (const group of groups) {
                subactions.push({
                  type: "delete_group",
                  name: group.name
                });
              }
              for (const term of state.customTerms) {
                subactions.push({
                  type: "delete_customTerm",
                  name: term.name
                });
              }
            }
            subactions.push({ type: "cohort_set", activeCohort: i });
            if (toBeCleared.length) {
              const confirm = window.confirm(
                `Changing the cohort will clear all ${joinByComma(
                  toBeCleared
                )}. To proceed, click "OK". To save the session, click "Cancel" and then click the "Session" button at the top of the page.`
              );
              if (!confirm) {
                event2.preventDefault();
                return;
              }
            }
            app.dispatch({
              type: "app_refresh",
              subactions
            });
          } else app.dispatch({ type: "cohort_set", activeCohort: i });
        });
        td0.append("label").style("font-size", "1.4em").attr("for", radioId).attr("colspan", 2).style("cursor", "pointer").html((d2) => d2.label);
        tr.selectAll("td").style("max-width", "600px").style("padding-bottom", "10px").style("padding-right", "20px").style("vertical-align", "top");
      });
      this.dom.cohortTable = this.subheader.append("div").attr("data-testid", "sjpp-about-cohort-table").style("margin-left", "12px");
      if (this.selectCohort.asterisk) {
        this.dom.cohortAsterisk = this.subheader.append("div").attr("data-testid", "sjpp-about-cohort-asterisk").style("margin", "10px").style("font-size", ".8em").text(this.selectCohort.asterisk);
      }
    };
    this.renderCohortsTable = async () => {
      if (!this.dom.cohortTable) return;
      this.dom.cohortTable.selectAll("*").remove();
      const columns = [{ label: "Feature" }];
      const rows = [];
      const result = await this.app.vocabApi.getCohortsData();
      if ("error" in result) throw result.error;
      if (this.selectCohort?.values) {
        const values = this.selectCohort.values;
        result.cohorts.sort((a, b) => {
          const aIndex = values.findIndex((v) => v.keys.toString() == a.cohort);
          const bIndex = values.findIndex((v) => v.keys.toString() == b.cohort);
          if (aIndex < bIndex) return -1;
          if (aIndex > bIndex) return 1;
          return 0;
        });
      }
      if (!result.cfeatures.length) return;
      for (const feature of result.features) rows.push([{ value: feature.name }]);
      for (const cohort of result.cohorts) {
        if (cohort.subcohorts?.length) continue;
        columns.push({ label: cohort.name });
        for (const [i, feature] of result.features.entries()) {
          const cf = result.cfeatures.find((cf2) => cf2.idfeature === feature.idfeature && cf2.cohort === cohort.cohort);
          if (cf) rows[i].push({ value: cf.value });
        }
      }
      renderTable({
        rows,
        columns,
        div: this.dom.cohortTable,
        showLines: false,
        maxHeight: "60vh",
        header: { style: { "font-size": "1.2em", "font-weight": "bold" } }
      });
      this.dom.cohortTable.select("table").style("border-collapse", "collapse");
      this.dom.cohortTable.selectAll(`tbody > tr > td`).style("background-color", "transparent").style("padding", "6px");
      const state = this.app.getState();
      const selectCohort = state.termdbConfig.selectCohort;
      const activeCohort = state.activeCohort;
      const keys = selectCohort.values[activeCohort].keys;
      let selector = `tbody > tr > td:nth-child(${activeCohort + 2})`;
      const combined = keys.length > 1;
      if (combined) {
        selector = "";
        for (const key of keys) {
          const i = result.cohorts.map((c) => c.cohort).indexOf(key);
          if (selector !== "") selector += ",";
          selector += `tbody > tr > td:nth-child(${i + 2})`;
        }
      }
      const activeColumns = this.dom.cohortTable.selectAll(selector);
      const color = state.termdbConfig.massNav?.activeColor || cohortTableActiveColor;
      activeColumns.style("background-color", color);
    };
    this.initCustomHtml = () => {
      if (this.selectCohort != null) return;
      if (!this.aboutOverrides?.html) return;
      this.subheader.append("div").attr("data-testid", "sjpp-custom-about-content").style("padding", "10px").html(this.aboutOverrides.html);
    };
    this.initActiveItems = () => {
      if (!this.aboutOverrides?.activeItems) return;
      const div = this.subheader.append("div").attr("data-testid", "sjpp-custom-about-activeItems").style("padding", "0px 0px 10px 20px");
      for (const item of this.aboutOverrides.activeItems.items) {
        div.append("div").style("display", "inline-block").style("margin", "5px").attr("class", "sja_menuoption").attr("data-testid", "sjpp-custom-active-item-btn").html(item.title).on("click", async () => {
          await this.app.dispatch({
            type: "tab_set",
            activeTab: 1
          });
          this.app.dispatch({
            type: "plot_create",
            id: getId(),
            config: structuredClone(item.plot)
          });
        });
      }
    };
    this.initDisclaimer = () => {
      const d = this.aboutOverrides?.disclaimer;
      if (!d) return;
      const div = this.subheader.append("div").attr("data-testid", "sjpp-custom-about-disclaimer").style("margin", "10px 0 20px 10px").style("opacity", 0.7).style("padding", "10px").style("background-color", "#f8f9fa").style("border-left", "4px solid #6c757d").style("font-size", "0.8em");
      div.append("div").style("margin-bottom", "8px").style("font-weight", "bold").text("DISCLAIMER");
      div.append("div").text(d.text);
    };
    this.showServerInfo = () => {
      const state = this.app.getState();
      const about = state.termdbConfig.massNav?.tabs?.about;
      if (!about && !this.app.opts.pkgver && !this.app.opts.launchDate) return;
      const dataRelease = about?.dataRelease;
      const additionalInfo = about?.additionalInfo;
      const div = this.subheader.append("div").attr("data-testid", "sjpp-about-server-info").style("margin", "10px").style("font-size", ".8em");
      const htmlArr = [];
      if (dataRelease) {
        htmlArr.push(`Data Release: <a href=${dataRelease.link} target=_blank>${dataRelease.version}</a>`);
      }
      if (this.app.opts.pkgver) {
        htmlArr.push(
          `Software Release: <a href=https://github.com/stjude/proteinpaint/pkgs/container/ppfull target=_blank>${this.app.opts.pkgver}</a>`
        );
      }
      if (this.app.opts.launchDate) {
        htmlArr.push(`Server Launched: ${this.app.opts.launchDate}`);
      }
      if (additionalInfo) {
        htmlArr.push(additionalInfo);
      }
      div.append("div").html(htmlArr.join("; "));
    };
    this.opts = opts;
    this.type = _MassAbout.type;
    this.app = opts.app;
    this.subheader = opts.subheader;
    this.instanceNum = opts.instanceNum;
    this.aboutOverrides = opts.aboutOverrides;
    this.selectCohort = opts.selectCohort;
    this.dom = {
      cohortDescription: this.subheader.append("div").attr("data-testid", "sjpp-about-cohort-desc").style("margin-left", "10px")
    };
    if (opts?.selectCohort?.title) {
      this.dom.cohortTitle = opts.subheader.append("h2").attr("data-testid", "sjpp-about-cohort-title").style("margin-left", "10px").text(opts.selectCohort.title);
    }
    if (opts.selectCohort?.prompt) {
      this.dom.cohortPrompt = this.subheader.append("div").attr("data-testid", "sjpp-about-cohort-prompt").style("margin-left", "10px").style("padding-top", "30px").style("padding-bottom", "10px").style("font-weight", "bold").style("font-size", "1.2em").text(opts.selectCohort.prompt);
    }
    if (opts.selectCohort) {
      this.dom.cohortOpts = this.subheader.append("div").style("margin-bottom", "30px").style("margin-left", "10px");
    }
  }
  static {
    this.type = "about";
  }
  /* render all contents into this.subheader
  do it through init() means that contents are rendered just once on launching mass ui and won't be rerendered or updated
  since there's no reason to update about tab contents while user is interacting with mass ui
  */
  init(appState) {
    this.initCohort(appState);
    this.initCustomHtml();
    this.initActiveItems();
    this.initDisclaimer();
    this.showServerInfo();
  }
  async main() {
    if (this.state.nav.activeTab !== 0) return;
    await this.renderCohortsTable();
    if (this.opts.selectCohort) {
      if (this.opts.selectCohort.description) {
        this.dom.cohortDescription.html(this.opts.selectCohort.description);
      } else if (this.opts.selectCohort.descriptionByCohort) {
        this.dom.cohortDescription.html(
          this.opts.selectCohort.descriptionByCohort[this.state.termdbConfig.selectCohort.values[this.state.activeCohort].keys.join(",")]
        );
      }
    }
  }
};
var aboutInit = getCompInit(MassAbout);
function joinByComma(arr) {
  if (!arr.length) return "";
  else if (arr.length == 1) return arr[0];
  else if (arr.length == 2) return arr.join(" and ");
  else return `${arr.slice(0, -1).join(", ")}, and ${arr.slice(-1)}`;
}

// mass/chat.ts
var MIN_PROMPT_LENGTH_FOR_OMNISEARCH = 3;
var MAX_PROMPT_LENGTH_FOR_OMNISEARCH = 15;
var MIN_PROMPT_LENGTH_FOR_CHAT = 5;
var MassAiChatBot = class _MassAiChatBot {
  static {
    this.type = "chat";
  }
  constructor(opts) {
    this.type = _MassAiChatBot.type;
    this.opts = opts;
    this.app = opts.app;
    this.opts.usecase = this.opts.usecase ? this.opts.usecase : { target: "summary", detail: "term" };
    this.opts.targetType = this.opts.targetType ? this.opts.targetType : "Dictionary Variables";
    setRenderers2(this);
  }
  getState(appState) {
    return {
      cohortStr: appState.activeCohort == -1 || !appState.termdbConfig.selectCohort ? "" : appState.termdbConfig.selectCohort.values[appState.activeCohort].keys.slice().sort().join(","),
      search: appState.search,
      nav: appState.nav
    };
  }
  async init() {
    this.initDom();
  }
  // Search method, adapted from MassSearch.doSearch
  async doSearch(prompt2) {
    if (!prompt2) {
      this.clear({ hide: true });
      return;
    }
    const cohortStr = this.getState(this.app.getState()).cohortStr;
    const data = await this.app.vocabApi.findTerm(prompt2, cohortStr, this.opts.usecase, this.opts.targetType);
    if (!data.lst || data.lst.length == 0) {
      if (!this.dom.noMatchShown) {
        this.dom.noMatchShown = true;
        this.noResult();
      }
    } else {
      this.dom.noMatchShown = false;
      this.showTerms(data);
    }
  }
  initDom() {
    this.dom = {
      tip: new Menu({ padding: "5px" }),
      div: this.opts.subheader,
      error: this.opts.subheader.append("div").attr("id", "sjpp-corrVolcano-error").style("opacity", 0.75),
      bubbleDiv: this.opts.subheader.append("div").attr("class", "sjpp_show_scrollbar").style("margin", "5px 20px 0px 20px").style("height", "200px").style("overflow", "auto").style("scroll-behavior", "smooth")
    };
    const inputSel = this.dom.div.append("input").attr("type", "search").style("margin", "15px").style("padding", "17px").style("border-radius", "34px").attr("size", 70).attr("placeholder", "What are you looking for?");
    this.dom.inputNode = inputSel.node();
    this.dom.resultDiv = this.dom.tip.d.style("border-left", "solid 1px rgb(133,182,225)").style("padding-left", "5px").attr("tabindex", -1);
    inputSel.on("keyup.search", async (event2) => {
      if (keyupEnter(event2)) return;
      const prompt2 = event2.target.value.trim();
      if (!prompt2) {
        this.dom.noMatchShown = false;
        this.clear({ hide: true });
        return;
      }
      if (MIN_PROMPT_LENGTH_FOR_OMNISEARCH <= prompt2.length && prompt2.length <= MAX_PROMPT_LENGTH_FOR_OMNISEARCH) {
        try {
          await this.doSearch(prompt2);
        } catch (e) {
          if (e.stack) console.log(e.stack);
          sayerror(this.dom.resultDiv, "Error: " + (e.message || e));
        }
      } else {
        this.dom.noMatchShown = false;
        this.clear({ hide: true });
        return;
      }
    }).on("keyup.submit", async (event2) => {
      if (!keyupEnter(event2)) return;
      const prompt2 = event2.target.value.trim();
      if (!prompt2) return;
      this.addBubble({ msg: prompt2, me: 1 });
      event2.target.value = "";
      const serverBubble = this.addBubble({ msg: "..." });
      if (prompt2.length <= MIN_PROMPT_LENGTH_FOR_CHAT) {
        serverBubble.text("Your prompt is too short. Enter a longer prompt.");
        return;
      }
      const body = {
        genome: this.app.vocabApi.vocab.genome,
        dslabel: this.app.vocabApi.vocab.dslabel,
        filter: this.app.vocabApi.state.termfilter?.filter,
        prompt: prompt2
      };
      try {
        const data = await dofetch3("termdb/chat", { body });
        if (data.error) throw data.error;
        const result = data;
        if (result.type === "text") {
          serverBubble.text(result.text);
        } else if (result.type === "html") {
          serverBubble.html(result.html);
        } else if (result.type === "plot") {
          this.app.dispatch({
            type: "plot_create",
            config: result.plot
          });
          serverBubble.text(`${result.msg ? result.msg + ". " : ""}Please refer to the plot generated below.`);
        }
      } catch (e) {
        if (e.stack) console.log(e.stack);
        serverBubble.html(`Error: ${e.message || e}`);
      }
    }).node().focus();
  }
  addBubble(arg) {
    const bubble = this.dom.bubbleDiv.append("div").style("padding", "10px").html(`${arg.me ? '<span style="font-size:.7em">[ME]</span> ' : ""}${arg.msg}`);
    if (arg.me) bubble.style("background", "#f1f1f1");
    const n = this.dom.bubbleDiv.node();
    n.scrollTop = n.scrollHeight;
    return bubble;
  }
  main() {
  }
};
var chatInit = getCompInit(MassAiChatBot);
function setRenderers2(self) {
  self.noResult = () => {
    self.clear();
    self.dom.resultDiv.append("div").text("No match. Using the chatbot...").style("padding", "3px 3px 3px 0px").style("opacity", 0.5);
    setTimeout(() => {
      self.clear({ hide: true });
    }, 1500);
  };
  self.showTerms = (data) => {
    if (self.opts.disable_terms)
      data.lst.forEach((t) => {
        if (t.disabled) self.opts.disable_terms.push(t);
      });
    self.clear({ hide: !data.lst.length });
    if (data.lst.length) {
      self.dom.resultDiv.append("table").selectAll().data(data.lst).enter().append("tr").each(self.showTerm);
    }
  };
  self.showTerm = function(term) {
    const tr = select_default(this);
    const button = tr.append("td").text(term.name);
    if (term.type) {
      button.style("cursor", "pointer").attr("class", "sja_menuoption").attr("data-testid", `sjpp-mass-chat-term-${term.id}`).on("click", async () => {
        if (self.state?.nav?.activeTab == 0) {
          await self.app.dispatch({ type: "tab_set", activeTab: 1 });
        }
        self.app.dispatch({
          type: "plot_create",
          config: {
            chartType: term.type == "survival" ? "survival" : "summary",
            term: { term }
          }
        });
        self.dom.inputNode.value = "";
        self.clear({ hide: true });
      });
    } else {
      button.style("padding", "5px 10px").style("opacity", 0.5);
    }
    tr.append("td").text((term.__ancestorNames || []).join(" > ")).style("opacity", 0.5).style("font-size", ".7em");
  };
  self.clear = (opts = {}) => {
    self.dom.tip.clear();
    if (opts.hide) self.dom.tip.hide();
    else self.dom.tip.showunder(self.dom.inputNode);
  };
}

// mass/nav.js
var activeTabBgColor = "#ececec";
var activeTabBgColorHover = "#e0e0e0";
var activeTabTextColor = "black";
var inactiveTabBgColorHover = "#fcfced";
var inactiveTabTextColor = "gray";
var instanceNum = 0;
var idPrefix = "_MASS_AUTOID_" + Math.random().toString().slice(-6);
var id = (+/* @__PURE__ */ new Date()).toString().slice(-8);
var headtip = new Menu({ padding: "0px", offsetX: 0, offsetY: 0 });
headtip.d.style("z-index", 5555);
var aboutTab = { top: "ABOUT", mid: "", btm: "", subheader: "about" };
var chartTab = { top: "CHARTS", mid: "NONE", btm: "", subheader: "charts" };
var groupsTab = { top: "GROUPS", mid: "NONE", btm: "", subheader: "groups" };
var filterTab = { top: "FILTER", mid: "NONE", btm: "", subheader: "filter" };
var chatTab = { top: "CHAT", mid: "", btm: "", subheader: "chat" };
function getId() {
  return idPrefix + "_" + id++;
}
var TdbNav = class _TdbNav {
  static type = "nav";
  constructor(opts) {
    this.type = _TdbNav.type;
    this.instanceNum = instanceNum++;
    this.tabs = [];
    this.activeTab = 0;
    this.activeCohort = 0;
    this.samplecounts = {};
    this.searching = false;
    this.massSessionDuration = opts.massSessionDuration;
    this.sessionDaysLeft = opts.app.opts.sessionDaysLeft || null;
    this.sessionId = opts.app.opts.sessionId || null;
    this.pkgver = opts.pkgver || null;
    setInteractivity2(this);
    setRenderers3(this);
  }
  async init(appState) {
    try {
      this.cohortFilter = getFilterItemByTag(appState.termfilter.filter, "cohortFilter");
      this.initUI(appState);
      if (appState?.termdbConfig?.selectCohort) {
        this.dom.tds.filter((d) => d.colNum === 0).style("display", "");
        this.cohortNames = appState.termdbConfig.selectCohort.values.map((d) => d.keys.slice().sort().join(","));
      }
      this.components = await multiInit({
        search: searchInit({
          app: this.app,
          holder: this.dom.searchDiv,
          usecase: { target: "summary", detail: "term" },
          targetType: "Dictionary Variables"
        }),
        filter: filterRxCompInit({
          app: this.app,
          vocabApi: this.app.vocabApi,
          holder: this.dom.subheader.filter.append("div").style("margin", "5px"),
          hideLabel: this.opts.header_mode === "with_tabs",
          emptyLabel: "+Add new filter",
          callback: (filter) => {
            this.app.dispatch({
              type: "filter_replace",
              filter
            });
          }
        }),
        charts: chartsInit({
          app: this.app,
          holder: this.dom.subheader.charts,
          vocab: this.opts.vocab
        }),
        groups: groupsInit({
          app: this.app,
          holder: this.dom.subheader.groups,
          vocab: this.opts.vocab
        }),
        recover: recoverInit({
          app: this.app,
          holder: this.dom.recoverDiv,
          // TODO: ???? may limit the tracked state to only the filter, activeCohort ???
          getState: (appState2) => appState2,
          reactsTo: (action) => action.type != "plot_edit",
          maxHistoryLen: 5
        }),
        sessionBtn: sessionBtnInit({
          app: this.app,
          button: this.dom.saveBtn,
          massSessionDuration: this.opts.massSessionDuration,
          sessionDaysLeft: this.app.opts.sessionDaysLeft || null
        }),
        about: aboutInit({
          app: this.app,
          subheader: this.dom.subheader.about,
          instanceNum: this.instanceNum,
          aboutOverrides: appState?.termdbConfig?.massNav?.tabs?.about || null,
          selectCohort: appState?.termdbConfig?.selectCohort || null
        }),
        chat: chatInit({
          app: this.app,
          subheader: this.dom.subheader.chat
        })
      });
      this.mayShowMessage_sessionDaysLeft();
    } catch (e) {
      throw e;
    }
  }
  reactsTo(action) {
    if (action.type.includes("cache_termq")) return true;
    if (action.type.startsWith("filter")) return true;
    if (action.type.startsWith("cohort")) return true;
    if (action.type.startsWith("tab")) return true;
    if (action.type == "plot_prep") return true;
    if (action.type == "plot_create") return true;
    if (action.type == "plot_delete") return true;
    if (action.type == "app_refresh") return true;
    if (action.type.endsWith("_customTerm")) return true;
    if (action.type.endsWith("_group")) return true;
  }
  getState(appState) {
    return {
      searching: this.searching,
      // for detection of internal state change
      nav: appState.nav,
      activeCohort: appState.activeCohort,
      termdbConfig: appState.termdbConfig,
      filter: appState.termfilter.filter,
      plots: appState.plots,
      groups: appState.groups
    };
  }
  async main() {
    this.dom.tabDiv.style("display", this.state.nav.header_mode === "with_tabs" ? "inline-block" : "none");
    this.dom.tip.hide();
    this.activeTab = this.state.nav.activeTab;
    this.prevCohort = this.activeCohort;
    this.activeCohort = +this.state.activeCohort;
    this.filterUiRoot = getFilterItemByTag(this.state.filter, "filterUiRoot");
    this.cohortFilter = getFilterItemByTag(this.state.filter, "cohortFilter");
    if (this.cohortNames) {
      this.activeCohortName = this.cohortNames[this.activeCohort];
      if (this.activeCohort !== -1)
        this.activeCohortLabel = this.state.termdbConfig.selectCohort.values[this.activeCohort].shortLabel;
    }
    this.filterJSON = JSON.stringify(this.state.filter);
    this.cohortsData = await this.app.vocabApi.getCohortsData();
    if (this.state.nav.header_mode === "with_tabs") {
      if (!(this.activeCohort in this.samplecounts)) {
        this.samplecounts[this.activeCohort] = await this.app.vocabApi.getCohortSampleCount(this.activeCohortName);
      }
      if (!(this.filterJSON in this.samplecounts)) {
        if (!this.filterUiRoot || !this.filterUiRoot.lst.length) {
          this.samplecounts[this.filterJSON] = this.samplecounts[this.activeCohort];
        } else {
          const n = await this.app.vocabApi.getFilteredSampleCount(this.filterJSON);
          this.samplecounts[this.filterJSON] = n;
        }
      }
    }
    this.updateUI();
  }
};
var navInit = getCompInit(TdbNav);
function setRenderers3(self) {
  self.initUI = (appState) => {
    const verifiedToken = self.app.vocabApi.verifiedToken;
    const invalidTokenErrorHandling = appState.termdbConfig.invalidTokenErrorHandling;
    if (!verifiedToken && invalidTokenErrorHandling?.affectedCharts?.includes("*"))
      throw new Error(appState.termdbConfig.invalidTokenErrorHandling.errorMessage);
    const header = self.opts.holder.append("div").style("white-space", "nowrap");
    const massNav = appState.termdbConfig?.massNav || {};
    let titleDiv = header.append("div").style("display", "inline-block").style("float", "right").style("font-size", "1.1em").style("margin", "50px 10px 0 0").text(massNav?.title?.text);
    const tabDiv = header.append("div").style("display", "none").style("vertical-align", "bottom");
    const controlsDiv = header.append("div").style("vertical-align", "top").style("margin", "10px").style("display", "inline-block").style("float", "right");
    if (self.opts.header_mode === "only_buttons") {
      tabDiv.style("display", "none");
      titleDiv.style("display", "none");
      controlsDiv.style("display", "none");
    }
    self.opts.holder.attr("class", "sjpp-nav");
    self.dom = {
      holder: self.opts.holder,
      header,
      tabDiv,
      controlsDiv,
      searchDiv: controlsDiv.append("div").style("margin", "10px"),
      sessionDiv: controlsDiv.append("div").style("display", "inline-block"),
      recoverDiv: controlsDiv.append("div").style("display", "inline-block"),
      pdfDiv: controlsDiv.append("div").style("display", "inline-block").style("padding", "4px").style("vertical-align", "middle"),
      deleteAllDiv: controlsDiv.append("div").style("display", "inline-block").style("padding", "4px").style("vertical-align", "middle"),
      helpDiv: controlsDiv.append("div").style("display", "none"),
      sessionElapsedMessageDiv: controlsDiv.append("div").style("display", "none"),
      subheaderDiv: self.opts.holder.append("div").style("display", "block").style("border-bottom", "1px solid #000"),
      messageDiv: self.opts.holder.append("div").style("margin", "30px").style("display", "none"),
      titleDiv,
      tip: new Menu({ padding: "5px" })
    };
    icons["trash"](self.dom.deleteAllDiv, {
      handler: self.deletePlots,
      title: "Delete all plots. To revert, click Undo button"
    });
    icons["pdf"](self.dom.pdfDiv, {
      handler: self.opts.downloadPlots,
      title: "Generate a PDF of all the plots opened"
    });
    if (self.opts.header_mode === "with_cohortHtmlSelect") {
      self.dom.cohortStandaloneDiv = header.append("div").style("display", "inline-block").style("margin", "10px").style("vertical-align", "top");
      self.dom.cohortStandaloneDiv.append("label").html("Cohort: ");
      self.dom.cohortSelect = self.dom.cohortStandaloneDiv.append("select").on("change", async function() {
        self.app.dispatch({ type: "cohort_set", activeCohort: +this.value });
      });
      self.dom.cohortSelect.selectAll("option").data(appState.termdbConfig.selectCohort.values).enter().append("option").attr("value", (d, i) => i).property("selected", (d, i) => i === appState.activeCohort).html((d) => d.shortLabel);
    }
    self.dom.subheader = Object.freeze({
      // For either the COHORT or ABOUT tab
      about: self.dom.subheaderDiv.append("div").attr("data-testid", "sjpp-mass-nav-subheader-about").style("display", "none"),
      search: self.dom.subheaderDiv.append("div").attr("data-testid", "sjpp-mass-nav-subheader-search").style("display", "none"),
      groups: self.dom.subheaderDiv.append("div").attr("data-testid", "sjpp-mass-nav-subheader-groups").style("display", "none"),
      charts: self.dom.subheaderDiv.append("div").attr("data-testid", "sjpp-mass-nav-subheader-charts").style("display", "none"),
      filter: self.dom.subheaderDiv.append("div").attr("data-testid", "sjpp-mass-nav-subheader-filter").style("display", "none"),
      chat: self.dom.subheaderDiv.append("div").attr("data-testid", "sjpp-mass-nav-subheader-chat").style("display", "none")
      // cart: self.dom.subheaderDiv.append('div').style('display', 'none').html('<br/>Cart feature under construction - work in progress<br/>&nbsp;<br/>'),
    });
    if (!massNav.tabs?.about?.hide) {
      self.tabs.push(aboutTab);
      const override = Object.assign({}, massNav.tabs?.about);
      if (appState.termdbConfig?.selectCohort) {
        if (!override.top) override.top = "COHORT";
      } else {
        if (!override.mid) override.mid = self.pickDatasetLabel(appState);
      }
      Object.assign(aboutTab, override);
    }
    if (!massNav.tabs?.charts?.hide) {
      self.tabs.push(chartTab);
      Object.assign(chartTab, massNav.tabs?.charts);
    }
    if (!massNav.tabs?.groups?.hide) {
      self.tabs.push(groupsTab);
      Object.assign(groupsTab, massNav.tabs?.groups);
    }
    if (!massNav.tabs?.filter?.hide) {
      self.tabs.push(filterTab);
      Object.assign(filterTab, massNav.tabs?.filter);
    }
    if (appState.termdbConfig?.queries?.chat && !massNav.tabs?.chat?.hide) {
      self.tabs.push(chatTab);
      Object.assign(chatTab, massNav.tabs?.chat);
    }
    const table = self.dom.tabDiv.append("table").attr("data-testid", "sjpp-nav-tabs-table").style("border-collapse", "collapse");
    table.selectAll("tr").data(["top", "mid", "btm"]).enter().append("tr").attr("class", (d, i) => `sjpp-nav-tabs-row-${i}`).style("font-size", (d, i) => i == 1 ? "20px" : "12px").selectAll("td").data(
      (key, i) => self.tabs.map((row, colNum) => {
        return {
          rowNum: i,
          key,
          colNum,
          label: row[key],
          subheader: row.subheader,
          disabled: row.disabled,
          disabledMessage: row.disabledMessage
        };
      })
    ).enter().append("td").attr("class", (d) => `sjpp-nav-tab-col-${d.colNum}`).style("display", "none").style("width", "100px").style("padding", (d) => d.rowNum === 0 ? "12px 12px 3px 12px" : "3px 12px").style("text-align", "center").style("border-left", "1px solid #ccc").style("border-right", "1px solid #ccc").style("color", "#aaa").style("opacity", (d) => d.disabled ? 0.5 : 1).style("cursor", (d) => d.disabled ? "not-allowed" : "pointer").attr("title", (d) => d.disabledMessage || "").html((d) => d.label).on("click", (event2, d) => {
      self.setTab(event2, d);
    }).on("mouseover", (event2, d) => {
      self.mouseover(event2, d);
    }).on("mouseout", () => {
      self.mouseout();
    });
    self.dom.trs = table.selectAll("tr");
    self.dom.tds = table.selectAll("td");
    self.subheaderKeys = self.tabs.map((d) => d.subheader);
    self.dom.saveBtn = self.dom.sessionDiv.append("button").attr("data-testid", "sjpp-nav-session-btn").style("margin", "10px").text("Session \u25BC");
    if (self.sessionDaysLeft != null) {
      self.dom.fileBtn = self.dom.sessionDiv.append("button").attr("data-testid", "sjpp-nav-file-btn").style("margin", "10px").text("Export Session").on("click", (event2) => {
        self.getSessionFile(event2);
      });
    }
  };
  self.deletePlots = () => {
    const state = self.app.getState();
    const subactions = [];
    for (const plot of state.plots) subactions.push({ type: "plot_delete", id: plot.id });
    self.app.dispatch({ type: "app_refresh", subactions });
  };
  self.mayShowMessage_sessionDaysLeft = () => {
    if (!Number.isFinite(self.sessionDaysLeft)) {
      return;
    }
    self.dom.sessionElapsedMessageDiv.style("display", "block");
    self.dom.remainingDaysMessage = self.dom.sessionElapsedMessageDiv.append("div").style("display", "block").style("opacity", "0.65").html(
      `<u>${self.sessionDaysLeft} days</u> left until this session is removed. Click the "Session \u25BC" button and select "Save" option to create a new one.`
    );
  };
  self.updateUI = async () => {
    if (!self.dom.subheaderDiv) return;
    if (self.activeTab && self.state.termdbConfig.selectCohort && self.activeCohort == -1) {
      self.dom.subheaderDiv.style("display", "none");
      self.dom.messageDiv.selectAll("text").remove();
      self.dom.messageDiv.style("display", "").text('No cohort selected. Please select a cohort in the "COHORT" tab.');
    } else {
      if (self.dom.subheaderDiv) self.dom.subheaderDiv.style("display", self.activeTab != -1 ? "block" : "none");
      if (self.dom.messageDiv) self.dom.messageDiv.style("display", "none");
    }
    const selectCohort = self.state.termdbConfig.selectCohort;
    const massNav = self.state.termdbConfig.massNav;
    self.dom.searchDiv.style("display", selectCohort && self.activeCohort == -1 ? "none" : "inline-block");
    self.dom.header.style("border-bottom", self.state.nav.header_mode === "with_tabs" ? "1px solid #000" : "");
    self.dom.tds.style("display", "").style("color", (d) => d.colNum == self.activeTab ? activeTabTextColor : inactiveTabTextColor).style(
      "background-color",
      (d) => d.colNum == self.activeTab && self.dom.subheaderDiv.style("display") != "none" ? self.state.termdbConfig.massNav?.activeColor || activeTabBgColor : "transparent"
    ).html(function(d, i) {
      if (d.key == "top") return this.innerHTML;
      if (d.subheader == "groups") {
        if (d.key == "mid") return self.state.groups.length || "NONE";
        return "";
      }
      if (d.subheader === "charts") {
        const n = self.state.plots.filter((p) => !p.parentId).length;
        if (d.key == "mid") return !n ? "NONE" : n;
        else return "";
      } else if (d.subheader === "about") {
        if (self.activeCohort != -1 && self.activeCohort in self.samplecounts) {
          const aboutMap = {
            top: this.innerHTML,
            mid: self.activeCohortLabel,
            btm: self.samplecounts[self.activeCohort]
          };
          return aboutMap[d.key] || "";
        } else if (!selectCohort) {
          const aboutMap = {
            top: massNav?.tabs?.about?.top || "ABOUT",
            mid: massNav?.tabs?.about?.mid || this.innerHTML,
            btm: massNav?.tabs?.about?.btm || self.samplecounts[self.activeCohort]
          };
          return aboutMap[d.key] || "";
        } else {
          return d.key === "mid" ? "NONE" : this.innerHTML;
        }
      } else if (d.subheader === "filter") {
        const filter = self.filterUiRoot ? self.filterUiRoot : { lst: [] };
        if (filter.lst.length === 0) {
          return d.key === "mid" ? "NONE" : "";
        } else {
          const n = self.samplecounts[self.filterJSON] != void 0 ? "" + self.samplecounts[self.filterJSON] : "";
          return d.key === "mid" ? filter.lst.length : n;
        }
      } else {
        return d.key === "mid" ? this.innerHTML : "&nbsp;";
      }
    });
    for (const key in self.dom.subheader) {
      self.dom.subheader[key].style(
        "display",
        self.activeTab == -1 ? "none" : self.tabs[self.activeTab].subheader === key ? "block" : "none"
      );
    }
    if (self.opts.header_mode === "with_cohortHtmlSelect") {
      self.dom.cohortSelect.selectAll("option").property("value", self.activeCohort);
    }
  };
}
function setInteractivity2(self) {
  self.setTab = async (event2, d) => {
    if (d.disabled) return;
    if (d.colNum === self.activeTab && !self.searching) {
      if (self.activeTab == 0) return;
      self.prevCohort = self.activeCohort;
      await self.updateUI();
      self.app.dispatch({ type: "tab_set", activeTab: -1 });
      return;
    }
    self.activeTab = d.colNum;
    self.searching = false;
    self.app.dispatch({ type: "tab_set", activeTab: self.activeTab });
    const chartsIdx = self.subheaderKeys.indexOf("charts");
    if (self.activeTab == chartsIdx && self.activeCohort != -1 && !self.state.plots.length) {
      const defaultChartType = self.state.termdbConfig.defaultChartType || "dictionary";
      self.app.dispatch({
        type: "plot_create",
        id: getId(),
        config: { chartType: defaultChartType }
      });
    }
  };
  self.mouseover = (event2, d) => {
    const defaultActiveColor = self.state.termdbConfig.massNav?.activeColor || activeTabBgColor;
    self.dom.tds.style("background-color", (t) => {
      if (t.colNum === d.colNum)
        return self.activeTab == t.colNum ? self.state.termdbConfig.massNav?.activeColorHover || activeTabBgColorHover : inactiveTabBgColorHover;
      return self.activeTab == t.colNum ? defaultActiveColor : "transparent";
    });
  };
  self.mouseout = () => {
    const defaultActiveColor = self.state.termdbConfig.massNav?.activeColor || activeTabBgColor;
    self.dom.tds.style("background-color", (t) => self.activeTab == t.colNum ? defaultActiveColor : "transparent");
  };
  self.getSessionFile = async (event2) => {
    const res = await dofetch3(`/massSession?id=${self.sessionId}`);
    const a = document.createElement("a");
    const dataStr = "data:application/json;charset=utf-8," + encodeURIComponent(JSON.stringify(res.state));
    a.setAttribute("href", dataStr);
    a.download = `${self.sessionId}.json`;
    a.click();
    a.remove();
  };
  self.pickDatasetLabel = (appState) => {
    const dslabel = appState.vocab.dslabel.toUpperCase();
    return dslabel.length > 10 ? `${dslabel.slice(0, 10)}...` : dslabel;
  };
}

export {
  getId,
  navInit
};
//# sourceMappingURL=chunk-CXQ6KS3Z.js.map
