<template>
  <div
    :class="{ 'hideable-element--greyed-out': isHidden }"
    class="content-block__container hideable-element content-list__parent"
    ref="contentBlockDiv"
  >
    <div
      :class="specialClass"
      :style="instrumentStyle"
      class="content-block"
      data-cy="content-block"
    >
      <div
        class="block-actions"
        v-if="canEditModule && !isInstrumentBlock"
      >
        <user-widget
          v-bind="me"
          class="block-actions__user-widget content-block__user-widget"
          v-if="isMine"
        />
        <more-options-widget>
          <li
            class="popover-links__link"
            v-if="!isInstrumentBlock"
          >
            <popover-link
              data-cy="duplicate-content-block-link"
              text="Duplizieren"
              @link-action="duplicateContentBlock()"
            />
          </li>
          <li
            class="popover-links__link"
            v-if="isMine"
          >
            <popover-link
              data-cy="delete-content-block-link"
              text="Löschen"
              @link-action="deleteContentBlock()"
            />
          </li>

          <li
            class="popover-links__link"
            v-if="isMine"
          >
            <popover-link
              text="Bearbeiten"
              @link-action="editContentBlock()"
            />
          </li>
        </more-options-widget>
      </div>
      <div class="content-block__visibility">
        <visibility-action
          :block="contentBlock"
          v-if="canEditModule"
        />
      </div>

      <!-- eslint-disable vue/no-v-html -->
      <h3
        class="content-block__instrument-label"
        data-cy="instrument-label"
        :style="instrumentLabelStyle"
        v-if="instrumentLabel !== ''"
        v-html="instrumentLabel"
      />
      <h4
        class="content-block__title"
        v-if="!contentBlock.indent"
      >
        {{ contentBlock.title }}
        <copy-link
          v-if="!isInstrumentBlock"
          :id="contentBlock.id"
        />
      </h4>

      <content-component
        :component="component"
        :root="root"
        :parent="contentBlock"
        :bookmarks="contentBlock.bookmarks"
        :notes="contentBlock.notes"
        :edit-mode="editMode"
        v-for="component in contentBlocksWithContentLists.contents"
        :key="component.id"
      />
    </div>

    <add-content-button
      :where="{ after: contentBlock }"
      v-if="canEditModule"
    />
  </div>
</template>

<script setup lang="ts">
import { defineAsyncComponent, inject, onMounted, ref, computed, onUnmounted } from 'vue';

import { useMutation } from '@vue/apollo-composable';
import AddContentButton from '@/components/AddContentButton.vue';
import MoreOptionsWidget from '@/components/MoreOptionsWidget.vue';
import UserWidget from '@/components/UserWidget.vue';
import VisibilityAction from '@/components/visibility/VisibilityAction.vue';
import PopoverLink from '@/components/ui/PopoverLink.vue';
import CopyLink from '@/components/CopyLink.vue';
import { useRoute, useRouter } from 'vue-router';
import { hidden } from '@/helpers/visibility';
import { insertAtIndex, removeAtIndex } from '@/graphql/immutable-operations';
import { instrumentCategory } from '@/helpers/instrumentType';
import { CONTENT_TYPE } from '@/consts/types';
import { EDIT_CONTENT_BLOCK_PAGE } from '@/router/module.names';
import { getMe } from '@/mixins/me';

import DUPLICATE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/duplicateContentBlock.gql';
import CHAPTER_QUERY from '@/graphql/gql/queries/chapterQuery.gql';
import DELETE_CONTENT_BLOCK_MUTATION from '@/graphql/gql/mutations/deleteContentBlock.gql';

import type { ExtendedContentBlockNode } from '@/@types';

import type { Modal } from '@/plugins/modal.types';

import { PAGE_LOAD_TIMEOUT } from '@/consts/navigation.consts';

import { getSelectionHandler } from '@/directives/highlight';

export interface Props {
  contentBlock: ExtendedContentBlockNode;
  parent?: any;
  editMode?: boolean;
}

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

const { me, schoolClass } = getMe();

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

const route = useRoute();
const router = useRouter();
const modal = inject('modal') as Modal;

const props = withDefaults(defineProps<Props>(), {
  editMode: false,
});

const { mutate: duplicateContentBlock } = useMutation(DUPLICATE_CONTENT_BLOCK_MUTATION, () => ({
  variables: {
    input: {
      id: props.contentBlock.id,
    },
  },
  update: (
    store,
    {
      data: {
        duplicateContentBlock: { contentBlock },
      },
    }
  ) => {
    const id = props.parent.id;
    const contentBlockId = props.contentBlock.id;
    if (contentBlock) {
      const query = CHAPTER_QUERY;
      const variables = {
        id,
      };

      const { chapter }: any = store.readQuery({
        query,
        variables,
      });
      const index = chapter.contentBlocks.findIndex(
        (contentBlock: ExtendedContentBlockNode) => contentBlock.id === contentBlockId
      );
      const contentBlocks = insertAtIndex(chapter.contentBlocks, index, contentBlock);
      const data = {
        chapter: {
          ...chapter,
          contentBlocks,
        },
      };

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

const { mutate: doDeleteContentBlock } = useMutation(DELETE_CONTENT_BLOCK_MUTATION, () => ({
  variables: {
    input: {
      id: props.contentBlock.id,
    },
  },
  update: (
    store,
    {
      data: {
        deleteContentBlock: { success },
      },
    }
  ) => {
    if (success) {
      const query = CHAPTER_QUERY;

      const variables = {
        id: props.parent.id,
      };

      const { chapter }: any = store.readQuery({
        query,
        variables,
      });
      const index = chapter.contentBlocks.findIndex(
        (contentBlock: ExtendedContentBlockNode) => contentBlock.id === props.contentBlock.id
      );

      if (index < 0) {
        throw Error('ContentBlock not found');
      }

      const contentBlocks = removeAtIndex(chapter.contentBlocks, index);

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

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

onMounted(() => {
  const element = contentBlockDiv.value;

  if (element !== null) {
    if (route.hash === `#${props.contentBlock.id}`) {
      setTimeout(() => {
        const rect = element.getBoundingClientRect();

        window.scrollTo({
          top: rect.y,
          behavior: 'smooth',
        });
      }, PAGE_LOAD_TIMEOUT);
    }

    // add the listener from highlights
    element.addEventListener('mouseup', getSelectionHandler(element, props.contentBlock));
  }
});

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

  if (element !== null) {
    console.log('unmounting');
    element.removeEventListener('mouseup', getSelectionHandler);
  }
});

// methods
const createContentListOrBlocks = (contentList: any[]) => {
  return [
    {
      type: 'content_list',
      contents: contentList,
      id: contentList[0].id,
    },
  ];
};

const editContentBlock = () => {
  const route = {
    name: EDIT_CONTENT_BLOCK_PAGE,
    params: {
      id: props.contentBlock.id,
    },
  };
  router.push(route);
};

const deleteContentBlock = () => {
  modal
    .open('confirm')
    .then(() => {
      doDeleteContentBlock();
    })
    .catch();
};

// computed properties
const canEditModule = computed(() => !props.contentBlock.indent && props.editMode);
const specialClass = computed(() => `content-block--${props.contentBlock.type.toLowerCase()}`);
const isInstrumentBlock = computed(() => !!props.contentBlock.instrumentCategory);

// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
const instrumentStyle = computed(() =>
  isInstrumentBlock.value ? { backgroundColor: props.contentBlock.instrumentCategory?.background || '' } : {}
);
const instrumentLabel = computed(() => {
  const contentType = props.contentBlock.type.toLowerCase();
  if (contentType.startsWith('base')) {
    // all legacy instruments start with `base`
    return instrumentCategory(contentType);
  }
  if (isInstrumentBlock.value) {
    return instrumentCategory(props.contentBlock.instrumentCategory?.name);
  }
  return '';
});
// todo: use dynamic css class with v-bind once we're on Vue 3: https://vuejs.org/api/sfc-css-features.html#v-bind-in-css
const instrumentLabelStyle = computed(() => {
  if (isInstrumentBlock.value) {
    return {
      color: props.contentBlock.instrumentCategory?.foreground,
    };
  }

  return {};
});

const isMine = computed(() => props.contentBlock.mine);

const contentBlocksWithContentLists = computed(() => {
  /*
         collects all content_list_items in content_lists:
         {
         text_block,
          content_list_item: [contents...],
          content_list_item: [contents...],
          text_block
          } becomes
          {
          text_block,
          content_list: [content_list_item: [contents...], content_list_item: [contents...]],
          text_block
          }
       */
  let contentList: any[] = [];
  let newContent = props.contentBlock.contents.reduce((newContents, content, index) => {
    // collect content_list_items
    if (content.type === 'content_list_item') {
      contentList = [...contentList, content];
      if (index === props.contentBlock.contents.length - 1) {
        // content is last element of contents array
        let updatedContent = [...newContents, ...createContentListOrBlocks(contentList)];
        return updatedContent;
      }
      return newContents;
    } else {
      // handle all other items and reset current content_list if necessary
      if (contentList.length !== 0) {
        newContents = [...newContents, ...createContentListOrBlocks(contentList), content];
        contentList = [];
        return newContents;
      } else {
        return [...newContents, content];
      }
    }
  }, []);
  return Object.assign({}, props.contentBlock, {
    contents: newContent,
  });
});

const isHidden = computed(() =>
  hidden({
    block: props.contentBlock,
    schoolClass: schoolClass.value,
    type: CONTENT_TYPE,
  })
);

// we need the root content block id, not the generated content block if inside a content list block
const root = computed(() => (props.contentBlock.root ? props.contentBlock.root : props.contentBlock.id));
</script>
<style lang="scss">
/* todo: re-add `scoped` after switch to vite or this bug is fixed: https://github.com/vuejs/vue-loader/issues/1915 */
@import 'styles/helpers';

.content-block {
  margin-bottom: $section-spacing;
  position: relative;

  &__container {
    position: relative;
  }

  &__title {
    line-height: 1.5;
    margin-top: -0.5rem; // to offset the 1.5 line height, it leaves a padding on top
    display: flex;
    justify-content: space-between;
    padding-bottom: calc($small-spacing / 2);
  }

  &__instrument-label {
    margin-bottom: $medium-spacing;
    @include regular-text();
  }

  &__action-button {
    cursor: pointer;
  }

  &__user-widget {
    margin-right: 0;
  }

  &--base_communication {
    @include content-box($color-accent-1-list);

    .content-block__instrument-label {
      color: $color-accent-1-dark;
    }
  }

  &--task {
    @include light-border(bottom);

    .content-block__title {
      color: $color-brand;
      margin-top: $default-padding;
      margin-bottom: $large-spacing;
      @include light-border(bottom);

      @include desktop {
        margin-top: 0;
      }
    }
  }

  &--base_society {
    @include content-box($color-accent-2-list);

    .content-block__instrument-label {
      color: $color-accent-2-dark;
    }
  }

  &--base_interdisciplinary {
    @include content-box($color-accent-4-list);

    .content-block__instrument-label {
      color: $color-accent-4-dark;
    }
  }

  &--instrument {
    @include content-box-base;
  }

  // todo: re-add :deep() once  we re-add scoped
  p {
    line-height: 1.5;
    margin-bottom: 1em;

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

  // todo: re-add :deep() once  we re-add scoped
  .text-block {
    ul {
      @include list-parent;
    }

    li {
      @include list-child;
      line-height: 1.5;
    }
  }
}
</style>
