import type { McpServerConfig } from '#cli/compat/mcp'
import type {
  RuntimeConfig,
  SessionSnapshot,
  ToolApprovalMode,
} from '#cli/compat/runtime'
import path from 'node:path'
import {
  normalizeSessionUiMetadata,
  SESSION_QUEUE_LIMIT,
} from '#cli/core/session-meta'
import {
  snapshotToUiMessages,
  uiMessagesToRuntimeMessages,
} from '#cli/core/session-ui-messages'
import {
  readJsonFile,
  RUNTIME_SNAPSHOT_DIR,
  runtimeSnapshotPath,
  writeJsonFile,
} from '#cli/compat/runtime-host'

export const RUNTIME_SESSION_METADATA_KEY = 'dimAgentRuntime'
export const LEGACY_MIGRATION_METADATA_KEY = 'dimcodeLegacyMigration'
const RUNTIME_IMPORT_MARKER_DIR = 'migrations'
const RUNTIME_IMPORT_VERSION = 2
const RUNTIME_IMPORT_MARKER_FILE = `runtime-import-v${RUNTIME_IMPORT_VERSION}.json`
const VALID_RUNTIME_ADAPTERS = new Set<NonNullable<RuntimeConfig['adapter']>>([
  'openai',
  'openai-responses',
  'gemini',
  'anthropic',
])

type RuntimeSessionModeId = 'agent' | 'plan'
export type LegacySnapshotKind = 'session-json' | 'bridge-snapshot'

type LegacySessionCandidate = Readonly<{
  sessionId: string
  rootDir: string
  sourceKind: LegacySnapshotKind
  sourcePath: string
  snapshot: Record<string, unknown>
  createdAt: number
  updatedAt: number
}>

export type LegacySessionPayload = Readonly<{
  sessionId: string
  cwd: string
  createdAt: number
  updatedAt: number
  title?: string
  modeId: RuntimeSessionModeId
  runtimeConfig: RuntimeConfig
  toolApprovalMode: ToolApprovalMode
  mcpServers: McpServerConfig[]
  lastStopReason?: string | null
  metadata: Record<string, unknown>
  messages: SessionSnapshot['messages']
}>

type RuntimeImportMarker = Readonly<{
  version: number
  completedAt: string
  sourceRoots: string[]
  importedSessions: Array<{
    sessionId: string
    sourceKind: LegacySnapshotKind
    sourcePath: string
    targetPath: string
  }>
  skippedSessions: Array<{
    sessionId: string
    sourceKind: LegacySnapshotKind
    sourcePath: string
    reason: string
  }>
  failedSources: Array<{
    sessionId?: string
    sourceKind: LegacySnapshotKind
    sourcePath: string
    error: string
  }>
}>

function normalizeText(value: unknown): string {
  return String(value ?? '').trim()
}

function normalizeModeId(value: unknown): RuntimeSessionModeId {
  return value === 'plan' ? 'plan' : 'agent'
}

function normalizeToolApprovalMode(value: unknown): ToolApprovalMode {
  if (value === 'auto' || value === 'normal' || value === 'strict')
    return value
  if (value === 'all')
    return 'auto'
  return null
}

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

function cloneJson<T>(value: T): T {
  return JSON.parse(JSON.stringify(value ?? null)) as T
}

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

function normalizeStringMap(
  value: unknown,
): Record<string, string> | undefined {
  if (!value || typeof value !== 'object' || Array.isArray(value))
    return undefined
  const next = Object.entries(value).reduce<Record<string, string>>(
    (result, [key, entry]) => {
      const normalized = normalizeText(entry)
      if (normalized)
        result[key] = normalized
      return result
    },
    {},
  )
  return Object.keys(next).length > 0 ? next : undefined
}

function normalizeRuntimeAdapter(
  value: unknown,
  fallback: RuntimeConfig['adapter'],
): RuntimeConfig['adapter'] {
  const normalized = normalizeText(value)
  if (
    normalized
    && VALID_RUNTIME_ADAPTERS.has(
      normalized as NonNullable<RuntimeConfig['adapter']>,
    )
  ) {
    return normalized as NonNullable<RuntimeConfig['adapter']>
  }
  return fallback
}

function normalizeMcpServerConfig(raw: unknown): McpServerConfig | null {
  const record = asRecord(raw)
  const id = normalizeText(record.id)
  const name = normalizeText(record.name)
  const transport = normalizeText(record.transport)
  if (!id || !name)
    return null

  const enabled
    = typeof record.enabled === 'boolean' ? record.enabled : undefined
  const requestTimeoutMs = normalizePositiveNumber(record.requestTimeoutMs)

  if (transport === 'http' || transport === 'sse') {
    const url = normalizeText(record.url)
    if (!url)
      return null
    const authRecord = asRecord(record.auth)
    const authToken = normalizeText(authRecord.token)
    return {
      id,
      name,
      transport,
      url,
      ...(authRecord.type === 'bearer' && authToken
        ? {
            auth: {
              type: 'bearer' as const,
              token: authToken,
            },
          }
        : {}),
      ...(normalizeStringMap(record.headers)
        ? { headers: normalizeStringMap(record.headers) }
        : {}),
      ...(requestTimeoutMs ? { requestTimeoutMs } : {}),
      ...(typeof enabled === 'boolean' ? { enabled } : {}),
    }
  }

  if (transport === 'stdio') {
    const command = normalizeText(record.command)
    if (!command)
      return null
    const args = Array.isArray(record.args)
      ? record.args.map(value => normalizeText(value)).filter(Boolean)
      : []
    const cwd = normalizeText(record.cwd) || undefined
    const autoRestart
      = typeof record.autoRestart === 'boolean' ? record.autoRestart : undefined
    const restartDelayMs = normalizePositiveNumber(record.restartDelayMs)
    const maxRestarts = normalizePositiveNumber(record.maxRestarts)
    const restartWindowMs = normalizePositiveNumber(record.restartWindowMs)
    const env = normalizeStringMap(record.env)

    return {
      id,
      name,
      transport: 'stdio',
      command,
      args,
      ...(env ? { env } : {}),
      ...(cwd ? { cwd } : {}),
      ...(typeof autoRestart === 'boolean' ? { autoRestart } : {}),
      ...(requestTimeoutMs ? { requestTimeoutMs } : {}),
      ...(restartDelayMs ? { restartDelayMs } : {}),
      ...(maxRestarts ? { maxRestarts } : {}),
      ...(restartWindowMs ? { restartWindowMs } : {}),
      ...(typeof enabled === 'boolean' ? { enabled } : {}),
    }
  }

  return null
}

function normalizeMcpServers(raw: unknown): McpServerConfig[] {
  if (!Array.isArray(raw))
    return []
  return raw
    .map(normalizeMcpServerConfig)
    .filter((server): server is McpServerConfig => server !== null)
}

function isSafeSessionId(value: string): boolean {
  return Boolean(value)
    && value !== '.'
    && value !== '..'
    && !value.includes('/')
    && !value.includes('\\')
    && !value.includes('\0')
}

function resolveLegacySessionId(value: unknown, fallback: string): string {
  const preferred = normalizeText(value)
  if (isSafeSessionId(preferred))
    return preferred
  const normalizedFallback = normalizeText(fallback)
  return isSafeSessionId(normalizedFallback) ? normalizedFallback : ''
}

function readTimestamp(snapshot: Record<string, unknown>): {
  createdAt: number
  updatedAt: number
} {
  const state = asRecord(snapshot.state)
  const createdAt = Number(snapshot.createdAt ?? Date.now()) || Date.now()
  const updatedAt = Number(
    snapshot.updatedAt
    ?? state.updatedAt
    ?? state.lastActiveAt
    ?? createdAt,
  ) || createdAt
  return { createdAt, updatedAt }
}

function hasLegacyRuntimeMetadata(snapshot: Record<string, unknown>): boolean {
  const metadata = asRecord(snapshot.metadata)
  const runtimeMeta = asRecord(metadata[RUNTIME_SESSION_METADATA_KEY])
  return Number(runtimeMeta.version) === 1
}

function isSupportedRuntimeSnapshot(
  snapshot: Record<string, unknown> | null,
): snapshot is SessionSnapshot & Record<string, unknown> {
  if (!snapshot)
    return false
  const schemaVersion = Number(snapshot.schemaVersion ?? 0)
  if (![1, 2, 3].includes(schemaVersion))
    return false
  const sessionId = normalizeText(snapshot.sessionId)
  if (!sessionId)
    return false
  return Array.isArray(snapshot.messages)
}

function isLegacyBridgeSnapshot(
  snapshot: Record<string, unknown> | null,
): boolean {
  if (!snapshot || isSupportedRuntimeSnapshot(snapshot))
    return false
  if (Number(snapshot.schemaVersion ?? 0) > 0)
    return false
  const sessionId = normalizeText(snapshot.sessionId ?? snapshot.id)
  if (!sessionId)
    return false
  const context = asRecord(snapshot.context)
  const rawMessages = Array.isArray(snapshot.messages)
    ? snapshot.messages
    : Array.isArray(context.messages)
      ? context.messages
      : null
  if (!Array.isArray(rawMessages))
    return false
  return hasLegacyRuntimeMetadata(snapshot)
}

function parseRuntimeConfig(
  raw: unknown,
  fallback: RuntimeConfig,
): RuntimeConfig {
  const record = asRecord(raw)
  const modelId = normalizeText(record.modelId) || fallback.modelId
  const adapter = normalizeRuntimeAdapter(record.adapter, fallback.adapter)
  const provider = normalizeText(record.provider) || fallback.provider || adapter
  const baseUrl = normalizeText(record.baseUrl) || fallback.baseUrl
  const apiKey = normalizeText(record.apiKey) || fallback.apiKey
  const maxInputTokens = normalizePositiveNumber(
    record.maxInputTokens ?? fallback.maxInputTokens,
  )
  const maxOutputTokens = normalizePositiveNumber(
    record.maxOutputTokens ?? fallback.maxOutputTokens,
  )
  const env
    = normalizeStringMap(record.env)
      ?? (fallback.env ? cloneJson(fallback.env) : undefined)

  return {
    modelId,
    ...(adapter ? { adapter } : {}),
    ...(provider ? { provider } : {}),
    ...(baseUrl ? { baseUrl } : {}),
    ...(apiKey ? { apiKey } : {}),
    ...(record.nextApiOAuth === true || fallback.nextApiOAuth === true
      ? { nextApiOAuth: true }
      : {}),
    ...(maxInputTokens ? { maxInputTokens } : {}),
    ...(maxOutputTokens ? { maxOutputTokens } : {}),
    ...(env ? { env } : {}),
  }
}

function readLegacyRuntimeEnvelope(
  snapshot: Record<string, unknown>,
  fallback: RuntimeConfig,
): Readonly<{
  runtimeConfig: RuntimeConfig
  modeId: RuntimeSessionModeId
  toolApprovalMode: ToolApprovalMode
  mcpServers: McpServerConfig[]
  lastStopReason?: string | null
}> {
  const metadata = asRecord(snapshot.metadata)
  const runtimeMeta = asRecord(metadata[RUNTIME_SESSION_METADATA_KEY])
  const state = asRecord(snapshot.state)
  const status = normalizeText(state.status)
  const lastStopReason = normalizeText(
    runtimeMeta.lastStopReason ?? metadata.lastStopReason,
  )

  return {
    runtimeConfig: parseRuntimeConfig(
      runtimeMeta.runtimeConfig ?? metadata.runtimeConfig,
      fallback,
    ),
    modeId: normalizeModeId(runtimeMeta.modeId ?? metadata.modeId),
    toolApprovalMode: normalizeToolApprovalMode(
      runtimeMeta.toolApprovalMode ?? metadata.toolApprovalMode,
    ),
    mcpServers: normalizeMcpServers(
      Array.isArray(runtimeMeta.mcpServers)
        ? runtimeMeta.mcpServers
        : metadata.mcpServers,
    ),
    lastStopReason:
      lastStopReason
      || (status === 'paused'
        ? 'cancelled'
        : status === 'error'
          ? 'error'
          : null),
  }
}

function pickFirstDefined<T>(
  sources: readonly Record<string, unknown>[],
  key: string,
): T | undefined {
  for (const source of sources) {
    if (Object.prototype.hasOwnProperty.call(source, key))
      return source[key] as T
  }
  return undefined
}

function extractUiMetadata(
  snapshot: Record<string, unknown>,
): Record<string, unknown> | undefined {
  const metadata = asRecord(snapshot.metadata)
  const runtimeMetadata = asRecord(
    asRecord(metadata[RUNTIME_SESSION_METADATA_KEY]).metadata,
  )
  const uiSources = [
    asRecord(metadata.ui),
    asRecord(metadata.goatchainUi),
    asRecord(metadata.goatchain),
    asRecord(runtimeMetadata.ui),
  ]
  const valueSources = [...uiSources, runtimeMetadata, metadata]
  const normalized = normalizeSessionUiMetadata({
    queue: pickFirstDefined(valueSources, 'queue'),
    queueLength: pickFirstDefined(valueSources, 'queueLength'),
    status: pickFirstDefined(valueSources, 'status'),
    lastUpdatedAt: pickFirstDefined(valueSources, 'lastUpdatedAt'),
    lastErrorCode: pickFirstDefined(valueSources, 'lastErrorCode'),
    pinnedMessageIds: pickFirstDefined(valueSources, 'pinnedMessageIds'),
    titleSuggestedAt: pickFirstDefined(valueSources, 'titleSuggestedAt'),
    reasoningDiagnosticsByTurn: pickFirstDefined(
      valueSources,
      'reasoningDiagnosticsByTurn',
    ),
  })

  const extras: Record<string, unknown> = {}
  for (const key of [
    'interruptedMessages',
    'compactMessages',
    'compressionMessages',
    'errorMessages',
  ]) {
    const value = pickFirstDefined<unknown[]>(valueSources, key)
    if (Array.isArray(value))
      extras[key] = cloneJson(value)
  }

  const hasCore
    = normalized.queue.length > 0
      || normalized.status !== 'idle'
      || normalized.lastUpdatedAt > 0
      || Boolean(normalized.lastErrorCode)
      || normalized.pinnedMessageIds.length > 0
      || Number(normalized.titleSuggestedAt ?? 0) > 0
      || Object.keys(normalized.reasoningDiagnosticsByTurn ?? {}).length > 0
  if (!hasCore && Object.keys(extras).length === 0)
    return undefined

  return {
    ...normalized,
    queue: normalized.queue.slice(0, SESSION_QUEUE_LIMIT),
    ...extras,
  }
}

function extractLegacyMetadata(
  snapshot: Record<string, unknown>,
  sourceKind: LegacySnapshotKind,
  sessionId: string,
  createdAt: number,
  updatedAt: number,
): Record<string, unknown> {
  const metadata = asRecord(snapshot.metadata)
  const runtimeMetadata = asRecord(
    asRecord(metadata[RUNTIME_SESSION_METADATA_KEY]).metadata,
  )
  const next: Record<string, unknown> = {
    ...cloneJson(runtimeMetadata),
    ...cloneJson(metadata),
  }

  for (const key of [
    RUNTIME_SESSION_METADATA_KEY,
    'title',
    'cwd',
    'ui',
    'goatchainUi',
    'goatchain',
    'interruptedMessages',
    'compactMessages',
    'compressionMessages',
    'errorMessages',
    'runtimeConfig',
    'modeId',
    'toolApprovalMode',
    'mcpServers',
    'lastStopReason',
  ]) {
    delete next[key]
  }

  const ui = extractUiMetadata(snapshot)
  if (ui)
    next.ui = ui

  next[LEGACY_MIGRATION_METADATA_KEY] = {
    ...(sourceKind === 'session-json' ? { migratedFrom: 'session.json' } : {}),
    source: sourceKind,
    migratedAt: new Date().toISOString(),
  }
  next.dimcodeLegacyHistory = {
    sessionId,
    createdAt,
    updatedAt,
    messages: cloneJson(
      Array.isArray(snapshot.messages)
        ? snapshot.messages
        : Array.isArray(asRecord(snapshot.context).messages)
          ? asRecord(snapshot.context).messages
          : [],
    ),
  }

  return next
}

function convertLegacyMessages(
  snapshot: Record<string, unknown>,
): SessionSnapshot['messages'] {
  const context = asRecord(snapshot.context)
  const rawMessages = Array.isArray(context.messages)
    ? context.messages
    : Array.isArray(snapshot.messages)
      ? snapshot.messages
      : []
  const uiMessages = snapshotToUiMessages({
    context: { messages: rawMessages },
  })
  return uiMessagesToRuntimeMessages(uiMessages) as SessionSnapshot['messages']
}

function buildLegacySessionPayload(
  sessionId: string,
  snapshot: Record<string, unknown>,
  sourceKind: LegacySnapshotKind,
  fallbackRuntimeConfig: RuntimeConfig,
  fallbackCwd: string,
): LegacySessionPayload | null {
  if (!sessionId)
    return null

  const { createdAt, updatedAt } = readTimestamp(snapshot)
  const metadata = asRecord(snapshot.metadata)
  const state = asRecord(snapshot.state)
  const {
    runtimeConfig,
    modeId,
    toolApprovalMode,
    mcpServers,
    lastStopReason,
  } = readLegacyRuntimeEnvelope(snapshot, fallbackRuntimeConfig)
  const title = normalizeText(
    snapshot.title ?? state.title ?? metadata.title,
  ) || undefined
  const cwd = normalizeText(metadata.cwd ?? snapshot.cwd) || fallbackCwd
  const messages = convertLegacyMessages(snapshot)

  return {
    sessionId,
    cwd,
    createdAt,
    updatedAt,
    ...(title ? { title } : {}),
    modeId,
    runtimeConfig,
    toolApprovalMode,
    mcpServers,
    ...(typeof lastStopReason === 'string' || lastStopReason === null
      ? { lastStopReason }
      : {}),
    metadata: extractLegacyMetadata(
      snapshot,
      sourceKind,
      sessionId,
      createdAt,
      updatedAt,
    ),
    messages,
  }
}

export function patchImportedSnapshot(
  snapshot: SessionSnapshot,
  payload: LegacySessionPayload,
): SessionSnapshot {
  const metadata = asRecord(snapshot.metadata)
  const runtimeMeta = asRecord(metadata[RUNTIME_SESSION_METADATA_KEY])

  return {
    ...snapshot,
    cwd: payload.cwd,
    createdAt: payload.createdAt,
    updatedAt: payload.updatedAt,
    metadata: {
      ...metadata,
      ...cloneJson(payload.metadata),
      title: payload.title ?? metadata.title ?? null,
      [RUNTIME_SESSION_METADATA_KEY]: {
        ...runtimeMeta,
        title: payload.title ?? runtimeMeta.title ?? null,
        modeId: payload.modeId,
        runtimeConfig: payload.runtimeConfig,
        toolApprovalMode: payload.toolApprovalMode,
        mcpServers: cloneJson(payload.mcpServers),
        metadata: cloneJson(payload.metadata),
        lastStopReason:
          payload.lastStopReason ?? runtimeMeta.lastStopReason ?? null,
      },
    },
  }
}

async function collectLegacySessionCandidates(opts: Readonly<{
  activeStateDir: string
  stateRoots: readonly string[]
  hasRuntimeSnapshot: (sessionId: string) => Promise<boolean>
  includeActiveStateLegacySessionJson?: boolean
}>): Promise<LegacySessionCandidate[]> {
  const includeActiveStateLegacySessionJson
    = opts.includeActiveStateLegacySessionJson !== false
  const candidates = new Map<string, LegacySessionCandidate>()

  const consider = (candidate: LegacySessionCandidate | null) => {
    if (!candidate)
      return
    const existing = candidates.get(candidate.sessionId)
    if (!existing) {
      candidates.set(candidate.sessionId, candidate)
      return
    }
    const preferCandidate
      = candidate.updatedAt > existing.updatedAt
        || (
          candidate.updatedAt === existing.updatedAt
          && candidate.sourceKind === 'bridge-snapshot'
          && existing.sourceKind !== 'bridge-snapshot'
        )
    if (preferCandidate)
      candidates.set(candidate.sessionId, candidate)
  }

  for (const root of opts.stateRoots) {
    const rootDir = path.resolve(root)
    const stateEntries = await import('node:fs/promises')
      .then(({ readdir }) => readdir(rootDir, {
        withFileTypes: true,
        encoding: 'utf8',
      }))
      .catch(() => [])

    for (const entry of stateEntries) {
      if (!entry.isDirectory())
        continue
      if (
        entry.name === RUNTIME_SNAPSHOT_DIR
        || entry.name === RUNTIME_IMPORT_MARKER_DIR
      ) {
        continue
      }
      if (
        !includeActiveStateLegacySessionJson
        && rootDir === path.resolve(opts.activeStateDir)
      ) {
        continue
      }
      const sourcePath = path.join(rootDir, entry.name, 'session.json')
      const snapshot = await readJsonFile(sourcePath)
      if (!snapshot)
        continue
      const sessionId = resolveLegacySessionId(
        snapshot.sessionId ?? snapshot.id,
        entry.name,
      )
      if (!sessionId || await opts.hasRuntimeSnapshot(sessionId))
        continue
      const { createdAt, updatedAt } = readTimestamp(snapshot)
      consider({
        sessionId,
        rootDir,
        sourceKind: 'session-json',
        sourcePath,
        snapshot,
        createdAt,
        updatedAt,
      })
    }

    const runtimeEntries = await import('node:fs/promises')
      .then(({ readdir }) => readdir(path.join(rootDir, RUNTIME_SNAPSHOT_DIR), {
        withFileTypes: true,
        encoding: 'utf8',
      }))
      .catch(() => [])

    for (const entry of runtimeEntries) {
      if (!entry.isFile() || !entry.name.endsWith('.json'))
        continue
      const sourcePath = path.join(rootDir, RUNTIME_SNAPSHOT_DIR, entry.name)
      const snapshot = await readJsonFile(sourcePath)
      if (!snapshot || !isLegacyBridgeSnapshot(snapshot))
        continue
      const sessionId = resolveLegacySessionId(
        snapshot.sessionId ?? snapshot.id,
        entry.name.replace(/\.json$/u, ''),
      )
      if (!sessionId || await opts.hasRuntimeSnapshot(sessionId))
        continue
      const { createdAt, updatedAt } = readTimestamp(snapshot)
      consider({
        sessionId,
        rootDir,
        sourceKind: 'bridge-snapshot',
        sourcePath,
        snapshot,
        createdAt,
        updatedAt,
      })
    }
  }

  return [...candidates.values()].sort(
    (left, right) => left.updatedAt - right.updatedAt,
  )
}

function runtimeImportMarkerPath(stateDir: string): string {
  return path.join(
    path.resolve(stateDir),
    RUNTIME_IMPORT_MARKER_DIR,
    RUNTIME_IMPORT_MARKER_FILE,
  )
}

async function readRuntimeImportMarker(
  markerPath: string,
): Promise<RuntimeImportMarker | null> {
  const marker = await readJsonFile(markerPath).catch(() => null)
  if (!marker)
    return null
  const version = Number(marker.version ?? 0)
  const completedAt = normalizeText(marker.completedAt)
  if (version !== RUNTIME_IMPORT_VERSION || !completedAt)
    return null

  return {
    version,
    completedAt,
    sourceRoots: Array.isArray(marker.sourceRoots)
      ? marker.sourceRoots.map(root => normalizeText(root)).filter(Boolean)
      : [],
    importedSessions: Array.isArray(marker.importedSessions)
      ? cloneJson(marker.importedSessions as RuntimeImportMarker['importedSessions'])
      : [],
    skippedSessions: Array.isArray(marker.skippedSessions)
      ? cloneJson(marker.skippedSessions as RuntimeImportMarker['skippedSessions'])
      : [],
    failedSources: Array.isArray(marker.failedSources)
      ? cloneJson(marker.failedSources as RuntimeImportMarker['failedSources'])
      : [],
  }
}

export async function importLegacySessions(opts: Readonly<{
  activeStateDir: string
  stateRoots: readonly string[]
  fallbackRuntimeConfig: RuntimeConfig
  fallbackCwd: string
  hasRuntimeSnapshot: (sessionId: string) => Promise<boolean>
  materializeSession: (payload: LegacySessionPayload) => Promise<void>
  includeActiveStateLegacySessionJson?: boolean
}>): Promise<RuntimeImportMarker> {
  const markerPath = runtimeImportMarkerPath(opts.activeStateDir)
  const existingMarker = await readRuntimeImportMarker(markerPath)
  if (existingMarker)
    return existingMarker

  const importedSessions: RuntimeImportMarker['importedSessions'] = []
  const skippedSessions: RuntimeImportMarker['skippedSessions'] = []
  const failedSources: RuntimeImportMarker['failedSources'] = []

  const candidates = await collectLegacySessionCandidates({
    activeStateDir: opts.activeStateDir,
    stateRoots: opts.stateRoots,
    hasRuntimeSnapshot: opts.hasRuntimeSnapshot,
    includeActiveStateLegacySessionJson: opts.includeActiveStateLegacySessionJson,
  })

  for (const candidate of candidates) {
    if (await opts.hasRuntimeSnapshot(candidate.sessionId)) {
      skippedSessions.push({
        sessionId: candidate.sessionId,
        sourceKind: candidate.sourceKind,
        sourcePath: candidate.sourcePath,
        reason: 'existing-runtime-snapshot',
      })
      continue
    }

    const payload = buildLegacySessionPayload(
      candidate.sessionId,
      candidate.snapshot,
      candidate.sourceKind,
      opts.fallbackRuntimeConfig,
      opts.fallbackCwd,
    )
    if (!payload) {
      failedSources.push({
        sessionId: candidate.sessionId,
        sourceKind: candidate.sourceKind,
        sourcePath: candidate.sourcePath,
        error: 'Failed to build runtime payload',
      })
      continue
    }

    try {
      await opts.materializeSession(payload)
      importedSessions.push({
        sessionId: payload.sessionId,
        sourceKind: candidate.sourceKind,
        sourcePath: candidate.sourcePath,
        targetPath: runtimeSnapshotPath(opts.activeStateDir, payload.sessionId),
      })
    }
    catch (error) {
      failedSources.push({
        sessionId: payload.sessionId,
        sourceKind: candidate.sourceKind,
        sourcePath: candidate.sourcePath,
        error: error instanceof Error ? error.message : String(error),
      })
    }
  }

  const marker: RuntimeImportMarker = {
    version: RUNTIME_IMPORT_VERSION,
    completedAt: new Date().toISOString(),
    sourceRoots: [...opts.stateRoots].map(root => path.resolve(root)),
    importedSessions,
    skippedSessions,
    failedSources,
  }
  await writeJsonFile(markerPath, marker)
  return marker
}

export type { McpServerConfig }
