<template>
  <div
    class="instrument"
    ref="instrumentDiv"
  >
    <h1
      class="instrument__title"
      data-cy="instrument-title"
    >
      {{ instrument.title }}
    </h1>

    <!-- eslint-disable vue/no-v-html -->
    <div
      class="instrument__intro intro"
      data-cy="instrument-intro"
      ref="highlightIntro"
      v-html="instrument.intro"
    />

    <content-component
      :component="component"
      :root="instrument.slug"
      :parent="instrument"
      :bookmarks="instrument.bookmarks"
      :notes="instrument.notes"
      :highlights="instrument.highlights"
      :edit-mode="false"
      v-for="component in instrument.contents"
      :key="component.id"
    />
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent, nextTick, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { graphql } from '@/__generated__';
import { useQuery } from '@vue/apollo-composable';
import { computed } from '@vue/reactivity';
import { AddHighlightArgument, HighlightNode, InstrumentNode } from '@/__generated__/graphql';
import {
  createHighlightCurry,
  getSelectionHandler,
  markHighlight,
  SelectionHandlerOptions,
  SelectionHandlerType,
} from '@/helpers/highlight';
import { doUpdateHighlight } from '@/graphql/mutations';
import highlightSidebar from '@/helpers/highlight-sidebar';
import Mark from 'mark.js';

const instrumentDiv = ref<HTMLElement | null>(null);
const highlightIntro = ref<HTMLElement | null>(null);

const ContentComponent = defineAsyncComponent(() => import('@/components/content-blocks/ContentComponent.vue'));

graphql(`
  fragment InstrumentParts on InstrumentNode {
    id
    title
    intro
    slug
    language
    bookmarks {
      uuid
      note {
        id
        text
      }
    }
    type {
      id
      name
      category {
        id
        name
        foreground
        background
      }
      type
    }
    contents
    highlights {
      ...HighlightParts
    }
  }
`);

const route = useRoute();

const { result, onResult } = useQuery(
  graphql(`
    query InstrumentQuery($slug: String!) {
      instrument(slug: $slug) {
        ...InstrumentParts
      }
    }
  `),
  {
    slug: route.params.slug as string,
  }
);

const instrument = computed(() => (result.value?.instrument as InstrumentNode) || {});

const instrumentHighlightsFragment = graphql(`
  fragment instrumentHighlightsFragment on InstrumentNode {
    id
    slug
    __typename
    highlights {
      ...HighlightParts
    }
  }
`);

const markHighlights = (highlights: HighlightNode[], element: HTMLElement) => {
  for (const highlight of highlights) {
    markHighlight(highlight, element, element);
  }
};

let contentSelectionHandler: SelectionHandlerType, introSelectionHandler: SelectionHandlerType;
onResult(async () => {
  /*
   * Important reminder: onResult gets called everytime the query result changes, also on cache updates!
   */
  const element = instrumentDiv.value;
  const intro = highlightIntro.value;
  const fragment = instrumentHighlightsFragment;
  const fragmentName = 'instrumentHighlightsFragment';
  const cacheSignature = {
    slug: instrument.value.slug, // this value is only known onResult
    __typename: 'InstrumentNode',
  };
  const createContentHighlight = createHighlightCurry({
    fragment,
    fragmentName,
    cacheSignature,
    isContentHighlight: true,
  });
  const createIntroHighlight = createHighlightCurry({
    fragment,
    fragmentName,
    cacheSignature,
    isContentHighlight: false,
  });
  const openSidebar = (highlight: HighlightNode) => {
    highlightSidebar.open({
      highlight,
      onUpdateText: (text: string) => {
        doUpdateHighlight({
          input: {
            note: text,
            id: highlight.id,
          },
        });
      },
    });
  };

  if (element !== null) {
    const el = element;
    const page = instrument.value;
    const introOptions: SelectionHandlerOptions = {
      el,
      page,
      onChangeColor: (newHighlight: AddHighlightArgument) => {
        createIntroHighlight(newHighlight);
      },
      onCreateNote: (newHighlight: AddHighlightArgument) => {
        // todo: the same as the other one in ContentBlock.vue, possible to merge
        // we also open the sidebar when clicking on the note icon
        createIntroHighlight(newHighlight).then(openSidebar);
      },
      parentSelector: 'intro',
    };
    const contentOptions: SelectionHandlerOptions = {
      el,
      page,
      onChangeColor: (newHighlight: AddHighlightArgument) => {
        createContentHighlight(newHighlight);
      },
      onCreateNote: (newHighlight: AddHighlightArgument) => {
        // todo: the same as the other one in ContentBlock.vue, possible to merge
        // we also open the sidebar when clicking on the note icon
        createContentHighlight(newHighlight).then(openSidebar);
      },
    };
    contentSelectionHandler = getSelectionHandler(contentOptions);
    introSelectionHandler = getSelectionHandler(introOptions);
    element.addEventListener('mouseup', contentSelectionHandler);
    element.addEventListener('mouseup', introSelectionHandler);
  }

  if (intro !== null) {
    await nextTick();
    unmark();
    markHighlights(introHighlights.value, intro);
  }
});

const introHighlights = computed(() => {
  return instrument.value?.highlights?.filter((h) => h.contentUuid === null) || [];
});

onUnmounted(() => {
  const element = instrumentDiv.value;

  if (element !== null) {
    element.removeEventListener('mouseup', contentSelectionHandler);
    element.removeEventListener('mouseup', introSelectionHandler);
  }
});

const unmark = () => {
  for (const paragraph of (highlightIntro.value as HTMLElement).children) {
    const instance = new Mark(paragraph);
    instance.unmark();
  }
};
</script>

<style scoped lang="scss">
@import 'styles/helpers';

.instrument {
  padding-top: 2 * $large-spacing;

  &__intro {
    position: relative;
  }

  &__title {
    font-size: toRem(35px);
    margin-bottom: $large-spacing;
    line-height: $default-heading-line-height;
    overflow-wrap: break-word;
  }

  & :deep() {
    & p {
      margin-bottom: $large-spacing;
    }

    & p:last-child {
      margin-bottom: 0;
    }

    & ul {
      @include list-parent;
    }

    & p + ul {
      margin-top: -30px;
    }

    & li {
      @include list-child;
      line-height: 1.5;
    }

    .brand {
      color: $color-brand;
      font-weight: 600;
    }

    .secondary {
      color: $secondary-color;
      font-weight: 600;
    }
  }
}
</style>
