๐Ÿ“„ crypto-util.ts  โ€ข  22026 bytes
/**
 * CmdCode V0.5 - ๅŠ ๅฏ†ๅญ˜ๅ‚จ็ณป็ปŸ
 * 
 * ๐Ÿ” ๅฎ‰ๅ…จๅŽŸๅˆ™๏ผš
 * - ๅฏ†้’ฅๆฐธไธๅœจไปฃ็ ไธญ็กฌ็ผ–็ 
 * - ๅฏ†้’ฅๆฐธไธๅœจ็Žฏๅขƒๅ˜้‡ไธญไผ ้€’
 * - ๅฏ†้’ฅๆฐธไธๅœจๅ‘ฝไปค่กŒๅ‚ๆ•ฐไธญๅ‡บ็Žฐ
 * - ๅฏ†้’ฅๆฐธไธๅœจๆ—ฅๅฟ—ๆ–‡ไปถไธญ่ฎฐๅฝ•
 * - ๅฏ†้’ฅไป…ๅœจๅ†…ๅญ˜ไธญ่งฃๅฏ†๏ผŒ็”จๅฎŒๅณ็„š
 */
import { createCipheriv, createDecipheriv, randomBytes, createHash } from 'node:crypto'
import { readFileSync, writeFileSync, existsSync, mkdirSync, unlinkSync, chmodSync } from 'node:fs'
import { join } from 'node:path'
import { homedir } from 'node:os'

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ้…็ฝฎๅธธ้‡
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

const SECRETS_FILE = join(homedir(), '.cmdcode', 'secrets.enc')
const CMD_DIR = join(homedir(), '.cmdcode')

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ็ฑปๅž‹ๅฎšไน‰
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/** ๅฏ†้’ฅๆฑ ๆก็›ฎ๏ผˆๅญ˜ๅ‚จๅฝขๅผ๏ผ‰ */
export interface KeyPoolEntry {
  name: string
  model: string
  baseUrl: string
  apiKeyEncrypted: string // ๅŠ ๅฏ†ๅŽ็š„ API Key
}

/** ๆ•ๆ„Ÿ้…็ฝฎ็ป“ๆž„ */
export interface Secrets {
  // ็”จๆˆท่‡ชๅฎšไน‰ๅฏ†้’ฅ๏ผˆๅ•ๅฏ†้’ฅๆจกๅผ๏ผ‰
  apiKey?: string
  model?: string
  baseUrl?: string
  
  // ๅฏ†้’ฅๆฑ ๏ผˆๅคšๅฏ†้’ฅๆจกๅผ๏ผ‰
  chatKeyPool?: KeyPoolEntry[]
  embeddingKeyPool?: KeyPoolEntry[]
  
  // ๅ‘้‡่ฎฐๅฟ†็ณป็ปŸ้…็ฝฎ
  memorySearch?: {
    provider: string
    model: string
    baseUrl: string
  }
  
  // QQ Bot
  qqBotApiKey?: string
  qqBotApiSecret?: string
  
  // ็”จๆˆท็™ปๅฝ•
  userToken?: string
  userInfo?: { username: string; workspaceDir: string; quota?: number }
  
  // ๅ…ถไป–
  [key: string]: any
}

export interface KeyStore { apiKey: string; baseUrl: string }

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ไธปๅฏ†้’ฅ็ฎก็†
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/** ๆ˜ฏๅฆไฝฟ็”จไบ† fallback ไธปๅฏ†้’ฅ๏ผˆๅฎ‰ๅ…จ่ญฆๅ‘Š๏ผ‰ */
let usingFallbackKey = false

/** ็”จๆˆท่ฎพ็ฝฎ็š„ไธปๅฏ†็ ๏ผˆไฝœไธบ้ขๅค–็†ตๆบ๏ผ‰ */
let userPassphrase: string | null = null

/** ๆฃ€ๆŸฅๆ˜ฏๅฆไฝฟ็”จไบ†ไธๅฎ‰ๅ…จ็š„ไธปๅฏ†้’ฅ */
export function isUsingFallbackKey(): boolean {
  return usingFallbackKey
}

/** ่ฎพ็ฝฎ็”จๆˆทไธปๅฏ†็ ๏ผˆๅขžๅผบๅฎ‰ๅ…จๆ€ง๏ผ‰ */
export function setMasterPassphrase(passphrase: string): void {
  userPassphrase = passphrase
  // ่ฎพ็ฝฎๅŽๆธ…้™ค fallback ่ญฆๅ‘Š
  usingFallbackKey = false
}

/** ่Žทๅ–ๅŠ ๅฏ†ๅฏ†้’ฅ - ไปŽๆœบๅ™จIDๆดพ็”Ÿ */
function getEncryptionKey(): Buffer {
  let machineId = ''
  let entropySource = ''
  
  try {
    machineId = readFileSync('/etc/machine-id', 'utf-8').trim()
    entropySource = machineId
  } catch {
    // machine-id ไธๅญ˜ๅœจ
    entropySource = ''
  }
  
  // ๅฆ‚ๆžœ็”จๆˆท่ฎพ็ฝฎไบ†ไธปๅฏ†็ ๏ผŒไฝฟ็”จไธปๅฏ†็ ไฝœไธบไธป่ฆ็†ตๆบ
  if (userPassphrase && userPassphrase.length >= 8) {
    return createHash('sha256')
      .update(userPassphrase + '-cmdcode-secrets-v2')
      .digest()
  }
  
  // ๅฆ‚ๆžœ machine-id ๅฏ็”จ๏ผŒไฝฟ็”จๆœบๅ™จIDๆดพ็”Ÿ
  if (entropySource) {
    return createHash('sha256')
      .update(entropySource + '-cmdcode-secrets')
      .digest()
  }
  
  // โš ๏ธ ๅฑ้™ฉ๏ผšไฝฟ็”จไบ†็กฌ็ผ–็  fallback
  // ๅœจๅฎนๅ™จ/WSL2/ไบ‘็Žฏๅขƒไธญ๏ผŒๆ‰€ๆœ‰ๅฎžไพ‹ๅ…ฑไบซๅŒไธ€ๅฏ†้’ฅ
  usingFallbackKey = true
  console.error('โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—')
  console.error('โ•‘  โš ๏ธ  ๅฎ‰ๅ…จ่ญฆๅ‘Š๏ผšๆ— ๆณ•่Žทๅ–ๆœบๅ™จๅ”ฏไธ€ๆ ‡่ฏ†                          โ•‘')
  console.error('โ•‘  ๅฝ“ๅ‰็Žฏๅขƒๅฏ่ƒฝไธบๅฎนๅ™จ/WSL2/็ฒพ็ฎ€Linux๏ผŒๅŠ ๅฏ†ๅฎ‰ๅ…จๆ€ง้™ไฝŽ          โ•‘')
  console.error('โ•‘  ๅปบ่ฎฎ่ฟ่กŒไปฅไธ‹ๅ‘ฝไปค่ฎพ็ฝฎไธปๅฏ†็ ๏ผš                               โ•‘')
  console.error('โ•‘    /masterkey <ไฝ ็š„ๅฏ†็ >                                    โ•‘')
  console.error('โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')
  
  // ไปไฝฟ็”จๅ›บๅฎšๅญ—็ฌฆไธฒ๏ผŒไฝ†ๆ ‡่ฎฐไธบไธๅฎ‰ๅ…จ
  return createHash('sha256')
    .update('cmdcode-v05-unsafe-fallback-key-do-not-use-in-production')
    .digest()
}

/** ๅŠ ๅฏ†ๆ•ฐๆฎ */
export function encrypt(data: any): string {
  const key = getEncryptionKey()
  const iv = randomBytes(16)
  const cipher = createCipheriv('aes-256-cbc', key, iv)
  const json = JSON.stringify(data)
  const encrypted = Buffer.concat([cipher.update(json, 'utf8'), cipher.final()])
  return iv.toString('hex') + ':' + encrypted.toString('hex')
}

/** ่งฃๅฏ†ๆ•ฐๆฎ */
export function decrypt<T = any>(encryptedText: string): T {
  const key = getEncryptionKey()
  const [ivHex, dataHex] = encryptedText.split(':')
  if (!ivHex || !dataHex) throw new Error('Invalid format')
  const iv = Buffer.from(ivHex, 'hex')
  const data = Buffer.from(dataHex, 'hex')
  const decipher = createDecipheriv('aes-256-cbc', key, iv)
  const json = decipher.update(data, undefined, 'utf8') + decipher.final('utf8')
  return JSON.parse(json) as T
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅญ—ๆฎต็บงๅŠ ๅฏ†๏ผˆ็”จไบŽๅฏ†้’ฅๆฑ ๏ผ‰
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/** ๅŠ ๅฏ†ๅ•ไธชๅญ—ๆฎต๏ผˆๅค็”จ encrypt๏ผŒ็ป“ๆžœๅฝขๅฆ‚ iv:data๏ผ‰ */
export function encryptField(plaintext: string): string {
  return encrypt(plaintext)
}

/** ่งฃๅฏ†ๅ•ไธชๅญ—ๆฎต */
export function decryptField(encrypted: string): string {
  return decrypt<string>(encrypted)
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ้…็ฝฎๆ–‡ไปถ็ฎก็†
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

function ensureDir(): void {
  if (!existsSync(CMD_DIR)) mkdirSync(CMD_DIR, { recursive: true })
}

function setPermission(filePath: string): void {
  try { chmodSync(filePath, 0o600) } catch { /* ignore */ }
}

/** ๅŠ ่ฝฝๆ‰€ๆœ‰ๆ•ๆ„Ÿ้…็ฝฎ */
export function loadSecrets(): Secrets {
  if (!existsSync(SECRETS_FILE)) return {}
  try {
    return decrypt<Secrets>(readFileSync(SECRETS_FILE, 'utf-8'))
  } catch {
    return {}
  }
}

/** ไฟๅญ˜ๆ‰€ๆœ‰ๆ•ๆ„Ÿ้…็ฝฎ */
export function saveSecrets(secrets: Secrets): void {
  ensureDir()
  writeFileSync(SECRETS_FILE, encrypt(secrets), 'utf-8')
  setPermission(SECRETS_FILE)
}

/** ๆ›ดๆ–ฐ้ƒจๅˆ†ๆ•ๆ„Ÿ้…็ฝฎ */
export function updateSecrets(partial: Partial<Secrets>): void {
  const current = loadSecrets()
  saveSecrets({ ...current, ...partial })
}

/** ๆธ…้™คๆ‰€ๆœ‰ๆ•ๆ„Ÿ้…็ฝฎ */
export function clearSecrets(): void {
  if (existsSync(SECRETS_FILE)) {
    try { unlinkSync(SECRETS_FILE) } catch { /* ignore */ }
  }
}

export function hasSecrets(): boolean {
  return existsSync(SECRETS_FILE)
}

export function maskSecret(value?: string): string {
  if (!value) return '(ๆœช้…็ฝฎ)'
  return 'โœ“ ๅทฒ้…็ฝฎ'
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ่Šๅคฉๅฏ†้’ฅๆฑ ็ฎก็†
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/** ่€—ๅฐฝๆ ‡่ฎฐ๏ผˆๅŸบไบŽ name๏ผŒไธๅ—็ดขๅผ•ๅ˜ๅŠจๅฝฑๅ“๏ผ‰ */
const exhaustedChatKeyNames = new Set<string>()
let migrationWarned = false  // P3 #35: ๅฏ†้’ฅไฝ“็ณป่ฟ็งปๆ็คบ๏ผˆๅชๆ˜พ็คบไธ€ๆฌก๏ผ‰
const exhaustedEmbeddingKeyNames = new Set<string>()

/** ๅนถๅ‘้”๏ผˆไฟๆŠคๅฏ†้’ฅๆฑ ๆ“ไฝœ๏ผ‰ - ๅ•็บฟ็จ‹็Žฏๅขƒไธ‹ๅŸบๆœฌๅฎ‰ๅ…จ๏ผŒไฝ†ไธบๆœชๆฅๅคš็บฟ็จ‹้ข„็•™ */
let keyPoolOperationInProgress = false
const pendingKeyPoolOperations: (() => void)[] = []

/** ๆ‰ง่กŒๅธฆ้”็š„ๅฏ†้’ฅๆฑ ๆ“ไฝœ */
async function withKeyPoolLock<T>(fn: () => T): Promise<T> {
  if (keyPoolOperationInProgress) {
    return new Promise((resolve, reject) => {
      pendingKeyPoolOperations.push(() => {
        try {
          resolve(fn())
        } catch (e) {
          reject(e)
        }
      })
    })
  }
  
  keyPoolOperationInProgress = true
  try {
    return fn()
  } finally {
    keyPoolOperationInProgress = false
    // ๆ‰ง่กŒ็ญ‰ๅพ…ไธญ็š„ๆ“ไฝœ
    const next = pendingKeyPoolOperations.shift()
    if (next) {
      setTimeout(next, 0) // ๅผ‚ๆญฅๆ‰ง่กŒ๏ผŒ้ฟๅ…้€’ๅฝ’
    }
  }
}

/** ๅŠ ่ฝฝ่Šๅคฉๅฏ†้’ฅๆฑ  */
export function loadChatKeyPool(): KeyPoolEntry[] {
  const s = loadSecrets()
  return s.chatKeyPool || []
}

/** ไฟๅญ˜่Šๅคฉๅฏ†้’ฅๆฑ  */
export function saveChatKeyPool(pool: KeyPoolEntry[]): void {
  updateSecrets({ chatKeyPool: pool })
}

/** ๆทปๅŠ ่Šๅคฉๅฏ†้’ฅ */
export function addChatKey(name: string, model: string, baseUrl: string, apiKey: string): void {
  const pool = loadChatKeyPool()
  if (pool.some(e => e.name === name)) {
    throw new Error(`Key "${name}" already exists`)
  }
  pool.push({
    name,
    model,
    baseUrl,
    apiKeyEncrypted: encryptField(apiKey),
  })
  saveChatKeyPool(pool)
  // ้‡็ฝฎ่€—ๅฐฝ็Šถๆ€๏ผŒ็กฎไฟๆ–ฐ Key ๅฏ็”จ
  resetChatKeyPool()
}

/** ๅˆ ้™ค่Šๅคฉๅฏ†้’ฅ */
export function removeChatKey(name: string): boolean {
  const pool = loadChatKeyPool()
  const idx = pool.findIndex(e => e.name === name)
  if (idx === -1) return false
  pool.splice(idx, 1)
  saveChatKeyPool(pool)
  resetChatKeyPool()
  return true
}

/** ้‡็ฝฎ่Šๅคฉๅฏ†้’ฅๆฑ ่€—ๅฐฝ็Šถๆ€ */
export function resetChatKeyPool(): void {
  exhaustedChatKeyNames.clear()
}

/** ๆ ‡่ฎฐ่Šๅคฉๅฏ†้’ฅไธบ่€—ๅฐฝ */
export function markChatKeyExhausted(name: string): void {
  exhaustedChatKeyNames.add(name)
}

/** ่Žทๅ–่Šๅคฉๅฏ†้’ฅๆฑ ็Šถๆ€ */
export function getChatKeyPoolStatus(): { total: number; exhausted: number; remaining: number } {
  const pool = loadChatKeyPool()
  const total = pool.length
  const exhausted = pool.filter(e => exhaustedChatKeyNames.has(e.name)).length
  return { total, exhausted, remaining: total - exhausted }
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// Embedding ๅฏ†้’ฅๆฑ ็ฎก็†
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

export function loadEmbeddingKeyPool(): KeyPoolEntry[] {
  const s = loadSecrets()
  return s.embeddingKeyPool || []
}

export function saveEmbeddingKeyPool(pool: KeyPoolEntry[]): void {
  updateSecrets({ embeddingKeyPool: pool })
}

export function addEmbeddingKey(name: string, model: string, baseUrl: string, apiKey: string): void {
  const pool = loadEmbeddingKeyPool()
  if (pool.some(e => e.name === name)) {
    throw new Error(`Key "${name}" already exists`)
  }
  pool.push({
    name,
    model,
    baseUrl,
    apiKeyEncrypted: encryptField(apiKey),
  })
  saveEmbeddingKeyPool(pool)
  resetEmbeddingKeyPool()
}

export function removeEmbeddingKey(name: string): boolean {
  const pool = loadEmbeddingKeyPool()
  const idx = pool.findIndex(e => e.name === name)
  if (idx === -1) return false
  pool.splice(idx, 1)
  saveEmbeddingKeyPool(pool)
  resetEmbeddingKeyPool()
  return true
}

export function resetEmbeddingKeyPool(): void {
  exhaustedEmbeddingKeyNames.clear()
}

export function markEmbeddingKeyExhausted(name: string): void {
  exhaustedEmbeddingKeyNames.add(name)
}

export function getEmbeddingKeyPoolStatus(): { total: number; exhausted: number; remaining: number } {
  const pool = loadEmbeddingKeyPool()
  const total = pool.length
  const exhausted = pool.filter(e => exhaustedEmbeddingKeyNames.has(e.name)).length
  return { total, exhausted, remaining: total - exhausted }
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅฏ†้’ฅ่Žทๅ–๏ผˆ่งฃๅฏ†ๅŽ่ฟ”ๅ›žๆ˜Žๆ–‡๏ผŒ็”จๅฎŒ็ซ‹ๅณๆธ…็†๏ผ‰
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/** ๅ†…้ƒจ๏ผš่งฃๅฏ†ๅฏ†้’ฅๆฑ ๆก็›ฎ */
function decryptKeyPoolEntry(entry: KeyPoolEntry): { name: string; model: string; apiKey: string; baseUrl: string } | null {
  try {
    const apiKey = decryptField(entry.apiKeyEncrypted)
    return { name: entry.name, model: entry.model, apiKey, baseUrl: entry.baseUrl }
  } catch (e: any) {
    // ่งฃๅฏ†ๅคฑ่ดฅๆ—ถ่ฎฐๅฝ•่ญฆๅ‘Š๏ผˆไธๆšด้œฒๆ•ๆ„Ÿไฟกๆฏ๏ผ‰
    console.warn(`โš ๏ธ ๅฏ†้’ฅ [${entry.name}] ่งฃๅฏ†ๅคฑ่ดฅ๏ผŒๅฏ่ƒฝๅทฒๆŸๅๆˆ–ไธปๅฏ†้’ฅไธๅŒน้…`)
    console.warn(`   ้”™่ฏฏ: ${e.message?.slice(0, 50) || 'unknown'}`)
    console.warn(`   ๅปบ่ฎฎ: ไฝฟ็”จ /keypool remove ${entry.name} ๅˆ ้™คๅŽ้‡ๆ–ฐๆทปๅŠ `)
    return null
  }
}

/** ่Žทๅ–่Šๅคฉ API Key - P3 #35: ๆ–ฐๆ—งๅฏ†้’ฅไฝ“็ณปๅนถๅญ˜ๆ็คบ */
export function getChatApiKey(): { name: string; model: string; apiKey: string; baseUrl: string } | null {
  // 1. ็”จๆˆท่‡ชๅฎšไน‰ๅฏ†้’ฅไผ˜ๅ…ˆ๏ผˆๅ•ๅฏ†้’ฅๆจกๅผ๏ผ‰
  const s = loadSecrets()
  if (s.apiKey) {
    // P3 #35: ๅฆ‚ๆžœๅŒๆ—ถๆœ‰ๅฏ†้’ฅๆฑ ๏ผŒๆ็คบ่ฟ็งป
    if (s.chatKeyPool && s.chatKeyPool.length > 0 && !migrationWarned) {
      migrationWarned = true
      console.log('  ๐Ÿ’ก ๆ็คบ๏ผšๅฝ“ๅ‰ไฝฟ็”จๅ•ๅฏ†้’ฅๆจกๅผใ€‚/keypool ๅฏๆทปๅŠ ๆ›ดๅคšๅฏ†้’ฅๅฎž็Žฐ่ฝฎๆข')
    }
    return {
      name: 'user-custom',
      model: s.model || 'MiniMax-M2.7',
      apiKey: s.apiKey,
      baseUrl: s.baseUrl || 'https://api.minimaxi.com/v1'
    }
  }
  
  // 2. ๅฏ†้’ฅๆฑ ๏ผˆๅคšๅฏ†้’ฅๆจกๅผ๏ผ‰
  const pool = loadChatKeyPool()
  for (const entry of pool) {
    if (!exhaustedChatKeyNames.has(entry.name)) {
      const decrypted = decryptKeyPoolEntry(entry)
      if (decrypted) {
        return decrypted
      }
    }
  }
  
  return null
}

/** ๅˆ‡ๆข่Šๅคฉๅฏ†้’ฅ๏ผˆ429 ๆ—ถ่ฐƒ็”จ๏ผ‰ */
export function rotateChatApiKey(): { name: string; model: string; apiKey: string; baseUrl: string } | null {
  const current = getChatApiKey()
  if (!current) return null
  
  // ๆ ‡่ฎฐๅฝ“ๅ‰ไธบ่€—ๅฐฝ
  markChatKeyExhausted(current.name)
  console.log(`  ๐Ÿ”„ ๅฏ†้’ฅ [${current.name}] ๅทฒๆ ‡่ฎฐไธบ่€—ๅฐฝ`)
  
  // ่ฟ”ๅ›žไธ‹ไธ€ไธชๅฏ็”จๅฏ†้’ฅ
  return getChatApiKey()
}

/** ่Žทๅ– Embedding API Key */
export function getEmbeddingApiKey(): { name: string; apiKey: string } | null {
  const pool = loadEmbeddingKeyPool()
  for (const entry of pool) {
    if (!exhaustedEmbeddingKeyNames.has(entry.name)) {
      const decrypted = decryptKeyPoolEntry(entry)
      if (decrypted) {
        return { name: decrypted.name, apiKey: decrypted.apiKey }
      }
    }
  }
  return null
}

/** ๅˆ‡ๆข Embedding ๅฏ†้’ฅ */
export function rotateEmbeddingApiKey(): { name: string; apiKey: string } | null {
  const current = getEmbeddingApiKey()
  if (!current) return null
  
  markEmbeddingKeyExhausted(current.name)
  return getEmbeddingApiKey()
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅบ”็”จ้…็ฝฎ็ฎก็†๏ผˆ้žๅฏ†้’ฅ๏ผ‰
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

export interface AppSettings {
  permissions?: { allow?: string[]; defaultMode?: string }
  modelType?: string
  model?: string
  language?: string
  effortLevel?: string
  skipDangerousModePermissionPrompt?: boolean
  pluginWorkerForce?: string
  systemPrompt?: string  // ๅ›บๅฎšๆ็คบ่ฏ๏ผŒๅŠ ๅœจ็”จๆˆท่พ“ๅ…ฅๅ‰
  [key: string]: any
}

export function saveAppConfig(config: AppSettings): void {
  const s = loadSecrets()
  saveSecrets({ ...s, ...config })
}

export function loadAppConfig(): AppSettings | null {
  const s = loadSecrets()
  const result: AppSettings = {}
  if (s.permissions) result.permissions = s.permissions
  if (s.modelType) result.modelType = s.modelType
  if (s.model) result.model = s.model
  if (s.language) result.language = s.language
  if (s.effortLevel) result.effortLevel = s.effortLevel
  if (s.skipDangerousModePermissionPrompt) result.skipDangerousModePermissionPrompt = s.skipDangerousModePermissionPrompt
  if (s.pluginWorkerForce) result.pluginWorkerForce = s.pluginWorkerForce
  if (s.systemPrompt) result.systemPrompt = s.systemPrompt
  return Object.keys(result).length > 0 ? result : null
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ็”จๆˆท็ผ“ๅญ˜็ฎก็†
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

export function saveUserCache(token: string, userInfo: { username: string; workspaceDir: string; quota?: number }): void {
  updateSecrets({ userToken: token, userInfo })
}

export function loadUserCache(): { token: string; username: string; workspaceDir: string; quota?: number } | null {
  const s = loadSecrets()
  if (!s.userToken || !s.userInfo) return null
  return { token: s.userToken, ...s.userInfo }
}

export function clearUserCache(): void {
  const s = loadSecrets()
  delete s.userToken
  delete s.userInfo
  saveSecrets(s)
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅ‘้‡่ฎฐๅฟ†้…็ฝฎ
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

export const DEFAULT_MEMORY_SEARCH = {
  provider: 'openai',
  model: 'doubao-embedding-vision',
  baseUrl: 'https://ark.cn-beijing.volces.com/api/coding/v3',
}

export function saveMemorySearchConfig(config: { baseUrl?: string; model?: string }): void {
  const s = loadSecrets()
  s.memorySearch = {
    provider: 'openai',
    model: config.model || DEFAULT_MEMORY_SEARCH.model,
    baseUrl: config.baseUrl || DEFAULT_MEMORY_SEARCH.baseUrl,
  }
  saveSecrets(s)
}

export function loadMemorySearchConfig(): { provider: string; model: string; baseUrl: string } {
  const s = loadSecrets()
  if (s.memorySearch) {
    return s.memorySearch
  }
  return DEFAULT_MEMORY_SEARCH
}

export function hasMemorySearchConfig(): boolean {
  const pool = loadEmbeddingKeyPool()
  return pool.length > 0
}

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅ…ผๅฎนๆ—ง API
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

/** @deprecated ไฝฟ็”จ getChatApiKey */
export const getCurrentMinimaxKey = getChatApiKey

/** @deprecated ไฝฟ็”จ rotateChatApiKey */
export const rotateMinimaxKey = rotateChatApiKey

/** @deprecated ไฝฟ็”จ getChatKeyPoolStatus */
export const getMinimaxKeyPoolStatus = getChatKeyPoolStatus

/** @deprecated ไฝฟ็”จ resetChatKeyPool */
export const resetMinimaxKeyPool = resetChatKeyPool

// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
// ๅฎšๆ—ถ้‡็ฝฎ๏ผˆๆฏๆ—ฅๅ‡Œๆ™จ้‡็ฝฎ่€—ๅฐฝ็Šถๆ€๏ผ‰
// โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

let resetTimer: ReturnType<typeof setInterval> | null = null

/** ๅฏๅŠจๆฏๆ—ฅ่‡ชๅŠจ้‡็ฝฎ */
export function startDailyKeyPoolReset(): void {
  if (resetTimer) return
  
  // ่ฎก็ฎ—ๅˆฐไธ‹ไธ€ไธชๅ‡Œๆ™จ็š„ๆ—ถ้—ด
  const now = new Date()
  const tomorrow = new Date(now)
  tomorrow.setDate(tomorrow.getDate() + 1)
  tomorrow.setHours(0, 0, 0, 0)
  const msUntilMidnight = tomorrow.getTime() - now.getTime()
  
  // ๅ…ˆ่ฎพ็ฝฎไธ€ไธชๅฎšๆ—ถๅ™จๅˆฐๅ‡Œๆ™จ
  setTimeout(() => {
    resetAllKeyPools()
    // ็„ถๅŽๆฏ24ๅฐๆ—ถ้‡็ฝฎไธ€ๆฌก
    resetTimer = setInterval(resetAllKeyPools, 24 * 60 * 60 * 1000)
  }, msUntilMidnight)
  
  console.log(`  โฐ ๅฏ†้’ฅๆฑ ๅฐ†ๅœจ ${Math.round(msUntilMidnight / 60000)} ๅˆ†้’ŸๅŽ่‡ชๅŠจ้‡็ฝฎ`)
}

/** ๅœๆญข่‡ชๅŠจ้‡็ฝฎ */
export function stopDailyKeyPoolReset(): void {
  if (resetTimer) {
    clearInterval(resetTimer)
    resetTimer = null
  }
}

/** ้‡็ฝฎๆ‰€ๆœ‰ๅฏ†้’ฅๆฑ  */
export function resetAllKeyPools(): void {
  resetChatKeyPool()
  resetEmbeddingKeyPool()
  console.log('  โœ… ๆ‰€ๆœ‰ๅฏ†้’ฅๆฑ ็Šถๆ€ๅทฒ้‡็ฝฎ๏ผˆๆฏๆ—ฅ่‡ชๅŠจ๏ผ‰')
}