import {
  DataPointInteractions,
  PlotBase,
  axisstyle,
  controlsInit,
  createLollipopFromGene,
  filterInit,
  getCombinedTermFilter,
  getNormalRoot,
  make_one_checkbox,
  sayerror,
  showResultsTable,
  table2col,
  to_svg
} from "./chunk-SA7APTJR.js";
import "./chunk-HJ6L54YS.js";
import "./chunk-LSEFWW72.js";
import "./chunk-SKREEF3H.js";
import {
  Menu
} from "./chunk-HYOEWQ5P.js";
import "./chunk-HBW42TDT.js";
import "./chunk-G6O3URDN.js";
import "./chunk-FN5XPUPH.js";
import {
  icons
} from "./chunk-IIT367QZ.js";
import "./chunk-RZGEKL77.js";
import "./chunk-UI7OCM2A.js";
import "./chunk-YZK37YCC.js";
import "./chunk-7EMWHCVW.js";
import "./chunk-7IYJZZQI.js";
import {
  copyMerge,
  getCompInit
} from "./chunk-M3J4MINX.js";
import "./chunk-PF4DSFDR.js";
import "./chunk-QH6W3NVF.js";
import "./chunk-NOEAT6CX.js";
import {
  dt2lesion,
  dtcnv,
  dtfusionrna,
  dtsnvindel,
  dtsv,
  mclass,
  proteinChangingMutations
} from "./chunk-4UWS5Y3N.js";
import "./chunk-DD4R5P6W.js";
import "./chunk-JNITUVXP.js";
import "./chunk-KSGA62R2.js";
import {
  axisLeft
} from "./chunk-LOZEKOES.js";
import "./chunk-TOU7EVFQ.js";
import {
  linear
} from "./chunk-OAWQ6LOO.js";
import "./chunk-TLT4YIG3.js";
import "./chunk-KYBIQBXE.js";
import {
  select_default
} from "./chunk-I6Y4O3RR.js";
import "./chunk-OMR2DT66.js";
import "./chunk-DQC5FFGV.js";
import "./chunk-HFNDKYVF.js";

// plots/grin2/model/GRIN2Model.ts
var GRIN2Model = class {
  constructor(vocabApi) {
    this.vocabApi = vocabApi;
  }
  async fetchGrin2Data(requestData, signal) {
    return this.vocabApi.getGrin2Data(requestData, signal);
  }
};

// plots/grin2/viewModel/GRIN2ViewModel.ts
var GRIN2ViewModel = class {
  constructor(response, manhattanSettings, dtUsage) {
    this.viewData = {
      manhattan: response.pngImg ? { plotData: response, settings: manhattanSettings } : null,
      topGenes: this.buildTopGenes(response, manhattanSettings, dtUsage),
      statsSections: response.stats?.lst || []
    };
  }
  buildTopGenes(response, manhattanSettings, dtUsage) {
    if (!response.topGeneTable || !response.stats?.lst) return null;
    const headerText = `Top Genes (showing ${response.stats.lst[0].rows[1][1]} of ${response.stats.lst[0].rows[0][1]})`;
    const qValueEntries = this.buildQValueEntries(response.topGeneTable.columns, dtUsage);
    const lesionTypeCircleCache = this.buildCircleCache(manhattanSettings.lesionTypeColors);
    const qValueThreshold = manhattanSettings.qValueThreshold;
    const rows = response.topGeneTable.rows.map((row) => {
      const circles = qValueEntries.filter(({ colIndex }) => {
        const qValue = row[colIndex]?.value;
        return typeof qValue === "number" && qValue < qValueThreshold;
      }).map(({ type }) => lesionTypeCircleCache.get(type));
      return [{ value: "", html: circles.join("") }, ...row];
    });
    return {
      headerText,
      columns: [{ label: "", width: "20px" }, ...response.topGeneTable.columns],
      rows,
      dataItems: response.topGeneTable.rows
    };
  }
  buildQValueEntries(columns, dtUsage) {
    const dtMapping = {};
    Object.entries(dt2lesion).forEach(([dt, cfg]) => {
      dtMapping[dt] = cfg.lesionTypes.map((lt) => ({
        col: `Q-value (${lt.name})`,
        type: lt.lesionType
      }));
    });
    const entries = [];
    Object.entries(dtUsage).forEach(([key, info]) => {
      const isChecked = typeof info === "object" ? info?.checked : !!info;
      if (isChecked && dtMapping[key]) {
        dtMapping[key].forEach(({ col, type }) => {
          const colIndex = columns.findIndex((c) => c.label === col);
          if (colIndex !== -1) entries.push({ colIndex, type });
        });
      }
    });
    return entries;
  }
  buildCircleCache(lesionTypeColors) {
    return new Map(
      Object.entries(lesionTypeColors).map(([type, color]) => [
        type,
        `<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background-color:${color};margin-right:3px;"></span>`
      ])
    );
  }
};

// plots/manhattan/manhattan.ts
function plotManhattan(div, data, settings, app) {
  settings = {
    ...settings
  };
  let interactivePoints = data.plotData.points;
  if (data.plotData.points.length > settings.interactiveDotsCap) {
    interactivePoints = data.plotData.points.sort((a, b) => b.y - a.y).slice(0, settings.interactiveDotsCap);
  }
  div.style("position", "relative");
  const geneTip = new Menu({ padding: "" });
  const svg = div.append("svg").attr("data-testid", "sjpp-manhattan").attr("width", settings.plotWidth + 2 * settings.pngDotRadius + settings.yAxisX + settings.yAxisSpace).attr("height", settings.plotHeight + 2 * settings.pngDotRadius + settings.yAxisY * 4);
  const yPlot = linear().domain([data.plotData.y_min, data.plotData.y_max]).range([settings.plotHeight + 2 * settings.pngDotRadius, 0]);
  const yAxisScale = linear().domain([0, data.plotData.y_max - settings.pngDotRadius]).range([yPlot(0), yPlot(data.plotData.y_max - settings.pngDotRadius)]);
  const axisG = svg.append("g").attr("transform", `translate(${settings.yAxisX + settings.yAxisSpace - settings.yAxisPad},${settings.yAxisY})`);
  axisG.call(
    axisLeft(yAxisScale).tickSizeOuter(0)
    // removes top/bottom cap lines for clean look
  );
  axisstyle({
    axis: axisG,
    color: settings.axisColor,
    fontsize: settings.fontSize + 2,
    showline: settings.showYAxisLine
  });
  svg.append("text").attr("x", -((settings.plotHeight + 2 * settings.pngDotRadius) / 2) - settings.yAxisY).attr("y", settings.yAxisX / 2).attr("transform", "rotate(-90)").attr("text-anchor", "middle").attr("font-size", `${settings.fontSize + 4}px`).attr("fill", "black").text(data.plotData.has_capped_points ? "-log\u2081\u2080(q-value) [capped]" : "-log\u2081\u2080(q-value)");
  svg.append("image").attr("transform", `translate(${settings.yAxisX + settings.yAxisSpace},${settings.yAxisY})`).attr("width", settings.plotWidth + 2 * settings.pngDotRadius).attr("height", settings.plotHeight + 2 * settings.pngDotRadius).attr("href", `data:image/png;base64,${data.pngImg || data.png}`);
  const xScale = linear().domain([-data.plotData.x_buffer, data.plotData.total_genome_length + data.plotData.x_buffer]).range([0, settings.plotWidth + 2 * settings.pngDotRadius]);
  if (settings.showInteractiveDots && data.plotData.points && data.plotData.points.length > 0) {
    const hoverLayer = svg.append("g").attr("transform", `translate(${settings.yAxisX + settings.yAxisSpace},${settings.yAxisY})`).style("pointer-events", "none");
    const cover = select_default(svg.node().parentNode).append("div").style("position", "absolute").style("left", `${settings.yAxisX + settings.yAxisSpace}px`).style("top", `${settings.yAxisY}px`).style("width", `${settings.plotWidth + 2 * settings.pngDotRadius}px`).style("height", `${settings.plotHeight + 2 * settings.pngDotRadius}px`).style("pointer-events", "all");
    const circlePath = (r) => `M${r},0 A${r},${r} 0 1,1 ${-r},0 A${r},${r} 0 1,1 ${r},0 Z`;
    const interactions = new DataPointInteractions({
      cover,
      hoverLayer,
      hoverTip: geneTip,
      points: interactivePoints,
      getX: (d) => d.pixel_x,
      getY: (d) => d.pixel_y,
      hitRadius: settings.pngDotRadius + 3,
      toHoverSpec: (d) => ({
        path: circlePath(settings.pngDotRadius),
        transform: `translate(${d.pixel_x},${d.pixel_y})`,
        fill: "none",
        stroke: "black",
        strokeWidth: settings.interactiveDotStrokeWidth
      }),
      maxTooltipRows: settings.maxTooltipGenes,
      itemNoun: "gene",
      renderSingleHoverTooltip: (d, container) => {
        const table = table2col({ holder: container.append("div"), margin: "10px" });
        table.addRow("Gene", d.gene);
        table.addRow("Position", `${d.chrom}:${d.start}-${d.end}`);
        const [t1, t2] = table.addRow();
        t1.text("Type");
        t2.html(`<span style="color:${d.color}">\u25CF</span> ${d.type.charAt(0).toUpperCase() + d.type.slice(1)}`);
        table.addRow("Q-value", d.q_value.toPrecision(3));
        table.addRow("Subject count", d.nsubj);
      },
      buildMultiHitTableData: (dots) => ({
        columns: [
          { label: "Gene" },
          { label: "Position" },
          { label: "Type" },
          { label: "Q-value", sortable: true },
          { label: "Subject count", sortable: true }
        ],
        rows: dots.map((d) => [
          { value: d.gene },
          { value: `${d.chrom}:${d.start}-${d.end}` },
          {
            html: `<span style="color:${d.color}">\u25CF</span> ${d.type.charAt(0).toUpperCase() + d.type.slice(1)}`
          },
          { value: d.q_value.toPrecision(3) },
          { value: d.nsubj }
        ])
      }),
      // Manhattan single-click goes straight to a lollipop launch — no menu.
      // Release hover-suppression immediately so the cursor's next move re-engages.
      onSingleClick: (d, _event, ctx) => {
        ctx.dismiss();
        if (app) createLollipopFromGene(d.gene, app);
      },
      // Manhattan multi-click shows showResultsTable directly with `app + clickMenu`
      // so the table renders inline Matrix/Lollipop buttons. Reuses the module's
      // clickMenu so its onHide cleanup (clear flag, clear hover) fires on dismiss.
      // Content is built BEFORE show2 so Menu can measure the populated rect for
      // its right-edge clamp — otherwise the wide table is placed at cursor+offsetX
      // and extends off the right edge of the viewport.
      onMultiClick: (dots, event, ctx) => {
        if (!app) {
          ctx.dismiss();
          return;
        }
        ctx.clickMenu.clear();
        const holder = ctx.clickMenu.d.append("div").style("margin", "10px");
        showResultsTable({ tableDiv: holder, hits: dots, app, clickMenu: ctx.clickMenu });
        ctx.clickMenu.show2(event.clientX, event.clientY);
      }
    });
    interactions.attach();
  }
  if (data.plotData.chrom_data) {
    const chromLabelY = settings.plotHeight + 2 * settings.pngDotRadius + settings.yAxisY + 10;
    Object.entries(data.plotData.chrom_data).forEach(([chrom, chromData]) => {
      const chromLabel = chrom.replace("chr", "");
      if (chromLabel === "M") return;
      const centerPos = settings.yAxisX + settings.yAxisSpace + xScale(chromData.center);
      svg.append("text").attr("x", centerPos).attr("y", chromLabelY).attr("text-anchor", "middle").attr("font-size", `${settings.fontSize + 2}px`).text(chromLabel);
    });
  }
  svg.append("text").attr("x", settings.yAxisX + settings.yAxisSpace + (settings.plotWidth + 2 * settings.pngDotRadius) / 2).attr("y", settings.plotHeight + 2 * settings.pngDotRadius + settings.yAxisY + settings.xAxisLabelPad).attr("text-anchor", "middle").attr("font-size", `${settings.fontSize + 4}px`).attr("fill", "black").text("Chromosomes");
  svg.append("text").attr("x", settings.yAxisX + settings.yAxisSpace).attr("y", settings.yAxisY / 2).attr("font-weight", "bold").attr("font-size", `${settings.fontSize + 2}px`).text("Manhattan Plot");
  if (settings.showDownload) {
    const downloadDiv = div.append("div").style("position", "absolute").style("top", "5px").style("left", `${settings.yAxisX + settings.yAxisSpace + 108}px`);
    icons["download"](downloadDiv, {
      width: 16,
      height: 16,
      title: "Download Manhattan plot",
      handler: () => {
        const svgNode = svg.node();
        const clone = svgNode.cloneNode(true);
        const bbox = svgNode.getBBox();
        clone.setAttribute("width", bbox.width.toString());
        clone.setAttribute("height", bbox.height.toString());
        clone.setAttribute("viewBox", `${bbox.x} ${bbox.y} ${bbox.width} ${bbox.height}`);
        to_svg(clone, `manhattan_plot_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}`, {
          apply_dom_styles: true
        });
      }
    });
  }
  const mutationTypes = [...new Set(data.plotData.points.map((p) => p.type))];
  const legendData = mutationTypes.map((type) => {
    const point = data.plotData.points.find((p) => p.type === type);
    return {
      type: String(type).charAt(0).toUpperCase() + String(type).slice(1),
      color: point?.color
    };
  });
  if (settings.showLegend && legendData.length > 0) {
    const legendY = settings.yAxisY / 2;
    const totalWidth = legendData.length * settings.legendItemWidth;
    const legendX = settings.yAxisX + settings.yAxisSpace + (settings.plotWidth + 2 * settings.pngDotRadius) - totalWidth - settings.legendRightOffset;
    legendData.forEach((item, i) => {
      const x = legendX + i * settings.legendItemWidth;
      svg.append("circle").attr("cx", x + 8).attr("cy", legendY).attr("r", settings.legendDotRadius).attr("fill", item.color);
      svg.append("text").attr("x", x + 8 + settings.legendTextOffset).attr("y", legendY + settings.legendVerticalOffset).attr("font-size", `${settings.legendFontSize + 2}px`).text(item.type);
    });
  }
}

// plots/grin2/view/GRIN2ResultsView.ts
var sectionMargin = "20px 0";
var btnMargin = "10px";
var headerMargin = "0 10px 0 0";
var headerFontSize = 14;
var statsTableFontWeight = "bold";
var backgroundColor = "#f8f8f8";
var GRIN2ResultsView = class {
  constructor(holder, app) {
    this.holder = holder;
    this.app = app;
  }
  clear() {
    this.holder.selectAll("*").remove();
  }
  render(viewData) {
    if (viewData.manhattan) {
      plotManhattan(this.holder, viewData.manhattan.plotData, viewData.manhattan.settings, this.app);
    }
    if (viewData.topGenes) {
      const { headerText, columns, rows, dataItems } = viewData.topGenes;
      const tableContainer = this.holder.append("div").style("margin", sectionMargin);
      const headerDiv = tableContainer.append("div").style("display", "flex").style("align-items", "center").style("margin", btnMargin);
      headerDiv.append("h3").style("margin", headerMargin).style("font-size", `${headerFontSize}px`).text(headerText);
      showResultsTable({
        tableDiv: tableContainer.append("div"),
        app: this.app,
        columns,
        rows,
        dataItems,
        getRowKey: (row) => row[0]?.value,
        matrixButtonFormat: "Matrix ({n} genes selected)",
        maxHeight: "400px",
        maxWidth: "100%",
        dataTestId: "sjpp-grin2-top-genes-table",
        resize: "both",
        selectAll: false,
        allowRestoreRowOrder: true,
        restoreButtonInFooter: true,
        download: {
          fileName: `grin2_top_genes_${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, -5)}.tsv`
        },
        header: {
          allowSort: true,
          style: {
            "font-weight": statsTableFontWeight,
            "background-color": backgroundColor
          }
        }
      });
    }
    if (viewData.statsSections.length > 0) {
      const tablesContainer = this.holder.append("div").style("margin-top", "50px");
      for (const section of viewData.statsSections) {
        tablesContainer.append("h4").style("margin", headerMargin).style("margin-top", "15px").style("font-size", `${headerFontSize - 2}px`).text(section.name);
        const table = table2col({ holder: tablesContainer.append("div"), margin: "2px 8px" });
        for (const [k, v] of section.rows) {
          table.addRow(k, v);
        }
      }
    }
  }
};

// plots/grin2/settings/defaults.ts
var CNV_LOSS_THRESHOLD_FALLBACK = -0.4;
var CNV_GAIN_THRESHOLD_FALLBACK = 0.4;
var CNV_MAX_SEG_LENGTH_FALLBACK = 2e6;
function getDefaultGRIN2Settings(opts) {
  const defaults = {
    manhattan: {
      // Core plot dimensions
      plotWidth: 1e3,
      plotHeight: 400,
      pngDotRadius: 2,
      // Layout spacing
      yAxisX: 70,
      yAxisY: 40,
      yAxisSpace: 20,
      xAxisLabelPad: 30,
      yAxisPad: 5,
      axisColor: "#545454",
      showYAxisLine: true,
      // Typography
      fontSize: 12,
      // Legend settings
      showLegend: true,
      legendItemWidth: 80,
      legendDotRadius: 3,
      legendRightOffset: 15,
      legendTextOffset: 12,
      legendVerticalOffset: 4,
      legendFontSize: 12,
      // Interactive dots
      showInteractiveDots: true,
      interactiveDotRadius: 2,
      interactiveDotStrokeWidth: 1,
      // Download options
      showDownload: true,
      // Max genes to show in table, interactive dots cap, and tooltip genes
      maxGenesToShow: 500,
      interactiveDotsCap: 5e3,
      maxTooltipGenes: 5,
      // Q-value threshold for significance indicators in the table, tooltips, and for determining which dots become interactive
      qValueThreshold: 0.05,
      // Colors for lesion types (currently used for table significance indicators. Long term will also be used for the rust code colors)
      lesionTypeColors: {
        mutation: "#44AA44",
        // green
        loss: "#4444FF",
        // blue
        gain: "#FF4444",
        // red
        fusion: "#FFA500",
        // orange
        sv: "#9932CC"
        // purple
      },
      // Threshold for the rust code when determining if we need to raise the cap value from the default
      maxCappedPoints: 5,
      // Bin size for cap calculations
      binSize: 10,
      // Hard cap regardless of data distribution
      hardCap: 200
    }
  };
  return Object.assign(defaults, opts?.overrides);
}

// plots/grin2/view/GRIN2ControlsView.ts
var optionsTextFontSize = 12;
var tableFontSize = 11;
var inputWidth = "80px";
var inputPadding = "2px 4px";
var inputBorderColor = "#ddd";
var inputBorderRadius = "2px";
var checkboxContainerMaxHeight = "150px";
var checkboxContainerBorder = "1px solid #ddd";
var controlGap = "8px";
var checkboxMarginBottom = "2px";
var GRIN2ControlsView = class {
  constructor(opts) {
    this.snvindelCheckbox = null;
    this.cnvCheckbox = null;
    this.fusionCheckbox = null;
    this.svCheckbox = null;
    this.runButton = null;
    this.consequenceCheckboxes = {};
    this.snvindelSelectAllBtn = null;
    this.snvindelClearAllBtn = null;
    this.snvindelDefaultBtn = null;
    this.cnv_lossThreshold = null;
    this.cnv_gainThreshold = null;
    this.cnv_maxSegLength = null;
    this.snvindelMafFilter = null;
    this.headerHolder = opts.headerHolder;
    this.controlsHolder = opts.controlsHolder;
    this.config = opts.config;
    this.vocabApi = opts.vocabApi;
    this.callbacks = opts.callbacks;
  }
  build() {
    this.headerHolder.style("margin", "15px").html(
      "GRIN2 stands for Genomic Random Interval (GRIN) statistical model. For details, see <a href=https://pubmed.ncbi.nlm.nih.gov/23842812/ target=_blank>Pounds, S. et al. Bioinformatics 2013</a>."
    );
    const table = table2col({ holder: this.controlsHolder, disableScroll: true });
    const queries = this.vocabApi.termdbConfig.queries;
    if (queries.snvindel) this.addSnvindelRow(table);
    if (queries.cnv) this.addCnvRow(table);
    if (queries.svfusion?.dtLst?.includes(dtfusionrna)) this.addFusionRow(table);
    if (queries.svfusion?.dtLst?.includes(dtsv)) this.addSvRow(table);
    this.runButton = this.controlsHolder.append("button").attr("data-testid", "sjpp-grin2-run-button").style("margin-left", "100px").text("Run GRIN2").on("click", () => this.callbacks.onRun());
    this.updateRunButtonFromCheckboxes();
  }
  getDtUsage() {
    const dtUsage = structuredClone(this.config.settings.dtUsage);
    if (dtUsage[dtsnvindel]) dtUsage[dtsnvindel].checked = this.snvindelCheckbox.property("checked");
    if (dtUsage[dtcnv]) dtUsage[dtcnv].checked = this.cnvCheckbox.property("checked");
    if (dtUsage[dtfusionrna]) dtUsage[dtfusionrna].checked = this.fusionCheckbox.property("checked");
    if (dtUsage[dtsv]) dtUsage[dtsv].checked = this.svCheckbox.property("checked");
    return dtUsage;
  }
  getConfigValues(dtUsage) {
    const requestConfig = {};
    if (dtUsage[dtsnvindel]?.checked) {
      requestConfig.snvindelOptions = {
        consequences: this.getSelectedConsequences()
      };
      if (this.snvindelMafFilter) {
        requestConfig.snvindelOptions.mafFilter = this.snvindelMafFilter;
      }
    }
    if (dtUsage[dtcnv]?.checked) {
      requestConfig.cnvOptions = {
        lossThreshold: parseFloat(this.cnv_lossThreshold.property("value")),
        gainThreshold: parseFloat(this.cnv_gainThreshold.property("value")),
        maxSegLength: parseFloat(this.cnv_maxSegLength.property("value"))
      };
    }
    if (dtUsage[dtfusionrna]?.checked) requestConfig.fusionOptions = {};
    if (dtUsage[dtsv]?.checked) requestConfig.svOptions = {};
    return requestConfig;
  }
  setBusy(busy) {
    this.controlsHolder?.style("pointer-events", busy ? "none" : "auto").style("opacity", busy ? "0.5" : "1");
    this.runButton?.property("disabled", busy).text(busy ? "Running GRIN2..." : "Run GRIN2");
  }
  updateRunButtonFromCheckboxes() {
    const dtUsage = this.snvindelCheckbox ? this.getDtUsage() : this.config.settings.dtUsage;
    const anyChecked = Object.values(dtUsage).some((info) => info.checked);
    this.runButton?.property("disabled", !anyChecked);
  }
  getSelectedConsequences() {
    const consequences = [];
    Object.entries(this.consequenceCheckboxes).forEach(([classKey, checkbox]) => {
      if (checkbox.property("checked")) consequences.push(classKey);
    });
    return consequences;
  }
  addSnvindelRow(table) {
    const [left, right] = table.addRow();
    const t2 = table2col({ holder: right });
    {
      const [labelCell, containerCell] = t2.addRow();
      labelCell.text("Consequences").style("padding-top", "8px");
      this.createConsequenceCheckboxes(containerCell);
    }
    const mafFilterConfig = this.vocabApi.termdbConfig.queries?.snvindel?.mafFilter;
    if (mafFilterConfig) {
      this.snvindelMafFilter = structuredClone(
        this.config.settings?.snvindelOptions?.mafFilter || mafFilterConfig.filter
      );
      const [td1, td2] = t2.addRow();
      td1.text("MAF filter");
      filterInit({
        emptyLabel: "+",
        holder: td2,
        header_mode: "hide_search",
        vocab: { terms: mafFilterConfig.terms },
        callback: async (filter) => {
          this.snvindelMafFilter = filter;
        }
      }).main(this.snvindelMafFilter);
    }
    const isChecked = this.config.settings.dtUsage[dtsnvindel].checked;
    t2.table.style("display", isChecked ? "" : "none");
    this.snvindelCheckbox = make_one_checkbox({
      holder: left,
      labeltext: dt2lesion[dtsnvindel].uilabel,
      checked: isChecked,
      testid: "sjpp-grin2-checkbox-snvindel",
      callback: (checked) => {
        t2.table.style("display", checked ? "" : "none");
        this.updateRunButtonFromCheckboxes();
      }
    });
  }
  addCnvRow(table) {
    const [left, right] = table.addRow();
    const t2 = table2col({ holder: right });
    const useSaved = this.config.settings.runAnalysis === true;
    const savedCnv = useSaved ? this.config.settings.cnvOptions : void 0;
    const cnvQuery = this.vocabApi.termdbConfig.queries.cnv;
    this.cnv_lossThreshold = this.addOptionRowToTable(
      t2,
      "Loss Threshold",
      savedCnv?.lossThreshold ?? cnvQuery?.cnvLossCutoff ?? CNV_LOSS_THRESHOLD_FALLBACK,
      -5,
      0,
      0.05
    );
    this.cnv_gainThreshold = this.addOptionRowToTable(
      t2,
      "Gain Threshold",
      savedCnv?.gainThreshold ?? cnvQuery?.cnvGainCutoff ?? CNV_GAIN_THRESHOLD_FALLBACK,
      0,
      5,
      0.05
    );
    this.cnv_maxSegLength = this.addOptionRowToTable(
      t2,
      "Max Segment Length",
      savedCnv?.maxSegLength ?? cnvQuery?.cnvMaxLength ?? CNV_MAX_SEG_LENGTH_FALLBACK,
      0,
      1e9,
      1e3
    );
    const dtUsage = this.config.settings.dtUsage;
    const isChecked = useSaved && dtUsage[dtcnv]?.checked !== void 0 ? dtUsage[dtcnv].checked : !!cnvQuery;
    t2.table.style("display", isChecked ? "" : "none");
    this.cnvCheckbox = make_one_checkbox({
      holder: left,
      labeltext: dt2lesion[dtcnv].uilabel,
      checked: isChecked,
      testid: "sjpp-grin2-checkbox-cnv",
      callback: (checked) => {
        t2.table.style("display", checked ? "" : "none");
        this.updateRunButtonFromCheckboxes();
      }
    });
  }
  addFusionRow(table) {
    const [left, right] = table.addRow();
    const t2 = table2col({ holder: right });
    const isChecked = this.config.settings.dtUsage[dtfusionrna].checked;
    t2.table.style("display", isChecked ? "" : "none");
    this.fusionCheckbox = make_one_checkbox({
      holder: left,
      labeltext: dt2lesion[dtfusionrna].uilabel,
      checked: isChecked,
      testid: "grin2-checkbox-fusion",
      callback: (checked) => {
        t2.table.style("display", checked ? "" : "none");
        this.updateRunButtonFromCheckboxes();
      }
    });
  }
  addSvRow(table) {
    const [left, right] = table.addRow();
    const t2 = table2col({ holder: right });
    const isChecked = this.config.settings.dtUsage[dtsv].checked;
    t2.table.style("display", isChecked ? "" : "none");
    this.svCheckbox = make_one_checkbox({
      holder: left,
      labeltext: dt2lesion[dtsv].uilabel,
      checked: isChecked,
      testid: "sjpp-grin2-checkbox-sv",
      callback: (checked) => {
        t2.table.style("display", checked ? "" : "none");
        this.updateRunButtonFromCheckboxes();
      }
    });
  }
  addOptionRowToTable(table, label, defaultValue, min, max, step) {
    const [labelCell, inputCell] = table.addRow();
    labelCell.text(label);
    const input = inputCell.append("input").attr("type", "number").attr("value", defaultValue).style("width", inputWidth).style("padding", inputPadding).style("border", `1px solid ${inputBorderColor}`).style("border-radius", inputBorderRadius).style("font-size", `${optionsTextFontSize}px`);
    if (min !== null && min !== void 0) input.attr("min", min);
    if (max !== null && max !== void 0) input.attr("max", max);
    if (step !== null && step !== void 0) input.attr("step", step);
    return input;
  }
  createConsequenceCheckboxes(container) {
    const snvIndelClasses = Object.entries(mclass).filter(
      ([key, cls]) => cls.dt === dtsnvindel && key !== "Blank" && key !== "WT"
    );
    const saved = this.config.settings.snvindelOptions?.consequences;
    const useSaved = this.config.settings.runAnalysis === true && !!saved && saved.length > 0;
    const canonicalDefault = /* @__PURE__ */ new Set([...proteinChangingMutations, "StartLost", "StopLost"]);
    const initialChecked = useSaved ? new Set(saved) : canonicalDefault;
    const controlDiv = container.append("div").style("margin-bottom", "6px").style("display", "flex").style("gap", controlGap);
    this.snvindelSelectAllBtn = controlDiv.append("button").style("font-size", `${tableFontSize}px`).text("Select All");
    this.snvindelClearAllBtn = controlDiv.append("button").style("font-size", `${tableFontSize}px`).text("Clear All");
    this.snvindelDefaultBtn = controlDiv.append("button").style("font-size", `${tableFontSize}px`).text("Default");
    const checkboxContainer = container.append("div").style("max-height", checkboxContainerMaxHeight).style("overflow-y", "auto").style("border", checkboxContainerBorder);
    this.consequenceCheckboxes = {};
    snvIndelClasses.forEach(([classKey, classInfo]) => {
      const checkboxDiv = checkboxContainer.append("div").style("margin-bottom", checkboxMarginBottom);
      const checkbox = make_one_checkbox({
        holder: checkboxDiv,
        labeltext: classInfo.label,
        checked: initialChecked.has(classKey),
        divstyle: { "font-size": `${tableFontSize}px` },
        callback: () => {
        }
      });
      checkboxDiv.select("label").attr("title", classInfo.desc);
      this.consequenceCheckboxes[classKey] = checkbox;
    });
    this.snvindelSelectAllBtn.on("click", () => {
      Object.values(this.consequenceCheckboxes).forEach((cb) => cb.property("checked", true));
    });
    this.snvindelClearAllBtn.on("click", () => {
      Object.values(this.consequenceCheckboxes).forEach((cb) => cb.property("checked", false));
    });
    this.snvindelDefaultBtn.on("click", () => {
      Object.entries(this.consequenceCheckboxes).forEach(([classKey, checkbox]) => {
        checkbox.property("checked", canonicalDefault.has(classKey));
      });
    });
  }
};

// plots/grin2/grin2.ts
var GRIN2 = class extends PlotBase {
  constructor(opts, api) {
    super(opts, api);
    this.type = "grin2";
    this.controlsView = null;
    this.opts = opts;
    this.components = { controls: {} };
    opts.holder.classed("sjpp-grin2-main", true);
    this.dom = {
      massControls: opts.holder.append("div").style("display", "inline-block"),
      headerText: opts.holder.append("div").style("display", "inline-block"),
      controls: opts.holder.append("div"),
      div: opts.holder.append("div").style("margin", "20px")
    };
    if (opts.header) this.dom.header = opts.header.text("GRIN2");
  }
  static {
    this.type = "grin2";
  }
  getState(appState) {
    const config = appState.plots.find((p) => p.id === this.id);
    if (!config) {
      throw `No plot with id='${this.id}' found. Did you set this.id before this.api = getComponentApi(this)?`;
    }
    const parentConfig = appState.plots.find((p) => p.id === this.parentId);
    const termfilter = getCombinedTermFilter(appState, config.filter || parentConfig?.filter);
    return { config, termfilter };
  }
  async init() {
    this.model = new GRIN2Model(this.app.vocabApi);
    this.resultsView = new GRIN2ResultsView(this.dom.div, this.app);
    this.components.controls = await controlsInit({
      app: this.app,
      id: this.id,
      holder: this.dom.massControls.style("display", "inline-block"),
      inputs: []
    });
    const burgerMenu = this.dom.massControls.select("div > svg.bi.bi-copy");
    if (burgerMenu) burgerMenu.remove();
    const downloadBtn = this.dom.massControls.select("div > svg.bi.bi-download");
    if (downloadBtn) downloadBtn.remove();
    this.components.controls.on("helpClick.grin2", () => {
      window.open("https://github.com/stjude/proteinpaint/wiki/Grin2");
    });
  }
  async main() {
    const config = structuredClone(this.state.config);
    if (config.childType != this.type && config.chartType != this.type) return;
    if (!this.controlsView) {
      this.controlsView = new GRIN2ControlsView({
        headerHolder: this.dom.headerText,
        controlsHolder: this.dom.controls,
        config: this.state.config,
        vocabApi: this.app.vocabApi,
        callbacks: { onRun: () => this.handleRun() }
      });
      this.controlsView.build();
      if (this.state.config.settings.runAnalysis) this.handleRun();
    }
  }
  async handleRun() {
    if (!this.controlsView) return;
    this.controlsView.setBusy(true);
    try {
      const dtUsage = this.controlsView.getDtUsage();
      this.resultsView.clear();
      const configValues = this.controlsView.getConfigValues(dtUsage);
      const manhattan = this.state.config.settings.manhattan;
      const requestData = {
        filter: getNormalRoot(this.state.termfilter.filter),
        filter0: this.state.termfilter.filter0,
        width: manhattan?.plotWidth,
        height: manhattan?.plotHeight,
        pngDotRadius: manhattan?.pngDotRadius,
        devicePixelRatio: window.devicePixelRatio,
        maxGenesToShow: manhattan?.maxGenesToShow,
        lesionTypeColors: manhattan?.lesionTypeColors,
        qValueThreshold: manhattan?.qValueThreshold,
        maxCappedPoints: manhattan?.maxCappedPoints,
        hardCap: manhattan?.hardCap,
        binSize: manhattan?.binSize,
        ...configValues
      };
      const response = await this.model.fetchGrin2Data(requestData, this.api.getAbortSignal());
      if (response.status === "error") throw `GRIN2 analysis failed: ${response.error}`;
      const vm = new GRIN2ViewModel(response, manhattan, dtUsage);
      this.resultsView.render(vm.viewData);
      this.app.dispatch({
        type: "plot_edit",
        id: this.id,
        config: {
          ...this.state.config,
          settings: {
            ...this.state.config.settings,
            ...configValues,
            dtUsage,
            runAnalysis: true
          }
        }
      });
    } catch (error) {
      if (this.dom.div) {
        sayerror(this.dom.div, `Error running GRIN2: ${error instanceof Error ? error.message : error}`);
      }
    } finally {
      this.controlsView?.setBusy(false);
    }
  }
};
var grin2Init = getCompInit(GRIN2);
var componentInit = grin2Init;
async function getPlotConfig(opts, app) {
  const queries = app.vocabApi.termdbConfig.queries;
  const defaultSettings = getDefaultGRIN2Settings(opts);
  const dtUsage = {};
  if (queries?.snvindel) {
    dtUsage[dtsnvindel] = { checked: true, label: dt2lesion[dtsnvindel].uilabel };
  }
  if (queries?.cnv) {
    dtUsage[dtcnv] = { checked: true, label: dt2lesion[dtcnv].uilabel };
  }
  if (queries?.svfusion) {
    if (queries.svfusion.dtLst.includes(dtfusionrna)) {
      dtUsage[dtfusionrna] = { checked: false, label: dt2lesion[dtfusionrna].uilabel };
    }
    if (queries.svfusion.dtLst.includes(dtsv)) {
      dtUsage[dtsv] = { checked: false, label: dt2lesion[dtsv].uilabel };
    }
  }
  const config = {
    chartType: "grin2",
    settings: {
      controls: {},
      dtUsage,
      runAnalysis: false,
      manhattan: {
        ...defaultSettings.manhattan,
        ...opts?.manhattan
      }
    }
  };
  return copyMerge(config, opts);
}
export {
  componentInit,
  getPlotConfig,
  grin2Init
};
//# sourceMappingURL=grin2-RKJXYWJ5.js.map
