import {
  PlotBase,
  addNewGroup,
  controlsInit,
  fillTermWrapper,
  getCombinedTermFilter,
  getFilter,
  getSamplelstTW,
  select2Terms,
  term0_term2_defaultQ
} 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 "./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 {
  isNumericTerm
} from "./chunk-NOEAT6CX.js";
import "./chunk-4UWS5Y3N.js";
import "./chunk-DD4R5P6W.js";
import "./chunk-JNITUVXP.js";
import "./chunk-KSGA62R2.js";
import "./chunk-LOZEKOES.js";
import "./chunk-TOU7EVFQ.js";
import "./chunk-OAWQ6LOO.js";
import {
  roundValueAuto
} from "./chunk-TLT4YIG3.js";
import "./chunk-KYBIQBXE.js";
import "./chunk-I6Y4O3RR.js";
import "./chunk-OMR2DT66.js";
import "./chunk-DQC5FFGV.js";
import "./chunk-HFNDKYVF.js";

// plots/facet.js
var Facet = class _Facet extends PlotBase {
  static type = "facet";
  constructor(opts) {
    super(opts);
    this.type = _Facet.type;
    const holder = opts.holder;
    const controlsHolder = holder.append("div").style("display", "inline-block");
    const mainDiv = holder.append("div").style("display", "inline-block");
    this.dom = {
      holder: opts.holder.style("padding", "20px"),
      header: opts.header,
      controlsHolder,
      mainDiv,
      tip: opts.tip || new Menu()
    };
  }
  async init(appState) {
  }
  getState(appState) {
    const config = appState.plots.find((p) => p.id === this.id);
    if (this.dom.header)
      this.dom.header.html(
        `${config.columnTw.term.name} <span style="font-size:.8em">(COLUMN)</span> ${config.rowTw.term.name} <span style="font-size:.8em">(ROW) &nbsp; FACET TABLE</span>`
      );
    const parentConfig = this.parentId && appState.plots.find((p) => p.id === this.parentId);
    const termfilter = getCombinedTermFilter(appState, config.filter || parentConfig?.filter);
    return {
      config,
      vocab: appState.vocab,
      termfilter,
      groups: appState.groups
    };
  }
  async main() {
    this.config = JSON.parse(JSON.stringify(this.state.config));
    this.hasTermCollection = this.config.columnTw.term.type == "termCollection" || this.config.rowTw.term.type == "termCollection";
    await this.setControls();
    await this.renderTable();
  }
  async renderTable() {
    const config = this.config;
    this.dom.mainDiv.selectAll("*").remove();
    this.dom.tip.clear().hide();
    const table = this.dom.mainDiv.append("table");
    const tbody = table.append("tbody");
    const headerRow = tbody.append("tr").style("text-align", "center");
    headerRow.append("th");
    const samplesAuth = this.app.vocabApi.hasVerifiedToken();
    if (samplesAuth) {
      table.style("border-spacing", "0px");
      const { result, categories, categories2 } = await this.getSampleTableData(config);
      if (!categories.length || !categories2.length) {
        this.showNoSampleMessage(this.dom.mainDiv);
        return;
      }
      for (const category of categories) {
        const label = config.columnTw.term.type == "termCollection" ? getTermCollectionName(config.columnTw, category) : config.columnTw.term.values?.[category]?.label || category;
        this.addHeader(headerRow, label);
      }
      this.renderSampleTable(tbody, config, result, categories, categories2);
    } else {
      table.style("border-spacing", "5px");
      const { rows, filteredCols, total } = await this.getStaticTableData(config);
      if (!rows.size || !filteredCols.length) {
        this.showNoSampleMessage(this.dom.mainDiv);
        return;
      }
      for (const col of filteredCols) {
        const label = config.columnTw.term.values?.[col.seriesId]?.label || col.seriesId;
        this.addHeader(headerRow, label);
      }
      this.renderStaticTable(tbody, config, rows, total);
    }
  }
  renderSampleTable(tbody, config, result, categories, categories2) {
    const cells = {};
    const aggregateBy = config.settings.facet.aggregateBy;
    if (this.hasTermCollection) {
      const isColTermCollection = config.columnTw.term.type == "termCollection";
      const isRowTermCollection = config.rowTw.term.type == "termCollection";
      if (isColTermCollection && isRowTermCollection) throw "row and col terms cannot both be termCollection";
      if (aggregateBy != "avg" && aggregateBy != "sum") throw "unexpected aggregateBy value";
      for (const category2 of categories2) {
        cells[category2] = {};
        const tr = tbody.append("tr");
        const label2 = isRowTermCollection ? getTermCollectionName(config.rowTw, category2) : config.rowTw.term.values?.[category2]?.label || category2;
        this.addRowLabel(tr, label2);
        for (const category of categories) {
          const td = tr.append("td");
          const samples = result.lst.filter((s) => {
            const colPass = isColTermCollection ? getTermCollectionData(s, config.columnTw, category) : s[config.columnTw.$id]?.key == category;
            const rowPass = isRowTermCollection ? getTermCollectionData(s, config.rowTw, category2) : s[config.rowTw.$id]?.key == category2;
            return colPass && rowPass;
          });
          if (samples.length) {
            const data = samples.map(
              (s) => isColTermCollection ? getTermCollectionData(s, config.columnTw, category) : getTermCollectionData(s, config.rowTw, category2)
            );
            const total = data.reduce((sum, v) => sum + v, 0);
            const value = aggregateBy == "avg" ? total / data.length : total;
            td.classed("sja_menuoption", true).style("text-align", "center").style("border", "2.5px solid white").text(roundValueAuto(value));
          } else {
            td.classed("highlightable-cell", true);
          }
        }
      }
    } else {
      for (const category2 of categories2) {
        cells[category2] = {};
        const tr = tbody.append("tr");
        const label2 = config.rowTw.term.values?.[category2]?.label || category2;
        this.addRowLabel(tr, label2);
        for (const category of categories) {
          const samples = result.lst.filter(
            (s) => s[config.columnTw.$id]?.key == category && s[config.rowTw.$id]?.key == category2
          );
          const percent = this.config.settings.facet.showPercents ? ` <span style="color:gray;"> (${roundValueAuto(
            samples.length / result.lst.length * 100,
            true,
            1
          )}%)</span>` : "";
          cells[category2][category] = { samples, selected: false };
          const td = tr.append("td");
          if (!samples.length) td.classed("highlightable-cell", true);
          if (samples.length > 0) {
            const colIdx = categories.indexOf(category) + 2;
            td.classed("sja_menuoption", true).style("text-align", "center").style("border", "2.5px solid white").html(`${samples.length}${percent}`).on("mouseover", () => {
              this.highlightColRow(tbody, tr, colIdx, "#fffec8");
            }).on("mouseout", () => {
              this.highlightColRow(tbody, tr, colIdx, "transparent");
            }).on("click", () => {
              const selected = cells[category2][category].selected = !cells[category2][category].selected;
              if (selected) {
                td.style("border", "1px solid blue");
              } else {
                td.style("border", "2.5px solid white");
              }
              for (const category22 of categories2) {
                for (const category3 of categories) {
                  if (cells[category22][category3].selected) {
                    buttonDiv.style("display", "");
                    prompt.text("Choose how to use samples:");
                    return;
                  }
                }
              }
              buttonDiv.style("display", "none");
              prompt.text("Click on cells to select samples");
            });
          }
        }
      }
    }
    const prompt = this.dom.mainDiv.append("div").attr("data-testid", "sjpp-facet-start-prompt").style("margin", "20px 0px 0px 15px").style("opacity", "0.7").text(
      this.hasTermCollection ? `Values in cells are ${aggregateBy == "avg" ? "averages" : "sums"}` : "Click on cells to select samples"
    );
    const buttonDiv = this.dom.mainDiv.append("div").style("margin", "20px 0px 0px 25px").style("display", "none");
    const btns = [
      {
        text: "Show samples view",
        // disabled: () => {}, add this if needed later
        callback: () => {
          const samples = this.getSelectedSamples(categories, categories2, cells);
          this.app.dispatch({
            type: "plot_create",
            config: {
              chartType: "sampleView",
              samples: samples.map((d) => ({
                sampleId: d.sample,
                sampleName: result.refs.bySampleId[d.sample].label
              }))
            }
          });
        }
      },
      {
        text: "List samples",
        callback: () => {
          const samples = this.getSelectedSamples(categories, categories2, cells);
          const sampleRows = samples.map((d) => [
            result.refs.bySampleId[d.sample].label,
            d[config.columnTw.$id].key,
            d[config.rowTw.$id].key
          ]);
          this.dom.tip.clear().showunder(buttonDiv.node());
          const tbody2 = this.dom.tip.d.append("table").append("tbody");
          const headerRow = tbody2.append("tr").style("text-align", "center");
          headerRow.append("th").text("Sample");
          headerRow.append("th").text(config.columnTw.term.name);
          headerRow.append("th").text(config.rowTw.term.name);
          for (const row of sampleRows) {
            const tr = tbody2.append("tr");
            tr.append("td").text(row[0]);
            tr.append("td").text(row[1]);
            tr.append("td").text(row[2]);
          }
        }
      },
      {
        text: "Create group",
        callback: () => {
          this.addGroup(categories, categories2, cells);
        }
      }
      // {
      // 	text: 'Add group and filter',
      // 	callback: () => {
      // 		const groupFilter = this.addGroup(categories, categories2, cells)
      // 		const filterUiRoot = getFilterItemByTag(this.state.termfilter.filter, 'filterUiRoot')
      // 		const filter = filterJoin([filterUiRoot, groupFilter])
      // 		filter.tag = 'filterUiRoot'
      // 		this.app.dispatch({
      // 			type: 'filter_replace',
      // 			filter
      // 		})
      // 	}
      // }
    ];
    for (const btn of btns) {
      this.addBtn(buttonDiv, btn);
    }
  }
  highlightColRow = (tbody, tr, colIdx, color) => {
    tbody.selectAll(`td.highlightable-cell:nth-child(${colIdx})`).style("background-color", `${color}`).style("border", `2.5px solid ${color}`);
    tbody.select(`th:nth-child(${colIdx})`).style("background-color", `${color}`).style("border", `2.5px solid ${color}`);
    tr.style("background-color", `${color}`);
  };
  addBtn(div, btn) {
    return div.append("button").classed("sja_menuoption", true).style("padding", "0px 10px").style("margin", "0px 5px").text(btn.text).on("click", btn.callback);
  }
  addGroup(categories, categories2, cells) {
    const group = {
      name: "Group",
      items: this.getSelectedSamples(categories, categories2, cells)
    };
    const filter = getFilter(getSamplelstTW([group]));
    addNewGroup(this.app, filter, this.state.groups);
    return filter;
  }
  getSelectedSamples(categories, categories2, cells) {
    const samples = [];
    for (const category2 of categories2) {
      for (const category of categories) {
        if (cells[category2][category].selected) {
          samples.push(...cells[category2][category].samples);
        }
      }
    }
    return samples;
  }
  async getSampleTableData(config) {
    const result = await this.app.vocabApi.getAnnotatedSampleData({
      filter: this.state.termfilter.filter,
      terms: [config.columnTw, config.rowTw]
    });
    const categories = this.getCategories(config.columnTw, result.lst);
    const categories2 = this.getCategories(config.rowTw, result.lst);
    return { result, categories, categories2 };
  }
  getCategories(tw, data) {
    let categories = [];
    if (tw.term.type == "termCollection") {
      for (const sample of data) {
        const value = sample[tw.$id]?.value;
        if (value) {
          const termids = Object.keys(value);
          categories.push(...termids);
        }
      }
    } else {
      for (const sample of data) {
        let key = sample[tw.$id]?.key;
        if (key) {
          if (!isNaN(key)) key = Number(key);
          categories.push(key);
        }
      }
    }
    const set = new Set(categories);
    categories = Array.from(set).sort();
    if (isNumericTerm(tw.term)) {
      if (tw.term.values) {
        Object.values(tw.term.values).forEach((i) => {
          if (i?.uncomputable) {
            const index = categories.indexOf(i.label);
            if (index > -1) categories.splice(index, 1);
          }
        });
      }
      categories = this.orderColNames(categories);
    }
    return categories;
  }
  orderColNames(cols) {
    const tmpNums = [];
    const tmpStrings = [];
    for (const col of cols) {
      const c = col.split(" to ");
      const cx = c[0].replace(/[\>\≥\<\≤]/g, "");
      const x = Number(cx);
      if (!isNaN(x)) {
        const key = !c[1] && (col.includes("<") || col.includes("\u2264")) ? x - 1 : !c[1] && (col.includes("\u2265") || col.includes(">")) ? x + 1 : x;
        tmpNums.push({ key, label: col });
      } else tmpStrings.push(cx);
    }
    return [...tmpNums.sort((a, b) => a.key - b.key).map((i) => i.label), ...tmpStrings.sort()];
  }
  renderStaticTable(tbody, config, rows, totalSamples) {
    for (const row of rows) {
      const tr = tbody.append("tr");
      const label = config.rowTw.term.values?.[row[0]]?.label || row[0];
      this.addRowLabel(tr, label);
      for (const col of row[1]) {
        const label2 = col[1].value > 0 ? col[1].value : "";
        const percent = this.config.settings.facet.showPercents ? `<span style="color:gray;"> (${roundValueAuto(col[1].value / totalSamples * 100, true, 1)}%)</span>` : "";
        const td = tr.append("td");
        if (label2)
          td.classed("sja_menuoption", true).style("border", "2.5px solid white").style("text-align", "center").html(`${label2}${percent}`);
      }
    }
  }
  async getStaticTableData(config) {
    const opts = { term: config.columnTw, term2: config.rowTw, filter: this.state.termfilter.filter };
    if (this.state.termfilter.filter0) opts.filter0 = this.state.termfilter.filter0;
    await this.getDescrStats(opts.term);
    await this.getDescrStats(opts.term2);
    const result = await this.app.vocabApi.getNestedChartSeriesData(opts);
    const rows = /* @__PURE__ */ new Map();
    const filteredCols = result.data.refs.cols.map((col) => result.data.charts[0].serieses.find((s) => s.seriesId == col));
    result.data.refs.rows.forEach((row) => {
      rows.set(row, /* @__PURE__ */ new Map());
      for (const col of filteredCols) {
        rows.get(row).set(col.seriesId, { value: col.data.find((d) => d.dataId == row)?.total || 0, selected: false });
      }
    });
    return { rows, filteredCols, total: result.data.charts[0].total };
  }
  async getDescrStats(tw) {
    if (isNumericTerm(tw.term)) {
      const data = await this.app.vocabApi.getDescrStats(tw, this.state.termfilter);
      if (data.error) throw data.error;
      tw.q.descrStats = data.values;
    }
  }
  showNoSampleMessage(div) {
    div.append("div").style("padding", "0px 50px").style("font-size", "1.15em").text("No overlapping samples");
    return;
  }
  addHeader(headerRow, text) {
    headerRow.append("th").attr("data-testid", "sjpp-facet-col-header").style("border", "2.5px solid white").style("padding", "0px 25px").text(text);
  }
  addRowLabel(tr, label) {
    tr.append("td").attr("data-testid", "sjpp-facet-row-label").style("border", "2.5px solid white").style("font-weight", "bold").text(label);
  }
  async setControls() {
    this.dom.controlsHolder.selectAll("*").remove();
    const inputs = [
      {
        type: "term",
        configKey: "columnTw",
        chartType: this.type,
        usecase: { target: this.type },
        title: "Facet column categories",
        label: "Columns",
        vocabApi: this.app.vocabApi,
        numericEditMenuVersion: ["discrete"],
        defaultQ4fillTW: term0_term2_defaultQ
      },
      {
        type: "term",
        configKey: "rowTw",
        chartType: this.type,
        usecase: { target: this.type },
        title: "Facet row categories",
        label: "Rows",
        vocabApi: this.app.vocabApi,
        numericEditMenuVersion: ["discrete"],
        defaultQ4fillTW: term0_term2_defaultQ
      },
      {
        boxLabel: "",
        label: "Show percent",
        type: "checkbox",
        chartType: "facet",
        settingsKey: "showPercents",
        title: `Option to show/hide percents per cell`
      }
    ];
    if (this.hasTermCollection) {
      inputs.push({
        label: "Aggregate by",
        type: "radio",
        chartType: "facet",
        settingsKey: "aggregateBy",
        title: "Aggregate values by",
        options: [
          { label: "Average", value: "avg" },
          { label: "Sum", value: "sum" }
        ]
      });
    }
    this.components = {
      controls: await controlsInit({
        app: this.app,
        id: this.id,
        holder: this.dom.controlsHolder,
        inputs
      })
    };
  }
};
function makeChartBtnMenu(holder, chartsInstance) {
  const callback = (xterm, yterm) => {
    const config = {
      chartType: "facet",
      columnTw: { term: xterm },
      rowTw: { term: yterm }
    };
    if (isNumericTerm(xterm)) config.columnTw.term.q = { mode: "discrete" };
    if (isNumericTerm(yterm)) config.rowTw.term.q = { mode: "discrete" };
    chartsInstance.app.dispatch({
      type: "plot_create",
      config
    });
  };
  select2Terms(chartsInstance.dom.tip, chartsInstance.app, "facet", "", callback);
}
var facetInit = getCompInit(Facet);
var componentInit = facetInit;
async function getPlotConfig(opts, app) {
  const config = {
    settings: {
      facet: {
        showPercents: false,
        aggregateBy: "avg"
      }
    }
  };
  if (!opts.columnTw) throw ".columnTw{} missing";
  await fillTermWrapper(
    opts.columnTw,
    app.vocabApi,
    opts.columnTw.bins || opts.columnTw.q ? void 0 : term0_term2_defaultQ
  );
  if (!opts.rowTw) throw ".rowTw{} missing";
  await fillTermWrapper(opts.rowTw, app.vocabApi, opts.rowTw.bins || opts.rowTw.q ? void 0 : term0_term2_defaultQ);
  const result = copyMerge(config, opts);
  return result;
}
function getTermCollectionName(tw, termid) {
  const term = tw.term.termlst.find((t) => t.id == termid);
  if (!term) throw "term is missing";
  return term.name;
}
function getTermCollectionData(s, tw, termid) {
  if (tw.term.type != "termCollection") return;
  const value = s[tw.$id]?.value;
  if (value) {
    const data = value[termid];
    return data;
  }
}
export {
  componentInit,
  facetInit,
  getPlotConfig,
  makeChartBtnMenu
};
//# sourceMappingURL=facet-2FBUAD7P.js.map
