<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"
      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';
import highlightSidebar from '@/helpers/highlight-sidebar';

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 popover from '@/helpers/popover';
import { graphql } from '@/__generated__';

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 (contentComponentDiv.value) {
    const parent = contentComponentDiv.value.querySelector('.content-component__content');
    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 [];
});

watch(
  () => filteredHighlights.value.map((h) => h.color),
  () => {
    for (const paragraph of childElements.value) {
      const instance = new Mark(paragraph);
      instance.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, variables, update, optimisticResponse } = constructContentComponentBookmarkMutation(
  props.component.id,
  bookmarked.value,
  props.parent,
  props.root
);
const { mutate: mutateBookmarkContent } = useMutation(mutation, {
  update,
  optimisticResponse,
});

const bookmarkContent = (bookmarked: boolean) => {
  const newVars = {
    input: {
      ...variables.input,
      bookmarked,
    },
  };
  mutateBookmarkContent(newVars);
};

const { mutate: doUpdateHighlight } = useMutation(
  graphql(`
    mutation UpdateHighlight($input: UpdateHighlightInput!) {
      updateHighlight(input: $input) {
        highlight {
          ...HighlightParts
        }
      }
    }
  `)
);

const { mutate: doDeleteHighlight } = useMutation(
  graphql(`
    mutation DeleteHighlight($input: DeleteHighlightInput!) {
      deleteHighlight(input: $input) {
        success
      }
    }
  `)
);

const markHighlights = () => {
  for (const highlight of filteredHighlights.value) {
    const element = childElements.value[highlight.paragraphIndex];
    const instance = new Mark(element);
    const ranges: Mark.Range[] = [
      {
        start: highlight.startPosition,
        length: highlight.selectionLength,
      },
    ];
    instance.markRanges(ranges, {
      className: `highlight highlight--${highlight.color}`,
      each: (newMark: HTMLElement) => {
        newMark.dataset.id = highlight.id;
        newMark.addEventListener('click', () => {
          if (contentComponentDiv.value) {
            popover.show({
              wrapper: contentComponentDiv.value,
              offsetTop: 0,
              onChooseColor: (color: string) => {
                doUpdateHighlight({
                  input: {
                    color: color,
                    id: highlight.id,
                  },
                });
              },
              onDelete: () => {
                console.log(`deleting ${highlight.id}`);
                doDeleteHighlight(
                  {
                    input: {
                      id: highlight.id,
                    },
                  },
                  {
                    update: (
                      cache,
                      {
                        data: {
                          deleteHighlight: { success },
                        },
                      }
                    ) => {
                      if (success) {
                        const fragment = graphql(`
                          fragment ContentBlockHighlightsWithIdOnlyFragment on ContentBlockNode {
                            highlights {
                              id
                            }
                          }
                        `);
                        const id = cache.identify({
                          __typename: 'ContentBlockNode',
                          id: highlight.contentBlock.id,
                        });
                        const contentBlock = cache.readFragment({
                          fragment,
                          id,
                        });
                        const data = {
                          ...contentBlock,
                          highlights: contentBlock.highlights.filter((h) => h.id !== highlight.id),
                        };
                        cache.writeFragment({
                          fragment,
                          id,
                          data,
                        });
                      }
                    },
                  }
                );
              },
            });
          }
          highlightSidebar.open({
            highlight,
            onUpdateText: (text: string) => {
              doUpdateHighlight({
                input: {
                  note: text,
                  id: highlight.id,
                },
              });
            },
          });
        });
      },
    });
  }
};

onMounted(() => {
  console.log('onMounted ContentComponent called');
  setTimeout(markHighlights, 500);
});
</script>

<style lang="postcss">
.highlight {
  background-color: Highlight;
  color: HighlightText;
  cursor: pointer;

  &--alpha {
    background-color: var(--mark-alpha);
    color: var(--color-charcoal-dark);
  }

  &--beta {
    background-color: var(--mark-beta);
    color: var(--color-charcoal-dark);
  }

  &--gamma {
    background-color: var(--mark-gamma);
    color: var(--color-charcoal-dark);
  }
}
</style>

<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>
