import type { Message } from '#cli/core/model'
import type { ThemeId } from '#cli/core/theme'
import { computed, watch } from 'vue'

interface UiSyncState {
  ui: {
    locale: string
    theme: {
      mode: ThemeId
    }
    upgradeNotification: {
      visible: boolean
      message: string
    }
  }
  selectedToolCallId: string | null
  expandedBashOutputs: ReadonlySet<string>
  messages: Message[]
  sessionId: string
}

type AssistantMessage = Extract<Message, { role: 'assistant' }>

function hasFencedMarkdown(message: AssistantMessage): boolean {
  return message.parts.some(
    p => p.type === 'markdown' && String(p.markdown ?? '').includes('```'),
  )
}

function assistantMarkdownFenceSignature(message: AssistantMessage): string {
  return message.parts
    .filter(
      (p): p is Extract<(typeof message.parts)[number], { type: 'markdown' }> =>
        p.type === 'markdown',
    )
    .map((p) => {
      const md = String(p.markdown ?? '')
      if (!md.includes('```')) return ''
      const tail = md.length > 16 ? md.slice(-16) : md
      return `${md.length}:${tail}`
    })
    .filter(Boolean)
    .join('|')
}

export function createUiSync(
  opts: Readonly<{
    state: UiSyncState
    initialLocale: string
    setLocale: (locale: string) => void
    normalizeThemeMode: (
      mode: ThemeId | string | undefined | null,
    ) => ThemeId
    scheduleToolCallReadHighlightByCallId: (opts: {
      state: UiSyncState
      callId: string
      mode: 'collapsed' | 'expanded'
      updateAssistant: (
        assistantId: string,
        fn: (
          m: Extract<Message, { role: 'assistant' }>,
        ) => Extract<Message, { role: 'assistant' }>,
      ) => void
    }) => void
    scheduleToolCallDiffHighlightByCallId: (opts: {
      state: UiSyncState
      callId: string
      updateAssistant: (
        assistantId: string,
        fn: (
          m: Extract<Message, { role: 'assistant' }>,
        ) => Extract<Message, { role: 'assistant' }>,
      ) => void
    }) => void
    scheduleAssistantMarkdownCodeHighlight: (opts: {
      state: UiSyncState
      assistantId: string
      updateAssistant: (
        assistantId: string,
        fn: (
          m: Extract<Message, { role: 'assistant' }>,
        ) => Extract<Message, { role: 'assistant' }>,
      ) => void
    }) => void
    updateAssistant: (
      assistantId: string,
      fn: (
        m: Extract<Message, { role: 'assistant' }>,
      ) => Extract<Message, { role: 'assistant' }>,
    ) => void
  }>,
): void {
  watch(
    () => opts.state.ui.locale,
    (next) => {
      const locale = String(next || '').trim() || opts.initialLocale
      opts.setLocale(locale)
    },
    { immediate: true },
  )

  watch(
    () => opts.state.ui.theme.mode,
    (mode, prev) => {
      const nextMode = opts.normalizeThemeMode(mode)
      const prevMode = opts.normalizeThemeMode(prev)
      if (nextMode === prevMode) return

      const selected = opts.state.selectedToolCallId
      if (selected) {
        const viewMode = opts.state.expandedBashOutputs.has(selected)
          ? 'expanded'
          : 'collapsed'
        opts.scheduleToolCallReadHighlightByCallId({
          state: opts.state,
          callId: selected,
          mode: viewMode,
          updateAssistant: opts.updateAssistant,
        })
        opts.scheduleToolCallDiffHighlightByCallId({
          state: opts.state,
          callId: selected,
          updateAssistant: opts.updateAssistant,
        })
      }

      for (const message of opts.state.messages) {
        if (message.role !== 'assistant') continue
        const id = String(message.id ?? '').trim()
        if (!id || !hasFencedMarkdown(message)) continue
        opts.scheduleAssistantMarkdownCodeHighlight({
          state: opts.state,
          assistantId: id,
          updateAssistant: opts.updateAssistant,
        })
      }
    },
  )

  // When the transcript is replaced wholesale (e.g. /session restore, fork, or snapshot hydrate),
  // re-schedule markdown code highlighting so fenced code blocks render with tokens.
  const mdHighlightSigByAssistantId = new Map<string, string>()
  const transcriptSignature = computed(() => {
    const msgs = opts.state.messages
    const firstId = String(msgs[0]?.id ?? '')
    const lastId = String(msgs[msgs.length - 1]?.id ?? '')
    const sid = String(opts.state.sessionId ?? '')
    return `${sid}|${msgs.length}|${firstId}|${lastId}`
  })
  watch(transcriptSignature, () => {
    for (const message of opts.state.messages) {
      if (message.role !== 'assistant') continue
      const id = String(message.id ?? '').trim()
      if (!id || !hasFencedMarkdown(message)) continue
      const sig = assistantMarkdownFenceSignature(message)
      if (!sig) continue
      if (mdHighlightSigByAssistantId.get(id) === sig) continue
      mdHighlightSigByAssistantId.set(id, sig)
      opts.scheduleAssistantMarkdownCodeHighlight({
        state: opts.state,
        assistantId: id,
        updateAssistant: opts.updateAssistant,
      })
    }
  })
}
