/*___________________________________________________________________________*/
export class LimResults extends HTMLElement {
   #saveScheduled
   #stateObject
   #paneId
   #docId

   static observedAttributes = [ "docId", "paneId" ];

   constructor() {
      super();
   }

   get paneId() {
      return this.#paneId;
   }

   set paneId(value) {
      this.#paneId = value;
      if (document.contains(this) && !this.#stateObject && this.paneId && this.docId) {
         this.loadStateObject();
      }
   }

   get docId() {
      return this.#docId;
   }

   set docId(value) {
      this.#docId = value;
      if (document.contains(this) && !this.#stateObject && this.paneId && this.docId) {
         this.loadStateObject();
      }
   }

   async loadStateObject() {
      const requestUrl = new URL(`/api/v1/runtime_state`, window.location.origin)
      requestUrl.searchParams.append("pane_id", this.paneId);
      requestUrl.searchParams.append("doc_id", this.docId);
      const response = await fetch(requestUrl);
      this.#stateObject = await response.json();

      if (this.#stateObject?.panes) {
         for (let pane of this.#stateObject.panes) {
            this.appendPane(LimClassFactory.createClass(pane.className, pane.state, this.#docId, this.#paneId));
         }
      }
   }

   saveStateObject() {
      const requestUrl = new URL(`/api/v1/runtime_state`, window.location.origin)
      requestUrl.searchParams.append("pane_id", this.paneId);
      requestUrl.searchParams.append("doc_id", this.docId);
      requestUrl.searchParams.append("state", JSON.stringify(this.#stateObject));
      fetch(requestUrl, { method: "PUT" });
   }

   connectedCallback() {
      this.#saveScheduled = false;
      this.className = "lim-pane";
      this.style.display = "flex";
      this.style.flexWrap = "nowrap";
      this.style.flexDirection = "row";
      if (!this.#stateObject && this.paneId && this.docId) {
         this.loadStateObject();
      }
   }

   disconnectedCallback() {
   }

   adoptedCallback() {
   }

   attributeChangedCallback(name, oldValue, newValue) {
      if (name === "paneId") {
         this.paneId = newValue;
      }
      else if (name === "docId") {
         this.docId = newValue;
      }

   }

   stateChanged() {
      if (!this.#saveScheduled) {
         this.#saveScheduled = true;
         setTimeout(() => {
            this.saveStateObject();
            this.#saveScheduled = false;
         }, 2000);
      }
   }

   paneById(id) {
      return this.children.filter(item => item.id === id);
   }

   paneAt(index) {
      return this.children.item(index);
   }

   appendPane(p, flexStyles) {
      this.appendChild(p);

      if (typeof flexStyles === "object") {
         for (let f of Object.getOwnPropertyNames(flexStyles))
            p.style[f] = flexStyles[f];
      }
      else
         this.updateFlexUniform();
      return p;
   }

   removeAllPanes() {
      while (this.children.length)
         this.removeChild(this.children[0]);
   }

   updateFlexUniform() {
      const percent = 100. / this.children.length;
      for (let child of this.children) {
         child.style.flexGrow = 1;
         child.style.flexShrink = 1;
         child.style.flexBasis = `${percent}%`;
         child.style.minWidth = "0px";
         child.style.maxWidth = "100000px";
      }
   }

   setPaneFlex(index, basis, grow, shrink, min, max) {
      const child = this.children[index];
      child.style.flexGrow = grow;
      child.style.flexShrink = shrink;
      child.style.flexBasis = basis;
      child.style.minWidth = min;
      child.style.maxWidth = max;
   }

   onReady() {
      for (let child of this.children)
         child?.onReady?.();
   }

   static paletteToScheme(obj) {
      return Number(obj.text.replace('#', '0x')) < Number(obj.base.replace('#', '0x')) ? 'light' : 'dark';
   }
}

/*___________________________________________________________________________*/
class LimEditableNotes extends HTMLElement {
   #editMode
   #stateObject

   constructor() {
      super();
   }

   get limResults() {
      return this.closest("lim-results");
   }

   initialize(stateObject, docId, paneId) {
      this.#stateObject = stateObject ?? {};
      this.className = "lim-fill";

      if (stateObject?.title)
         this.title = stateObject.title;

      this.name = stateObject?.name || "Notes";
      this.iconres = stateObject?.iconres || "/res/gnr_core_gui/CoreGUI/Icons/base/notes_page.svg";

      if (typeof(this.#stateObject.text) === 'undefined')
         this.#stateObject.text = "";

      this.optionList = new Map();
      this.optionList.set("edit", {
         type: "option",
         title: "Edit Notes",
         iconres: "/res/gnr_core_gui/CoreGUI/Icons/base/InsertText.svg"
      });

      this.#editMode = false;
      Object.defineProperties(this, {
         editOptionValue: {
            get() { return this.#editMode; },
            set(val) {
               if (this.#editMode === val)
                  return;
               this.#editMode = val;
               this.update();
            }
         },
         editOptionValueChanged: { value: new LimSignal([]), writable: false }
      });
   }

   connectedCallback() {
      this.update();
   }

   disconnectedCallback() {
   }

   adoptedCallback() {
   }

   attributeChangedCallback(name, oldValue, newValue) {
   }

   update() {
      let txt = null;
      if (this.#editMode) {
         this.innerText = "";
         txt = document.createElement("textarea");
         txt.wrap = "off";
         txt.value = this.#stateObject.text;
         txt.style.overflow = "scroll";
         setTimeout(() => { txt.focus(); }, 100);
      }
      else {
         const edit = this.getElementsByTagName("textarea");
         if (edit.length) {
            this.#stateObject.text = edit[0].value;
            this.limResults?.saveStateObject?.();
         }

         this.innerText = "";
         txt = document.createElement("pre");
         txt.innerText = this.#stateObject.text;
         txt.style.overflow = "auto";
      }

      txt.className = "lim-fill lim-notes";
      txt.style.padding = "1em";
      this.appendChild(txt);
   }
}

/*___________________________________________________________________________*/
class LimPane extends HTMLElement {
   #toolbar
   #content
   #toolbarTabItems
   #currentTabToolbarItems
   #docId
   #paneId
   #tabs
   #stateObject

   constructor() {
      super();
   }

   get limResults() {
      return this.closest("lim-results");
   }

   initialize(stateObject, docId, paneId) {
      this.#stateObject = stateObject ?? {};
      this.#docId = docId;
      this.#paneId = paneId;
   }

   connectedCallback() {
      this.className = "lim-pane";
      this.#toolbarTabItems = [];
      this.#currentTabToolbarItems = [];
      this.#toolbar = new LimToolbar();
      this.#toolbar.className = "lim-toolbar lim-fill-width"
      this.appendChild(this.#toolbar);

      const requstedCurrentTabIndex = this.#stateObject?.currentTabIndex;

      this.#tabs = [];
      this.#stateObject.currentTabIndex = undefined;

      this.onFeatureListChanged = that => this.updateToolbar();

      let tabs = [];
      if (this.#stateObject?.tabs) {
         for (let tab of this.#stateObject.tabs) {
            tabs.push(LimClassFactory.createClass(tab.className, tab.state, this.#docId, this.#paneId));
         }
      }

      this.tabs = tabs;

      if (Array.isArray(this.#stateObject?.reports) && this.#stateObject.reports.length) {
         const ui = document.createElement("button");
         ui.title = "Report";
         const img = document.createElement("img");
         img.src = "/res/gnr_core_gui/CoreGUI/Icons/base/Report.svg";
         ui.appendChild(img);
         ui.onclick = () => {
            this.limResults?.saveStateObject?.();
            const { name, privateTableName } = this.#stateObject.reports[0];

            if (this.limResults?.onReportRequested)
               this.limResults.onReportRequested(name, privateTableName);

            else {
               const requestUrl = new URL(`/api/v1/show_report`, window.location.origin)
               requestUrl.searchParams.append("pane_id", this.#paneId);
               requestUrl.searchParams.append("doc_id", this.#docId);
               requestUrl.searchParams.append("table_name", privateTableName);
               requestUrl.searchParams.append("name", name);
               fetch(requestUrl);
            }
         };
         this.#toolbar.appendItem(ui);
      }

      if (this.tabs.length) {
         this.currentTabIndex = requstedCurrentTabIndex ?? 0;
         if (0 <= this.currentTabIndex && this.currentTabIndex < this.#toolbarTabItems.length)
            this.#toolbarTabItems[this.currentTabIndex].ariaPressed = "true";
      }
   }

   disconnectedCallback() {
      while (0 < this.childElementCount) {
         this.removeChild(this.lastElementChild);
      }
   }

   adoptedCallback() {
   }

   attributeChangedCallback(name, oldValue, newValue) {
   }

   disconnectToolbarUi() {
      for (let ui of this.#currentTabToolbarItems) {
         if (ui.boundOptionName && ui.onOptionValueChanged && this.currentTab[`${ui.boundOptionName}OptionValueChanged`])
            this.currentTab[`${ui.boundOptionName}OptionValueChanged`].disconnect(ui.onOptionValueChanged);
         if (ui.boundOption && ui.onOptionEnabledChanged && this.currentTab[`${ui.boundOptionName}OptionEnabledChanged`])
            this.currentTab[`${ui.boundOptionName}OptionEnabledChanged`].disconnect(ui.onOptionEnabledChanged);
         if (ui.boundOption && ui.onOptionValuesChanged && this.currentTab[`${ui.boundOptionName}OptionValuesChanged`])
            this.currentTab[`${ui.boundOptionName}OptionValuesChanged`].disconnect(ui.onOptionValuesChanged);
      }
   }

   updateToolbar() {
      if (!this.currentTab)
         return;

      this.disconnectToolbarUi();

      this.#toolbar.removeItems(this.#currentTabToolbarItems);
      this.#currentTabToolbarItems = [];

      if (this.currentTab.optionList) {
         for (let opt of this.currentTab.optionList.keys()) {
            const meta = this.currentTab.optionList.get(opt);
            const disabled = typeof this.currentTab[`${opt}OptionEnabled`] === "boolean" ? !this.currentTab[`${opt}OptionEnabled`] : false;
            let ui = null;
            if (meta.type === "info") {
               ui = document.createElement("span");
               ui.innerHTML = this.currentTab?.[`${opt}OptionValue`];
               if (meta?.className)
                  ui.className = meta.className;
               if (meta?.style) {
                  for (const prop in meta.style)
                     ui.style[prop] = meta.style[prop];
               }

               if (ui) {
                  ui.style["user-select"] = "none";
                  if (this.currentTab[`${opt}OptionValueChanged`]) {
                     ui.onOptionValueChanged = that => { ui.innerHTML = that[`${opt}OptionValue`] };
                     this.currentTab[`${opt}OptionValueChanged`].connect(ui.onOptionValueChanged);
                  }
                  if (this.currentTab?.[`${opt}OptionTrigger`]) {
                     ui.onclick = (e, that) => {
                        this.currentTab?.[`${opt}OptionTrigger`]?.();
                     }
                  }
               }
            }

            else if (meta.type === "action") {
               ui = document.createElement("button");
               ui.title = meta.title;
               ui.disabled = disabled;
               ui.onclick = () => { this.currentTab?.[`${opt}OptionTrigger`]?.(); };
               if (meta.iconres) {
                  const img = document.createElement("img");
                  img.src = meta.iconres;
                  ui.appendChild(img);
               }
               else {
                  ui.innerHTML = meta?.text ?? "";
               }
            }

            else if (meta.type === "action-popup") {
               ui = document.createElement("button");
               ui.title = meta.title;
               ui.disabled = disabled;
               ui.items = () => this.currentTab?.[`${opt}OptionItems`];
               if (meta.iconres) {
                  const img = document.createElement("img");
                  img.src = meta.iconres;
                  ui.appendChild(img);
               }
               else {
                  ui.innerHTML = meta?.text ?? "";
               }
               LimAddDropdownMenu(ui);
            }

            else if (meta.type === "option") {
               ui = document.createElement("button");
               ui.title = meta.title;
               ui.disabled = disabled;
               ui.ariaPressed = this.currentTab[`${opt}OptionValue`] ? "true" : "false";
               ui.onclick = () => {
                  ui.ariaPressed = ui.ariaPressed === "true" ? "false" : "true";
                  this.currentTab[`${opt}OptionValue`] = ui.ariaPressed === "true";
               };

               if (meta.iconres) {
                  const img = document.createElement("img");
                  img.src = meta.iconres;
                  ui.appendChild(img);
               }
               else {
                  ui.innerHtml = meta?.text ?? "";
               }

               if (ui) {
                  if (this.currentTab[`${opt}OptionValueChanged`]) {
                     ui.onOptionValueChanged = that => { ui.ariaPressed = that[`${opt}OptionValue`] };
                     this.currentTab[`${opt}OptionValueChanged`].connect(ui.onOptionValueChanged);
                  }
               }
            }

            else if (meta.type === "selection") {
               ui = new LimDropdown();
               ui.title = meta.title;
               ui.iconres = meta.iconres;
               ui.type = "select-one";
               ui.onchange = (e, that) => { this.currentTab[`${opt}OptionValue`] = that.value; }

               const v = this.currentTab[`${opt}OptionValue`];
               ui.options = this.currentTab[`${opt}OptionValues`].map(item => ({ value: item[0], text: item[1], selected: item[0] === v }));

               if (ui) {
                  if (this.currentTab[`${opt}OptionValueChanged`]) {
                     ui.onOptionValueChanged = that => { ui.value = that[`${opt}OptionValue`] };
                     this.currentTab[`${opt}OptionValueChanged`].connect(ui.onOptionValueChanged);
                  }
                  if (this.currentTab[`${opt}OptionValuesChanged`]) {
                     ui.onOptionValuesChanged = that => { ui.options = that[`${opt}OptionValues`].map(item => ({ value: item[0], text: item[1], selected: item[0] === that[`${opt}OptionValue`] })) };
                     this.currentTab[`${opt}OptionValuesChanged`].connect(ui.onOptionValuesChanged);
                  }
               }
            }

            else if (meta.type === "multi-selection") {
               ui = new LimDropdown();
               ui.title = meta.title;
               ui.iconres = meta.iconres;
               ui.type = "select-multiple";
               ui.onchange = (e, that) => { this.currentTab[`${opt}OptionValue`] = that.value; }

               const v = this.currentTab[`${opt}OptionValue`];
               ui.options = this.currentTab[`${opt}OptionValues`].map(item => ({ value: item[0], text: item[1], selected: Array.isArray(v) && v.includes(item[0]) }));

               if (ui) {
                  if (this.currentTab[`${opt}OptionValueChanged`]) {
                     ui.onOptionValueChanged = that => { ui.value = that[`${opt}OptionValue`] };
                     this.currentTab[`${opt}OptionValueChanged`].connect(ui.onOptionValueChanged);
                  }
                  if (this.currentTab[`${opt}OptionValuesChanged`]) {
                     ui.onOptionValuesChanged = that => { ui.options = that[`${opt}OptionValues`].map(item => ({ value: item[0], text: item[1], selected: Array.isArray(that[`${opt}OptionValue`]) && that[`${opt}OptionValue`].includes(item[0]) })) };
                     this.currentTab[`${opt}OptionValuesChanged`].connect(ui.onOptionValuesChanged);
                  }
               }
            }

            if (ui) {
               ui.boundOptionName = opt;
               if (this.currentTab[`${opt}OptionEnabledChanged`]) {
                  ui.onOptionEnabledChanged = that => { ui.disabled = !that[`${opt}OptionEnabled`] };
                  this.currentTab[`${opt}OptionEnabledChanged`].connect(ui.onOptionEnabledChanged);
               }

               if (0 === this.#currentTabToolbarItems?.length && 0 < (this.#toolbar?.children?.length ?? 0)) {
                  const separator = document.createElement("span");
                  separator.className = "lim-sep-vert";
                  this.#toolbar.appendItem(separator);
                  this.#currentTabToolbarItems.push(separator);
               }

               this.#toolbar.appendItem(ui);
               this.#currentTabToolbarItems.push(ui);
            }
         }
      }
   }

   get tabs() {
      return this.#tabs;
   }

   set tabs(val) {
      this.#tabs = val;
      this.#toolbar.removeItems(this.#toolbarTabItems);
      this.#toolbarTabItems = [];
      if (1 < this.#tabs.length) {
         for (let i = 0; i < this.#tabs.length; i++) {
            const ui = document.createElement("button");
            ui.title = this.#tabs[i].name ?? this.#tabs[i].title;
            ui.ariaPressed = i === this.#stateObject.currentTabIndex;
            const img = document.createElement("img");
            img.src = this.#tabs[i].iconres;
            ui.appendChild(img);
            ui.onclick = (event) => {
               for (let i = 0; i < this.#toolbarTabItems.length; i++) {
                  const item = this.#toolbarTabItems[i];
                  if (item.contains(event.target)) {
                     item.ariaPressed = true;
                     this.currentTabIndex = i;
                  }
                  else
                     item.ariaPressed = false;
               }
            }
            this.#toolbarTabItems.push(ui);
            this.#toolbar.appendItem(ui);
         }
      }
   }

   get currentTab() {
      return this.#tabs[this.#stateObject.currentTabIndex];
   }

   get currentTabIndex() {
      return this.#stateObject.currentTabIndex;
   }

   set currentTabIndex(val) {
      if (this.#stateObject.currentTabIndex === val)
         return;

      if (typeof this.#stateObject.currentTabIndex === "number" && 0 <= this.#stateObject.currentTabIndex && this.#stateObject.currentTabIndex < this.#tabs.length) {
         this.disconnectToolbarUi();
         this.removeChild(this.#content);
         this.#content = null;
      }

      this.#stateObject.currentTabIndex = val;

      if (typeof this.#stateObject.currentTabIndex === "number" && 0 <= this.#stateObject.currentTabIndex && this.#stateObject.currentTabIndex < this.#tabs.length) {
         if (this.currentTab instanceof Node) {
            this.#content = this.currentTab;
            this.appendChild(this.#content);
         }
         else
            console.log("Unexpected tab:", this.currentTab);
      }

      this.updateToolbar();
      this.limResults?.stateChanged?.();
   }

   get toolbar() {
      return this.#toolbar;
   }

   onReady() {
      this.#tabs?.map?.(item => item?.onReady?.());
   }
}

customElements.define('lim-results', LimResults);

customElements.define('lim-editable-notes', LimEditableNotes);
LimClassFactory.registerConstructor("LimEditableNotes", (...pars) => {
   const el = new LimEditableNotes();
   el.initialize(...pars);
   return el;
});

customElements.define('lim-pane', LimPane);
LimClassFactory.registerConstructor("LimPane", (...pars) => {
   const el = new LimPane();
   el.initialize(...pars);
   return el;
});


