import type { Style } from '@simon_he/vue-tui'
import type { DimSdkSessionListItem } from '../../../keys'
import { stripLeadingFocusFilesBlock } from '#cli/core/focus-files'
import { t } from '#cli/i18n'
import { padEndByCells, sliceByCells, textCellWidth } from '@simon_he/vue-tui'
import { clamp } from '../../utils/math'

export type SessionsDialogOption = Readonly<{
  label: string
  detail: string
  sessionId: string
  isActive?: boolean
  style?: Style
}>

export type SessionsDialogViewModel = Readonly<{
  w: number
  h: number
  innerW: number
  listY: number
  listH: number
  offset: number
  windowSize: number
  totalSessions: number
  selectedIndex: number
  selectedOptionIndex: number
  title: string
  headerText: string
  options: readonly SessionsDialogOption[]
}>

// Backwards-compatible re-export (older code imports `clamp` from this module).
export { clamp }

export function formatRelativeTime(
  ts: number,
  nowTs: number = Date.now(),
): string {
  if (!Number.isFinite(ts)) return ''

  const deltaMs = Number(ts) - Number(nowTs)
  if (!Number.isFinite(deltaMs)) return ''

  const absMs = Math.abs(deltaMs)

  // UX: keep units coarse and predictable (minute → hour → day).
  if (absMs < 1000 * 60) return t('sessionsDialog.timeNow')

  if (absMs < 1000 * 60 * 60) {
    const minutes = Math.max(1, Math.floor(absMs / (1000 * 60)))
    return t('sessionsDialog.timeMinutes', { count: minutes })
  }

  if (absMs < 1000 * 60 * 60 * 24) {
    const hours = Math.max(1, Math.floor(absMs / (1000 * 60 * 60)))
    return t('sessionsDialog.timeHours', { count: hours })
  }

  const days = Math.max(1, Math.floor(absMs / (1000 * 60 * 60 * 24)))
  return t('sessionsDialog.timeDays', { count: days })
}

function ellipsisByCells(text: unknown, maxCells: number): string {
  maxCells = Math.max(0, Math.floor(maxCells))
  const s = String(text ?? '')
    .replace(/\s+/g, ' ')
    .trim()
  if (!s || maxCells <= 0) return ''
  if (textCellWidth(s) <= maxCells) return s
  if (maxCells <= 1) return sliceByCells(s, maxCells)
  return `${sliceByCells(s, maxCells - 1)}…`
}

function ellipsisStartByCells(text: unknown, maxCells: number): string {
  maxCells = Math.max(0, Math.floor(maxCells))
  const s = String(text ?? '')
    .replace(/\s+/g, ' ')
    .trim()
  if (!s || maxCells <= 0) return ''
  if (textCellWidth(s) <= maxCells) return s
  if (maxCells <= 1) return sliceByCells(s, maxCells)

  const targetCells = maxCells - 1
  let remainingCells = targetCells
  const tailParts: string[] = []
  for (let end = s.length; end > 0 && remainingCells > 0; ) {
    let start = end - 1
    const cu = s.charCodeAt(start)
    if (cu >= 0xdc00 && cu <= 0xdfff && start - 1 >= 0) {
      const prev = s.charCodeAt(start - 1)
      if (prev >= 0xd800 && prev <= 0xdbff) start -= 1
    }
    const seg = s.slice(start, end)
    const w = textCellWidth(seg)
    if (w > remainingCells) break
    tailParts.push(seg)
    remainingCells -= w
    end = start
  }

  if (tailParts.length === 0) return `…${sliceByCells(s, targetCells)}`

  tailParts.reverse()
  return `…${tailParts.join('')}`
}

export function formatCwdTail(cwd: unknown, maxSegments: number = 3): string {
  const raw = String(cwd ?? '').trim()
  if (!raw) return ''

  const normalized = raw.replace(/[\\/]+/g, '/')
  const parts = normalized.split('/').filter(Boolean)
  if (parts.length === 0) return raw.startsWith('/') ? '/' : raw

  const drive = /^[A-Z]:$/i.test(parts[0]) ? parts[0] : ''
  const segs = drive ? parts.slice(1) : parts
  const keep = segs.slice(-Math.max(1, Math.floor(maxSegments)))
  const truncated = segs.length > keep.length

  if (drive) {
    if (truncated) return `${drive}/…/${keep.join('/')}`
    return `${drive}/${keep.join('/')}`
  }

  if (truncated) return `…/${keep.join('/')}`
  return keep.join('/')
}

function sessionSummaryText(s: DimSdkSessionListItem): string {
  // Prefer user-provided title (e.g. /rename) over derived summary.
  const raw = s.title || s.summary || t('sessionsDialog.newSession')
  return stripLeadingFocusFilesBlock(raw)
}

function deriveSessionTags(s: DimSdkSessionListItem): string[] {
  const raw =
    `${s.title || ''} ${s.summary || ''} ${s.lastMessage || ''}`.toLowerCase()
  if (!raw.trim()) return []
  const tags: string[] = []
  const pushTag = (tag: string) => {
    if (!tags.includes(tag)) tags.push(tag)
  }
  if (/\blint\b/.test(raw)) pushTag('lint')
  if (/\btest(?:s|ing)?\b/.test(raw)) pushTag('tests')
  if (/\bbuild\b|\bcompile\b|\btsc\b/.test(raw)) pushTag('build')
  if (/\bdocs?\b|\breadme\b/.test(raw)) pushTag('docs')
  if (/\brefactor\b|\bcleanup\b/.test(raw)) pushTag('refactor')
  if (/\bfix\b|\bbug\b|\berror\b/.test(raw)) pushTag('fix')
  if (/\bperf\b|\boptimi/.test(raw)) pushTag('perf')
  if (/\bfeature\b|\bfeat\b/.test(raw)) pushTag('feature')
  return tags.slice(0, 2)
}

function sessionStatusIcon(s: DimSdkSessionListItem): string {
  const status = String((s as any).status ?? '').trim()
  if (status === 'running') return '⟳'
  if (status === 'paused_confirm') return '!'
  if (status === 'error') return '×'
  return '•'
}

function sessionQueueBadge(s: DimSdkSessionListItem): string {
  const raw = Number((s as any).queueLength ?? 0)
  if (!Number.isFinite(raw) || raw <= 0) return ''
  if (raw > 5) return '5+'
  return String(Math.floor(raw))
}

function sessionWhenText(s: DimSdkSessionListItem, nowTs: number): string {
  return formatRelativeTime(s.updatedAt, nowTs)
}

function sessionCwdTailText(s: DimSdkSessionListItem): string {
  return formatCwdTail(s.cwd, 3)
}

export function getSessionSearchText(s: DimSdkSessionListItem): string {
  const cwd = sessionCwdTailText(s)
  const summary = sessionSummaryText(s)
  const tags = deriveSessionTags(s)
  const status = sessionStatusIcon(s)
  const badge = sessionQueueBadge(s)
  return `${status} ${badge} ${cwd} ${summary} ${tags.join(' ')}`.trim()
}

function computeLabelWidths(
  labelMaxW: number,
): Readonly<{ cwdW: number; summaryW: number; colGap: number }> {
  const colGap = 2
  const minCwdW = 14
  const minSummaryW = 18

  // If we can't reasonably show both, prioritize summary only.
  if (labelMaxW < minCwdW + colGap + minSummaryW)
    return { cwdW: 0, summaryW: labelMaxW, colGap }

  // Allocate more space to cwd so the workspace is easier to recognize.
  const extra = Math.max(0, labelMaxW - (minCwdW + colGap + minSummaryW))
  const cwdW = minCwdW + Math.floor(extra * 0.6)
  const summaryW = Math.max(0, labelMaxW - colGap - cwdW)
  return { cwdW, summaryW, colGap }
}

export function buildSessionsDialogViewModel(
  opts: Readonly<{
    modelValue: boolean
    loading: boolean
    deleteArmed: boolean
    cwdOnly?: boolean
    currentCwd?: string
    activeSessionId?: string
    allSessionsCount?: number
    sessions: readonly DimSdkSessionListItem[]
    selectedIndex: number
    w?: number
    h?: number
  }>,
): SessionsDialogViewModel {
  const w = Math.floor(opts.w ?? 78)
  const h = Math.floor(opts.h ?? 20)

  const innerW = Math.max(0, w - 4)
  const innerH = Math.max(0, h - 4)
  const contentH = Math.max(0, innerH - 1)

  const listY = 2
  const bottomGap = 1
  const sessions = Array.isArray(opts.sessions) ? opts.sessions : []
  const totalSessions = sessions.length
  const runningCount = sessions.filter(
    (s) => (s as any).status === 'running',
  ).length
  const pausedCount = sessions.filter(
    (s) => (s as any).status === 'paused_confirm',
  ).length
  const listH = clamp(
    totalSessions,
    1,
    Math.max(1, contentH - listY - bottomGap),
  )

  const selectedIndex = clamp(
    opts.selectedIndex,
    0,
    Math.max(0, totalSessions - 1),
  )
  const nowTs = Date.now()
  const maxWhenW = Math.max(
    0,
    ...sessions.map((s) => textCellWidth(sessionWhenText(s, nowTs))),
  )
  const minDetailGap = 1 // must match TSelect's minGap
  const labelMaxW = Math.max(0, innerW - minDetailGap - maxWhenW)
  const { cwdW, summaryW, colGap } = computeLabelWidths(labelMaxW)

  const windowSize = Math.max(1, listH)
  const maxOffset = Math.max(0, totalSessions - windowSize)
  const offset = clamp(selectedIndex - Math.floor(windowSize / 2), 0, maxOffset)
  const visible = sessions.slice(offset, offset + windowSize)

  const activeSessionId = String(opts.activeSessionId ?? '').trim()
  const options = visible.map((s) => {
    const parts: string[] = []

    if (cwdW > 0) {
      const cwdText = sessionCwdTailText(s) || t('common.empty')
      const cwd = padEndByCells(ellipsisStartByCells(cwdText, cwdW), cwdW)
      parts.push(cwd)
    }

    if (summaryW > 0) {
      const statusIcon = sessionStatusIcon(s)
      const badge = sessionQueueBadge(s)
      const prefix = `${statusIcon}${badge ? ` [${badge}]` : ''}`.trim()
      const summaryText = sessionSummaryText(s)
      const tags = deriveSessionTags(s)
      const tagText = tags.length ? ` · ${tags.join(' · ')}` : ''
      const summaryWithMeta = prefix
        ? `${prefix} ${summaryText}${tagText}`
        : `${summaryText}${tagText}`
      const summary = padEndByCells(
        ellipsisByCells(summaryWithMeta, summaryW),
        summaryW,
      )
      parts.push(summary)
    }

    const isActive = Boolean(activeSessionId) && s.id === activeSessionId
    return {
      label: parts.filter(Boolean).join(' '.repeat(colGap)).trimEnd(),
      detail: sessionWhenText(s, nowTs),
      sessionId: s.id,
      isActive,
      ...(isActive ? { style: { bg: 'blackBright' as const } } : {}),
    }
  })

  const selectedOptionIndex = clamp(
    selectedIndex - offset,
    0,
    Math.max(0, options.length - 1),
  )

  const cwdOnly = Boolean(opts.cwdOnly)
  const allCount = Number.isFinite(opts.allSessionsCount as any)
    ? Math.max(0, Math.floor(Number(opts.allSessionsCount)))
    : totalSessions
  const title =
    cwdOnly && allCount !== totalSessions
      ? t('sessionsDialog.titleScoped', {
          count: totalSessions,
          total: allCount,
        })
      : t('sessionsDialog.title', { count: totalSessions })
  const scopeText = cwdOnly
    ? t('sessionsDialog.scope', {
        cwd: formatCwdTail(opts.currentCwd, 3) || t('common.empty'),
      })
    : ''

  const rangeSuffix =
    totalSessions > windowSize
      ? t('sessionsDialog.headerRange', {
          start: offset + 1,
          end: Math.min(totalSessions, offset + windowSize),
        })
      : ''
  const runningSuffix =
    runningCount > 0
      ? t('sessionsDialog.runningCount', { count: runningCount })
      : ''
  const pausedSuffix =
    pausedCount > 0
      ? t('sessionsDialog.pausedCount', { count: pausedCount })
      : ''
  const activitySuffix = [runningSuffix, pausedSuffix]
    .filter(Boolean)
    .join(' · ')
  const activityText = activitySuffix ? ` • ${activitySuffix}` : ''

  const baseHeaderText = opts.loading
    ? t('sessionsDialog.loading')
    : opts.deleteArmed
      ? t('sessionsDialog.confirmDelete')
      : totalSessions
        ? t('sessionsDialog.header', {
            count: totalSessions,
            range: rangeSuffix,
            activity: activityText,
          })
        : cwdOnly && allCount > 0
          ? t('sessionsDialog.noSessionsCwd')
          : t('sessionsDialog.noSessions')

  const headerText = scopeText
    ? `${baseHeaderText} • ${scopeText}`
    : baseHeaderText

  return {
    w,
    h,
    innerW,
    listY,
    listH,
    offset,
    windowSize,
    totalSessions,
    selectedIndex,
    selectedOptionIndex,
    title,
    headerText,
    options,
  }
}
