import {
  getDefaultProfilePlotSettings,
  getProfilePlotConfig,
  profilePlot
} from "./chunk-P3IENRWJ.js";
import {
  fillTermWrapper,
  fillTwLst
} from "./chunk-SA7APTJR.js";
import "./chunk-HJ6L54YS.js";
import "./chunk-LSEFWW72.js";
import "./chunk-SKREEF3H.js";
import "./chunk-HYOEWQ5P.js";
import {
  Tabs
} from "./chunk-HBW42TDT.js";
import "./chunk-G6O3URDN.js";
import "./chunk-FN5XPUPH.js";
import "./chunk-IIT367QZ.js";
import "./chunk-RZGEKL77.js";
import "./chunk-UI7OCM2A.js";
import "./chunk-YZK37YCC.js";
import {
  dofetch3
} from "./chunk-7EMWHCVW.js";
import "./chunk-7IYJZZQI.js";
import {
  copyMerge,
  getCompInit
} from "./chunk-M3J4MINX.js";
import "./chunk-PF4DSFDR.js";
import "./chunk-QH6W3NVF.js";
import "./chunk-NOEAT6CX.js";
import "./chunk-4UWS5Y3N.js";
import "./chunk-DD4R5P6W.js";
import "./chunk-JNITUVXP.js";
import "./chunk-KSGA62R2.js";
import {
  axisBottom,
  axisTop
} from "./chunk-LOZEKOES.js";
import "./chunk-TOU7EVFQ.js";
import {
  linear
} from "./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/profile/forms2.ts
var YES_NO_TAB = "Yes/No Barchart";
var IMPRESSIONS_TAB = "Impressions";
var LIKERT_TAB = "Likert Scale";
var ProfileForms2 = class extends profilePlot {
  static {
    this.type = "profileForms2";
  }
  constructor(opts) {
    super(opts, "profileForms2");
    this.opts = opts;
    this.id2SCTW = {};
  }
  async init(appState) {
    super.init(appState);
    const config = structuredClone(appState.plots.find((p) => p.id === this.id));
    this.twLst = await this.app.vocabApi.getMultivalueTWs({ parent_id: config.tw.term.id });
    const settings = config.settings.profileForms2;
    this.tabs = [];
    for (const plot of config.options) {
      const tws = this.twLst.filter((tw) => tw.term.subtype == plot.subtype);
      if (!tws.length) continue;
      const tab = {
        label: plot.name,
        callback: () => {
          this.app.dispatch({ type: "plot_edit", id: this.id, config: { activeTab: plot.name } });
        },
        active: false
      };
      if (plot.name == config.activeTab) tab.active = true;
      if (plot.hasSC) {
        this.id2SCTW[plot.name] = {};
        for (const tw of this.twLst) {
          if (tw.term.subtype != plot.subtype) continue;
          const scTermId = tw.term.id.replace(/^POC/, "");
          const scTW = { id: scTermId };
          await fillTermWrapper(scTW, this.app.vocabApi);
          this.id2SCTW[plot.name][scTermId] = scTW;
        }
        this.twLst.push(...Object.values(this.id2SCTW[plot.name]));
      }
      this.tabs.push(tab);
    }
    const rightDiv = this.dom.rightDiv;
    const topDiv = rightDiv.append("div");
    const domainDiv = topDiv.append("div").style("padding-bottom", "10px").style("font-weight", "bold");
    const headerDiv = rightDiv.append("div").style("padding-bottom", "10px");
    if (this.tabs.length > 1)
      await new Tabs({
        holder: topDiv,
        tabsPosition: "horizontal",
        tabs: this.tabs
      }).main();
    const shift = 690;
    const shiftTop = 50;
    const width = settings.svgw + shift + 500;
    const svg = rightDiv.style("padding", "20px").append("svg").attr("width", width);
    svg.append("defs").append("pattern").attr("id", `${this.id}_diagonalHatch`).attr("patternUnits", "userSpaceOnUse").attr("width", 4).attr("height", 4).append("path").attr("d", "M-1,1 l2,-2 M0,4 l4,-4 M3,5 l2,-2").attr("stroke-width", 1).attr("stroke", "gray");
    this.shiftTop = shiftTop;
    this.shift = shift;
    const mainG = svg.append("g").attr("transform", `translate(${shift}, ${shiftTop})`);
    const gridG = svg.append("g").attr("transform", `translate(${shift}, ${shiftTop})`);
    const labelsG = svg.append("g");
    this.filterG = svg.append("g").attr("transform", `translate(${shift + settings.svgw + 60}, ${shiftTop + 40})`);
    this.legendG = svg.append("g");
    const xAxisG = svg.append("g").attr("transform", `translate(${shift}, ${shiftTop / 2})`);
    const scaleAxisG = svg.append("g");
    this.dom = copyMerge(this.dom, {
      svg,
      headerDiv,
      mainG,
      gridG,
      labelsG,
      legendG: this.legendG,
      xAxisG,
      scaleAxisG,
      domainDiv
    });
  }
  async main() {
    super.main();
    if (this.tabs.length == 0) return;
    const activeTab = this.state.config.activeTab || this.tabs[0].label;
    this.activePlot = this.state.config.options.find((p) => p.name == activeTab);
    this.scoreTerms = this.twLst.filter((tw) => tw.term.subtype == this.activePlot.subtype);
    this.scScoreTerms = this.activePlot.hasSC ? Object.values(this.id2SCTW[this.activePlot.name]) : [];
    const parents = this.config.tw.term.id.split("__");
    this.module = parents[1];
    const domain = parents.slice(1).join(" / ");
    this.dom.domainDiv.text(domain);
    this.categories = /* @__PURE__ */ new Set();
    this.dom.mainG.selectAll("*").remove();
    this.dom.gridG.selectAll("*").remove();
    this.dom.labelsG.selectAll("*").remove();
    this.dom.xAxisG.selectAll("*").remove();
    this.dom.scaleAxisG.selectAll("*").remove();
    this.dom.legendG.selectAll("*").remove();
    await this.setControls();
    this.renderPlot();
    this.filterG.selectAll("*").remove();
    this.addFilterLegend();
  }
  /**
   * Override setControls() to fetch data from the dedicated forms2 route.
   * The base class setControls() builds filter UI and sets this.filter but
   * skips the data fetch for profileForms2 (see profilePlot.ts).
   */
  async setControls(additionalInputs = []) {
    await super.setControls(additionalInputs);
    this.data = await this.fetchFormsAggregatedScores();
    if (this.data && "error" in this.data) throw this.data.error;
  }
  async fetchFormsAggregatedScores() {
    return dofetch3("termdb/profileForms2Scores", {
      body: {
        genome: this.state.vocab.genome,
        dslabel: this.state.vocab.dslabel,
        // q is optional in the route type (getMultivalueTWs returns wrappers without q),
        // but normalize to {} here so downstream server code can always read .q without a guard.
        scoreTerms: this.scoreTerms.map((t) => ({ term: { id: t.term.id }, q: t.q ?? {} })),
        scScoreTerms: (this.scScoreTerms || []).map((t) => ({ term: { id: t.term.id }, q: t.q ?? {} })),
        filter: this.filter,
        filterByUserSites: this.settings?.filterByUserSites
      }
    });
  }
  renderPlot() {
    try {
      this.dom.headerDiv.style("display", "none");
      switch (this.activePlot.name) {
        case IMPRESSIONS_TAB:
          this.renderImpressions();
          break;
        case LIKERT_TAB:
          this.renderLikert();
          break;
        case YES_NO_TAB:
          this.renderYesNo();
      }
    } catch (e) {
      console.error("Error rendering profile forms2 plot:", e);
      throw e;
    }
  }
  renderImpressions() {
  }
  renderLikert() {
    this.dom.headerDiv.style("display", "block");
    this.dom.headerDiv.selectAll("*").remove();
    let y = 0;
    const step = 30;
    const height = (this.scoreTerms.length + 2) * step;
    this.dom.svg.attr("height", height + 120);
    this.categories = /* @__PURE__ */ new Set();
    const labelPadding = 80;
    const tempG = this.dom.svg.append("g").style("visibility", "hidden");
    let maxLabelWidth = 0;
    for (const tw of this.scoreTerms) {
      if (tw.term.type != "multivalue") continue;
      const text = getText(tw.term.details || tw.term.name);
      const tempText = tempG.append("text").style("font-size", "0.85em").text(text);
      const w = tempText.node().getBBox().width;
      if (w > maxLabelWidth) maxLabelWidth = w;
    }
    tempG.remove();
    const shift = maxLabelWidth + labelPadding;
    this.dom.mainG.attr("transform", `translate(${shift}, ${this.shiftTop})`);
    this.dom.gridG.attr("transform", `translate(${shift}, ${this.shiftTop})`);
    this.filterG.attr("transform", `translate(${shift + this.settings.svgw + 60}, ${this.shiftTop + 40})`);
    this.dom.svg.attr("width", shift + this.settings.svgw + 500);
    this.shift = shift;
    for (const tw of this.scoreTerms) {
      if (tw.term.type != "multivalue") continue;
      const dict = this.getPercentageDict(tw);
      this.renderLikertBar(dict, y, 25, tw);
      y += step;
    }
    y += step * 2;
    const width = this.settings.svgw;
    const posScale = linear().domain([0, 100]).range([shift, shift + width]);
    const posAxisBottom = axisBottom(posScale).ticks(10).tickFormat((d) => d + "%");
    this.dom.scaleAxisG.attr("transform", `translate(0, ${y})`);
    posAxisBottom(this.dom.scaleAxisG);
    const likertLegendOrder = [
      "I Don't Know",
      "Do Not Know",
      "No",
      "Not Available",
      "Not Applicable For My Role",
      "Almost Never",
      "Infrequently",
      "Sometimes",
      "Frequently",
      "Almost Always"
    ];
    const catUpperSet = new Set([...this.categories].map((c) => c.toUpperCase()));
    const orderedCategories = likertLegendOrder.filter((c) => catUpperSet.has(c.toUpperCase()));
    const colorMap = this.state.termdbConfig.colorMap;
    const legendG = this.dom.legendG.attr("transform", `translate(0, ${y + 50})`);
    const rectSize = 14;
    const rectTextGap = 5;
    const itemGap = 12;
    let x = 0;
    for (const category of orderedCategories) {
      const key = category.toUpperCase();
      const color = colorMap[this.module][key] || colorMap["*"][key] || colorMap["*"][category];
      const itemG = legendG.append("g").attr("transform", `translate(${x}, 0)`);
      itemG.append("rect").attr("width", rectSize).attr("height", rectSize).attr("fill", color);
      const label = itemG.append("text").attr("x", rectSize + rectTextGap).attr("y", rectSize / 2).attr("dominant-baseline", "central").style("font-size", "0.8em").text(category);
      x += rectSize + rectTextGap + label.node().getBBox().width + itemGap;
    }
  }
  renderYesNo() {
    const step = 30;
    const height = this.scoreTerms.length * step;
    this.dom.svg.attr("height", height * 3 + 200);
    const labelPadding = 50;
    const tempG = this.dom.svg.append("g").style("visibility", "hidden");
    let maxLabelWidth = 0;
    for (const tw of this.scoreTerms) {
      const text2 = getText(tw.term.name);
      const tempText = tempG.append("text").style("font-size", "0.8em").text(text2);
      const w = tempText.node().getBBox().width;
      if (w > maxLabelWidth) maxLabelWidth = w;
    }
    tempG.remove();
    const shift = maxLabelWidth + labelPadding;
    this.dom.mainG.attr("transform", `translate(${shift}, ${this.shiftTop})`);
    this.dom.gridG.attr("transform", `translate(${shift}, ${this.shiftTop})`);
    this.dom.xAxisG.attr("transform", `translate(${shift}, ${this.shiftTop / 2})`);
    this.filterG.attr("transform", `translate(${shift + this.settings.svgw + 60}, ${this.shiftTop + 40})`);
    this.dom.svg.attr("width", shift + this.settings.svgw + 500);
    this.shift = shift;
    this.xAxisScale = linear().domain([0, 100]).range([0, this.settings.svgw]);
    this.dom.xAxisG.call(axisTop(this.xAxisScale));
    let y = 0;
    let showSCBar = false;
    let anyShowSCBar = false;
    for (const tw of this.scoreTerms) {
      const percents = this.getPercentageDict(tw);
      const scTermId = tw.term.id.replace(/^POC/, "");
      const scTW = this.id2SCTW[this.activePlot.name][scTermId];
      const scPercents = this.getPercentageDict(scTW);
      const scPercentKeys = Object.keys(scPercents).filter((k) => scPercents[k] > 0).map((k) => scTW.term.values?.[k]?.label || k);
      const scTotal = Object.values(scPercents).reduce((a, b) => a + b, 0);
      showSCBar = scTotal > 1;
      anyShowSCBar ||= showSCBar;
      this.renderYesNoBar(percents, y, step, scTotal == 1 ? scPercentKeys : []);
      if (showSCBar) {
        y += step + 10;
        this.renderYesNoBar(scPercents, y, step, []);
        this.dom.mainG.append("text").text("SC").style("font-size", "0.7em").attr("x", this.settings.svgw + 8).attr("y", y + step * 0.6);
      }
      this.dom.mainG.append("text").text("POC").style("font-size", "0.7em").attr("x", this.settings.svgw + 8).attr("y", showSCBar ? y - step + 0.35 * step : y + step * 0.6);
      this.dom.mainG.append("text").attr("x", -this.shift).attr("y", showSCBar ? y : y + step / 2).text(getText(tw.term.name)).attr("font-size", "0.8em").on("mouseenter", (event) => this.showText(event, tw.term.name)).on("mouseleave", () => this.tip.hide());
      y += step + 40;
    }
    this.renderLines(y - 20);
    if (anyShowSCBar) this.dom.legendG.attr("transform", `translate(${550}, ${30 + this.settings.svgh})`);
    else this.dom.legendG.attr("transform", `translate(${550}, ${this.settings.svgh * 0.8})`);
    let x = 0;
    for (const category of this.activePlot.categories) {
      x += this.drawLegendRect(x, 0, category.name, this.dom.legendG, true);
    }
    let text = this.dom.legendG.append("text").attr("x", x + 80).attr("y", 18);
    text.append("tspan").style("font-weight", "bold").text("SC:");
    text.append("tspan").text("Site Coordinator");
    text = this.dom.legendG.append("text").attr("x", x + 250).attr("y", 18);
    text.append("tspan").style("font-weight", "bold").text("POC:");
    text.append("tspan").text("Point of Care Staff");
  }
  onMouseOver(event) {
    if (event.target.tagName == "rect") {
      const path = event.target;
      const d = path.__data__;
      if (!d?.value) return this.onMouseOut(event);
      const menu = this.tip.clear();
      const percent = roundValueAuto(d.value, true, 1);
      const row = menu.d.append("div").style("display", "flex").style("align-items", "center").style("gap", "5px");
      if (d.color)
        row.append("div").style("width", "12px").style("height", "12px").style("background-color", d.color).style("flex-shrink", "0");
      row.append("span").text(`${d.key}: ${percent}%`);
      menu.show(event.clientX, event.clientY, true, true);
    } else this.onMouseOut(event);
  }
  getColor(key) {
    return key == "Yes" ? this.activePlot.color : key == "No" ? "#aaa" : `url(#${this.id}_diagonalHatch)`;
  }
  renderLikertBar(dict, y, height, tw) {
    const itemG = this.dom.mainG.append("g");
    let total = 0;
    for (const key in dict) total += dict[key];
    const key2num = {};
    for (const key in tw.term.values) {
      const category = tw.term.values[key].label.toUpperCase();
      key2num[category] = Number(key);
    }
    let x = 0;
    const keys = Object.keys(dict).sort((a, b) => key2num[a.toUpperCase()] - key2num[b.toUpperCase()]);
    for (const key of keys) {
      this.categories.add(key);
      const width = this.renderCategory(key, dict, itemG, x, height, total);
      x += width;
    }
    const text = getText(tw.term.details || tw.term.name);
    const textG = this.dom.labelsG.append("g").attr("transform", `translate(0, ${y + this.shiftTop})`);
    textG.append("text").text(text).attr("y", height * 2 / 3).style("font-size", "0.85em").on("mouseenter", (event) => this.showText(event, tw.term.name)).on("mouseleave", () => this.tip.hide());
    itemG.attr("transform", `translate(0, ${y})`);
  }
  renderCategory(category, dict, itemG, x, height, total, showPercent = false) {
    const key = category.toUpperCase();
    const module = this.module;
    const colorMap = this.state.termdbConfig.colorMap;
    const color = colorMap[module][key] || colorMap["*"][key];
    const entry = Object.entries(dict).find((k) => k[0].toUpperCase() == key);
    if (!entry) return 0;
    const value = entry[1];
    this.categories.add(category);
    const percent = value / total * 100;
    const width = percent / 100 * this.settings.svgw;
    itemG.append("rect").attr("x", x).attr("width", width).attr("height", height).attr("stroke", "gray").attr("stroke-width", 0.5).attr("stroke-opacity", 0.5).attr("fill", color).datum({ key: category, value: percent, color }).on("mouseover", (event) => this.onMouseOver(event));
    if (showPercent)
      itemG.append("text").text(`${roundValueAuto(percent, true, 1)}%`).style("font-size", "0.8em").attr("x", x + width + 10).attr("y", height * 0.6);
    return width;
  }
  renderYesNoBar(percents, y, height, scPercentKeys) {
    const percentsOrdered = Object.keys(percents).sort((a, b) => -a.localeCompare(b));
    const total = Object.values(percents).reduce((a, b) => a + b, 0);
    let x = 0;
    for (const key of percentsOrdered) {
      const color = this.getColor(key);
      const value = percents[key];
      const width = value / total * this.settings.svgw;
      const percent = value / total * 100;
      this.dom.mainG.append("rect").attr("x", x).attr("y", y).attr("width", width).attr("height", height).attr("stroke", "gray").attr("fill", color).datum({ key, value: percent, color });
      if (scPercentKeys.includes(key)) {
        const arrowX = x + width / 2;
        const arrowSize = 6;
        this.dom.mainG.append("polygon").attr(
          "points",
          `${arrowX},${y} ${arrowX - arrowSize},${y - arrowSize * 1.5} ${arrowX + arrowSize},${y - arrowSize * 1.5}`
        ).attr("fill", "#c0392b").attr("pointer-events", "none");
        this.dom.mainG.append("text").text(`SC ${roundValueAuto(percent, true, 1)}%`).style("font-size", "0.7em").style("fill", "#c0392b").attr("font-weight", "bold").attr("text-anchor", "middle").attr("x", arrowX).attr("y", y - arrowSize * 1.5 - 3).attr("pointer-events", "none");
      }
      x += width;
    }
  }
  renderLines(y) {
    const width = this.settings.svgw;
    const color = "lightgray";
    const opacity = 0.5;
    const bins = 4;
    const size = width / bins;
    let x;
    const gridG = this.dom.gridG;
    for (let i = 0; i <= bins; i++) {
      x = i * size;
      gridG.append("line").attr("x1", x).attr("x2", x).attr("y1", 0).attr("y2", y).style("stroke", color).style("stroke-opacity", opacity).style("stroke-dasharray", "5, 5");
    }
  }
  drawLegendRect(x, y, text, legendG, isYesNo = false) {
    const key = text.toUpperCase();
    const colorMap = this.state.termdbConfig.colorMap;
    const color = isYesNo ? this.getColor(text) : colorMap[this.module][key] || colorMap["*"][key] || colorMap["*"][text];
    const size = 20;
    const gap = 15;
    const itemG = legendG.append("g").attr("transform", `translate(${x}, ${y})`);
    itemG.append("rect").attr("x", 0).attr("y", 0).attr("width", size).attr("height", size).attr("fill", color).attr("stroke", "gray").attr("stroke-width", 0.5).attr("stroke-opacity", 0.5).attr("pointer-events", "none");
    const label = itemG.append("text").attr("transform", `translate(${size + gap}, ${size / 2})`).attr("dominant-baseline", "central").style("font-size", "0.85em").text(text);
    const labelWidth = label.node().getBBox().width;
    return size + gap + labelWidth + gap;
  }
  showText(event, text, size = 110) {
    if (text.length <= size) return;
    const menu = this.tip.clear();
    menu.d.style("padding", "5px").text(text);
    menu.showunder(event.target);
  }
  getPercentageDict(tw) {
    return this.data.term2Score[tw.term.id];
  }
};
async function makeChartBtnMenu(holder, chartsInstance) {
  const action = {
    type: "plot_create",
    id: (+/* @__PURE__ */ new Date()).toString(16),
    config: {
      chartType: "profileForms2",
      activeCohort: chartsInstance.state.activeCohort
    }
  };
  const termdbConfig = chartsInstance.app.vocabApi.termdbConfig;
  const cohortKey = termdbConfig?.selectCohort?.values?.[chartsInstance.state.activeCohort]?.keys?.[0];
  const cohortForms2 = termdbConfig?.plotConfigByCohort?.[cohortKey]?.profileForms2;
  const domains = cohortForms2?.domains || [];
  const tabOrder = (cohortForms2?.options || []).map((o) => o.name);
  const subtypeNames = [...new Set(domains.flatMap((d) => d.plotTypes))].sort((a, b) => tabOrder.indexOf(a) - tabOrder.indexOf(b));
  if (!subtypeNames.length) {
    holder.append("div").style("padding", "15px").style("color", "#777").style("font-style", "italic").text("No templates are currently available for this cohort.");
    return;
  }
  const tabsHolder = holder.append("div").style("padding", "4px 8px");
  const treeHolder = holder.append("div");
  let activeSubtype = subtypeNames[0];
  let renderSeq = 0;
  const renderTree = async () => {
    const mySeq = ++renderSeq;
    treeHolder.selectAll("*").remove();
    const termdb = await import("./app-AIIN4WDE.js");
    if (mySeq !== renderSeq) return;
    const innerHolder = treeHolder.append("div");
    await termdb.appInit({
      vocabApi: chartsInstance.app.vocabApi,
      holder: innerHolder,
      state: {
        activeCohort: chartsInstance.state.activeCohort,
        nav: { header_mode: "search_only" },
        tree: {
          usecase: { target: "profileForms2", detail: "tw", cohort: cohortKey, subtype: activeSubtype }
        }
      },
      tree: {
        click_term: (term) => {
          const tw = term.term ? term : { term };
          action.config.tw = tw;
          chartsInstance.dom.tip.hide();
          chartsInstance.app.dispatch(action);
        }
      }
    });
    if (mySeq !== renderSeq) innerHolder.remove();
  };
  const { Tabs: Tabs2 } = await import("./toggleButtons-YK7TIFF2.js");
  const tabs = subtypeNames.map((name) => ({
    label: name,
    active: name === activeSubtype,
    callback: () => {
      activeSubtype = name;
      renderTree();
    }
  }));
  await new Tabs2({ holder: tabsHolder, tabsPosition: "horizontal", tabs }).main();
}
async function getPlotConfig(opts, app, _activeCohort) {
  const activeCohort = _activeCohort === void 0 ? app.getState().activeCohort : _activeCohort;
  const formsConfig = await getProfilePlotConfig(activeCohort, app, opts);
  let config = formsConfig;
  config.settings = getDefaultProfileForms2Settings();
  config.headerTitle = "Templates: Visualization tools to provide insights and assist in leveraging data";
  config = copyMerge(structuredClone(config), opts);
  for (const plot of config.options) {
    if (plot.terms) await fillTwLst(plot.terms, app.vocabApi);
  }
  return config;
}
function getDefaultProfileForms2Settings() {
  const settings = {
    controls: {
      isOpen: false
    },
    profileForms2: { svgw: 420, svgh: 480 }
  };
  const profilePlotSettings = getDefaultProfilePlotSettings();
  settings.profileForms2 = copyMerge(settings.profileForms2, profilePlotSettings);
  return settings;
}
var profileForms2Init = getCompInit(ProfileForms2);
var componentInit = profileForms2Init;
function getText(name, size = 110) {
  if (name.length > size) name = name.slice(0, size) + "...";
  return name;
}
export {
  componentInit,
  getDefaultProfileForms2Settings,
  getPlotConfig,
  makeChartBtnMenu,
  profileForms2Init
};
//# sourceMappingURL=forms2-ZBT5BZW3.js.map
