📄 status.ts  •  6455 bytes
/**
 * UI 组件 - 状态显示
 * Phase 5: 状态指示器
 */

/** 状态类型 */
export type StatusType = 'idle' | 'loading' | 'success' | 'error' | 'warning' | 'info'

/** 状态配置 */
export interface StatusOptions {
  message?: string
  icon?: string
  color?: string
  showTimestamp?: boolean
  persist?: boolean  // 是否持续显示(不自动清除)
}

/** ANSI 颜色 */
const ESC = '\x1b'
const RESET = `${ESC}[0m`
const CLEAR_LINE = `${ESC}[2K\r`
const MOVE_UP = `${ESC}[1A`

/** 状态图标 */
export const STATUS_ICONS: Record<StatusType, string> = {
  idle: '○',
  loading: '◐',
  success: '●',
  error: '✕',
  warning: '⚠',
  info: 'ℹ',
}

/** 状态颜色 */
export const STATUS_COLORS: Record<StatusType, string> = {
  idle: '\x1b[38;5;240m',
  loading: '\x1b[38;5;51m',
  success: '\x1b[38;5;208m',
  error: '\x1b[38;5;196m',
  warning: '\x1b[38;5;220m',
  info: '\x1b[38;5;51m',
}

/** 状态管理器 */
export class StatusIndicator {
  private type: StatusType
  private message: string
  private startTime: number
  private lines: number = 1
  private frame: number = 0
  private intervalId?: ReturnType<typeof setInterval>
  
  constructor(type: StatusType = 'idle', message: string = '', options: StatusOptions = {}) {
    this.type = type
    this.message = message || this.getDefaultMessage(type)
    this.startTime = Date.now()
    
    if (options.icon) STATUS_ICONS[type] = options.icon
    if (options.color) STATUS_COLORS[type] = options.color
  }
  
  /** 获取默认消息 */
  private getDefaultMessage(type: StatusType): string {
    const messages: Record<StatusType, string> = {
      idle: '就绪',
      loading: '加载中',
      success: '完成',
      error: '失败',
      warning: '警告',
      info: '提示',
    }
    return messages[type]
  }
  
  /** 更新状态 */
  update(type: StatusType, message?: string): void {
    this.type = type
    if (message) this.message = message
    this.render()
  }
  
  /** 开始动画 */
  start(): void {
    this.render()
    if (this.type === 'loading') {
      this.intervalId = setInterval(() => {
        this.frame++
        this.render()
      }, 200)
    }
  }
  
  /** 停止动画 */
  stop(): void {
    if (this.intervalId) {
      clearInterval(this.intervalId)
      this.intervalId = undefined
    }
  }
  
  /** 清除显示 */
  clear(): void {
    this.stop()
    for (let i = 0; i < this.lines; i++) {
      process.stdout.write(`${CLEAR_LINE}${MOVE_UP}`)
    }
    process.stdout.write(CLEAR_LINE)
  }
  
  /** 渲染状态 */
  private render(): void {
    const icon = this.getAnimatedIcon()
    const color = STATUS_COLORS[this.type]
    const timestamp = this.getTimestamp()
    
    const line = `  ${color}${icon}${RESET} ${this.message}`
    const suffix = timestamp ? ` ${ESC}[38;5;240m${timestamp}${RESET}` : ''
    
    // 清除之前的行
    if (this.lines > 1) {
      for (let i = 0; i < this.lines - 1; i++) {
        process.stdout.write(`${CLEAR_LINE}${MOVE_UP}`)
      }
    }
    process.stdout.write(CLEAR_LINE)
    process.stdout.write(line + suffix)
  }
  
  /** 获取动画图标 */
  private getAnimatedIcon(): string {
    const baseIcon = STATUS_ICONS[this.type]
    
    if (this.type !== 'loading') {
      return baseIcon
    }
    
    // 旋转动画
    const frames = ['◐', '◓', '◑', '◒']
    return frames[this.frame % frames.length]
  }
  
  /** 获取时间戳 */
  private getTimestamp(): string {
    const elapsed = Math.floor((Date.now() - this.startTime) / 1000)
    if (elapsed < 1) return ''
    
    if (elapsed < 60) {
      return `${elapsed}s`
    }
    
    const minutes = Math.floor(elapsed / 60)
    const seconds = elapsed % 60
    return `${minutes}m ${seconds}s`
  }
  
  /** 成功状态 */
  success(message?: string): void {
    this.update('success', message || '操作成功')
    setTimeout(() => this.clear(), 2000)
  }
  
  /** 失败状态 */
  error(message?: string): void {
    this.update('error', message || '操作失败')
  }
  
  /** 警告状态 */
  warning(message?: string): void {
    this.update('warning', message || '警告')
  }
  
  /** 信息状态 */
  info(message?: string): void {
    this.update('info', message || '提示')
  }
}

/** 全局状态管理 */
class GlobalStatusManager {
  private current?: StatusIndicator
  
  set(type: StatusType, message?: string): StatusIndicator {
    this.clear()
    this.current = new StatusIndicator(type, message)
    this.current.start()
    return this.current
  }
  
  clear(): void {
    if (this.current) {
      this.current.clear()
      this.current = undefined
    }
  }
  
  success(message?: string): void {
    if (this.current) {
      this.current.success(message)
    } else {
      console.log(`  ${STATUS_COLORS.success}●${RESET} ${message || '成功'}`)
    }
  }
  
  error(message?: string): void {
    if (this.current) {
      this.current.error(message)
    } else {
      console.log(`  ${STATUS_COLORS.error}✕${RESET} ${message || '失败'}`)
    }
  }
}

/** 全局实例 */
export const status = new GlobalStatusManager()

/** 创建状态指示器 */
export function createStatus(type: StatusType = 'idle', message?: string): StatusIndicator {
  return new StatusIndicator(type, message)
}

/** Loading 动画 */
export async function withStatus<T>(
  message: string,
  operation: () => Promise<T>
): Promise<T> {
  const indicator = createStatus('loading', message)
  indicator.start()
  
  try {
    const result = await operation()
    indicator.success('完成')
    return result
  } catch (error: any) {
    indicator.error(error?.message || '失败')
    throw error
  }
}

/** 加载动画组 */
export class SpinnerGroup {
  private spinners: Map<string, StatusIndicator> = new Map()
  
  add(key: string, message: string): void {
    const spinner = createStatus('loading', message)
    spinner.start()
    this.spinners.set(key, spinner)
  }
  
  complete(key: string, success: boolean = true): void {
    const spinner = this.spinners.get(key)
    if (spinner) {
      spinner.stop()
      if (success) {
        console.log(`  ${STATUS_COLORS.success}●${RESET} ${spinner['message']}`)
      } else {
        console.log(`  ${STATUS_COLORS.error}✕${RESET} ${spinner['message']}`)
      }
      this.spinners.delete(key)
    }
  }
  
  clear(): void {
    this.spinners.forEach(s => s.clear())
    this.spinners.clear()
  }
}