<template>
  <div
    :class="componentClass"
    :data-scrollto="component.id"
    :data-uuid="component.id"
    data-cy="content-component"
    ref="contentComponentDiv"
  >
    <bookmark-actions
      :bookmarked="bookmarked"
      :note="note"
      :edit-mode="editMode"
      v-if="showBookmarkActions"
      @add-note="addNote(component.id)"
      @edit-note="editNote"
      @bookmark="bookmarkContent(!bookmarked)"
    />
    <component
      v-bind="component"
      :parent="parent"
      :highlights="filteredHighlights"
      class="content-component__content"
      :is="componentType"
    />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue';
import { useStore } from 'vuex';
import { constructContentComponentBookmarkMutation } from '@/helpers/update-content-bookmark-mutation';
import { useMutation } from '@vue/apollo-composable';
import { HighlightNode } from '@/__generated__/graphql';
import Mark from 'mark.js';

export interface Props {
  // todo: use useful types here
  component: any;
  parent: any;
  highlights: HighlightNode[];
  bookmarks: any[];
  notes: any[];
  root: string;
  editMode: boolean;
}
import TextBlock from '@/components/content-blocks/TextBlock.vue';
import InstrumentWidget from '@/components/content-blocks/InstrumentWidget.vue';
import ImageBlock from '@/components/content-blocks/ImageBlock.vue';
import ImageUrlBlock from '@/components/content-blocks/ImageUrlBlock.vue';
import VideoBlock from '@/components/content-blocks/VideoBlock.vue';
import LinkBlock from '@/components/content-blocks/LinkBlock.vue';
import DocumentBlock from '@/components/content-blocks/DocumentBlock.vue';
import CmsDocumentBlock from '@/components/content-blocks/CmsDocumentBlock.vue';
import InfogramBlock from '@/components/content-blocks/InfogramBlock.vue';
import ThinglinkBlock from '@/components/content-blocks/ThinglinkBlock.vue';
import GeniallyBlock from '@/components/content-blocks/GeniallyBlock.vue';
import SubtitleBlock from '@/components/content-blocks/SubtitleBlock.vue';
import SectionTitleBlock from '@/components/content-blocks/SectionTitleBlock.vue';
import ContentListBlock from '@/components/content-blocks/ContentListBlock.vue';
import ModuleRoomSlug from '@/components/content-blocks/ModuleRoomSlug.vue';
import Assignment from '@/components/content-blocks/assignment/Assignment.vue';
import Survey from '@/components/content-blocks/SurveyBlock.vue';
import Solution from '@/components/content-blocks/Solution.vue';
import Instruction from '@/components/content-blocks/Instruction.vue';
import BookmarkActions from '@/components/notes/BookmarkActions.vue';
import { markHighlight } from '@/helpers/highlight';

type ContentComponentType =
  | typeof TextBlock
  | typeof InstrumentWidget
  | typeof InstrumentWidget
  | typeof ImageBlock
  | typeof ImageUrlBlock
  | typeof VideoBlock
  | typeof LinkBlock
  | typeof DocumentBlock
  | typeof InfogramBlock
  | typeof GeniallyBlock
  | typeof SubtitleBlock
  | typeof SectionTitleBlock
  | typeof ContentListBlock
  | typeof ModuleRoomSlug
  | typeof ThinglinkBlock
  | typeof CmsDocumentBlock
  | typeof Survey
  | typeof Solution
  | typeof Instruction
  | typeof Assignment;

const props = withDefaults(defineProps<Props>(), {
  component: () => ({}),
  parent: () => ({}),
  bookmarks: () => [],
  highlights: () => [],
  notes: () => [],
  root: '',
  editMode: false,
});

const store = useStore();

const components: Record<string, ContentComponentType> = {
  text_block: TextBlock,
  basic_knowledge: InstrumentWidget, // for legacy
  instrument: InstrumentWidget,
  image_block: ImageBlock,
  image_url_block: ImageUrlBlock,
  video_block: VideoBlock,
  link_block: LinkBlock,
  document_block: DocumentBlock,
  infogram_block: InfogramBlock,
  genially_block: GeniallyBlock,
  subtitle: SubtitleBlock,
  section_title: SectionTitleBlock,
  content_list: ContentListBlock,
  module_room_slug: ModuleRoomSlug,
  thinglink_block: ThinglinkBlock,
  cms_document_block: CmsDocumentBlock,
  survey: Survey,
  solution: Solution,
  instruction: Instruction,
  assignment: Assignment,
};

const contentComponentDiv = ref<HTMLElement | null>(null);

const componentType = computed(() => {
  return components[props.component.type] || '';
});

const filteredHighlights = computed(
  () => props.highlights.filter((highlight) => highlight.contentUuid === props.component.id) || []
);
const bookmarked = computed(
  () => props.bookmarks && !!props.bookmarks.find((bookmark) => bookmark.uuid === props.component.id)
);
const note = computed(() => {
  const bookmark = props.bookmarks && props.bookmarks.find((bookmark) => bookmark.uuid === props.component.id);
  return bookmark && bookmark.note;
});
const showBookmarkActions = computed(
  () => props.component.type !== 'content_list' && props.component.type !== 'basic_knowledge' && !props.editMode
);
const componentClass = computed(() => {
  let classes = ['content-component', `content-component--${props.component.type}`];
  if (bookmarked.value) {
    classes.push('content-component--bookmarked');
  }
  return classes;
});

const childElements = computed(() => {
  if (props.component.type === 'assignment') {
    // we don't need the childElements for an assignment component for now
    return [];
  }
  if (contentComponentDiv.value) {
    // todo: this is almost the same as the one in Assignment.vue, refactor and make one function
    let highlightParentSelector;
    highlightParentSelector = '.content-component__content';
    const parent = contentComponentDiv.value.querySelector(highlightParentSelector);
    if (!parent) {
      console.warn('Parent does not exist, this should not be possible');
      // or can it be, if the children did not load yet, e.g. with a dynamic component?
      return [];
    }
    // make a flat list from all <li>-elements and all the others
    const elements = Array.from(parent.children).reduce((acc: Element[], current: Element) => {
      if (current.tagName.toLowerCase() === 'ul') {
        return [...acc, ...current.children];
      }
      return [...acc, current];
    }, []);
    return elements as HTMLElement[];
  }
  return [];
});

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

watch(
  () => filteredHighlights.value.map((h) => h.color),
  () => {
    unmark();
    markHighlights();
  }
);

const addNote = (id: string) => {
  const type = Object.prototype.hasOwnProperty.call(props.parent, '__typename')
    ? props.parent.__typename
    : 'ContentBlockNode';

  store.dispatch('addNote', {
    content: id,
    type,
    block: props.root,
  });
};

const editNote = () => {
  store.dispatch('editNote', note);
};

const { mutation, updateCurry, getVariables, optimisticResponse } = constructContentComponentBookmarkMutation(
  props.component.id,
  props.parent,
  props.root
);
const { mutate: mutateBookmarkContent } = useMutation(mutation, {
  optimisticResponse,
});

const bookmarkContent = (bookmarked: boolean) => {
  const variables = getVariables(bookmarked);
  const update = updateCurry(bookmarked);
  mutateBookmarkContent(variables, {
    update,
  });
};

const markHighlights = () => {
  if (props.component.type === 'assignment') {
    // we handle that inside of the assignment component, as it is probably not loaded yet
    return;
  }
  for (const highlight of filteredHighlights.value) {
    const element = childElements.value[highlight.paragraphIndex];
    markHighlight(highlight, element, contentComponentDiv.value as HTMLElement);
  }
};

onMounted(() => {
  setTimeout(() => {
    unmark();
    markHighlights();
  }, 1);
});
</script>

<style lang="postcss" scoped>
.content-component {
  position: relative;

  &--subtitle {
    margin-top: 2 * var(--medium-spacing);
    margin-bottom: var(--medium-spacing);
  }

  &--section_title {
    margin-top: var(--section-spacing);
    margin-bottom: var(--large-spacing);
  }

  &--text_block {
    margin-bottom: var(--large-spacing);
  }

  &--document_block {
    margin-bottom: var(--large-spacing);
  }
}
</style>
