import type { ComputedRef } from 'vue'
import type { CliAgentBridge } from '../../agent-bridge'
import { t } from '#cli/i18n'
import { computed } from 'vue'
import { normalizeLastRequestBudget } from './request-budget'

interface SessionUiState {
  sessionId: string
  lastRequestBudget?: {
    runId?: string
    requestId?: string
    autoBudgetOutcome: 'budget-unknown' | 'compacted' | 'failed-to-fit' | 'not-needed'
    autoBudgetReasonCode?: string
    budgetSource?: string
    contextWindowTokens?: number
    estimatedInputTokens: number
    inputBudgetTokens?: number
    maxOutputTokens?: number
    protocolReserveTokens?: number
    reservedOutputTokens?: number
    thresholdTokens?: number
    maxInputTokens?: number
    triggerRatio?: number
    usedAutoCompaction: boolean
    updatedAt?: string
    updatedAtMs: number
  } | null
  ui: {
    upgradeNotification: {
      visible: boolean
      message: string
    }
  }
}

type SessionUiAgent = Readonly<Pick<CliAgentBridge, 'triggerManualCompression'>>
type CompactNoticeTone = 'success' | 'warning' | 'danger'

export interface CompactSessionResult {
  success: boolean
  message: string
  tone?: CompactNoticeTone
  errorCode?: string
  reason?: 'token_limit' | 'generic'
}

function asRecord(value: unknown): Record<string, unknown> {
  if (!value || typeof value !== 'object' || Array.isArray(value))
    return {}
  return value as Record<string, unknown>
}

function normalizeNonNegativeInteger(value: unknown): number | null {
  const numeric = Number(value)
  if (!Number.isFinite(numeric) || numeric < 0)
    return null
  return Math.floor(numeric)
}

function formatInteger(value: number): string {
  return Math.max(0, Math.floor(value)).toLocaleString('en-US')
}

function readCompactionMetrics(result: {
  messageCount?: number
  event?: unknown
}): {
  count: number | null
  tokensBefore: number | null
  tokensAfter: number | null
  thresholdTokens: number | null
  maxInputTokens: number | null
  runId?: string
  requestId?: string
} {
  const count = normalizeNonNegativeInteger(result.messageCount)
  const event = asRecord(result.event)
  const diagnostics = asRecord(event.diagnostics)
  const notification = asRecord(diagnostics.notification)
  const autoCompactAttempt = asRecord(diagnostics.autoCompactAttempt)
  const tokensBefore = normalizeNonNegativeInteger(
    event.tokensBefore
      ?? notification.estimatedInputTokensBefore
      ?? autoCompactAttempt.estimatedInputTokens,
  )
  const tokensAfter = normalizeNonNegativeInteger(
    event.tokensAfter
      ?? notification.estimatedInputTokensAfter
      ?? autoCompactAttempt.estimatedInputTokensAfter,
  )
  const thresholdTokens = normalizeNonNegativeInteger(
    event.threshold
      ?? notification.thresholdTokens
      ?? autoCompactAttempt.thresholdTokens,
  )
  const maxInputTokens = normalizeNonNegativeInteger(
    notification.maxInputTokens
      ?? autoCompactAttempt.maxInputTokens,
  )
  const runId = String(event.runId ?? '').trim() || undefined
  const requestId
    = String(
      notification.requestId
      ?? autoCompactAttempt.requestId
      ?? event.requestId
      ?? '',
    ).trim() || undefined
  return {
    count,
    tokensBefore,
    tokensAfter,
    thresholdTokens,
    maxInputTokens,
    ...(runId ? { runId } : {}),
    ...(requestId ? { requestId } : {}),
  }
}

function syncLastRequestBudgetFromCompaction(
  applyLastRequestBudget: (raw: unknown) => void,
  result: {
    event?: unknown
    messageCount?: number
  },
): void {
  const metrics = readCompactionMetrics(result)
  if (
    metrics.tokensAfter == null
    || metrics.thresholdTokens == null
    || metrics.maxInputTokens == null
    || metrics.maxInputTokens <= 0
  ) {
    return
  }
  const nextBudget = normalizeLastRequestBudget({
    runId: metrics.runId,
    requestId:
      metrics.requestId
      ?? `manual_compact_${Date.now().toString(36)}`,
    autoBudgetOutcome:
      metrics.tokensAfter <= metrics.maxInputTokens
        ? 'not-needed'
        : 'failed-to-fit',
    estimatedInputTokens: metrics.tokensAfter,
    inputBudgetTokens: metrics.maxInputTokens,
    thresholdTokens: metrics.thresholdTokens,
    maxInputTokens: metrics.maxInputTokens,
    triggerRatio: metrics.thresholdTokens / metrics.maxInputTokens,
    usedAutoCompaction: false,
    updatedAtMs: Date.now(),
  })
  applyLastRequestBudget(nextBudget)
}

function formatCompactSuccessMessage(result: {
  messageCount?: number
  event?: {
    tokensBefore?: number
    tokensAfter?: number
  }
}): Readonly<{ message: string, tone: CompactNoticeTone }> {
  const { count, tokensBefore, tokensAfter } = readCompactionMetrics(result)
  const savedTokens
    = tokensBefore != null && tokensAfter != null
      ? Math.max(0, tokensBefore - tokensAfter)
      : null

  if (
    count != null
    && tokensBefore != null
    && tokensAfter != null
    && savedTokens != null
  ) {
    if (tokensAfter < tokensBefore) {
      return {
        tone: 'success',
        message: t('chatPage.toast.compactSuccessDetailWithTokens', {
          count: formatInteger(count),
          before: formatInteger(tokensBefore),
          after: formatInteger(tokensAfter),
          saved: formatInteger(savedTokens),
        }),
      }
    }
    return {
      tone: 'warning',
      message: t('chatPage.toast.compactSuccessDetailNotReduced', {
        count: formatInteger(count),
        before: formatInteger(tokensBefore),
        after: formatInteger(tokensAfter),
      }),
    }
  }

  if (count != null) {
    return {
      tone: 'success',
      message: t('chatPage.toast.compactSuccessDetailCountOnly', {
        count: formatInteger(count),
      }),
    }
  }

  return {
    tone: 'success',
    message: t('chatPage.toast.compactSuccessDetailSummaryOnly'),
  }
}

function isLikelyTokenLimitError(message: string): boolean {
  const s = message.toLowerCase()
  return (
    s.includes('context length') ||
    s.includes('context window') ||
    s.includes('maximum context') ||
    s.includes('too many tokens') ||
    s.includes('max tokens') ||
    s.includes('token limit')
  )
}

export function createSessionUi(
  opts: Readonly<{
    state: SessionUiState
    agent: SessionUiAgent
    applyLastRequestBudget: (raw: unknown) => void
  }>,
): Readonly<{
  compactSession: () => Promise<CompactSessionResult>
  upgradeNotification: ComputedRef<{ visible: boolean; message: string }>
  showUpgradeNotification: (message: string) => void
  dismissUpgradeNotification: () => void
}> {
  async function compactSession(): Promise<CompactSessionResult> {
    const sessionId = String(opts.state.sessionId ?? '').trim()
    if (!sessionId)
      return { success: false, message: 'No active session', reason: 'generic' }

    const triggerManualCompression = opts.agent.triggerManualCompression
    if (typeof triggerManualCompression !== 'function') {
      return {
        success: false,
        message: 'Manual compression not supported',
        reason: 'generic',
      }
    }

    const result = await triggerManualCompression(sessionId)
    if (result?.success) {
      syncLastRequestBudgetFromCompaction(opts.applyLastRequestBudget, result)
      const formatted = formatCompactSuccessMessage(result)
      return {
        success: true,
        message: formatted.message,
        tone: formatted.tone,
      }
    }

    const errorCode =
      typeof result?.errorCode === 'string' && result.errorCode.trim()
        ? result.errorCode.trim()
        : undefined
    const rawError = String(result?.error ?? 'Compression failed').trim()
    const firstLine =
      rawError
        .split(/\r?\n/u)
        .find((line) => line.trim())
        ?.trim() || 'Compression failed'
    const reason =
      errorCode === 'compact_token_limit' || isLikelyTokenLimitError(firstLine)
        ? 'token_limit'
        : 'generic'

    return {
      success: false,
      message: firstLine,
      tone: 'danger',
      ...(errorCode ? { errorCode } : {}),
      reason,
    }
  }

  let upgradeNotificationTimer: ReturnType<typeof setTimeout> | null = null

  function showUpgradeNotification(message: string): void {
    opts.state.ui.upgradeNotification.visible = true
    opts.state.ui.upgradeNotification.message = message

    // Clear existing timer if any
    if (upgradeNotificationTimer) clearTimeout(upgradeNotificationTimer)

    // Auto-dismiss after 3 seconds
    upgradeNotificationTimer = setTimeout(() => {
      dismissUpgradeNotification()
      upgradeNotificationTimer = null
    }, 3000)
  }

  function dismissUpgradeNotification(): void {
    opts.state.ui.upgradeNotification.visible = false
    opts.state.ui.upgradeNotification.message = ''

    // Clear timer if notification is manually dismissed
    if (upgradeNotificationTimer) {
      clearTimeout(upgradeNotificationTimer)
      upgradeNotificationTimer = null
    }
  }

  const upgradeNotification = computed(() => opts.state.ui.upgradeNotification)

  return {
    compactSession,
    upgradeNotification,
    showUpgradeNotification,
    dismissUpgradeNotification,
  }
}
