/**
 * Shared user-local cache/config helpers.
 *
 * This preserves the existing DimCode storage layout while allowing the
 * ACP server and CLI packages to depend on a single implementation.
 */

import {
  resolveDimcodeConfigDir,
  resolveDimcodeUserDir,
} from '#cli/core/storage-paths'

type RuntimeGlobals = typeof globalThis & {
  process?: NodeJS.Process
}

export type GoatChainUserCacheV1 = Readonly<{
  version: 1
  updatedAt: number
  settings?: Readonly<{
    cwd?: string
    focusFiles?: readonly string[]
    model?: string
    authMethod?: 'oauth' | 'api'
    contextWindow?: number
    temperature?: number
    interleavedThinking?: boolean
    baseUrl?: string
    apiKey?: string
    toolApprovals?: 'auto' | 'normal' | 'strict'
    locale?: string
    themeMode?: string
    onboardingSeenVersion?: string
    statusCompact?: boolean
    tipIntensity?: 'off' | 'low' | 'standard'
    shortcuts?: Readonly<{
      commands?: string
      timeline?: string
      newSession?: string
    }>
    greetingLastSeenDate?: string
    providerFunnelCounts?: Readonly<Record<string, number>>
    workspaceSelectionByCwd?: Readonly<
      Record<string, Readonly<{ providerId: string, modelId?: string }>>
    >
  }>
  lastSessionId?: string
}>

type PersistedProviderModelLimitV1 = Readonly<{
  context?: number
  output?: number
}>

type PersistedProviderModelReasoningV1 = Readonly<{
  supported: boolean
  default?: boolean
}>

type PersistedProviderModelV1 = Readonly<{
  id: string
  name?: string
  limit?: PersistedProviderModelLimitV1
  vision?: boolean
  reasoning?: PersistedProviderModelReasoningV1
}>

export type GoatChainUserConfigV1 = Readonly<{
  version: 1
  updatedAt: number
  settings?: Readonly<{
    providerId?: string
    activeModelByProvider?: Readonly<Record<string, string>>
    providerConnections?: Readonly<
      Record<
        string,
        Readonly<{
          apiKey?: string
          baseUrl?: string
          models?: ReadonlyArray<PersistedProviderModelV1>
        }>
      >
    >
    customProviders?: ReadonlyArray<
      Readonly<{
        id: string
        name: string
        adapter: 'openai' | 'openai-responses' | 'gemini' | 'anthropic'
        baseUrl?: string
        apiKey?: string
        models?: ReadonlyArray<PersistedProviderModelV1>
      }>
    >
    customProvider?: Readonly<{
      baseUrl?: string
      apiKey?: string
      models?: ReadonlyArray<PersistedProviderModelV1>
    }>
  }>
}>

export function normalizeStr(v: unknown): string | undefined {
  const s = String(v ?? '').trim()
  return s || undefined
}

export function normalizeNum(v: unknown): number | undefined {
  if (typeof v === 'number' && Number.isFinite(v))
    return v
  const s = String(v ?? '').trim()
  if (!s)
    return undefined
  const n = Number(s)
  return Number.isFinite(n) ? n : undefined
}

export function toPlainJson<T>(v: T): T {
  return JSON.parse(JSON.stringify(v)) as T
}

function runtimeGlobals(): RuntimeGlobals {
  return globalThis as RuntimeGlobals
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return value != null && typeof value === 'object' && !Array.isArray(value)
}

function isDimcodeUserCacheV1(value: unknown): value is DimcodeUserCacheV1 {
  return isRecord(value) && value.version === 1
}

function isDimcodeUserConfigV1(value: unknown): value is DimcodeUserConfigV1 {
  return isRecord(value) && value.version === 1
}

function runtimeEnv(): Record<string, string | undefined> {
  return runtimeGlobals().process?.env ?? {}
}

function runtimePidLabel(): string {
  const pid = runtimeGlobals().process?.pid
  return typeof pid === 'number' ? String(pid) : 'p'
}

export type DimcodeUserCacheV1 = GoatChainUserCacheV1
export type DimcodeUserConfigV1 = GoatChainUserConfigV1

export async function getDimcodeUserDir(
  env?: Record<string, string | undefined>,
): Promise<string> {
  const source = env ?? runtimeEnv()
  return resolveDimcodeUserDir(source)
}

export async function getDimcodeConfigDir(
  env?: Record<string, string | undefined>,
): Promise<string> {
  const source = env ?? runtimeEnv()
  return resolveDimcodeConfigDir(source)
}

export async function loadDimcodeUserCache(
  env?: Record<string, string | undefined>,
): Promise<{ dir: string, filePath: string, cache: DimcodeUserCacheV1 }> {
  const dir = await getDimcodeUserDir(env)
  const [{ readFile }, path] = await Promise.all([
    import('node:fs/promises'),
    import('node:path'),
  ])
  const filePath = path.join(dir, 'cache.json')
  try {
    const raw = await readFile(filePath, 'utf8')
    const parsed = JSON.parse(raw)
    if (isDimcodeUserCacheV1(parsed))
      return { dir, filePath, cache: parsed }
  }
  catch {
    // ignore
  }
  return { dir, filePath, cache: { version: 1, updatedAt: 0 } as const }
}

export async function loadDimcodeUserConfig(
  env?: Record<string, string | undefined>,
): Promise<{ dir: string, filePath: string, config: DimcodeUserConfigV1 }> {
  const dir = await getDimcodeConfigDir(env)
  const [{ readFile }, path] = await Promise.all([
    import('node:fs/promises'),
    import('node:path'),
  ])
  const filePath = path.join(dir, 'config.json')
  try {
    const raw = await readFile(filePath, 'utf8')
    const parsed = JSON.parse(raw)
    if (isDimcodeUserConfigV1(parsed))
      return { dir, filePath, config: parsed }
  }
  catch {
    // ignore
  }
  return { dir, filePath, config: { version: 1, updatedAt: 0 } as const }
}

export async function saveDimcodeUserCache(
  filePath: string,
  cache: DimcodeUserCacheV1,
): Promise<void> {
  const [{ mkdir, writeFile, rename, chmod }, path] = await Promise.all([
    import('node:fs/promises'),
    import('node:path'),
  ])
  const dir = path.dirname(filePath)
  await mkdir(dir, { recursive: true })
  const pid = runtimePidLabel()
  const nonce = `${Date.now()}-${Math.random().toString(16).slice(2)}`
  const tmp = `${filePath}.${pid}.${nonce}.tmp`
  const data = `${JSON.stringify(cache, null, 2)}\n`
  await writeFile(tmp, data, { encoding: 'utf8', mode: 0o600 })
  await rename(tmp, filePath)
  try {
    await chmod(filePath, 0o600)
  }
  catch {
    // ignore
  }
}

export async function saveDimcodeUserConfig(
  filePath: string,
  config: DimcodeUserConfigV1,
): Promise<void> {
  const [{ mkdir, writeFile, rename, chmod }, path] = await Promise.all([
    import('node:fs/promises'),
    import('node:path'),
  ])
  const dir = path.dirname(filePath)
  await mkdir(dir, { recursive: true })
  const pid = runtimePidLabel()
  const nonce = `${Date.now()}-${Math.random().toString(16).slice(2)}`
  const tmp = `${filePath}.${pid}.${nonce}.tmp`
  const data = `${JSON.stringify(config, null, 2)}\n`
  await writeFile(tmp, data, { encoding: 'utf8', mode: 0o600 })
  await rename(tmp, filePath)
  try {
    await chmod(filePath, 0o600)
  }
  catch {
    // ignore
  }
}

// Back-compat aliases for legacy CLI imports.
export const getGoatChainUserDir = getDimcodeUserDir
export const getGoatChainConfigDir = getDimcodeConfigDir
export const loadGoatChainUserCache = loadDimcodeUserCache
export const loadGoatChainUserConfig = loadDimcodeUserConfig
export const saveGoatChainUserCache = saveDimcodeUserCache
export const saveGoatChainUserConfig = saveDimcodeUserConfig
