<template>
  <SessionsHistory 
    :class="agentStore.isSessionHistoryOpen ? 'translate-x-0' : '-translate-x-full'"
  />
  <div 
    v-if="agentStore.isSessionHistoryOpen"
    @click="agentStore.setSessionHistoryOpen(false)"
    class="absolute bg-black/10 backdrop-blur-md z-10 h-full w-full"
  >
  </div>
  <div ref="chatContainerRef" class="relative flex-1 min-h-0 overflow-hidden" @click="recalculateScroll()">
    <CustomAutoScrollContainer
      v-if="showScrollContainer"
      class="relative h-full flex flex-col overflow-y-auto translate-x-[-50%] left-1/2"
      ref="scrollContainer"
      :enabled="false"
      :threshold="THRESHOLD_TO_SHOW_BUTTON"
      behavior="smooth"
      :wrapperStyle = "{ 
        height: '100%',
        maxHeight: '100%',
        maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'rem' : '100%',
        width: '100%',
        marginLeft: 'auto',
        marginRight: 'auto',
      }"
      :style="{ 
        maxWidth: agentStore.isFullScreen ? agentStore.MAX_WIDTH+'rem' : '100%',
        transition: `
          max-width ${agentTransitions.TRANSITION_DURATION}ms ease-in-out,
          transform ${agentTransitions.TRANSITION_DURATION}ms ease-in-out
        `
      }"
    > 

      <div 
        v-for="(message, index) in props.messages" :key="index"
        class="flex flex-col w-full mt-2 pb-2"
        :class="message.role === 'user' ? 'self-end' : 'self-start'"
        ref="messagesRefs"
      >
        <MessageRenderer :message="message" :isLastMessageInChat="index === props.messages.length - 1"/>
      </div>
      <div 
        v-if="props.messages.length === 0"
        class="flex-1 flex flex-col items-center justify-center text-gray-400 tracking-widest text-xl font-medium h-max"
        :style="{
          height: chatContainerRef ? chatContainerRef.clientHeight + 'px' : '100%',
        }"
      >
        <p>{{ $t('Start the conversation') }}</p>
        <p class="tracking-normal text-base text">{{ $t('Give any input to begin') }}</p>
      </div>
      <div v-if="showBottomSpacer" class="w-full" :style="{ height: spacerHeight + 'px' }"></div>
    </CustomAutoScrollContainer>
    <Transition
      enter-active-class="transition-all duration-250 ease-out"
      enter-from-class="opacity-0 translate-y-3 scale-90"
      enter-to-class="opacity-100 translate-y-0 scale-100"
      leave-active-class="transition-all duration-200 ease-in"
      leave-from-class="opacity-100 translate-y-0 scale-100"
      leave-to-class="opacity-0 translate-y-3 scale-90"
    >
      <button 
        v-if="showScrollToBottomButton" 
        @click="scrollContainer.scrollToBottom();"
        class="absolute z-10 bottom-8 left-1/2 -translate-x-1/2 
          bg-lightPrimary/20 dark:bg-darkPrimary/10
          border border-lightPrimary dark:border-darkPrimary 
          text-lightPrimary dark:text-darkPrimary 
          backdrop-blur-sm rounded-full
          w-10 h-10 flex items-center justify-center gap-0.5"
        :class="{ 'w-12 h-8': showAnimationInScrollToBottomButton }"
      >
        <IconArrowDownOutline 
          v-if="!showAnimationInScrollToBottomButton"
          class="p-2 w-10 h-10 transition-opacity duration-100 ease-in " 
          :disabled="!showScrollToBottomButton"
        />
        <ThreeDotsAnimation 
          v-else
          :disabled="!showScrollToBottomButton"
        />
      </button>
    </Transition>
  </div>
</template>


<script setup lang="ts">
import type { IMessage } from '../types';
import { useTemplateRef, ref, onMounted, onUnmounted, watch, nextTick, defineAsyncComponent, computed } from 'vue';
import { IconArrowDownOutline } from '@iconify-prerendered/vue-flowbite';
import SessionsHistory from '../SessionsHistory.vue';
import { useAgentStore } from '../composables/useAgentStore';
import { useAgentTransitions } from '../composables/useAgentTransitions';
import MessageRenderer from './MessageRenderer.vue';
import ThreeDotsAnimation from './ThreeDotsAnimation.vue';

const CustomAutoScrollContainer = defineAsyncComponent(() => import('../CustomAutoScrollContainer.vue'));

const props = defineProps<{
  messages: IMessage[]
}>();

defineExpose({
  handleSendMessage
});

const scrollContainer = useTemplateRef('scrollContainer');
const showScrollToBottomButton = ref(false);
const innerScrollContainerRef = ref<HTMLElement | null>(null);
const agentStore = useAgentStore();
const agentTransitions = useAgentTransitions();
const showScrollContainer = ref(true);
const chatContainerRef = ref<HTMLElement | null>(null);
const messagesRefs = ref<Array<HTMLElement | null>>([]);
/*
* Whenever user sends a message, it adds a bottom spacer, that takes the remaining height
* without last user and last agent message. 
* 
* On send message, happens the following logic: 
* 1) showBottomSpacer is set to true
* 2) updateSpacerHeight is called, which calculates the height for spacer based on scroll container height and messages height
* 3) Spacer moves text up  
*/
const showBottomSpacer = ref(false);
const spacerHeight = ref(0);
const MASK_HEIGHT = 20;
const EMPTY_MESSAGE_HEIGHT = 18;
const THRESHOLD_TO_SHOW_BUTTON = 10;
let messageResizeObserver: ResizeObserver | null = null;
let observedLastUserMessageElement: HTMLElement | null = null;
let observedLastAgentMessageElement: HTMLElement | null = null;
let updateSpacerFrameId: number | null = null;
let pendingSpacerUpdate: Promise<void> | null = null;
let spacerUpdateQueued = false;

const showAnimationInScrollToBottomButton = computed(() => {
  return agentStore.isResponseInProgress && showScrollToBottomButton.value;
});

onMounted(async () => {
  messageResizeObserver = new ResizeObserver(() => {
    scheduleSpacerHeightUpdate();
  });

  await import('@incremark/theme/styles.css')
  await agentStore.fetchPlaceholderMessages()
  await refreshSpacerTracking();
});

onUnmounted(() => {
  if (innerScrollContainerRef.value) {
    innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
  }

  stopObservingLastMessages();
  messageResizeObserver?.disconnect();
  if (updateSpacerFrameId !== null) {
    cancelAnimationFrame(updateSpacerFrameId);
    updateSpacerFrameId = null;
  }
  agentStore.stopPlaceholderAnimation();
});

watch(() => agentStore.activeSessionId, () => {
  resetSpacer();
});

watch(() => agentStore.activeSessionId, async () => {
  showScrollContainer.value = false;
  await nextTick();
  showScrollContainer.value = true;
  await refreshSpacerTracking();
  recalculateScroll();
});

watch(() => props.messages.length, async () => {
  await refreshSpacerTracking();
});

watch(scrollContainer, async () => {
  if (innerScrollContainerRef.value) {
    innerScrollContainerRef.value.removeEventListener('scroll', recalculateScroll);
  }

  if (scrollContainer.value) {
    innerScrollContainerRef.value = scrollContainer.value.container.scrollEl;

    innerScrollContainerRef.value.addEventListener('scroll', recalculateScroll);
    await refreshSpacerTracking();
  }
})

function resetSpacer() {
  showBottomSpacer.value = false;
  spacerHeight.value = 0;
}

function getLastMessageElement(role: 'user' | 'assistant') {
  const lastMessageIndex = props.messages.findLastIndex((message: IMessage) => message.role === role);
  return messagesRefs.value[lastMessageIndex] ?? null;
}

function getHeightOfLastUserMessage() {
  return getLastMessageElement('user')?.clientHeight ?? 0;
}

function getHeightOfLastAgentMessage() {
  const lastUserIndex = props.messages.findLastIndex((message: IMessage) => message.role === 'user');
  const lastAgentIndex = props.messages.findLastIndex((message: IMessage) => message.role === 'assistant');
  // Only reserve spacer space for the current turn's answer, i.e. an assistant message that comes
  // after the last user message. Right after a send the "Thinking" placeholder isn't pushed yet, so
  // findLastIndex would return the previous turn's answer (which sits above the just-sent user
  // message). Subtracting its height shrinks the spacer, making scrollToBottom stop short — and once
  // the real placeholder arrives the spacer grows and the true bottom drops below where we scrolled.
  if (lastAgentIndex <= lastUserIndex) {
    return 0;
  }
  return messagesRefs.value[lastAgentIndex]?.clientHeight ?? 0;
}

function getScrollClientHeight() {
  return scrollContainer.value?.container.scrollEl.clientHeight ?? scrollContainer.value?.scrollParams.clientHeight ?? 0;
}

async function updateSpacerHeight() {
  if (!showBottomSpacer.value) {
    return;
  }

  const clientHeight = getScrollClientHeight();
  if (!clientHeight) {
    return;
  }

  const lastUserMessageHeight = getHeightOfLastUserMessage();
  const lastAgentMessageHeight = getHeightOfLastAgentMessage();
  const newSpacerHeight = Math.max(0, clientHeight - (lastUserMessageHeight + MASK_HEIGHT + lastAgentMessageHeight));
  spacerHeight.value = newSpacerHeight;
}

function scheduleSpacerHeightUpdate() {
  spacerUpdateQueued = true;

  if (updateSpacerFrameId !== null) {
    return;
  }

  updateSpacerFrameId = requestAnimationFrame(() => {
    updateSpacerFrameId = null;

    if (pendingSpacerUpdate) {
      return;
    }

    spacerUpdateQueued = false;
    pendingSpacerUpdate = updateSpacerHeight().finally(() => {
      pendingSpacerUpdate = null;

      // Auto-scroll to bottom if response generation is in progress
      // if (agentStore.isResponseInProgress) {
      //   nextTick(() => {
      //     scrollContainer.value?.scrollToBottom();
      //   });
      // }

      if (spacerUpdateQueued) {
        scheduleSpacerHeightUpdate();
      }
    });
  });
}

function stopObservingLastMessages() {
  if (!messageResizeObserver) {
    return;
  }

  if (observedLastUserMessageElement) {
    messageResizeObserver.unobserve(observedLastUserMessageElement);
    observedLastUserMessageElement = null;
  }

  if (observedLastAgentMessageElement) {
    messageResizeObserver.unobserve(observedLastAgentMessageElement);
    observedLastAgentMessageElement = null;
  }
}

function observeLastMessages() {
  if (!messageResizeObserver) {
    return;
  }

  stopObservingLastMessages();

  observedLastUserMessageElement = getLastMessageElement('user');
  observedLastAgentMessageElement = getLastMessageElement('assistant');

  if (observedLastUserMessageElement) {
    messageResizeObserver.observe(observedLastUserMessageElement);
  }

  if (observedLastAgentMessageElement) {
    messageResizeObserver.observe(observedLastAgentMessageElement);
  }
}

async function refreshSpacerTracking() {
  await nextTick();
  observeLastMessages();
  await updateSpacerHeight();
}

async function handleSendMessage() {
  const clientHeight = getScrollClientHeight();

  if (clientHeight) {
    showBottomSpacer.value = true;
    await nextTick(); // render the just-sent user message + spacer before measuring their heights
    await updateSpacerHeight();
    await nextTick(); // apply the computed spacer height before scrolling
    scrollContainer.value?.scrollToBottom();
  }
}

function recalculateScroll() {
  if (scrollContainer.value) {
    scrollContainer.value.handleScroll(false,spacerHeight.value + THRESHOLD_TO_SHOW_BUTTON);
    const isScrolledUp = scrollContainer.value.isUserScrolledUp();
    showScrollToBottomButton.value = !!isScrolledUp;
  }
}

</script>
