<!--
 Open MCT, Copyright (c) 2014-2024, United States Government
 as represented by the Administrator of the National Aeronautics and Space
 Administration. All rights reserved.

 Open MCT is licensed under the Apache License, Version 2.0 (the
 "License"); you may not use this file except in compliance with the License.
 You may obtain a copy of the License at
 http://www.apache.org/licenses/LICENSE-2.0.

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 License for the specific language governing permissions and limitations
 under the License.

 Open MCT includes source code licensed under additional open source
 licenses. See the Open Source Licenses file (LICENSES.md) included with
 this source code distribution or the Licensing information page available
 at runtime from the About dialog for additional information.
-->
<template>
  <div class="c-notebook" :class="[{ 'c-notebook--restricted': isRestricted }]">
    <div class="c-notebook__head">
      <Search
        class="c-notebook__search"
        :value="search"
        @input="search = $event"
        @clear="resetSearch()"
      />
    </div>
    <SearchResults
      v-if="search.length"
      ref="searchResults"
      :domain-object="domainObject"
      :results="searchResults"
      @cancel-edit="cancelTransaction"
      @editing-entry="startTransaction"
      @change-section-page="changeSelectedSection"
      @update-entries="updateEntries"
    />
    <div v-if="!search.length" class="c-notebook__body">
      <Sidebar
        ref="sidebar"
        class="c-notebook__nav c-sidebar c-drawer c-drawer--align-left"
        :class="sidebarClasses"
        :default-page-id="defaultPageId"
        :selected-page-id="getSelectedPageId()"
        :default-section-id="defaultSectionId"
        :selected-section-id="getSelectedSectionId()"
        :domain-object="domainObject"
        :page-title="domainObject.configuration.pageTitle"
        :section-title="domainObject.configuration.sectionTitle"
        :sections="sections"
        :sidebar-covers-entries="sidebarCoversEntries"
        @default-page-deleted="cleanupDefaultNotebook"
        @default-section-deleted="cleanupDefaultNotebook"
        @pages-changed="pagesChanged"
        @select-page="selectPage"
        @sections-changed="sectionsChanged"
        @select-section="selectSection"
        @toggle-nav="toggleNav"
      />
      <div
        class="c-notebook__page-view"
        :class="{
          'c-notebook--page-locked': selectedPage?.isLocked,
          'c-notebook--page-unlocked': !selectedPage?.isLocked
        }"
      >
        <div class="c-notebook__page-view__header">
          <button
            class="c-notebook__toggle-nav-button c-icon-button c-icon-button--major icon-menu-hamburger"
            @click="toggleNav"
          ></button>
          <div class="c-notebook__page-view__path c-path">
            <span class="c-notebook__path__section c-path__item">
              {{ selectedSection ? selectedSection.name : '' }}
            </span>
            <span class="c-notebook__path__page c-path__item">
              {{ selectedPage ? selectedPage.name : '' }}
            </span>
          </div>
          <div class="c-notebook__page-view__controls">
            <select v-model="showTime" class="c-notebook__controls__time">
              <option value="0" :selected="showTime === 0">Show all</option>
              <option value="1" :selected="showTime === 1">Last hour</option>
              <option value="8" :selected="showTime === 8">Last 8 hours</option>
              <option value="24" :selected="showTime === 24">Last 24 hours</option>
            </select>
            <select v-model="defaultSort" class="c-notebook__controls__time">
              <option value="newest" :selected="defaultSort === 'newest'">Newest first</option>
              <option value="oldest" :selected="defaultSort === 'oldest'">Oldest first</option>
            </select>
          </div>
        </div>
        <div
          v-if="selectedPage && !selectedPage.isLocked"
          :aria-disabled="activeTransaction"
          class="c-notebook__drag-area icon-plus"
          aria-dropeffect="link"
          aria-labelledby="newEntryLabel"
          @click="newEntry(null, $event)"
          @dragover="dragOver"
          @drop.capture="dropCapture"
          @drop="dropOnEntry($event)"
        >
          <span id="newEntryLabel" class="c-notebook__drag-area__label">
            To start a new entry, click here or drag and drop any object
          </span>
        </div>
        <ProgressBar
          v-if="savingTransaction"
          class="c-telemetry-table__progress-bar"
          :model="{ progressPerc: null }"
        />
        <div v-if="selectedPage && selectedPage.isLocked" class="c-notebook__page-locked-message">
          <div class="icon-lock"></div>
          <div class="c-notebook__page-locked-message-text">
            This page has been committed and cannot be modified or removed
          </div>
        </div>
        <div
          v-if="selectedSection && selectedPage"
          ref="notebookEntries"
          class="c-notebook__entries"
          aria-label="Notebook Entries"
        >
          <NotebookEntry
            v-for="entry in filteredAndSortedEntries"
            :key="entry.id"
            :entry="entry"
            :domain-object="domainObject"
            :notebook-annotations="notebookAnnotations[entry.id]"
            :selected-page="selectedPage"
            :selected-section="selectedSection"
            :read-only="false"
            :is-locked="selectedPage.isLocked"
            :selected-entry-id="selectedEntryId"
            @cancel-edit="cancelTransaction"
            @editing-entry="startTransaction"
            @delete-entry="deleteEntry"
            @update-entry="updateEntry"
            @update-annotations="loadAnnotations"
            @entry-selection="entrySelection(entry)"
          />
        </div>
        <div
          v-if="isRestricted && filteredAndSortedEntries?.length > 0 && !selectedPage.isLocked"
          class="c-notebook__commit-entries-control"
        >
          <button
            class="c-button commit-button icon-lock"
            title="Commit entries and lock this page from further changes"
            aria-labelledby="commitEntriesLabel"
            @click="lockPage()"
          >
            <span id="commitEntriesLabel" class="c-button__label">Commit Entries</span>
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
import { debounce } from 'lodash';

import Search from '@/ui/components/SearchComponent.vue';

import ProgressBar from '../../../ui/components/ProgressBar.vue';
import objectLink from '../../../ui/mixins/object-link.js';
import { isNotebookViewType, RESTRICTED_NOTEBOOK_TYPE } from '../notebook-constants.js';
import {
  addNotebookEntry,
  createNewEmbed,
  createNewImageEmbed,
  getEntryPosById,
  getNotebookEntries,
  mutateObject,
  selectEntry
} from '../utils/notebook-entries.js';
import {
  saveNotebookImageDomainObject,
  updateNamespaceOfDomainObject
} from '../utils/notebook-image.js';
import {
  clearDefaultNotebook,
  getDefaultNotebook,
  setDefaultNotebook,
  setDefaultNotebookPageId,
  setDefaultNotebookSectionId
} from '../utils/notebook-storage.js';
import NotebookEntry from './NotebookEntry.vue';
import SearchResults from './SearchResults.vue';
import Sidebar from './SidebarComponent.vue';
function objectCopy(obj) {
  return JSON.parse(JSON.stringify(obj));
}

export default {
  components: {
    NotebookEntry,
    Search,
    SearchResults,
    Sidebar,
    ProgressBar
  },
  inject: ['agent', 'openmct', 'snapshotContainer'],
  props: {
    domainObject: {
      type: Object,
      required: true
    }
  },
  data() {
    return {
      defaultPageId: this.getDefaultPageId(),
      defaultSectionId: this.getDefaultSectionId(),
      selectedSectionId: this.getSelectedSectionId(),
      selectedPageId: this.getSelectedPageId(),
      defaultSort: this.domainObject.configuration.defaultSort,
      focusEntryId: null,
      isRestricted: this.domainObject.type === RESTRICTED_NOTEBOOK_TYPE,
      search: '',
      searchResults: [],
      lastLocalAnnotationCreation: 0,
      showTime: this.domainObject.configuration.showTime || 0,
      showNav: false,
      sidebarCoversEntries: false,
      filteredAndSortedEntries: [],
      notebookAnnotations: {},
      selectedEntryId: undefined,
      activeTransaction: false,
      savingTransaction: false,
      sections: this.domainObject.configuration.sections || []
    };
  },
  computed: {
    pages() {
      return this.getPages() || [];
    },
    selectedPage() {
      const pages = this.getPages();
      if (!pages.length) {
        return undefined;
      }

      const selectedPage = pages.find((page) => page.id === this.selectedPageId);
      if (selectedPage) {
        return selectedPage;
      }

      const defaultPage = pages.find((page) => page.id === this.defaultPageId);
      if (defaultPage) {
        return defaultPage;
      }

      return this.pages[0];
    },
    selectedSection() {
      if (!this.sections.length) {
        return undefined;
      }

      const selectedSection = this.sections.find(
        (section) => section.id === this.selectedSectionId
      );
      if (selectedSection) {
        return selectedSection;
      }

      const defaultSection = this.sections.find((section) => section.id === this.defaultSectionId);
      if (defaultSection) {
        return defaultSection;
      }

      return this.sections[0];
    },
    sidebarClasses() {
      let sidebarClasses = [];
      if (this.showNav) {
        sidebarClasses.push('is-expanded');
      }

      if (this.sidebarCoversEntries) {
        sidebarClasses.push('c-drawer--overlays');
      } else {
        sidebarClasses.push('c-drawer--push');
      }

      return sidebarClasses;
    }
  },
  watch: {
    search() {
      this.getSearchResults();
    },
    defaultSort() {
      mutateObject(this.openmct, this.domainObject, 'configuration.defaultSort', this.defaultSort);
      this.filterAndSortEntries();
    },
    showTime() {
      mutateObject(this.openmct, this.domainObject, 'configuration.showTime', this.showTime);
    },
    notebookAnnotations: {
      handler() {
        this.filterAndSortEntries();
      },
      deep: true
    }
  },
  beforeMount() {
    this.getSearchResults = debounce(this.getSearchResults, 500);
    this.syncUrlWithPageAndSection = debounce(this.syncUrlWithPageAndSection, 100);
  },
  async created() {
    this.transaction = null;
    this.abortController = new AbortController();
    try {
      await this.loadAnnotations();
    } catch (err) {
      if (err.name !== 'AbortError') {
        throw err;
      }
    }
  },
  mounted() {
    this.formatSidebar();
    this.setSectionAndPageFromUrl();

    this.openmct.selection.on('change', this.updateSelection);

    window.addEventListener('orientationchange', this.formatSidebar);
    window.addEventListener('hashchange', this.setSectionAndPageFromUrl);
    this.filterAndSortEntries();
    this.unobserveEntries = this.openmct.objects.observe(
      this.domainObject,
      '*',
      this.filterAndSortEntries
    );
  },
  beforeUnmount() {
    this.abortController.abort();
    if (this.unlisten) {
      this.unlisten();
    }

    if (this.unobserveEntries) {
      this.unobserveEntries();
    }

    Object.keys(this.notebookAnnotations).forEach((entryID) => {
      const notebookAnnotationsForEntry = this.notebookAnnotations[entryID];
      notebookAnnotationsForEntry.forEach((notebookAnnotation) => {
        this.openmct.objects.destroyMutable(notebookAnnotation);
      });
    });

    window.removeEventListener('orientationchange', this.formatSidebar);
    window.removeEventListener('hashchange', this.setSectionAndPageFromUrl);
    this.openmct.selection.off('change', this.updateSelection);
  },
  updated: function () {
    this.$nextTick(() => {
      this.focusOnEntryId();
    });
  },
  methods: {
    changeSectionPage(newParams, oldParams, changedParams) {
      if (isNotebookViewType(newParams.view)) {
        return;
      }

      let pageId = newParams.pageId;
      let sectionId = newParams.sectionId;

      if (!pageId && !sectionId) {
        return;
      }

      this.sections.forEach((section) => {
        section.isSelected = Boolean(section.id === sectionId);

        if (section.isSelected) {
          section.pages.forEach((page) => {
            page.isSelected = Boolean(page.id === pageId);
          });
        }
      });
    },
    updateSelection(selection) {
      const keyString = this.openmct.objects.makeKeyString(this.domainObject.identifier);

      if (selection?.[0]?.[0]?.context?.targetDetails?.[keyString]?.entryId === undefined) {
        this.selectedEntryId = undefined;
      }
    },
    async loadAnnotations() {
      if (!this.openmct.annotation.getAvailableTags().length) {
        // don't bother loading annotations if there are no tags
        return;
      }

      this.lastLocalAnnotationCreation = this.domainObject.annotationLastCreated ?? 0;

      const foundAnnotations = await this.openmct.annotation.getAnnotations(
        this.domainObject.identifier,
        this.abortController.signal
      );

      foundAnnotations.forEach((foundAnnotation) => {
        const target = foundAnnotation.targets?.[0];
        const entryId = target.entryId;
        if (!this.notebookAnnotations[entryId]) {
          this.notebookAnnotations[entryId] = [];
        }

        const annotationExtant = this.notebookAnnotations[entryId].some((existingAnnotation) => {
          return this.openmct.objects.areIdsEqual(
            existingAnnotation.identifier,
            foundAnnotation.identifier
          );
        });
        if (!annotationExtant) {
          const annotationArray = this.notebookAnnotations[entryId];
          const mutableAnnotation = this.openmct.objects.toMutable(foundAnnotation);
          annotationArray.push(mutableAnnotation);
        }
      });
    },
    filterAndSortEntries() {
      const filterTime = this.openmct.time.now();
      const pageEntries =
        getNotebookEntries(this.domainObject, this.selectedSection, this.selectedPage) || [];
      const hours = parseInt(this.showTime, 10);
      const filteredPageEntriesByTime = hours
        ? pageEntries.filter((entry) => filterTime - entry.createdOn <= hours * 60 * 60 * 1000)
        : pageEntries;

      this.filteredAndSortedEntries =
        this.defaultSort === 'oldest'
          ? filteredPageEntriesByTime
          : [...filteredPageEntriesByTime].reverse();

      if (this.lastLocalAnnotationCreation < this.domainObject.annotationLastCreated) {
        this.loadAnnotations().catch((err) => {
          if (err.name !== 'AbortError') {
            throw err;
          }
        });
      }
    },
    changeSelectedSection({ sectionId, pageId }) {
      const sections = this.sections.map((s) => {
        s.isSelected = false;

        if (s.id === sectionId) {
          s.isSelected = true;
        }

        s.pages.forEach((p, i) => {
          p.isSelected = false;

          if (pageId && pageId === p.id) {
            p.isSelected = true;
          }

          if (!pageId && i === 0) {
            p.isSelected = true;
          }
        });

        return s;
      });

      this.sectionsChanged({ sections });
      this.resetSearch();
    },
    cleanupDefaultNotebook() {
      this.defaultPageId = undefined;
      this.defaultSectionId = undefined;
      this.removeDefaultClass(this.domainObject.identifier);
      clearDefaultNotebook();
    },
    lockPage() {
      let prompt = this.openmct.overlays.dialog({
        iconClass: 'alert',
        message:
          'This action will lock this page and disallow any new entries, or editing of existing entries. Do you want to continue?',
        buttons: [
          {
            label: 'Lock Page',
            callback: () => {
              this.selectedPage.isLocked = true;

              // cant be default if it's locked
              if (this.selectedPage.id === this.defaultPageId) {
                this.cleanupDefaultNotebook();
              }

              if (!this.selectedSection.isLocked) {
                this.selectedSection.isLocked = true;
              }

              mutateObject(
                this.openmct,
                this.domainObject,
                'configuration.sections',
                this.sections
              );

              if (!this.domainObject.locked) {
                mutateObject(this.openmct, this.domainObject, 'locked', true);
              }

              prompt.dismiss();
            }
          },
          {
            label: 'Cancel',
            callback: () => {
              prompt.dismiss();
            }
          }
        ]
      });
    },
    setSectionAndPageFromUrl() {
      let sectionId =
        this.getSectionIdFromUrl() || this.getDefaultSectionId() || this.getSelectedSectionId();
      let pageId = this.getPageIdFromUrl() || this.getDefaultPageId() || this.getSelectedPageId();

      this.selectSection(sectionId);
      this.selectPage(pageId);
    },
    createNotebookStorageObject() {
      const page = this.selectedPage;
      const section = this.selectedSection;

      return {
        name: this.domainObject.name,
        identifier: this.domainObject.identifier,
        link: this.getLinktoNotebook(),
        defaultSectionId: section.id,
        defaultPageId: page.id
      };
    },
    deleteEntry(entryId) {
      const entryPos = getEntryPosById(
        entryId,
        this.domainObject,
        this.selectedSection,
        this.selectedPage
      );
      if (entryPos === -1) {
        this.openmct.notifications.alert('Warning: unable to delete entry');
        console.error(
          `unable to delete entry ${entryId} from section ${this.selectedSection}, page ${this.selectedPage}`
        );

        this.cancelTransaction();

        return;
      }

      const dialog = this.openmct.overlays.dialog({
        iconClass: 'alert',
        message: 'This action will permanently delete this entry. Do you wish to continue?',
        buttons: [
          {
            label: 'Ok',
            emphasis: true,
            callback: () => {
              const entries = getNotebookEntries(
                this.domainObject,
                this.selectedSection,
                this.selectedPage
              );
              if (entries) {
                entries.splice(entryPos, 1);
                this.updateEntries(entries);
                this.filterAndSortEntries();
                this.removeAnnotations(entryId);
              } else {
                this.cancelTransaction();
              }

              dialog.dismiss();
            }
          },
          {
            label: 'Cancel',
            callback: () => {
              dialog.dismiss();
            }
          }
        ]
      });
    },
    removeAnnotations(entryId) {
      if (this.notebookAnnotations[entryId]) {
        this.openmct.annotation.deleteAnnotations(this.notebookAnnotations[entryId]);
      }
    },
    checkEntryPos(entry) {
      const entryPos = getEntryPosById(
        entry.id,
        this.domainObject,
        this.selectedSection,
        this.selectedPage
      );
      if (entryPos === -1) {
        this.openmct.notifications.alert('Warning: unable to tag entry');
        console.error(
          `unable to tag entry ${entry} from section ${this.selectedSection}, page ${this.selectedPage}`
        );

        return false;
      }

      return true;
    },
    dragOver(event) {
      event.preventDefault();
      event.dataTransfer.dropEffect = 'copy';
    },
    dropCapture(event) {
      const isEditing = this.openmct.editor.isEditing();
      if (isEditing) {
        this.openmct.editor.cancel();
      }
    },
    async dropOnEntry(dropEvent) {
      dropEvent.preventDefault();
      dropEvent.stopImmediatePropagation();

      const dataTransferFiles = Array.from(dropEvent.dataTransfer.files);
      const localImageDropped = dataTransferFiles.some((file) => file.type.includes('image'));
      const snapshotId = dropEvent.dataTransfer.getData('openmct/snapshot/id');
      const domainObjectData = dropEvent.dataTransfer.getData('openmct/domain-object-path');
      const imageUrl = dropEvent.dataTransfer.getData('URL');
      if (localImageDropped) {
        // local image(s) dropped from disk (file)
        const embeds = [];
        await Promise.all(
          dataTransferFiles.map(async (file) => {
            if (file.type.includes('image')) {
              const imageData = file;
              const imageEmbed = await createNewImageEmbed(
                imageData,
                this.openmct,
                imageData?.name
              );
              embeds.push(imageEmbed);
            }
          })
        );
        this.newEntry(embeds);
      } else if (imageUrl) {
        // remote image dropped (URL)
        try {
          const response = await fetch(imageUrl);
          const imageData = await response.blob();
          const imageEmbed = await createNewImageEmbed(imageData, this.openmct);
          this.newEntry([imageEmbed]);
        } catch (error) {
          this.openmct.notifications.alert(`Unable to add image: ${error.message} `);
          console.error(`Problem embedding remote image`, error);
        }
      } else if (snapshotId.length) {
        // snapshot object
        const snapshot = this.snapshotContainer.getSnapshot(snapshotId);
        this.newEntry([snapshot.embedObject]);
        this.snapshotContainer.removeSnapshot(snapshotId);

        const namespace = this.domainObject.identifier.namespace;
        const notebookImageDomainObject = updateNamespaceOfDomainObject(
          snapshot.notebookImageDomainObject,
          namespace
        );
        saveNotebookImageDomainObject(this.openmct, notebookImageDomainObject);
      } else if (domainObjectData) {
        // plain domain object
        const objectPath = JSON.parse(domainObjectData);
        const bounds = this.openmct.time.getBounds();
        const snapshotMeta = {
          bounds,
          link: null,
          objectPath,
          openmct: this.openmct
        };
        const embed = await createNewEmbed(snapshotMeta);
        this.newEntry([embed]);
      } else {
        this.openmct.notifications.error(
          `Unknown object(s) dropped and cannot embed. Try again with an image or domain object.`
        );
        console.warn(
          `Unknown object(s) dropped and cannot embed. Try again with an image or domain object.`
        );
        return;
      }
    },
    focusOnEntryId() {
      if (!this.focusEntryId) {
        return;
      }

      const element = this.$refs.notebookEntries.querySelector(`#${this.focusEntryId}`);

      if (!element) {
        return;
      }

      element.focus();
      this.focusEntryId = null;
    },
    formatSidebar() {
      /*
                Determine if the sidebar should slide over content, or compress it
                Slide over checks:
                - phone (all orientations)
                - tablet portrait
                - in a layout frame (within .c-so-view)
            */
      const isPhone = this.agent.isPhone();
      const isTablet = this.agent.isTablet();
      const isPortrait = this.agent.isPortrait();
      const isInLayout = Boolean(this.$el.closest('.c-so-view'));
      const sidebarCoversEntries = isPhone || (isTablet && isPortrait) || isInLayout;
      this.sidebarCoversEntries = sidebarCoversEntries;
    },
    getDefaultPageId() {
      return this.isDefaultNotebook() ? getDefaultNotebook().defaultPageId : undefined;
    },
    isDefaultNotebook() {
      const defaultNotebook = getDefaultNotebook();
      const defaultNotebookIdentifier = defaultNotebook && defaultNotebook.identifier;

      return (
        defaultNotebookIdentifier !== null &&
        this.openmct.objects.areIdsEqual(defaultNotebookIdentifier, this.domainObject.identifier)
      );
    },
    getDefaultSectionId() {
      return this.isDefaultNotebook() ? getDefaultNotebook().defaultSectionId : undefined;
    },
    getLinktoNotebook() {
      const objectPath = this.openmct.router.path;
      const link = objectLink.computed.objectLink.call({
        objectPath,
        openmct: this.openmct
      });

      const selectedSection = this.selectedSection;
      const selectedPage = this.selectedPage;
      const sectionId = selectedSection ? selectedSection.id : '';
      const pageId = selectedPage ? selectedPage.id : '';

      return `${link}?sectionId=${sectionId}&pageId=${pageId}`;
    },
    getPage(section, id) {
      return section.pages.find((p) => p.id === id);
    },
    getSection(id) {
      return this.sections.find((s) => s.id === id);
    },
    getSearchResults() {
      if (!this.search.length) {
        return [];
      }

      const output = [];
      const sections = this.domainObject.configuration.sections;
      const entries = this.domainObject.configuration.entries;
      const searchTextLower = this.search.toLowerCase();
      const originalSearchText = this.search;
      let sectionTrackPageHit;
      let pageTrackEntryHit;
      let sectionTrackEntryHit;

      sections.forEach((section) => {
        const pages = section.pages;
        let resultMetadata = {
          originalSearchText,
          sectionHit: section.name && section.name.toLowerCase().includes(searchTextLower)
        };
        sectionTrackPageHit = false;
        sectionTrackEntryHit = false;

        pages.forEach((page) => {
          resultMetadata.pageHit = page.name && page.name.toLowerCase().includes(searchTextLower);
          pageTrackEntryHit = false;

          if (resultMetadata.pageHit) {
            sectionTrackPageHit = true;
          }

          // check for no entries first
          if (entries[section.id] && entries[section.id][page.id]) {
            const pageEntries = entries[section.id][page.id];

            pageEntries.forEach((entry) => {
              const entryHit = entry.text && entry.text.toLowerCase().includes(searchTextLower);

              // any entry hit goes in, it's the most unique of the hits
              if (entryHit) {
                resultMetadata.entryHit = entryHit;
                pageTrackEntryHit = true;
                sectionTrackEntryHit = true;

                output.push(
                  objectCopy({
                    metadata: resultMetadata,
                    section,
                    page,
                    entry
                  })
                );
              }
            });
          }

          // all entries checked, now in pages,
          // if page hit, but not in results, need to add
          if (resultMetadata.pageHit && !pageTrackEntryHit) {
            resultMetadata.entryHit = false;

            output.push(
              objectCopy({
                metadata: resultMetadata,
                section,
                page
              })
            );
          }
        });

        // all pages checked, now in sections,
        // if section hit, but not in results, need to add and default page
        if (resultMetadata.sectionHit && !sectionTrackPageHit && !sectionTrackEntryHit) {
          resultMetadata.entryHit = false;
          resultMetadata.pageHit = false;

          output.push(
            objectCopy({
              metadata: resultMetadata,
              section,
              page: pages[0]
            })
          );
        }
      });

      this.searchResults = output;
    },
    getPages() {
      if (!this.selectedSection || !this.selectedSection.pages.length) {
        return [];
      }

      return this.selectedSection.pages;
    },
    getSelectedPageId() {
      return this.selectedPage?.id;
    },
    getSelectedSectionId() {
      return this.selectedSection?.id;
    },
    async newEntry(embeds, event) {
      this.startTransaction();
      this.resetSearch();
      const notebookStorage = this.createNotebookStorageObject();
      this.updateDefaultNotebook(notebookStorage);
      const id = await addNotebookEntry(this.openmct, this.domainObject, notebookStorage, embeds);

      const element = this.$refs.notebookEntries.querySelector(`#${id}`);
      const entryAnnotations = this.notebookAnnotations[id] ?? {};
      selectEntry({
        element,
        entryId: id,
        domainObject: this.domainObject,
        openmct: this.openmct,
        notebookAnnotations: entryAnnotations
      });
      if (event) {
        event.stopPropagation();
      }

      this.filterAndSortEntries();
      this.focusEntryId = id;
      this.selectedEntryId = id;

      // put entry into edit mode
      this.$nextTick(() => {
        element.dispatchEvent(new Event('click'));
      });
    },
    orientationChange() {
      this.formatSidebar();
    },
    pagesChanged({ pages = [], id = null }) {
      const selectedSection = this.selectedSection;
      if (!selectedSection) {
        return;
      }

      selectedSection.pages = pages;
      const sections = this.sections.map((section) => {
        if (section.id === selectedSection.id) {
          section = selectedSection;
        }

        return section;
      });

      this.sectionsChanged({ sections });
    },
    removeDefaultClass(identifier) {
      this.openmct.status.delete(identifier);
    },
    resetSearch() {
      this.search = '';
      this.searchResults = [];
    },
    toggleNav() {
      this.showNav = !this.showNav;
    },
    updateDefaultNotebook(updatedNotebookStorageObject) {
      if (!this.isDefaultNotebook()) {
        const persistedNotebookStorageObject = getDefaultNotebook();
        if (
          persistedNotebookStorageObject &&
          persistedNotebookStorageObject.identifier !== undefined
        ) {
          this.removeDefaultClass(persistedNotebookStorageObject.identifier);
        }

        setDefaultNotebook(this.openmct, updatedNotebookStorageObject, this.domainObject);
      }

      if (this.defaultSectionId !== updatedNotebookStorageObject.defaultSectionId) {
        setDefaultNotebookSectionId(updatedNotebookStorageObject.defaultSectionId);
        this.defaultSectionId = updatedNotebookStorageObject.defaultSectionId;
      }

      if (this.defaultPageId !== updatedNotebookStorageObject.defaultPageId) {
        setDefaultNotebookPageId(updatedNotebookStorageObject.defaultPageId);
        this.defaultPageId = updatedNotebookStorageObject.defaultPageId;
      }
    },
    updateDefaultNotebookSection(sections, id) {
      if (!id) {
        return;
      }

      const notebookStorage = getDefaultNotebook();
      if (!notebookStorage || notebookStorage.identifier.key !== this.domainObject.identifier.key) {
        return;
      }

      const defaultNotebookSectionId = notebookStorage.defaultSectionId;
      if (defaultNotebookSectionId === id) {
        const section = sections.find((s) => s.id === id);
        if (!section) {
          this.removeDefaultClass(this.domainObject.identifier);
          clearDefaultNotebook();

          return;
        }
      }

      if (id !== defaultNotebookSectionId) {
        return;
      }

      setDefaultNotebookSectionId(defaultNotebookSectionId);
    },
    updateEntry(entry) {
      const entries = getNotebookEntries(
        this.domainObject,
        this.selectedSection,
        this.selectedPage
      );
      const entryPos = getEntryPosById(
        entry.id,
        this.domainObject,
        this.selectedSection,
        this.selectedPage
      );
      entries[entryPos] = entry;

      this.updateEntries(entries);
    },
    updateEntries(entries) {
      const configuration = this.domainObject.configuration;
      const notebookEntries = configuration.entries || {};
      notebookEntries[this.selectedSection.id][this.selectedPage.id] = entries;

      mutateObject(this.openmct, this.domainObject, 'configuration.entries', notebookEntries);

      this.saveTransaction();
    },
    getPageIdFromUrl() {
      return this.openmct.router.getParams().pageId;
    },
    getSectionIdFromUrl() {
      return this.openmct.router.getParams().sectionId;
    },
    syncUrlWithPageAndSection() {
      this.openmct.router.updateParams({
        pageId: this.selectedPageId,
        sectionId: this.selectedSectionId
      });
    },
    sectionsChanged({ sections, id = undefined }) {
      this.sections = [...sections];
      this.startTransaction();
      mutateObject(this.openmct, this.domainObject, 'configuration.sections', sections);
      this.saveTransaction();
      this.updateDefaultNotebookSection(sections, id);
    },
    selectPage(pageId) {
      if (!pageId) {
        return;
      }

      this.selectedPageId = pageId;
      this.syncUrlWithPageAndSection();
      this.filterAndSortEntries();
    },
    selectSection(sectionId) {
      if (!sectionId) {
        return;
      }

      this.selectedSectionId = sectionId;

      const pageId = this.selectedSection.pages[0].id;
      this.selectPage(pageId);

      this.syncUrlWithPageAndSection();
      this.filterAndSortEntries();
    },
    startTransaction() {
      if (!this.openmct.objects.isTransactionActive()) {
        this.activeTransaction = true;
        this.transaction = this.openmct.objects.startTransaction();
      }
    },
    async saveTransaction() {
      if (this.transaction !== null) {
        this.savingTransaction = true;
        try {
          await this.transaction.commit();
        } finally {
          this.endTransaction();
        }
      }
    },
    async cancelTransaction() {
      if (this.transaction !== null) {
        try {
          await this.transaction.cancel();
        } finally {
          this.endTransaction();
        }
      }
    },
    entrySelection(entry) {
      this.selectedEntryId = entry.id;
    },
    endTransaction() {
      this.openmct.objects.endTransaction();
      this.transaction = null;
      this.savingTransaction = false;
      this.activeTransaction = false;
    }
  }
};
</script>
