<template>
  <div
    :data-scrollto="chapter.id"
    class="chapter"
    data-cy="chapter"
    ref="wrapper"
  >
    <div
      :class="{ 'hideable-element--greyed-out': titleGreyedOut }"
      class="hideable-element"
      v-if="!titleHidden"
    >
      <h3
        class="chapter__title"
        :id="'chapter-' + index"
      >
        {{ chapter.title }}
        <copy-link :id="chapter.id" />
      </h3>
    </div>

    <visibility-action
      :block="chapter"
      type="chapter-title"
      v-if="editMode"
    />

    <bookmark-actions
      :bookmarked="!!chapter.bookmark"
      :note="note"
      :edit-mode="editMode"
      class="chapter__bookmark-actions"
      data-cy="chapter-bookmark-actions"
      @add-note="addNote"
      @edit-note="editNote"
      @bookmark="bookmark()"
    />
    <div
      :class="{ 'hideable-element--greyed-out': descriptionGreyedOut }"
      class="chapter__intro intro hideable-element"
      data-cy="chapter-intro"
      v-if="!descriptionHidden"
      ref="chapterIntro"
    >
      <visibility-action
        :block="chapter"
        :chapter="true"
        type="chapter-description"
        v-if="editMode"
      />
      <p
        data-cy="chapter-description"
        class="chapter__description"
      >
        {{ chapter.description }}
      </p>
    </div>

    <add-content-button
      :where="{ parent: chapter }"
      v-if="editMode"
    />

    <content-block
      :content-block="contentBlock"
      :parent="chapter"
      :edit-mode="editMode"
      v-for="contentBlock in filteredContentBlocks"
      :key="contentBlock.id"
    />
  </div>
</template>

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue';
import ContentBlock from '@/components/ContentBlock.vue';
import AddContentButton from '@/components/AddContentButton.vue';
import BookmarkActions from '@/components/notes/BookmarkActions.vue';
import VisibilityAction from '@/components/visibility/VisibilityAction.vue';
import CopyLink from '@/components/CopyLink.vue';
import { hidden } from '@/helpers/visibility';
import { CHAPTER_DESCRIPTION_TYPE, CHAPTER_TITLE_TYPE, CONTENT_TYPE } from '@/consts/types';
import { useStore } from 'vuex';

import UPDATE_CHAPTER_BOOKMARK_MUTATION from '@/graphql/gql/mutations/updateChapterBookmark.gql';
import CHAPTER_QUERY from '@/graphql/gql/queries/chapterQuery.gql';
import { getMe } from '@/mixins/me';
import { useMutation } from '@vue/apollo-composable';
import { useRoute } from 'vue-router';
import { PAGE_LOAD_TIMEOUT } from '@/consts/navigation.consts';
import {
  SelectionHandlerOptions,
  SelectionHandlerType,
  createHighlightCurry,
  getSelectionHandler,
  markHighlight,
} from '@/helpers/highlight';
import { onUnmounted } from 'vue';
import { AddHighlightArgument, ChapterNode, HighlightNode } from '@/__generated__/graphql';
import { graphql } from '@/__generated__';
import highlightSidebar from '@/helpers/highlight-sidebar';
import { doUpdateHighlight } from '@/graphql/mutations';
import { watch } from 'vue';
import Mark from 'mark.js';

export interface Props {
  chapter: ChapterNode;
  index: number;
  editMode: boolean;
}

const { schoolClass } = getMe();

const store = useStore();
const route = useRoute();
const wrapper = ref<HTMLElement | null>(null);
const chapterIntro = ref<HTMLElement | null>(null);
const props = withDefaults(defineProps<Props>(), {
  index: 0,
  editMode: false,
});

const chapterHighlightsFragment = graphql(`
  fragment ChapterHighlightsFragment on ChapterNode {
    id
    __typename
    highlights {
      ...HighlightParts
    }
  }
`);

const createHighlight = createHighlightCurry({
  fragment: chapterHighlightsFragment,
  fragmentName: 'ChapterHighlightsFragment',
  cacheSignature: { id: props.chapter.id, __typename: 'ChapterNode' },
  isContentHighlight: false,
});
const markHighlights = () => {
  if (props.chapter.highlights && chapterIntro.value) {
    for (const highlight of props.chapter.highlights) {
      const highlightNode = highlight as HighlightNode;
      const element = chapterIntro.value.children[highlightNode.paragraphIndex] as HTMLElement;
      markHighlight(highlightNode, element, element);
    }
  }
};

let selectionHandler: SelectionHandlerType;

onMounted(() => {
  const element = wrapper.value;
  if (element !== null) {
    if (route.hash === `#${props.chapter.id}`) {
      setTimeout(() => {
        const rect = element.getBoundingClientRect();
        window.scrollTo({ top: rect.y, behavior: 'smooth' });
      }, PAGE_LOAD_TIMEOUT);
    }
  }

  const intro = chapterIntro.value;
  if (intro !== null) {
    const options: SelectionHandlerOptions = {
      el: intro,
      page: props.chapter,
      parentSelector: 'chapter__intro',
      onChangeColor: (newHighlight: AddHighlightArgument) => {
        createHighlight(newHighlight);
      },
      onCreateNote: async (newHighlight: AddHighlightArgument) => {
        // we also open the sidebar when clicking on the note icon
        const highlight = await createHighlight(newHighlight);
        highlightSidebar.open({
          highlight,
          onUpdateText: (text: string) => {
            doUpdateHighlight({
              input: {
                note: text,
                id: highlight.id,
              },
            });
          },
        });
      },
    };

    selectionHandler = getSelectionHandler(options);
    intro.addEventListener('mouseup', selectionHandler);
  }

  markHighlights();
});

onUnmounted(() => {
  const intro = chapterIntro.value;

  if (intro !== null) {
    intro.removeEventListener('mouseup', selectionHandler);
  }
});

const filteredContentBlocks = computed(() => {
  if (!(props.chapter && props.chapter.contentBlocks)) {
    return [];
  }
  if (props.editMode) {
    return props.chapter.contentBlocks;
  }
  return props.chapter.contentBlocks.filter(
    (contentBlock) =>
      !hidden({
        block: contentBlock,
        schoolClass: schoolClass.value,
        type: CONTENT_TYPE,
      })
  );
});
const note = computed(() => {
  if (props.chapter && props.chapter.bookmark) {
    return props.chapter.bookmark.note;
  }
  return false;
});
const titleGreyedOut = computed(() => {
  return textHidden(CHAPTER_TITLE_TYPE) && props.editMode;
});
const titleHidden = computed(() => {
  // never hidden when editing the module
  if (props.chapter.titleHidden === true) {
    return true;
  }
  return textHidden(CHAPTER_TITLE_TYPE) && !props.editMode;
});
const descriptionGreyedOut = computed(() => {
  return textHidden(CHAPTER_DESCRIPTION_TYPE) && props.editMode;
});
const descriptionHidden = computed(() => {
  // never hidden when editing the module
  if (props.chapter.descriptionHidden === true) {
    return true;
  }
  return textHidden(CHAPTER_DESCRIPTION_TYPE) && !props.editMode;
});

const { mutate: bookmark } = useMutation(UPDATE_CHAPTER_BOOKMARK_MUTATION, () => {
  const bookmarked = !props.chapter.bookmark;
  const id = props.chapter.id;
  return {
    variables: {
      input: {
        chapter: id,
        bookmarked,
      },
    },
    update: (store: any) => {
      const query = CHAPTER_QUERY;
      const variables = { id };
      const { chapter } = store.readQuery({
        query,
        variables,
      });

      let bookmark;

      if (bookmarked) {
        bookmark = {
          __typename: 'ChapterBookmarkNode',
          note: null,
        };
      } else {
        bookmark = null;
      }

      const data = {
        chapter: {
          ...chapter,
          bookmark,
        },
      };

      store.writeQuery({
        data,
        query,
        variables,
      });
    },

    optimisticResponse: {
      __typename: 'Mutation',
      updateChapterBookmark: {
        __typename: 'UpdateChapterBookmarkPayload',
        success: true,
      },
    },
  };
});

const addNote = (id: string) => {
  store.dispatch('addNote', {
    content: id,
    parent: props.chapter.id,
  });
};
const editNote = () => {
  store.dispatch('editNote', props.chapter.bookmark.note);
};
const textHidden = (type: string) => {
  return hidden({
    block: props.chapter,
    schoolClass: schoolClass.value,
    type,
  });
};

const unmark = () => {
  for (const paragraph of chapterIntro.value.children) {
    const instance = new Mark(paragraph);
    instance.unmark();
  }
};

const highlights = computed(() => {
  return props.chapter ? props.chapter.highlights : [];
});

watch(
  () => highlights.value?.filter((h) => h.color),
  () => {
    unmark();
    markHighlights();
  }
);
</script>

<style lang="scss">
/* todo: re-add `scoped`, same as in ContentBlock.vue  */
@import 'styles/helpers';

.chapter {
  position: relative;

  &__bookmark-actions {
    margin-top: 3px;
  }

  &__intro {
    position: relative;
  }

  &__title {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  &__description {
    @include lead-paragraph;

    margin-bottom: $large-spacing;
  }
}
</style>
