import {
  buildAnnotationsCsv
} from "./chunk-XJGPL2JZ.js";
import {
  PlotBase,
  filterInit,
  getNormalRoot,
  renderTable,
  sayerror
} from "./chunk-SA7APTJR.js";
import "./chunk-HJ6L54YS.js";
import {
  require_debounce
} from "./chunk-LSEFWW72.js";
import "./chunk-SKREEF3H.js";
import "./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 {
  clearServerDataCache,
  dofetch2,
  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 {
  getColors
} from "./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 {
  rgb
} from "./chunk-OMR2DT66.js";
import "./chunk-DQC5FFGV.js";
import {
  __toESM
} from "./chunk-HFNDKYVF.js";

// plots/aiProjectAdmin/defaults.ts
function getDefaultAIProjectAdminSettings(overrides = {}) {
  const defaults = {};
  return Object.assign(defaults, overrides);
}

// plots/aiProjectAdmin/repo/ProjectReposity.ts
var ProjectReposity = class {
  async getAuthorization(genome, dslabel, actions) {
    const body = {
      genome,
      dslabel,
      for: "auth",
      auth: actions
    };
    try {
      const response = await dofetch2("aiProjectAdmin", { body });
      return response || {};
    } catch (error) {
      console.error("Error fetching authorizations:", error);
      throw error;
    }
  }
  async getProjects(genome, dslabel) {
    const body = {
      genome,
      dslabel,
      for: "list"
    };
    try {
      const response = await dofetch3("aiProjectAdmin", { body });
      return response || [];
    } catch (error) {
      console.error("Error fetching projects:", error);
      throw error;
    }
  }
  /** Gets the image paths from db */
  async getImagePaths(genome, dslabel, project) {
    const body = {
      genome,
      dslabel,
      for: "images",
      project
    };
    try {
      return await dofetch3("aiProjectAdmin", { body });
    } catch (error) {
      console.error("Error fetching images:", error);
      throw error;
    }
  }
  async updateProject(_body, method) {
    const body = Object.assign({}, _body, { for: "admin" });
    try {
      if (method == "PUT" || method == "DELETE") {
        return await dofetch2("aiProjectAdmin", { method, body }, {});
      }
      return await dofetch3("aiProjectAdmin", { method, body });
    } catch (error) {
      console.error("Error fetching projects:", error);
      throw error;
    }
  }
  //TODO: Will need a method to call metadata API, then build dictionary
  async getTerms(app) {
    const root = { id: "__root", name: "root", __tree_isroot: true };
    try {
      await app.vocabApi.buildAdHocDictionary();
      const terms = await app.vocabApi.getTermChildren(root, []);
      return terms || [];
    } catch (error) {
      console.error("Error getting terms:", error);
      throw error;
    }
  }
  async getAnnotations(genome, dslabel, projectId) {
    try {
      const body = {
        genome,
        dslabel,
        projectId,
        wsimagesFilenames: ["all"]
      };
      const response = await dofetch3("aiProjectSelectedWSImages", { body });
      const wsimages = response?.wsimages || [];
      const annotations = [];
      for (const w of wsimages) {
        const anns = w.annotations || [];
        const filename = w.filename;
        if (Array.isArray(anns)) {
          for (const a of anns) {
            annotations.push(Object.assign({ filename }, a));
          }
        }
      }
      return annotations;
    } catch (error) {
      console.error("Error fetching annotations from server:", error);
      throw error;
    }
  }
};

// plots/aiProjectAdmin/view/ProjectAdminRender.ts
var import_debounce = __toESM(require_debounce(), 1);
var ProjectAdminRender = class {
  constructor(dom, projects, interactions, wsiApp) {
    this.dom = dom;
    this.projects = projects;
    this.interactions = interactions;
    this.wsiApp = wsiApp;
  }
  /** Renders project administration UI, allowing users to
   * create new projects or edit existing ones.*/
  renderProjectAdmin() {
    const projectDiv = this.dom.holder.append("div").attr("id", "sjpp-ai-prjt-admin-projects").attr("class", "sjpp-deletable-ai-prjt-admin-div");
    this.interactions.getAuthorization(["addProject"]).then((isAuthorized) => {
      console.log(isAuthorized);
      if (isAuthorized.addProject) {
        this.renderCreateProject(projectDiv);
      }
    }).catch((e) => {
      console.error("Error checking authorization:", e.message || e);
      sayerror(this.dom.errorDiv, "Error checking user authorization. Please try again later.");
    }).finally(() => {
      this.renderProjectSelection(projectDiv);
    });
  }
  /** Users submit a new project name before sample
   * filtering UI to finalize project creation.*/
  renderCreateProject(projectDiv) {
    const newProjectDiv = projectDiv.append("div").attr("class", "sjpp-project-select-new").style("padding", "10px");
    const input = newProjectDiv.append("input").attr("id", "sjpp-new-project-name").attr("display", "inline-block").attr("placeholder", "New project name");
    const button = newProjectDiv.append("div").text("Create Project").classed("sja_menuoption", true).style("display", "inline-block").property("disabled", true).on("click", async () => {
      const projectName = input.property("value");
      const prjtNameLen = projectName.trim().length;
      const showError = (msg) => {
        sayerror(this.dom.errorDiv, msg);
        input.property("value", "");
        button.property("disabled", true);
        setTimeout(() => {
          this.dom.errorDiv.selectAll("*").remove();
        }, 3e3);
      };
      if (!(await this.interactions.getAuthorization(["addProject"])).addProject) {
        return showError("Only users with admin role can create projects");
      }
      if (prjtNameLen == 0) {
        return showError("Project name cannot be empty");
      }
      if (prjtNameLen > 50 || prjtNameLen < 3) {
        return showError("Project name must be between 3 and 50 characters");
      }
      const notUnique = this.projects.some((p) => p.name === projectName.trim());
      if (notUnique) {
        return showError(`Project name '${projectName}' already exists`);
      }
      if (this.dom.header) this.dom.header.text(`Project: ${projectName}`);
      await this.interactions.appDispatchEdit({ project: { name: projectName, type: "new" } });
    });
    input.on("keydown", () => {
      const debouncer = () => {
        button.property("disabled", input.property("value").length == 0);
      };
      (0, import_debounce.debounce)(debouncer, 300)();
    });
  }
  /** Users may select an existing project from a table
   * returned for the db to edit or delete, depending
   * on user roles.*/
  renderProjectSelection(projectDiv) {
    if (!this.projects.length) {
      projectDiv.append("div").attr("class", "sjpp-project-select-table").style("padding", "10px").append("div").text("No projects found. Please create a new project or request one from your admin.");
      return;
    }
    const tableDiv = projectDiv.append("div").attr("class", "sjpp-project-select-table").style("padding", "10px");
    const columns = [{ label: "Project", sortable: true }];
    let rows = this.projects.map((p) => [{ value: p.name }]);
    const columnButtons = [
      {
        text: "Edit",
        class: "sja_menuoption",
        //_ = event, __ = idx
        callback: async (_, idx) => {
          const project = this.projects[idx];
          await this.interactions.appDispatchEdit({ project: { ...project, type: "edit" } });
          await this.interactions.launchViewer(this.dom.holder, []);
        }
      },
      {
        // Export button - pass only project.id and export will be CSV
        text: "Export",
        class: "sja_menuoption",
        callback: async (_, idx) => {
          const project = this.projects[idx];
          try {
            await this.interactions.exportAnnotations(project.id);
          } catch (_2) {
            sayerror(this.dom.errorDiv, "Failed to export annotations");
          }
        }
      }
    ];
    this.interactions.getAuthorization(["deleteProject", "logOut", "required"]).then((isAuthorized) => {
      if (isAuthorized.required) {
        columns.push({ label: "User", sortable: false });
        rows = this.projects.map((p) => [{ value: p.name }, { value: p.current_user ?? "Editable" }]);
      }
      if (isAuthorized.deleteProject) {
        columnButtons.push({
          text: "Delete",
          class: "sja_menuoption",
          callback: async (_, i) => {
            const project = this.projects[i];
            this.interactions.deleteProject(project).then((success) => {
              if (success) {
                this.projects.splice(i, 1);
                projectDiv.select(".sjpp-project-select-table").remove();
                this.renderProjectSelection(projectDiv);
              }
            });
          }
        });
      }
      if (isAuthorized.logOut) {
        columnButtons.push({
          text: "Force Checkout",
          class: "sja_menuoption",
          callback: async (_, i) => {
            const project = this.projects[i];
            await this.interactions.onLogOut(this.interactions.genome, this.interactions.dslabel, project.id);
          }
        });
      }
    }).catch((e) => {
      console.error("Error checking authorization:", e.message || e);
      sayerror(this.dom.errorDiv, "Error checking user authorization. Please try again later.");
    }).finally(() => {
      renderTable({
        div: tableDiv,
        rows,
        header: {
          allowSort: true
        },
        columns,
        singleMode: true,
        columnButtons
      });
    });
  }
};

// plots/aiProjectAdmin/view/LogoutRenderer.ts
var LogoutRenderer = class {
  constructor(interactions) {
    this.interactions = interactions;
  }
  render(holder, genome, dslabel, projectId) {
    holder.append("div").style("position", "absolute").style("top", "10px").attr("data-testid", "logout-button").style("right", "20px").style("z-index", "1000").append("button").attr("type", "submit").style("font-size", "1.25em").style("padding", "10px 25px").style("border-radius", "20px").style("border", "1px solid black").style("background-color", "white").style("cursor", "pointer").text("Check Out").on("click", async () => {
      holder.selectAll("*").remove();
      await this.interactions.onLogOut(genome, dslabel, projectId);
    });
  }
};

// plots/aiProjectAdmin/interactions/AIProjectAdminInteractions.ts
var AIProjectAdminInteractions = class {
  constructor(app, id, prjtRepo) {
    this.app = app;
    this.id = id;
    this.genome = app.vocabApi.vocab.genome;
    this.dslabel = app.vocabApi.vocab.dslabel;
    this.prjtRepo = prjtRepo;
  }
  async getAuthorization(actions) {
    try {
      return await this.prjtRepo.getAuthorization(this.genome, this.dslabel, actions);
    } catch (e) {
      console.error("Error checking authorization:", e.message || e);
      throw e;
    }
  }
  async addProject(opts) {
    const config = this.getConfig();
    const settings = config.settings || {};
    const projectObject = Object.assign({}, settings.project, opts.project);
    const body = {
      genome: this.genome,
      dslabel: this.dslabel,
      project: projectObject
    };
    try {
      await this.prjtRepo.updateProject(body, "PUT");
    } catch (e) {
      console.error("Error adding project:", e.message || e);
      throw e;
    }
  }
  async editProject(opts) {
    const config = this.getConfig();
    const settings = config.settings || {};
    const project = Object.assign(
      {},
      settings.project,
      {
        type: "edit"
      },
      opts.project
    );
    const body = {
      genome: this.genome,
      dslabel: this.dslabel,
      project
    };
    try {
      const res = await this.prjtRepo.updateProject(body, "POST");
      if (!project.id) project.id = res.projectId;
    } catch (e) {
      console.error("Error editing project:", e.message || e);
      throw e;
    }
    await this.appDispatchEdit({ project }, config);
  }
  async deleteProject(project) {
    let success = false;
    const body = {
      genome: this.genome,
      dslabel: this.dslabel,
      project: {
        name: project.value,
        id: project.id
      }
    };
    try {
      const response = await this.prjtRepo.updateProject(body, "DELETE");
      if (response && response.status === "ok") {
        success = true;
      }
    } catch (e) {
      console.error("Error deleting project:", e.message || e);
      throw e;
    }
    return success;
  }
  async getFilteredImages(filter) {
    const config = this.getConfig();
    const settings = config.settings || {};
    return await this.app.vocabApi.getFilteredAiImages(settings.project, filter);
  }
  async launchViewer(holder, _images) {
    const config = this.getConfig();
    const settings = config.settings;
    if (!settings.project || !settings.project.id) throw new Error("Project is required to launch viewer");
    let images = [];
    if (_images && _images.length > 0) {
      images = _images;
    } else {
      const response = await this.prjtRepo.getImagePaths(
        this.genome,
        this.dslabel,
        settings.project
      );
      if (response.status !== "ok") {
        await this.appDispatchEdit({ project: { name: "", type: "logout" } }, config);
        sayerror(holder, response.error || "Error fetching images");
        return;
      }
      holder.selectAll(".sjpp-deletable-ai-prjt-admin-div").remove();
      images = response.images;
    }
    const wsiViewer = await import("./plot.wsi-2MU5BDG3.js");
    const genome = this.genome;
    const dslabel = this.dslabel;
    const logoutRenderer = new LogoutRenderer(this);
    logoutRenderer.render(holder, genome, dslabel, settings.project.id);
    wsiViewer.default(this.dslabel, holder, { name: this.genome }, null, settings.project.id, images, true);
  }
  async appDispatchEdit(settings, config = {}) {
    if (!config?.settings) {
      config = this.getConfig();
      if (!config) throw new Error(`No plot with id='${this.id}' found.`);
    }
    const configSettings = config.settings;
    const mergedSettings = {
      project: { name: "", type: "logout", ...configSettings?.project, ...settings.project }
    };
    await this.app.dispatch({
      type: "plot_edit",
      id: this.id,
      config: { settings: mergedSettings }
    });
  }
  getConfig() {
    return this.app.getState().plots.find((p) => p.id === this.id);
  }
  async exportAnnotations(projectId) {
    const config = this.getConfig();
    if (!config || !config.settings) throw new Error(`No plot with id='${this.id}' found.`);
    const settings = config.settings || {};
    let annotations = [];
    try {
      annotations = await this.prjtRepo.getAnnotations(this.genome, this.dslabel, projectId);
    } catch (e) {
      console.error("Error fetching annotations for export:", e?.message || e);
      throw e;
    }
    const mime = "text/csv;charset=utf-8;";
    let content;
    if (!Array.isArray(annotations) || annotations.length === 0) {
      content = "";
    } else {
      content = buildAnnotationsCsv(annotations);
    }
    const rawName = settings.project && settings.project.name ? String(settings.project.name) : `project-${projectId}`;
    const safeName = rawName.replace(/[^a-z0-9_.-]/gi, "_").slice(0, 200);
    const filename = `${safeName}-annotations.csv`;
    try {
      const blob = new Blob([content], { type: mime });
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = filename;
      document.body.appendChild(a);
      a.click();
      a.remove();
      setTimeout(() => URL.revokeObjectURL(url), 1e3);
    } catch (e) {
      console.error("Error creating annotation download:", e?.message || e);
      throw e;
    }
  }
  async onLogOut(genome, dslabel, projectId) {
    try {
      await dofetch3("aiProjectAdmin", {
        body: {
          genome,
          dslabel,
          for: "logout",
          project: { id: projectId }
        }
      });
      clearServerDataCache();
      await this.appDispatchEdit({ project: { name: "", id: projectId, type: "logout" } });
    } catch (e) {
      console.error("Error logging out: " + (e.message || e));
      throw e;
    }
  }
};

// plots/aiProjectAdmin/view/ClassesTableRender.ts
var ClassesTableRender = class {
  constructor(holder, rows) {
    this.dom = {
      holder,
      tableDiv: holder.append("div"),
      btnDiv: holder.append("div")
    };
    this.columns = [
      { label: "" },
      // Empty for the delete icon
      {
        label: "NAME",
        editCallback: (rowIdx, cell) => {
          const newValue = cell.value.trim();
          const foundSameName = this.rows.some((r, i) => newValue === r[1].value.trim() && i !== rowIdx);
          if (foundSameName) {
            alert(`Class name '${newValue}' already exists`);
            cell.value = this.rowsCopy[rowIdx][1].value;
            cell.__td.text(this.rowsCopy[rowIdx][1].value);
            return;
          }
          this.rowsCopy = this.rows.map((r) => r.map((c) => ({ ...c })));
        }
      },
      { label: "COLOR" }
    ];
    const colorScale = getColors(5);
    this.rows = rows || this.setDefaultClasses(colorScale);
    this.rowsCopy = [];
    this.renderTable();
    this.renderAddClassBtn(colorScale);
  }
  renderTable() {
    this.dom.tableDiv.select("table").remove();
    if (!this.rows || this.rows.length === 0) return;
    this.rowsCopy = this.rows.map((r) => r.map((c) => ({ ...c })));
    renderTable({
      div: this.dom.tableDiv,
      columns: this.columns,
      rows: this.rows,
      striped: false,
      showLines: false,
      singleMode: false,
      resize: true
    });
    for (const [i, row] of this.rows.entries()) {
      const deleteBtn = row[0].__td.select(".sja_menuoption");
      deleteBtn.on("click", () => {
        this.rows.splice(i, 1);
        this.renderTable();
      });
    }
  }
  setDefaultClasses(colorScale) {
    const rows = [];
    for (let i = 0; i < 4; i++) {
      this.getNewClass(colorScale, rows);
    }
    return rows;
  }
  getNewClass(colorScale, rows) {
    const getName = (i2) => `New class ${i2}`;
    let i = 1;
    let found = true;
    while (found) {
      const checkName = getName(i);
      found = rows.some((r) => r[1].value == checkName);
      if (found) i++;
    }
    rows.push([
      { html: `<div class="sja_menuoption">&times;<div>` },
      { value: getName(i) },
      { color: rgb(colorScale(`${i}`)).formatHex() }
    ]);
  }
  renderAddClassBtn(colorScale) {
    this.dom.btnDiv.append("div").text("Add Class").classed("sja_menuoption", true).style("display", "inline-block").style("margin", "10px 0px 0px 0px").on("click", () => {
      if (this.rows.length >= 9) {
        alert("Maximum number of classes reached.");
        return;
      }
      this.getNewClass(colorScale, this.rows);
      this.renderTable();
    });
  }
};

// plots/aiProjectAdmin/view/SelectorTableRender.ts
var SelectorTableRender = class {
  constructor(holder, interactions, images) {
    this.images = { rows: [], cols: [] };
    this.selectedRows = /* @__PURE__ */ new Set();
    this.sortedIndexMap = [];
    this.dom = {
      holder,
      tableDiv: holder.append("div").attr("class", ".sjpp-deletable-ai-prjt-admin-div").attr("id", "sjpp-selector-table").style("padding", "10px 0"),
      btnDiv: holder.append("div").attr("class", ".sjpp-deletable-ai-prjt-admin-div").attr("id", "sjpp-selector-btns")
    };
    this.images = images;
    this.interactions = interactions;
    const selectedList = images?.selectedImages;
    const normalizeImageName = (s) => String(s ?? "").replace(/\.svs$/i, "").toLowerCase();
    const selectedSet = new Set((selectedList || []).map(normalizeImageName));
    this.selectedRows = /* @__PURE__ */ new Set();
    (this.images?.rows ?? []).forEach((row, i) => {
      const candidate = row?.[0]?.value;
      const n = normalizeImageName(candidate);
      if (selectedSet.has(n)) this.selectedRows.add(i);
    });
    this.render();
  }
  render() {
    if (this.images === void 0) this.dom.tableDiv.append("div").text("No images found").style("color", "red");
    const columns = this.images.cols.map((c) => ({ ...c, sortable: true })) || [];
    if (!columns.length) {
      this.dom.tableDiv.append("div").text("Missing columns data").style("color", "red");
      this.renderApplyBtn(
        /*disable=*/
        true
      );
      return;
    }
    const rows = this.images?.rows ?? [];
    const mapped = rows.map((row, origIdx) => ({
      row,
      origIdx,
      selected: this.selectedRows.has(origIdx)
    }));
    mapped.sort((a, b) => {
      const selDiff = Number(b.selected) - Number(a.selected);
      return selDiff || a.origIdx - b.origIdx;
    });
    const rowsSorted = mapped.map((m) => m.row);
    this.sortedIndexMap = mapped.map((m) => m.origIdx);
    const selectedRowsForRender = mapped.reduce((acc, m, idx) => {
      if (m.selected) acc.push(idx);
      return acc;
    }, []);
    renderTable({
      div: this.dom.tableDiv,
      columns,
      rows: rowsSorted,
      selectedRows: selectedRowsForRender,
      striped: true,
      header: { allowSort: true },
      noButtonCallback: (sortedIdx, node) => {
        const orig = this.sortedIndexMap?.[sortedIdx];
        if (orig === void 0) return;
        if (node.checked) {
          this.selectedRows.add(orig);
        } else {
          this.selectedRows.delete(orig);
        }
      }
    });
    this.renderApplyBtn(
      /*disable=*/
      false
    );
  }
  renderApplyBtn(disable = false) {
    const btn = this.dom.btnDiv.append("div").text("Apply").classed("sja_menuoption", true).style("display", "block").style("width", "fit-content").style("margin-left", "5vw");
    if (disable) btn.attr("disabled", true);
    btn.on("click", async () => {
      if (!this.selectedRows.size) {
        alert(`No rows selected. Please select some rows.`);
        return;
      }
      btn.attr("disabled", true);
      const images = Array.from(this.selectedRows).map((i) => `${this.images.rows[i][0].value}.svs`);
      await this.interactions.editProject({
        project: {
          images,
          type: "edit"
        }
      });
      this.dom.holder.selectAll("*").remove();
      await this.interactions.launchViewer(this.dom.holder, images);
    });
  }
};

// plots/aiProjectAdmin/view/UsersRender.ts
var UsersRender = class {
  constructor(dom, users = []) {
    this.dom = dom;
    this.users = users.slice();
  }
  render() {
    const container = this.dom.holder.append("div").attr("id", "sjpp-users-render").attr("class", "sjpp-deletable-ai-prjt-admin-div").style("padding", "10px");
    const inputRow = container.append("div").attr("class", "sjpp-users-input-row").style("margin-bottom", "10px");
    const input = inputRow.append("input").attr("type", "text").attr("placeholder", "user@example.com").attr("id", "sjpp-user-email-input").style("margin-right", "8px").style("width", "300px");
    const addBtn = inputRow.append("div").text("Add").classed("sja_menuoption", true).style("display", "inline-block").property("disabled", true).on("click", () => {
      const email = input.property("value").trim();
      const showError = (msg) => {
        sayerror(this.dom.errorDiv, msg);
        setTimeout(() => this.dom.errorDiv.selectAll("*").remove(), 3e3);
      };
      if (email.length === 0) {
        return showError("Email cannot be empty");
      }
      const emailRe = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/;
      if (!emailRe.test(email)) {
        return showError("Invalid email address");
      }
      if (this.users.includes(email)) {
        return showError("Email already added");
      }
      this.users.push(email);
      input.property("value", "");
      addBtn.property("disabled", true);
      this.renderTableArea(container);
    });
    input.on("input", () => {
      addBtn.property("disabled", input.property("value").trim().length === 0);
    });
    this.renderTableArea(container);
  }
  renderTableArea(container) {
    container.select(".sjpp-users-table-area").remove();
    const tableDiv = container.append("div").attr("class", "sjpp-users-table-area").style("padding", "6px 0");
    if (!this.users.length) {
      tableDiv.append("div").text("No users added yet.").style("color", "#666");
      return;
    }
    const columns = [{ label: "Email", sortable: true }];
    const columnButtons = [
      {
        text: "\xD7",
        class: "sja_menuoption",
        callback: (_, idx) => {
          this.users.splice(idx, 1);
          container.select(".sjpp-users-table-area").remove();
          this.renderTableArea(container);
        }
      }
    ];
    renderTable({
      div: tableDiv,
      rows: this.users.map((u) => [{ value: u }]),
      columns,
      columnButtons,
      singleMode: false,
      striped: false,
      showLines: false,
      resize: true
    });
    tableDiv.selectAll(".sja_menuoption").html("\xD7<div></div>");
  }
};

// plots/aiProjectAdmin/view/CreateProjectRender.ts
var CreateProjectRender = class {
  constructor(dom, app, interactions) {
    dom.holder.style("padding", "10px 20px").attr("class", "sjpp-deletable-ai-prjt-admin-div");
    const filterDiv = dom.holder.append("div").attr("id", "sjpp-ai-prjt-admin-filter-div");
    const wrapper = dom.holder.append("div").attr("id", "sjpp-ai-prjt-admin-classes-users-wrapper").style("display", "flex").style("gap", "20px").style("align-items", "flex-start").style("padding", "20px 0px");
    const left = wrapper.append("div").attr("id", "sjpp-ai-prjt-admin-classes-table").style("flex", "0 1 auto");
    const right = wrapper.append("div").attr("id", "sjpp-ai-prjt-admin-users").style("width", "320px").style("margin-left", "8px");
    this.dom = {
      holder: dom.holder,
      errorDiv: dom.errorDiv,
      filterDiv,
      classDiv: left,
      usersDiv: right
    };
    this.app = app;
    this.interactions = interactions;
    this.filter = null;
  }
  render() {
    this.renderFilter();
    this.classesTable = new ClassesTableRender(this.dom.classDiv);
    this.usersRender = new UsersRender({ holder: this.dom.usersDiv, errorDiv: this.dom.errorDiv }, []);
    this.usersRender.render();
    this.renderApplyBtn();
  }
  renderFilter() {
    const filter = filterInit({
      holder: this.dom.filterDiv,
      emptyLabel: "Add filter",
      vocabApi: this.app.vocabApi,
      termdbConfig: this.app.vocabApi.termdbConfig,
      callback: (filter2) => {
        this.filter = filter2;
      }
    });
    const state = this.app.getState();
    const root = getNormalRoot(state.termfilter.filter);
    filter.main(root);
  }
  renderApplyBtn() {
    const btn = this.dom.classDiv.append("div").text("Apply").classed("sja_menuoption", true).style("display", "inline-block").style("margin-left", "200px").on("click", async () => {
      btn.attr("disabled", true);
      const selections = await this.interactions.getFilteredImages(this.filter);
      if (this.filter && selections.status !== "ok" || selections.data?.selectedImages?.length === 0) {
        alert("No images match your filter criteria.");
        return;
      }
      if (!this.usersRender || !Array.isArray(this.usersRender.users) || this.usersRender.users.length === 0) {
        alert("Please add at least one user.");
        btn.attr("disabled", null);
        return;
      }
      await this.interactions.addProject({
        project: {
          filter: this.filter,
          classes: this.classesTable.rows.map((row, i) => {
            return { label: row[1].value, color: row[2].color, key_shortcut: `Digit${i + 1}` };
          }),
          users: this.usersRender?.users || []
        }
      });
      this.dom.holder.selectAll("*").remove();
      new SelectorTableRender(this.dom.holder, this.interactions, selections.data);
    });
  }
  /**** Uncomment before production */
  // private validateInput() {
  // 	const invalidInfo = {
  // 		entries: [] as { dataType: string; reason: string }[],
  // 		errorMsg: 'Please clear all "Data type: Class" errors before applying changes.'
  // 	}
  // 	//Show user error if no filter is defined.
  // 	if (!this.filter) {
  // 		invalidInfo.entries.push({ dataType: 'Filter', reason: 'No filter defined. Did you mean to add a filter?' })
  // 	}
  // 	//Check if all classes were deleted
  // 	const classes = this.classesTable?.rows.map(row => row[1].value) || []
  // 	if (classes.length === 0) {
  // 		invalidInfo.entries.push({ dataType: 'Class', reason: 'No classes defined. Did you mean to add a class?' })
  // 	}
  // 	//Check to see if any class is still a default class
  // 	const unchangedClasses =
  // 		this.classesTable?.rows
  // 			.filter(row => (row[1]?.value as string)?.startsWith('New class'))
  // 			.map(r => r[1]?.value as string) || []
  // 	for (const c of unchangedClasses) {
  // 		invalidInfo.entries.push({ dataType: 'Class', reason: `${c} is the default class name. Please rename.` })
  // 	}
  // 	return invalidInfo
  // }
};

// plots/aiProjectAdmin/AIProjectAdmin.ts
var AIProjectAdmin = class extends PlotBase {
  constructor(opts, api) {
    super(opts, api);
    this.type = "AIProjectAdmin";
    this.opts = opts;
    this.dom = {
      holder: opts.holder,
      errorDiv: opts.holder.append("div").style("margin", "3px").attr("class", "sjpp-ai-prjt-admin-error")
    };
    if (opts.header) this.dom.header = opts.header;
    this.prjtRepo = new ProjectReposity();
  }
  static {
    this.type = "AIProjectAdmin";
  }
  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)?`;
    }
    return {
      config,
      vocab: appState.vocab,
      filter: appState.termfilter.filter
    };
  }
  async displayProjects(state, interactions) {
    try {
      this.projects = await this.prjtRepo.getProjects(state.vocab.genome, state.vocab.dslabel);
    } catch (e) {
      console.error("Error initializing AIProjectAdmin:", e);
      throw e;
    }
    this.prjtAdminUI = new ProjectAdminRender(this.dom, this.projects, interactions, this.app);
    this.prjtAdminUI.renderProjectAdmin();
  }
  async init(appState) {
    this.interactions = new AIProjectAdminInteractions(this.app, this.id, this.prjtRepo);
    await this.displayProjects(appState, this.interactions);
  }
  async main() {
    const state = structuredClone(this.state);
    const config = state.config;
    if (config.chartType != this.type) return;
    const project = config.settings.project;
    if (!project) return;
    if (!this.interactions) throw new Error("AIProjectAdminInteractions not initialized");
    this.dom.holder.selectAll(".sjpp-deletable-ai-prjt-admin-div").remove();
    if (project.type === "new") {
      const terms = await this.prjtRepo.getTerms(this.app);
      if (!terms || terms.length === 0) {
        sayerror(this.dom.errorDiv, "No metadata found.");
        return;
      }
      const createProjectRender = new CreateProjectRender(this.dom, this.app, this.interactions);
      createProjectRender.render();
    }
    if (project.type == "existing") {
      if (!project.name) {
        sayerror(this.dom.errorDiv, "Project name not provided.");
        return;
      }
      if (!project.id) {
        const existingProject = this.projects?.find((p) => p.name === project.name);
        if (!existingProject) {
          sayerror(this.dom.errorDiv, `Project ${project.name} not found.`);
          return;
        }
        await this.app.save({
          type: "plot_edit",
          id: this.id,
          config: { settings: { project: { id: existingProject.id } } }
        });
      }
      await this.interactions.launchViewer(this.dom.holder, []);
    }
    if (project.type === "logout") {
      await this.displayProjects(state, this.interactions);
    }
  }
};
var aiProjectAdminInit = getCompInit(AIProjectAdmin);
var componentInit = aiProjectAdminInit;
async function getPlotConfig(opts) {
  const config = {
    chartType: "AIProjectAdmin",
    subfolder: "aiProjectAdmin",
    extension: "ts",
    hidePlotFilter: true,
    //Disable the filter in the sandbox header
    settings: getDefaultAIProjectAdminSettings(opts.overrides)
  };
  if (opts.settings?.project && !opts.settings?.project?.type) {
    opts.settings.project.type = "existing";
  }
  return copyMerge(config, opts);
}
export {
  aiProjectAdminInit,
  componentInit,
  getPlotConfig
};
//# sourceMappingURL=AIProjectAdmin-2OQOQXH4.js.map
