<template>
  <article
    v-if="matchedComponents.length > 0"
    :class="rootClasses"
    :style="cssVariableStyle"
    :data-message-id="dataMessageId"
  >
    <component
      v-for="(matchedComponent, index) in matchedComponents"
      :key="index"
      :is="matchedComponent"
    />

    <!-- Visually hidden focusable target for better keyboard navigation -->
    <div
      :id="`webchat-focus-target-${dataMessageId}`"
      :tabindex="-1"
      :class="$style.srOnly"
      aria-hidden="true"
    />
  </article>
</template>

<script setup lang="ts">
import {
  computed,
  reactive,
  watchEffect,
  useCssModule,
  type Component,
} from "vue";
import { match } from "../utils/matcher";
import {configColorsToCssVariables, themeFontToCssVariable, themeFontSizeToCssVariable} from "../utils/theme";
import { provideMessageContext } from "../composables/useMessageContext";
import { getMessageId, isMessagePlugin } from "../types";
import type { MessageProps, MessageContext } from "../types";

const $style = useCssModule();

// Import all message type components
import TextMessage from "./messages/TextMessage.vue";
import ImageMessage from "./messages/ImageMessage.vue";
import VideoMessage from "./messages/VideoMessage.vue";
import AudioMessage from "./messages/AudioMessage.vue";
import TextWithButtons from "./messages/TextWithButtons.vue";
import Gallery from "./messages/Gallery.vue";
import List from "./messages/List.vue";
import FileMessage from "./messages/FileMessage.vue";
import DatePicker from "./messages/DatePicker.vue";
import AdaptiveCard from "./messages/AdaptiveCard.vue";

const props = withDefaults(defineProps<MessageProps>(), {
  action: undefined,
  config: undefined,
  theme: undefined,
  prevMessage: undefined,
  plugins: undefined,
  onEmitAnalytics: undefined,
  disableHeader: false,
  messageParams: undefined,
  openXAppOverlay: undefined,
  onSlideChange: undefined,
  onImageClick: undefined,
});

// Generate a unique message ID for accessibility
const dataMessageId = computed(() => getMessageId(props.message));

// Provide message context for child components
// Using reactive object synced via watchEffect for proper reactivity tracking
const messageContext = reactive<MessageContext>({
  message: props.message,
  config: props.config,
  action: props.action,
  onEmitAnalytics: props.onEmitAnalytics,
  messageParams: props.messageParams,
  openXAppOverlay: props.openXAppOverlay,
  onSlideChange: props.onSlideChange,
  onImageClick: props.onImageClick,
});

// Keep context in sync with props changes
watchEffect(() => {
  messageContext.message = props.message
  messageContext.config = props.config
  messageContext.action = props.action
  messageContext.onEmitAnalytics = props.onEmitAnalytics
  messageContext.messageParams = props.messageParams
  messageContext.openXAppOverlay = props.openXAppOverlay
  messageContext.onSlideChange = props.onSlideChange
  messageContext.onImageClick = props.onImageClick
});

provideMessageContext(messageContext);

// Component map for internal match rules (maps rule names to Vue components)
const componentMap: Record<string, Component> = {
  Text: TextMessage,
  Image: ImageMessage,
  Video: VideoMessage,
  Audio: AudioMessage,
  TextWithButtons: TextWithButtons,
  Gallery: Gallery,
  List: List,
  File: FileMessage,
  DatePicker: DatePicker,
  AdaptiveCard: AdaptiveCard,
};

// Match message to appropriate components
const matchedComponents = computed(() => {
  const matched = match(props.message, props.config, props.plugins);

  if (!Array.isArray(matched) || matched.length < 1) {
    return [];
  }

  // Resolve components from match results
  return matched
    .map((rule) => {
      // External plugins (MessagePlugin) provide their own component
      if (isMessagePlugin(rule)) {
        return rule.component;
      }
      // Internal rules (MatchRule) use name lookup in componentMap
      return componentMap[rule.name] ?? null;
    })
    .filter((c): c is Component => c !== null);
});

// Compute direction based on source + config mapping
const directionMapping = computed(() => props.config?.settings?.widgetSettings?.sourceDirectionMapping)

const messageDirection = computed(() => {
  const source = props.message.source
  if (source === 'user') return directionMapping.value?.user || 'outgoing'
  if (source === 'bot') return directionMapping.value?.bot || 'incoming'
  if (source === 'agent') return directionMapping.value?.agent || 'incoming'
  return 'incoming'
})

// Root element classes
const rootClasses = computed(() => {
  return [
    'webchat-message-row',
    props.message.source,              // keep global source class (for ChatBubble color selectors)
    $style.message,
    props.message.source === "bot" && $style.bot,
    props.message.source === "user" && $style.user,
    props.message.source === "agent" && $style.agent,
    $style[messageDirection.value],    // incoming or outgoing alignment
  ].filter(Boolean)
})

// CSS variable injection from config colors and theme font
const cssVariableStyle = computed(() => {
  return {
    ...configColorsToCssVariables(props.config?.settings?.colors),
    ...themeFontToCssVariable(props.theme),
    ...themeFontSizeToCssVariable(props.theme),
  }
});
</script>

<style module>
.message {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
  font-family: var(--cc-font-family, 'Figtree', sans-serif);
  padding-left: 56px;
  padding-right: 16px;
}

.message.incoming {
  align-items: flex-start;
}

.message.outgoing {
  align-items: flex-end;
}

/* Screen reader only */
.srOnly {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}
</style>
