import {
  isURL,
  makeBtn,
  makeFileUpload,
  makePrompt,
  makeResetBtn,
  makeTextAreaInput,
  makeTextInput
} from "./chunk-5EBRF6Z7.js";
import {
  appInit
} from "./chunk-QNETLNPB.js";
import "./chunk-HKSNADSX.js";
import "./chunk-SKMFMGCD.js";
import "./chunk-BDIKFVWZ.js";
import "./chunk-FFEIP5CT.js";
import "./chunk-DR6H3QAA.js";
import "./chunk-MYF2BWKC.js";
import "./chunk-CXQ6KS3Z.js";
import "./chunk-4KY4XKJV.js";
import "./chunk-OEDZHPBJ.js";
import "./chunk-PCZNTK74.js";
import "./chunk-PRZWSBMA.js";
import {
  appear,
  sayerror
} 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 "./chunk-7EMWHCVW.js";
import "./chunk-7IYJZZQI.js";
import "./chunk-M3J4MINX.js";
import "./chunk-PF4DSFDR.js";
import "./chunk-QH6W3NVF.js";
import "./chunk-NOEAT6CX.js";
import "./chunk-4UWS5Y3N.js";
import "./chunk-DD4R5P6W.js";
import "./chunk-JNITUVXP.js";
import "./chunk-KSGA62R2.js";
import "./chunk-LOZEKOES.js";
import "./chunk-TOU7EVFQ.js";
import "./chunk-OAWQ6LOO.js";
import "./chunk-TLT4YIG3.js";
import "./chunk-KYBIQBXE.js";
import "./chunk-I6Y4O3RR.js";
import "./chunk-OMR2DT66.js";
import "./chunk-DQC5FFGV.js";
import "./chunk-HFNDKYVF.js";

// src/databrowser/dictionary.parse.js
function parseDictionary(input) {
  const terms = {};
  const lines = input.trim().split(/\r?\n/);
  const header = lines[0].split("	");
  const term_idIndex = header.findIndex((l) => l.toLowerCase().includes("term_id"));
  const variableIndex = header.findIndex((l) => l.toLowerCase().includes("variable"));
  if (term_idIndex != -1) parseDataDictionary(lines, header);
  if (variableIndex != -1) parsePhenotree(lines, header);
  if (variableIndex == -1 && term_idIndex == -1) {
    throw `Unrecognized file format. Please check the header names.`;
  }
  function parseDataDictionary(lines2, header2) {
    const parIdIndex = header2.findIndex((l) => l.toLowerCase().includes("parent_id"));
    if (parIdIndex == -1) {
      throw `Missing required 'parent_id' header`;
    }
    const nameIndex = header2.findIndex((l) => l.toLowerCase().includes("name"));
    if (nameIndex == -1) {
      throw `Missing required 'Name' header`;
    }
    const typeIndex = header2.findIndex((l) => {
      const v = l.trim().toLowerCase();
      return v === "type";
    });
    if (typeIndex == -1) {
      throw `Missing required 'Type' header`;
    }
    const valuesIndex = header2.findIndex((l) => l.toLowerCase().includes("values"));
    if (valuesIndex == -1) {
      throw `Missing required 'Values' header`;
    }
    if (parIdIndex == -1 || nameIndex == -1 || typeIndex == -1 || valuesIndex == -1) {
      throw `Missing required header(s)`;
    }
    const parentIds = /* @__PURE__ */ new Set();
    for (const [i, line] of lines2.entries()) {
      if (i === 0) continue;
      const lineNum = i + 1;
      try {
        const cols = line.split("	");
        for (const [i2, c] of cols.entries()) {
          const colNum = i2 + 1;
          let foundProblem;
          if (i2 == valuesIndex) continue;
          if (c == "" || c == "-") {
            foundProblem = true;
            throw `Blank or '-' entered for line ${lineNum}, column ${colNum}`;
          }
          if (foundProblem == true) throw `Invalid entry for line ${lineNum}, column ${colNum}`;
        }
        const termId = cols[term_idIndex];
        const type = cols[typeIndex];
        terms[termId] = {
          id: termId,
          name: cols[nameIndex].trim().replace(/"/g, ""),
          parent_id: cols[parIdIndex] != "root" ? cols[parIdIndex] : null,
          type: cols[typeIndex] != "non graphable" ? cols[typeIndex] : null
        };
        parentIds.add(terms[termId].parent_id);
        if (type == "categorical") {
          terms[termId].values = {};
        }
        const values = cols[valuesIndex].trim().replace(/"/g, "").split(";");
        for (const x of values) {
          const v = x.trim();
          if (v == "") continue;
          const segments = v.split("=");
          const key = segments.shift();
          const label = segments.join("=");
          if (!label) throw `Values="${v}" in line ${lineNum} not in a key = value format.`;
          if (!terms[termId].values) terms[termId].values = {};
          terms[termId].values[key] = { label };
        }
        const numValues = Object.keys(terms[termId].values).length;
        terms[termId].groupsetting = { disabled: numValues < 3 };
        validateNumericTermCategories(terms[termId]);
      } catch (e) {
        throw `Line ${lineNum} error: ${e}`;
      }
    }
    for (const t in terms) terms[t].isleaf = !parentIds.has(terms[t].id);
  }
  function parsePhenotree(lines2, header2) {
    if (variableIndex == -1) {
      throw `Missing required 'Variable' header`;
    }
    const typeIndex = header2.findIndex((l) => {
      const v = l.trim().toLowerCase();
      return v === "type";
    });
    if (typeIndex == -1) {
      throw `Missing required 'Type' header`;
    }
    const categoriesIndex = header2.findIndex((l) => l.toLowerCase().includes("categories"));
    const levelColIndexes = header2.map((c, i) => c.toLowerCase().includes("level_") ? i : -1).filter((i) => i != -1);
    if (!levelColIndexes.length) levelColIndexes.push(variableIndex);
    const unitIndex = header2.findIndex((l) => l.toLowerCase().includes("unit"));
    const sampleTypeIndex = header2.findIndex((l) => {
      const v = l.trim().toLowerCase();
      return v === "sample_type";
    });
    const additionalAttrIndexes = header2.findIndex((l) => l.toLowerCase().includes("additional attributes"));
    const termNameToId = {};
    const parentTermNames = /* @__PURE__ */ new Set();
    const parent2ChildOrder = /* @__PURE__ */ new Map();
    parent2ChildOrder.set(null, []);
    for (const [i, line] of lines2.entries()) {
      if (i === 0) continue;
      const lineNum = i + 1;
      try {
        const cols = line.split("	");
        const levelNames = levelColIndexes.map((i2) => cols[i2].trim().replace(/"/g, "")).filter((c) => c != "-");
        if (levelNames.length != new Set(levelNames).size) {
          throw `Non-unique levels in line ${lineNum}: ${JSON.stringify(levelNames)}`;
        }
        for (const [i2, lvlName] of levelNames.entries()) {
          if (i2 == 0) {
            if (parent2ChildOrder.get(null).indexOf(lvlName) == -1) parent2ChildOrder.get(null).push(lvlName);
          }
          if (i2 != levelNames.length - 1) {
            if (!parent2ChildOrder.has(lvlName)) parent2ChildOrder.set(lvlName, []);
          }
          for (const n of levelNames) {
            if (i2 == levelNames.indexOf(n) - 1) {
              if (parent2ChildOrder.get(lvlName).indexOf(n) == -1) parent2ChildOrder.get(lvlName).push(n);
            }
          }
        }
        const name = levelNames.pop();
        const firstDashIndex = cols.indexOf("-");
        if (firstDashIndex != -1 && firstDashIndex < cols.indexOf(name)) {
          throw `Blank or '-' value detected between levels in line ${lineNum}`;
        }
        const term = parseCategories(cols[typeIndex], cols[categoriesIndex], lineNum, name);
        const id = cols[variableIndex] || name;
        if (id in terms) {
          const orig = terms[id];
          throw `Error: Multiple config rows for term.id='${id}': lines# ${orig.lineNum} and ${lineNum}`;
        }
        terms[id] = {
          id,
          name,
          type: term.type,
          values: term.values,
          groupsetting: term.groupsetting,
          // *** temporary attributes to be deleted later ***
          ancestry: levelNames.slice(),
          // to be deleted later, used to fill in missing terms
          parent_name: levelNames.pop() || null,
          // will change this later to parent_id
          lineNum
          // to be deleted later
        };
        if (cols[unitIndex] != null) terms[id].unit = cols[unitIndex];
        if (cols[sampleTypeIndex] != null) terms[id].sample_type = cols[sampleTypeIndex];
        if (cols[additionalAttrIndexes]) {
          Object.assign(terms[id], JSON.parse(cols[additionalAttrIndexes]));
        }
        termNameToId[name] = id;
        parentTermNames.add(terms[id].parent_name);
      } catch (e) {
        throw `Line ${lineNum} error: ${e}`;
      }
    }
    trackMissingTerms(termNameToId, terms, parentTermNames, parent2ChildOrder);
    for (const id in terms) {
      const term = terms[id];
      term.child_order = parent2ChildOrder.get(term.parent_name).indexOf(term.name) + 1;
      term.isleaf = !parentTermNames.has(term.name);
      term.parent_id = termNameToId[term.parent_name] || null;
      delete term.parent_name;
      delete term.lineNum;
      delete term.ancestry;
    }
  }
  return { terms: Object.values(terms) };
}
function parseCategories(type, catJSON, lineNum, varName) {
  if (!type) throw `No type provided for variable: ${varName} on line ${lineNum}`;
  const term = {
    type,
    values: catJSON == "" || catJSON == void 0 ? {} : JSON.parse(catJSON)
  };
  validateNumericTermCategories(term);
  if (term.type == "categorical") {
    const numValues = Object.keys(term.values).length;
    term.groupsetting = { disabled: numValues < 3 };
  }
  return term;
}
function trackMissingTerms(termNameToId, terms, parentTermNames, parent2ChildOrder) {
  for (const id in terms) {
    const term = terms[id];
    for (const [i, name] of term.ancestry.entries()) {
      if (name in termNameToId) {
        const id2 = termNameToId[name];
        const ancestor = terms[id2];
        if (i - 1 < 0 && ancestor.parent_name || ancestor.parent_name != term.ancestry[i - 1]) {
          throw `Different parents for term=${name}, '${term.ancestry[i - 1]}' and '${ancestor.parent_name}'`;
        }
        continue;
      }
      terms[name] = {
        id: name,
        name,
        isleaf: false,
        ancestry: term.ancestry.slice(0, i)
      };
      terms[name].parent_name = terms[name].ancestry.slice(-1)[0] || null;
      termNameToId[name] = name;
      parentTermNames.add(name);
    }
  }
}
function validateNumericTermCategories(term) {
  if (term.type != "integer" && term.type != "float") return;
  if (!term.values) return;
  if (typeof term.values != "object") throw "numeric .values{} is not object";
  for (const key in term.values) {
    if (key == "") throw "Cannot use empty string as an uncomputable category";
    const tmp = Number(key);
    if (Number.isNaN(tmp)) {
      throw `Uncomputable category of a numeric term is required to be a number (here uses non-numeric value of ${key}).`;
    }
    term.values[key].uncomputable = true;
  }
}

// src/databrowser/databrowser.ui.js
function init_databrowserUI(holder, debugmode) {
  const wrapper = holder.append("div").style("margin", "20px 20px 20px 40px").style(
    "font-family",
    "'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif"
  ).style("place-items", "center left").style("overflow", "hidden").classed("sjpp-app-ui", true);
  const obj = {};
  infoSection(wrapper);
  makeSectionHeader(wrapper, "Data Dictionary");
  const tabs_div = wrapper.append("div").style("margin-left", "2vw");
  makeDataDictionaryTabs(tabs_div, obj);
  const controlBtns_div = wrapper.append("div").style("display", "flex").style("align-items", "center").style("margin", "40px 0px 40px 130px");
  submitButton(controlBtns_div, obj, wrapper, holder);
  makeResetBtn(controlBtns_div, obj, ".databrowser_input");
  if (debugmode) window.doms = obj;
  return obj;
}
function infoSection(div) {
  div.append("div").style("margin", "10px").style("opacity", "0.65").html(`
			<ul>
                <li>
                    Please see the <a href="https://github.com/stjude/proteinpaint/wiki/Data-Browser" target="_blank">documentation</a> for more information.
                </li>
				<li>
					Download an example data dictionary <a href="https://proteinpaint.stjude.org/ppdemo/databrowser/dictionaryDemoData.tar.gz" target="_self" "download>here</a>.
				</li>
            </ul>`);
}
function makeSectionHeader(div, text) {
  const header = makePrompt(div, text);
  header.style("font-size", "1.5em").style("color", "#003366").style("margin", "20px 10px 40px 10px").classed("sjpp-databrowser-section-header", true);
  const hr = div.append("hr");
  hr.style("color", "ligthgrey").style("margin", "-30px 0px 15px 0px").style("width", "50vw").style("opacity", "0.4");
}
function makeDataDictionaryTabs(tabs_div, obj) {
  const tabs = [
    {
      label: "Select File",
      active: true,
      width: 95,
      callback: async (event, tab) => {
        tab.contentHolder.style("border", "none").style("display", "block");
        appear(tab.contentHolder);
        tab.contentHolder.append("div").html(`<p style="margin-left: 10px; opacity: 0.65;">Select a file from your computer.</p>`);
        makeFileUpload2(tab.contentHolder, obj);
        delete tab.callback;
      }
    },
    {
      label: "Paste Data",
      active: false,
      width: 95,
      callback: async (event, tab) => {
        tab.contentHolder.style("border", "none").style("display", "block");
        appear(tab.contentHolder);
        tab.contentHolder.append("div").html(
          `<p style="margin-left: 10px; opacity: 0.65;">Paste data dictionary or phenotree in a tab delimited format.</p>`
        );
        makeCopyPasteInput(tab.contentHolder, obj);
        delete tab.callback;
      }
    },
    {
      label: "File Path",
      active: false,
      width: 95,
      callback: async (event, tab) => {
        tab.contentHolder.style("border", "none").style("display", "block");
        appear(tab.contentHolder);
        tab.contentHolder.append("div").html(`<p style="margin-left: 10px; opacity: 0.65;">Provide a URL file path.</p>`);
        makePrompt(tab.contentHolder, "URL");
        makeTextEntryFilePathInput(tab.contentHolder, obj);
        delete tab.callback;
      }
    }
  ];
  new Tabs({ holder: tabs_div, tabs }).main();
}
function makeTextEntryFilePathInput(div, obj) {
  const filepath_div = div.append("div").style("display", "inline-block");
  const filepath = makeTextInput(filepath_div).style("border", "1px solid rgb(138, 177, 212)").classed("databrowser_input", true).on("keyup", async () => {
    const data = filepath.property("value").trim();
    if (isURL(data)) {
      const txt = await fetch(data).then((req) => req.text()).then((txt2) => {
        obj.data = parseDictionary(txt2);
      });
    } else {
    }
  });
}
function makeFileUpload2(div, obj) {
  const upload_div = div.append("div").style("display", "inline-block");
  const upload = makeFileUpload(upload_div).classed("databrowser_input", true);
  upload.on("change", (event) => {
    const file = event.target.files[0];
    const reader = new FileReader();
    reader.onload = (event2) => {
      obj.data = parseDictionary(event2.target.result);
    };
    reader.readAsText(file, "utf8");
  });
}
function makeCopyPasteInput(div, obj) {
  const paste_div = div.append("div").style("display", "block");
  const paste = makeTextAreaInput({ div: paste_div, rows: 10 }).style("border", "1px solid rgb(138, 177, 212)").style("margin", "0px 0px 0px 20px").classed("databrowser_input", true).on("keyup", async () => {
    obj.data = parseDictionary(paste.property("value").trim());
  });
}
function submitButton(div, obj, wrapper, holder) {
  const submit = makeBtn({
    div,
    text: "Create Data Browser",
    color: "white",
    backgroundColor: "#001aff",
    border: "2px solid #001aff"
  });
  const errorMessage_div = div.append("div");
  submit.style("margin-right", "10px").style("font-size", "16px").classed("sjpp-ui-submitBtn", true).attr("type", "submit").on("click", () => {
    if (!obj.data || obj.data == void 0) {
      const sayerrorDiv = errorMessage_div.append("div").style("display", "inline-block").style("max-width", "20vw");
      sayerror(sayerrorDiv, "Please provide data");
      setTimeout(() => sayerrorDiv.remove(), 3e3);
    } else {
      wrapper.remove();
      appInit({
        holder,
        state: {
          vocab: {
            terms: obj.data.terms
          },
          plots: [
            {
              chartType: "dictionary"
            }
          ]
        }
      });
    }
  });
}
export {
  init_databrowserUI
};
//# sourceMappingURL=databrowser.ui-WGD7D3XV.js.map
