import ChatBot from '#amidala/wcs/shared/chat-bot/chat-bot.js'
import { marked } from 'marked'

const { h, preactHooks, matter, register } = globalThis.ami
const { useState } = preactHooks
const { Heading, Link, RichText } = matter

// API: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#activity-object
const ACTIVITY_MAP = {
  message: MessageActivity,
  typing: TypingActivity
}

const API_URL = 'https://directline.botframework.com/v3/directline/conversations'
const USER_ID = globalThis.crypto.randomUUID()

// -----------------------------------------------------------------------------
// Main chat bot component
// -----------------------------------------------------------------------------

function ChatbotMS ({ 'instance-id': instanceId, 'button-text': buttonText, 'header-text': headerText, icon, state }) {
  const [data, setData] = useState({
    user: { id: USER_ID, name: USER_ID },
    conv: {},
    messages: []
  })

  if (!instanceId) {
    return null
  }

  const start = async () => {
    // Start a conversation.
    const res = await fetch(API_URL, {
      method: 'post',
      headers: { authorization: `Bearer ${instanceId}`, 'content-type': 'application/json' },
      body: JSON.stringify({ user: data.user })
    })

    data.conv = await res.json()
    setData({ ...data })

    // Connect to a web socket.
    const socket = new globalThis.WebSocket(data.conv.streamUrl)
    socket.addEventListener('message', event => {
      if (!event.data) {
        return
      }

      // Get activities.
      const activities = JSON.parse(event.data)?.activities || []

      // Limit activities to supported only.
      const supportedActivities = activities.filter(activity => Object.keys(ACTIVITY_MAP).includes(activity.type))

      // Convert activities to messages.
      const messages = supportedActivities.map(activity => ({
        isTyping: activity.type === 'typing',
        isBot: activity.from.id !== data.user.id,
        children: h(ACTIVITY_MAP[activity.type], { activity, sendFromUser })
      }))

      // Remove all existing typing messages.
      data.messages = data.messages.filter(m => !m.isTyping)

      // Update messages.
      data.messages.push(...messages)
      setData({ ...data })
    })
  }

  const sendFromUser = async text => fetch(`${API_URL}/${data.conv.conversationId}/activities`, {
    method: 'post',
    headers: { authorization: `Bearer ${data.conv.token}`, 'content-type': 'application/json' },
    body: JSON.stringify({ locale: getLocale(), from: data.user, type: 'message', text })
  })

  return h(ChatBot, {
    buttonText,
    headerText,
    icon,
    forceOpen: state === 'open',
    messages: data.messages,
    onInit () {
      start()
    },
    onSend (userMessage) {
      sendFromUser(userMessage)
    }
  })
}

register(ChatbotMS, 'chatbot-ms')

// -----------------------------------------------------------------------------
// Activities
// -----------------------------------------------------------------------------

function MessageActivity ({ activity, sendFromUser }) {
  const actions = activity?.suggestedActions?.actions
  return [
    activity.text && h(Markdown, { text: activity.text }),
    activity.attachments && h(Attachments, { attachments: activity.attachments, sendFromUser }),
    actions?.length && h(SuggestedActions, { actions, sendFromUser })
  ]
}

function SuggestedActions ({ actions, sendFromUser }) {
  return h('div', { className: 'space-y-2' }, actions.map(action =>
    h('div',
      h(CardAction, { content: action, sendFromUser })
    )
  ))
}

function TypingActivity () {
  return h('span', '...')
}

// -----------------------------------------------------------------------------
// Attachments
// -----------------------------------------------------------------------------

// API: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#attachment-object
function Attachments ({ attachments, sendFromUser }) {
  const attachmentMap = {
    'application/vnd.microsoft.card.hero': HeroCard,
    'application/vnd.microsoft.card.adaptive': AdaptiveCard
  }

  return attachments.map(a => h('div',
    attachmentMap[a.contentType] && h(attachmentMap[a.contentType], { content: a.content, sendFromUser })
  ))
}

// Attachment type: application/vnd.microsoft.card.hero
function HeroCard ({ content, sendFromUser }) {
  return h('div', { className: 'space-y-2' },
    content.title && h(Heading, { _level: 3 }, content.title),
    content.subtitle && h('p', content.subtitle),
    content.text && h(Markdown, { text: content.text }),
    content.images?.map(im => h('img', { className: 'block w-full', src: im.url })),
    content.buttons && h('div', { className: 'space-y-2' }, content.buttons.map(b => // type: imBack, openUrl
      h('div',
        h(CardAction, { content: b, sendFromUser })
      ))
    )
  )
}

// Rabbit hole: https://adaptivecards.io
function AdaptiveCard ({ content, sendFromUser }) {
  return h('div', { className: 'space-y-2' },
    content.body?.map(b => // type: imBack, openUrl
      b.type === 'TextBlock' && h(Markdown, { text: b.text })
    ),
    (content?.actions ?? []).map(a =>
      h(CardAction, { content: { ...a, value: a.data }, sendFromUser })
    )
  )
}

// API: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#cardaction-object
// Types: https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-add-rich-cards?view=azure-bot-service-4.0#process-events-within-rich-cards
function CardAction ({ content, sendFromUser }) {
  const baseProps = {
    _variant: 'simple',
    className: 'rounded-md pt-1 pb-1 pl-2 pr-2 border border-txa/20 inline-block'
  }

  switch (content.type) {
    case 'call':
    case 'downloadFile':
    case 'openUrl':
    case 'playAudio':
    case 'playVideo':
    case 'showImage':
    case 'signin':
      return h(Link, {
        ...baseProps,
        _model: { name: content.title, value: content.value },
        target: '_blank',
        rel: 'noopener',
      })
    default:
      return content.url
        ? h(Link, { ...baseProps, _model: { name: content.title, value: content.url }, target: '_blank' })
        : h(Link, {
          _model: { name: content.title },
          ...baseProps,
          onClick () {
            sendFromUser(content.value)
          }
        })
  }
}

// -----------------------------------------------------------------------------
// Helpers
// -----------------------------------------------------------------------------

function getLocale () {
  return document.documentElement.lang ?? navigator.language ?? 'en'
}

function Markdown ({ text }) {
  // Make sure links open in a new window.
  const markedText = marked(text).replaceAll(/( href=")/gm, ' rel="noopener" target="_blank" $1')
  return h(RichText, markedText)
}
