import type { DimSdkStore } from '#cli/core/store'
import type { Ref } from 'vue'
import type { DimSdkSessionListItem, DimSdkSessionsApi } from '../../keys'
import { debugLog } from '#cli/core/debug-log'
import { isInterruptedAssistantMessage } from '#cli/core/store/acp-transform'
import { isSupplementalTranscriptAssistantMessage } from '#cli/core/supplemental-transcript'
import { t } from '#cli/i18n'
import { getCurrentInstance, onBeforeUnmount, ref, watch } from 'vue'
import { clamp } from './math'

export function useSessionsDialog(
  opts: Readonly<{
    store: DimSdkStore
    sessionsApi?: DimSdkSessionsApi
    showToast?: (text: string) => void
    pollIntervalMs?: number
  }>,
): Readonly<{
  showSessions: Ref<boolean>
  showSwitchConfirm: Ref<boolean>
  switchConfirmTitle: Ref<string>
  switchConfirmTargetLabel: Ref<string>
  switchConfirmReason: Ref<string>
  sessionsLoading: Ref<boolean>
  sessionsDeleteArmed: Ref<boolean>
  sessionsCwdOnly: Ref<boolean>
  sessionsAllCount: Ref<number>
  sessionsList: Ref<DimSdkSessionListItem[]>
  sessionsIndex: Ref<number>
  refreshSessions: (opts?: {
    preferIndex?: number
    preferId?: string
  }) => Promise<void>
  openSessionsDialog: () => Promise<void>
  openSelectedSession: (opts?: {
    after?: () => void | Promise<void>
  }) => Promise<void>
  createNewSession: (opts?: {
    after?: () => void | Promise<void>
  }) => Promise<void>
  confirmSwitch: () => Promise<void>
  cancelSwitch: () => void
}> {
  const showSessions = ref(false)
  const showSwitchConfirm = ref(false)
  const switchConfirmTitle = ref(t('switchSessionDialog.title'))
  const switchConfirmTargetLabel = ref('')
  const switchConfirmReason = ref('')
  const sessionsLoading = ref(false)
  const sessionsDeleteArmed = ref(false)
  const sessionsCwdOnly = ref(false)
  const sessionsAllCount = ref(0)
  const sessionsAllList = ref<DimSdkSessionListItem[]>([])
  const sessionsList = ref<DimSdkSessionListItem[]>([])
  const sessionsIndex = ref(0)
  const state = opts.store.state

  const POLL_INTERVAL_MS = Math.max(25, Math.floor(opts.pollIntervalMs ?? 3500))
  let pollTimer: ReturnType<typeof setInterval> | null = null

  type PendingSwitch = Readonly<{ run: () => Promise<void> }>
  const pendingSwitch = ref<PendingSwitch | null>(null)

  function normalizeCwd(cwd: unknown): string {
    const raw = String(cwd ?? '').trim()
    if (!raw) return ''

    const normalizedSlashes = raw.replace(/[\\/]+/g, '/')
    const drive = normalizedSlashes.match(/^([A-Z]):\//i)?.[1]
    const withDrive = drive
      ? `${drive.toUpperCase()}:/${normalizedSlashes.slice(3)}`
      : normalizedSlashes

    // Remove trailing slashes (but keep "/" and "C:/").
    const stripped = withDrive.replace(/\/+$/g, '')
    if (stripped === '') return '/'
    if (/^[A-Z]:$/i.test(stripped)) return `${stripped}/`
    return stripped
  }

  function currentCwd(): string {
    return normalizeCwd(state.context.cwd)
  }

  function comparableLocalMessageCount(): number {
    const all = state.messages
    const exclude = new Set<number>()
    for (let i = 0; i < all.length; i++) {
      const m = all[i]
      if (isSupplementalTranscriptAssistantMessage(m)) exclude.add(i)
      if (m.role === 'assistant' && isInterruptedAssistantMessage(m)) {
        exclude.add(i)
        // Interrupted transcript persistence stores the user prompt alongside the assistant interruption.
        const previous = all[i - 1]
        if (i > 0 && previous?.role === 'user') exclude.add(i - 1)
      }
    }
    return all.length - exclude.size
  }

  function hasPendingLocalRevertedMessages(): boolean {
    return state.revertedMessages.length > 0
  }

  function sessionMatchesCwd(s: DimSdkSessionListItem, cwd: string): boolean {
    if (!cwd) return true
    return normalizeCwd(s.cwd) === cwd
  }

  function sessionsEqual(
    a: DimSdkSessionListItem[],
    b: DimSdkSessionListItem[],
  ): boolean {
    if (a === b) return true
    if (!Array.isArray(a) || !Array.isArray(b)) return false
    if (a.length !== b.length) return false
    for (let i = 0; i < a.length; i += 1) {
      const left = a[i]
      const right = b[i]
      if (!left || !right) return false
      if (left.id !== right.id) return false
      if (left.title !== right.title) return false
      if (left.summary !== right.summary) return false
      if (left.updatedAt !== right.updatedAt) return false
      if (left.cwd !== right.cwd) return false
      if (left.lastMessage !== right.lastMessage) return false
      if (left.status !== right.status) return false
      if (left.queueLength !== right.queueLength) return false
      if (
        JSON.stringify(left.reasoningDiagnosticsByTurn ?? {})
        !== JSON.stringify(right.reasoningDiagnosticsByTurn ?? {})
      ) {
        return false
      }
    }
    return true
  }

  function applyFilterAndSelection(
    select: { preferIndex?: number; preferId?: string } = {},
    overrideAll?: DimSdkSessionListItem[],
  ): void {
    const prevList = sessionsList.value
    const prevIndex = sessionsIndex.value
    const prevPickedId = prevList[prevIndex]?.id ?? ''
    const all = Array.isArray(overrideAll ?? sessionsAllList.value)
      ? (overrideAll ?? sessionsAllList.value)
      : []
    if (sessionsAllCount.value !== all.length)
      sessionsAllCount.value = all.length

    const cwd = currentCwd()
    const next = sessionsCwdOnly.value
      ? all.filter((s) => sessionMatchesCwd(s, cwd))
      : all.slice()

    const listChanged = !sessionsEqual(prevList, next)

    const max = Math.max(0, next.length - 1)
    let idx = -1

    if (select.preferId) idx = next.findIndex((s) => s.id === select.preferId)
    if (idx < 0 && prevPickedId)
      idx = next.findIndex((s) => s.id === prevPickedId)
    if (idx < 0 && typeof select.preferIndex === 'number')
      idx = clamp(select.preferIndex, 0, max)
    if (idx < 0) {
      const curId = String(opts.store.state.sessionId ?? '')
      idx = next.findIndex((s) => s.id === curId)
    }

    const nextIndex = idx >= 0 ? idx : 0

    if (listChanged) sessionsList.value = next
    if (nextIndex !== prevIndex) sessionsIndex.value = nextIndex
  }

  function switchBusyReason(): string {
    const s = opts.store.state
    if (s.runningAgentRound) return t('sessionsDialog.busy.agentResponding')
    if (s.paused?.kind === 'ask_user')
      return t('sessionsDialog.busy.awaitingAskUser')
    if (s.paused?.kind === 'tool_approval')
      return t('sessionsDialog.busy.awaitingToolApproval')
    if (s.approval?.active?.status === 'pending')
      return t('sessionsDialog.busy.pendingApproval')
    return ''
  }

  function isSessionBusy(): boolean {
    const s = opts.store.state
    if (s.runningAgentRound) return true
    if (s.paused) return true
    return Boolean(s.approval?.active && s.approval.active.status === 'pending')
  }

  async function requestSwitch(
    opts2: Readonly<{
      title: string
      targetLabel: string
      run: () => Promise<void>
    }>,
  ): Promise<void> {
    const api = opts.sessionsApi
    if (!api) {
      opts.showToast?.(t('sessionsDialog.nodeOnly'))
      debugLog('session_switch_unavailable', {
        title: opts2.title,
        target: opts2.targetLabel,
      })
      return
    }

    if (!isSessionBusy()) {
      debugLog('session_switch', {
        title: opts2.title,
        target: opts2.targetLabel,
        busy: false,
      })
      await opts2.run()
      showSwitchConfirm.value = false
      pendingSwitch.value = null
      switchConfirmTitle.value = t('switchSessionDialog.title')
      switchConfirmTargetLabel.value = ''
      switchConfirmReason.value = ''
      showSessions.value = false
      sessionsDeleteArmed.value = false
      return
    }

    pendingSwitch.value = { run: opts2.run }
    switchConfirmTitle.value = opts2.title
    switchConfirmTargetLabel.value = opts2.targetLabel
    switchConfirmReason.value = switchBusyReason()
    showSwitchConfirm.value = true
    debugLog('session_switch_prompt', {
      title: opts2.title,
      target: opts2.targetLabel,
      reason: switchConfirmReason.value,
    })
  }

  async function refreshSessions(
    select: { preferIndex?: number; preferId?: string; silent?: boolean } = {},
  ): Promise<void> {
    const api = opts.sessionsApi
    if (!api) {
      sessionsAllList.value = []
      sessionsAllCount.value = 0
      sessionsList.value = []
      sessionsIndex.value = 0
      return
    }
    const silent = Boolean(select.silent)
    if (!silent) sessionsLoading.value = true
    try {
      const list = await api.list()
      const sorted: DimSdkSessionListItem[] = list
        .slice()
        .sort((a, b) => b.updatedAt - a.updatedAt)
      const allChanged = !sessionsEqual(sessionsAllList.value, sorted)
      if (allChanged) sessionsAllList.value = sorted
      applyFilterAndSelection(select, allChanged ? sorted : undefined)

      const sid = currentSessionId()
      const active = sid
        ? sorted.find(
            (item: DimSdkSessionListItem) => String(item?.id ?? '') === sid,
          )
        : undefined
      const localMessageCount = comparableLocalMessageCount()
      const remoteMessageCount = Number(active?.messageCount ?? -1)
      // Skip reload during silent background polls — reloading hydrates the
      // entire store which re-renders the message list and collapses expanded
      // tool blocks.  Only reload on explicit (non-silent) refreshes.
      const canReload =
        !silent &&
        Boolean(
          sid &&
          active &&
          typeof api.reload === 'function' &&
          remoteMessageCount >= 0 &&
          remoteMessageCount > localMessageCount,
        ) && !hasPendingLocalRevertedMessages()
      if (canReload) {
        try {
          await api.reload!(sid)
        } catch {
          // ignore reload failures and keep current state
        }
      }
    } catch {
      sessionsAllList.value = []
      sessionsAllCount.value = 0
      sessionsList.value = []
      sessionsIndex.value = 0
    } finally {
      if (!silent) sessionsLoading.value = false
    }
  }

  async function openSessionsDialog(): Promise<void> {
    if (!opts.sessionsApi) {
      opts.showToast?.(t('sessionsDialog.nodeOnly'))
      return
    }
    sessionsDeleteArmed.value = false
    showSessions.value = true
    await refreshSessions()
  }

  async function openSelectedSession(opts2?: {
    after?: () => void | Promise<void>
  }): Promise<void> {
    const api = opts.sessionsApi
    const picked = sessionsList.value[sessionsIndex.value]
    if (!api || !picked) return

    await requestSwitch({
      title: t('switchSessionDialog.title'),
      targetLabel: String(picked.title || picked.id),
      run: async () => {
        const from = String(opts.store.state.sessionId ?? '')
        await api.use(picked.id)
        debugLog('session_switch_run', {
          from,
          to: String(picked.id),
          kind: 'use',
        })
        await opts2?.after?.()
      },
    })
  }

  async function createNewSession(opts2?: {
    after?: () => void | Promise<void>
  }): Promise<void> {
    const api = opts.sessionsApi
    if (!api) {
      opts.showToast?.(t('sessionsDialog.nodeOnly'))
      return
    }
    await requestSwitch({
      title: t('sessionsDialog.newSessionTitle'),
      targetLabel: t('sessionsDialog.newSessionTarget'),
      run: async () => {
        const from = String(opts.store.state.sessionId ?? '')
        await api.createNew()
        debugLog('session_switch_run', {
          from,
          to: String(opts.store.state.sessionId ?? ''),
          kind: 'new',
        })
        await opts2?.after?.()
      },
    })
  }

  async function confirmSwitch(): Promise<void> {
    const pending = pendingSwitch.value
    if (!pending) return
    showSwitchConfirm.value = false
    pendingSwitch.value = null
    const s = opts.store
    s.interrupt()
    debugLog('session_switch_confirm', {
      title: switchConfirmTitle.value,
      target: switchConfirmTargetLabel.value,
      reason: switchConfirmReason.value,
    })
    await pending.run()
    showSessions.value = false
    sessionsDeleteArmed.value = false
    switchConfirmTitle.value = t('switchSessionDialog.title')
    switchConfirmTargetLabel.value = ''
    switchConfirmReason.value = ''
  }

  function cancelSwitch(): void {
    showSwitchConfirm.value = false
    pendingSwitch.value = null
    debugLog('session_switch_cancel', {
      title: switchConfirmTitle.value,
      target: switchConfirmTargetLabel.value,
      reason: switchConfirmReason.value,
    })
    switchConfirmTitle.value = t('switchSessionDialog.title')
    switchConfirmTargetLabel.value = ''
    switchConfirmReason.value = ''
  }

  function currentSessionId(): string {
    return String(opts.store.state.sessionId ?? '').trim()
  }

  const startPoll = () => {
    if (!opts.sessionsApi || pollTimer) return
    pollTimer = setInterval(() => {
      const sid = currentSessionId()
      const preferId = showSessions.value ? undefined : sid || undefined
      void refreshSessions({ preferId, silent: true })
    }, POLL_INTERVAL_MS)
  }

  const stopPoll = () => {
    if (!pollTimer) return
    clearInterval(pollTimer)
    pollTimer = null
  }

  watch([showSessions, currentSessionId], ([open, next]) => {
    if (!open) return
    if (!opts.sessionsApi || !next) return
    void refreshSessions({ preferId: next })
  })

  const currentContextCwd = () =>
    String(state.context.cwd ?? '').trim()

  watch([showSessions, sessionsCwdOnly, currentContextCwd], ([open]) => {
    if (!open) return
    applyFilterAndSelection()
  })

  // Only poll while the sessions dialog is open — background polling when the
  // dialog is closed triggers silent refreshSessions which can hydrate the
  // store and cause UI side-effects (e.g. collapsing expanded tool blocks).
  watch(
    [showSessions, () => opts.sessionsApi],
    ([open, api]) => {
      if (!open || !api) {
        stopPoll()
        return
      }
      startPoll()
    },
    { immediate: true },
  )

  if (getCurrentInstance()) {
    onBeforeUnmount(() => stopPoll())
  }

  return {
    showSessions,
    showSwitchConfirm,
    switchConfirmTitle,
    switchConfirmTargetLabel,
    switchConfirmReason,
    sessionsLoading,
    sessionsDeleteArmed,
    sessionsCwdOnly,
    sessionsAllCount,
    sessionsList,
    sessionsIndex,
    refreshSessions,
    openSessionsDialog,
    openSelectedSession,
    createNewSession,
    confirmSwitch,
    cancelSwitch,
  }
}
