import { randomUUID } from "node:crypto";
import { createReadStream } from "node:fs";
import type { DatabaseAdapter } from "../db.js";
import { hmacHex } from "../privacy.js";
import { isAllowedMcpServer } from "../repo-catalog.js";
import { FileImportError } from "./file-import-contract.js";

interface ImportContext {
  db: DatabaseAdapter;
  sourceFileId: string;
  machineId: string;
  hmacSalt: string;
}

interface ImportCounters {
  sessions_upserted: number;
  messages_upserted: number;
  runtime_events_upserted: number;
}

interface WarningEntry {
  code: string;
  message_code: string;
  severity: "info" | "warning" | "error";
  source_file_id?: string;
  details?: Record<string, number | string | boolean>;
}

type ModelReason = "MODEL_FIELD_MISSING" | "MODEL_FIELD_INVALID";
const MAX_CODEX_LINE_BYTES = 4 * 1024 * 1024;
const CODEX_NOOP_EVENT_MESSAGES = new Set([
  "agent_message", "agent_reasoning", "mcp_tool_call_end", "patch_apply_end", "task_started", "task_complete",
  "exec_command_end", "thread_settings_applied", "thread_rolled_back", "turn_aborted", "context_compacted",
  "thread_name_updated", "collab_waiting_end", "web_search_end", "sub_agent_activity", "item_completed",
  "collab_agent_interaction_end", "collab_resume_end", "thread_goal_updated", "error"
]);
const CODEX_NOOP_RESPONSE_ITEMS = new Set([
  "reasoning", "message", "custom_tool_call", "custom_tool_call_output", "tool_search_call", "tool_search_output",
  "web_search_call", "agent_message", "function_call_output"
]);
const CODEX_NOOP_EVENT_TYPES = new Set(["world_state", "compacted", "inter_agent_communication_metadata"]);

interface StreamSession {
  sourceSessionId: string;
  createdAt: string | null;
  updatedAt: string;
  mode: string;
  model: string | null;
  modelReason: ModelReason | null;
  cwd: string | null;
  messageCount: number;
  userMessageCount: number;
  assistantMessageCount: number;
  tokenAvailable: boolean;
  messageSeq: number;
  initialized: boolean;
}

function sanitizeWarning(input: WarningEntry): WarningEntry {
  return {
    code: input.code,
    message_code: input.code,
    severity: input.severity,
    ...(input.source_file_id ? { source_file_id: input.source_file_id } : {}),
    ...(input.details ? { details: input.details } : {})
  };
}

function incrementWarning(warnings: WarningEntry[], code: "RECORD_SCHEMA_UNSUPPORTED" | "INVALID_JSON_LINE_SKIPPED", sourceFileId: string): void {
  const existing = warnings.find((warning) => warning.code === code);
  if (existing) {
    existing.details = { count: Number(existing.details?.count ?? 0) + 1 };
    return;
  }
  warnings.push(sanitizeWarning({ code, message_code: code, severity: "warning", source_file_id: sourceFileId, details: { count: 1 } }));
}

function cleanMode(value: unknown): "chat" | "agent" | "edit" | "unknown" {
  if (value === "chat" || value === "agent" || value === "edit") return value;
  return "unknown";
}

function cleanRole(value: unknown): "user" | "assistant" | "unknown" {
  if (value === "user" || value === "assistant") return value;
  return "unknown";
}

function cleanModel(value: unknown): string | null {
  if (typeof value !== "string") return null;
  const v = value.trim();
  if (!v || v.length > 160 || v.includes("\n") || v.includes("\\") || v.startsWith("{")) return null;
  if (v.startsWith("codex/")) return v;
  return `codex/${v}`;
}

function parseCodexFunctionName(name: string): { server: string; tool: string } | null {
  const m = /^mcp__([a-z0-9_]+)__([a-zA-Z0-9_]+)$/u.exec(name);
  if (!m) return null;
  const server = m[1].replaceAll("_", "-");
  const tool = m[2];
  return { server, tool };
}

async function* readCodexLines(absolutePath: string): AsyncGenerator<{ line?: string; tooLarge?: true }> {
  const stream = createReadStream(absolutePath, { encoding: "utf8", highWaterMark: 64 * 1024 });
  let buffered = "";
  let discardingOversizedLine = false;
  for await (const chunk of stream) {
    let remaining = chunk as string;
    if (discardingOversizedLine) {
      const newlineIndex = remaining.indexOf("\n");
      if (newlineIndex < 0) continue;
      discardingOversizedLine = false;
      remaining = remaining.slice(newlineIndex + 1);
    }
    buffered += remaining;
    let newlineIndex = buffered.indexOf("\n");
    while (newlineIndex >= 0) {
      const line = buffered.slice(0, newlineIndex).trim();
      buffered = buffered.slice(newlineIndex + 1);
      if (Buffer.byteLength(line, "utf8") > MAX_CODEX_LINE_BYTES) yield { tooLarge: true };
      else if (line) yield { line };
      newlineIndex = buffered.indexOf("\n");
    }
    if (Buffer.byteLength(buffered, "utf8") > MAX_CODEX_LINE_BYTES) {
      buffered = "";
      discardingOversizedLine = true;
      yield { tooLarge: true };
    }
  }
  if (!discardingOversizedLine) {
    const line = buffered.trim();
    if (line) yield { line };
  }
}

function sessionFor(sessions: Map<string, StreamSession>, sourceSessionId: string, timestamp: string): StreamSession {
  const existing = sessions.get(sourceSessionId);
  if (existing) return existing;
  const created: StreamSession = { sourceSessionId, createdAt: timestamp, updatedAt: timestamp, mode: "chat", model: null, modelReason: null, cwd: null, messageCount: 0, userMessageCount: 0, assistantMessageCount: 0, tokenAvailable: false, messageSeq: 0, initialized: false };
  sessions.set(sourceSessionId, created);
  return created;
}

export async function importCodexFile(
  context: ImportContext,
  absolutePath: string
): Promise<{ counters: ImportCounters; warnings: WarningEntry[] }> {
  const counters: ImportCounters = { sessions_upserted: 0, messages_upserted: 0, runtime_events_upserted: 0 };
  const warnings: WarningEntry[] = [];
  let eventSeqGlobal = 0;
  let detectedFormat: "event" | "legacy" | null = null;
  let invalidCount = 0;
  let unsupportedCount = 0;
  let supportedRecordCount = 0;
  let lifecycleCount = 0;
  let activeSid: string | null = null;
  const sessions = new Map<string, StreamSession>();

  await context.db.exec("BEGIN IMMEDIATE TRANSACTION");
  try {
    const sessionIdFor = (session: StreamSession) => hmacHex(`${context.machineId}:codex:${session.sourceSessionId}`, context.hmacSalt);
    const persistSession = async (session: StreamSession): Promise<string> => {
      const sessionId = sessionIdFor(session);
      if (!session.initialized) {
        await context.db.run("DELETE FROM message_metrics WHERE session_id = ?", [sessionId]);
        await context.db.run("DELETE FROM runtime_events WHERE session_id = ?", [sessionId]);
        session.initialized = true;
        counters.sessions_upserted += 1;
      }
      const projectId = session.cwd ? `workspace:${hmacHex(session.cwd, context.hmacSalt).slice(0, 24)}` : null;
      await context.db.run(
        `
          INSERT INTO sessions(
            id, source, source_session_hash, source_file_id, machine_id, project_id, mode, model_primary, token_available,
            message_count, user_message_count, assistant_message_count, created_at, updated_at, created_at_source, client_surface, session_kind, metadata_json
          ) VALUES (?, 'codex', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'native', 'cli', ?, NULL)
          ON CONFLICT(id) DO UPDATE SET
            source_file_id = excluded.source_file_id,
            project_id = excluded.project_id,
            mode = excluded.mode,
            model_primary = excluded.model_primary,
            token_available = excluded.token_available,
            message_count = excluded.message_count,
            user_message_count = excluded.user_message_count,
            assistant_message_count = excluded.assistant_message_count,
            created_at = excluded.created_at,
            updated_at = excluded.updated_at,
            created_at_source = excluded.created_at_source,
            client_surface = excluded.client_surface,
            session_kind = excluded.session_kind
        `,
        [
          sessionId,
          hmacHex(session.sourceSessionId, context.hmacSalt),
          context.sourceFileId,
          context.machineId,
          projectId,
          cleanMode(session.mode),
          session.model,
          session.tokenAvailable ? 1 : 0,
          session.messageCount,
          session.userMessageCount,
          session.assistantMessageCount,
          session.createdAt,
          session.updatedAt,
          session.messageCount > 0 ? "main" : "unknown"
        ]
      );
      return sessionId;
    };

    const writeMetric = async (session: StreamSession, role: "user" | "assistant" | "unknown", createdAt: string, tokens: Record<string, unknown>, telemetry: boolean, model: string | null = null): Promise<void> => {
      const inTok = typeof tokens.input === "number" ? Math.max(0, Math.floor(tokens.input)) : 0;
      const outTok = typeof tokens.output === "number" ? Math.max(0, Math.floor(tokens.output)) : 0;
      const cacheRead = typeof tokens.cache_read === "number" ? Math.max(0, Math.floor(tokens.cache_read)) : 0;
      const cacheWrite = typeof tokens.cache_write === "number" ? Math.max(0, Math.floor(tokens.cache_write)) : 0;
      const reasoningTok = typeof tokens.reasoning === "number" ? Math.max(0, Math.floor(tokens.reasoning)) : 0;
      const tokenAvailable = inTok > 0 || outTok > 0 || cacheRead > 0 || cacheWrite > 0 || reasoningTok > 0;
      session.messageSeq += 1;
      if (!telemetry) {
        session.messageCount += 1;
        if (role === "user") session.userMessageCount += 1;
        if (role === "assistant") session.assistantMessageCount += 1;
      }
      session.tokenAvailable ||= tokenAvailable;
      const sessionId = await persistSession(session);
      const msgId = hmacHex(`${context.machineId}:${sessionId}:${session.messageSeq}`, context.hmacSalt);
      await context.db.run(
          `
            INSERT INTO message_metrics(
              id, session_id, source_file_id, seq, role, model, input_tokens, output_tokens, cache_read_tokens, cache_write_tokens,
              reasoning_tokens, token_available, partial_token_data, created_at, timestamp_source, metadata_json
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, ?, 'native', ?)
          `,
          [
            msgId,
            sessionId,
            context.sourceFileId,
            session.messageSeq,
            role,
            model,
            inTok,
            outTok,
            cacheRead,
            cacheWrite,
            reasoningTok,
            tokenAvailable ? 1 : 0,
            createdAt,
            session.model === null && session.modelReason !== null
              ? JSON.stringify({ ...(telemetry ? { metric_kind: "telemetry" } : {}), model_reason: session.modelReason })
              : (telemetry ? JSON.stringify({ metric_kind: "telemetry" }) : null)
          ]
        );
      counters.messages_upserted += 1;
    };

    const writeMcpCall = async (session: StreamSession, occurredAt: string, name: string, args: Record<string, unknown>): Promise<void> => {
      const parsed = parseCodexFunctionName(name);
      // Retain the observed session even when its MCP call is unsupported. This preserves the
      // legacy session contract while still refusing to persist an unapproved runtime event.
      const sessionId = await persistSession(session);
      if (!parsed || !isAllowedMcpServer(parsed.server)) {
          warnings.push(
            sanitizeWarning({
              code: "MCP_CALL_UNSUPPORTED",
              message_code: "MCP_CALL_UNSUPPORTED",
              severity: "warning",
              source_file_id: context.sourceFileId
            })
          );
        return;
      }
      eventSeqGlobal += 1;
      const eventName = `${parsed.server}.${parsed.tool}`;
      const keys = Object.keys(args).filter((k) => /^[a-zA-Z0-9_]{1,80}$/u.test(k)).sort();
        const argsKeysJson = JSON.stringify(keys);
        const argsBytes = Buffer.byteLength(JSON.stringify(args), "utf8");
        const argsHash = hmacHex(JSON.stringify(args), context.hmacSalt);
        const argsMarker = argsHash || "noargs";
        const eventId = hmacHex(`${context.machineId}:canon:mcp:${eventName}:${occurredAt}:${sessionId}:${argsMarker}`, context.hmacSalt);
        await context.db.run(
          `
            INSERT OR IGNORE INTO runtime_events(
              id, source, source_file_id, session_id, event_type, event_origin, event_name, mcp_server_name, tool_name,
              skill_name, hook_name, hook_event, source_line_no, event_seq, args_keys_json, args_hash, args_bytes,
              occurred_at, timestamp_source, is_self_event, metadata_json
            ) VALUES (?, 'codex', ?, ?, 'mcp', 'observed_structured', ?, ?, ?, NULL, NULL, NULL, NULL, ?, ?, ?, ?, ?, 'native', ?, NULL)
          `,
          [
            eventId,
            context.sourceFileId,
            sessionId,
            eventName,
            parsed.server,
            parsed.tool,
            eventSeqGlobal,
            argsKeysJson,
            argsHash,
            argsBytes,
            occurredAt,
            parsed.server === "analytics-mcp-server" ? 1 : 0
          ]
        );
        const observationId = hmacHex(`${eventId}:codex:${context.sourceFileId}:${eventSeqGlobal}:chat_structured`, context.hmacSalt);
        await context.db.run(
          `INSERT OR IGNORE INTO runtime_event_observations(
            id, runtime_event_id, observed_source, source_file_id, session_id, source_line_no, event_seq, observation_kind, observed_at
          ) VALUES (?, ?, 'codex', ?, ?, NULL, ?, 'chat_structured', ?)`,
          [observationId, eventId, context.sourceFileId, sessionId, eventSeqGlobal, occurredAt]
        );
      counters.runtime_events_upserted += 1;
    };

    const writeLifecycle = async (session: StreamSession, occurredAt: string, eventName: string): Promise<void> => {
      const sessionId = await persistSession(session);
      eventSeqGlobal += 1;
      const eventId = hmacHex(`${context.machineId}:canon:agent_lifecycle:${eventName}:${occurredAt}:${sessionId}`, context.hmacSalt);
        await context.db.run(
          `INSERT OR IGNORE INTO runtime_events(
            id, source, source_file_id, session_id, event_type, event_origin, event_name, mcp_server_name, tool_name,
            skill_name, hook_name, hook_event, source_line_no, event_seq, args_keys_json, args_hash, args_bytes,
            occurred_at, timestamp_source, is_self_event, metadata_json
          ) VALUES (?, 'codex', ?, ?, 'agent_lifecycle', 'observed_structured', ?, NULL, NULL, NULL, NULL, NULL, NULL, ?, '[]', NULL, 0, ?, 'native', 0, NULL)`,
          [eventId, context.sourceFileId, sessionId, eventName, eventSeqGlobal, occurredAt]
        );
        const observationId = hmacHex(`${eventId}:codex:${context.sourceFileId}:${eventSeqGlobal}:chat_structured`, context.hmacSalt);
        await context.db.run(
          `INSERT OR IGNORE INTO runtime_event_observations(
            id, runtime_event_id, observed_source, source_file_id, session_id, source_line_no, event_seq, observation_kind, observed_at
          ) VALUES (?, ?, 'codex', ?, ?, NULL, ?, 'chat_structured', ?)`,
          [observationId, eventId, context.sourceFileId, sessionId, eventSeqGlobal, occurredAt]
        );
      counters.runtime_events_upserted += 1;
    };

    const processEvent = async (record: Record<string, unknown>, payload: Record<string, unknown>): Promise<"record" | "noop" | false> => {
      const type = record.type as string;
      const timestamp = typeof record.timestamp === "string" ? record.timestamp : new Date().toISOString();
      const explicitSid = (typeof payload.id === "string" && payload.id) || (typeof payload.session_id === "string" && payload.session_id) || (typeof record.session_id === "string" && record.session_id);
      if (type === "session_meta") {
        const sid = typeof payload.id === "string" && payload.id;
        if (!sid) return false;
        activeSid = sid;
        const session = sessionFor(sessions, sid, timestamp);
        session.updatedAt = timestamp;
        if (typeof payload.mode === "string") session.mode = payload.mode;
        if (typeof payload.cwd === "string") session.cwd = payload.cwd;
        return "record";
      }
      if (type === "turn_context") {
        const sid = explicitSid || activeSid;
        if (!sid) return false;
        const session = sessionFor(sessions, sid, timestamp);
        session.updatedAt = timestamp;
        if (Object.hasOwn(payload, "model")) {
          const model = cleanModel(payload.model);
          if (model) { session.model = model; session.modelReason = null; }
          else if (session.model === null) session.modelReason = "MODEL_FIELD_INVALID";
        } else if (session.model === null) session.modelReason = "MODEL_FIELD_MISSING";
        if (session.initialized) await persistSession(session);
        return "record";
      }
      if (type === "event_msg") {
        const payloadType = typeof payload.type === "string" ? payload.type : "";
        const supportedMessage = payloadType === "token_count" || payloadType === "user_message" || payloadType === "assistant_message" ||
          payloadType === "collab_agent_spawn_end" || payloadType === "collab_close_end" || payload.role === "user" || payload.role === "assistant";
        const sid = explicitSid || activeSid;
        if (!sid) return false;
        if (payloadType === "thread_settings_applied") {
          const threadSettings = payload.thread_settings && typeof payload.thread_settings === "object" && !Array.isArray(payload.thread_settings)
            ? payload.thread_settings as Record<string, unknown>
            : null;
          const model = threadSettings ? cleanModel(threadSettings.model) : null;
          if (!model) return "noop";
          const session = sessionFor(sessions, sid, timestamp);
          session.updatedAt = timestamp;
          session.model = model;
          session.modelReason = null;
          if (session.initialized) await persistSession(session);
          return "record";
        }
        if (!supportedMessage) return CODEX_NOOP_EVENT_MESSAGES.has(payloadType) ? "noop" : false;
        const session = sessionFor(sessions, sid, timestamp);
        session.updatedAt = timestamp;
        if (payloadType === "token_count") {
          const info = payload.info && typeof payload.info === "object" ? payload.info as Record<string, unknown> : {};
          const last = info.last_token_usage && typeof info.last_token_usage === "object" ? info.last_token_usage as Record<string, unknown> : {};
          await writeMetric(session, "assistant", timestamp, { input: last.input_tokens, output: last.output_tokens, cache_read: last.cached_input_tokens, reasoning: last.reasoning_output_tokens }, true);
          return "record";
        }
        if (payloadType === "user_message" || payloadType === "assistant_message" || payload.role === "user" || payload.role === "assistant") {
          await writeMetric(session, payload.role === "user" || payloadType === "user_message" ? "user" : "assistant", timestamp, {}, false);
          return "record";
        }
        if (payloadType === "collab_agent_spawn_end" || payloadType === "collab_close_end") {
          lifecycleCount += 1;
          await writeLifecycle(session, timestamp, payloadType);
          return "record";
        }
        return false;
      }
      if (type === "response_item") {
        const sid = explicitSid || activeSid;
        if (!sid) return false;
        if (payload.type === "function_call" && (typeof payload.name !== "string" || !parseCodexFunctionName(payload.name))) return "noop";
        if (payload.type !== "function_call" || typeof payload.name !== "string" || !parseCodexFunctionName(payload.name)) {
          return typeof payload.type === "string" && CODEX_NOOP_RESPONSE_ITEMS.has(payload.type) ? "noop" : false;
        }
        const session = sessionFor(sessions, sid, timestamp);
        session.updatedAt = timestamp;
        await writeMcpCall(session, timestamp, payload.name, payload.arguments && typeof payload.arguments === "object" ? payload.arguments as Record<string, unknown> : {});
        return "record";
      }
      if (CODEX_NOOP_EVENT_TYPES.has(type) && detectedFormat === "event") return "noop";
      return false;
    };

    for await (const entry of readCodexLines(absolutePath)) {
      if (entry.tooLarge) {
        unsupportedCount += 1;
        activeSid = null;
        continue;
      }
      let parsed: unknown;
      try { parsed = JSON.parse(entry.line!); }
      catch (error) {
        if (!(error instanceof SyntaxError)) throw error;
        invalidCount += 1;
        activeSid = null;
        continue;
      }
      if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { unsupportedCount += 1; continue; }
      const record = parsed as Record<string, unknown>;
      const payload = record.payload && typeof record.payload === "object" && !Array.isArray(record.payload) ? record.payload as Record<string, unknown> : null;
      // Event envelopes are never reinterpreted as legacy merely because they also expose a
      // top-level session_id. The state transition below is the only event classifier.
      const eventEnvelope = typeof record.type === "string" && (payload !== null || CODEX_NOOP_EVENT_TYPES.has(record.type));
      const legacyShape = !eventEnvelope && typeof record.session_id === "string" && Boolean(record.session_id) && (
        Array.isArray(record.messages) || Object.hasOwn(record, "updated_at") || Object.hasOwn(record, "created_at") ||
        Object.hasOwn(record, "mode") || Object.hasOwn(record, "model") || Object.hasOwn(record, "mcp_calls")
      );
      if (eventEnvelope) {
        const outcome = detectedFormat === "legacy" ? false : await processEvent(record, payload ?? {});
        if (!outcome) { unsupportedCount += 1; continue; }
        if (outcome === "record") {
          detectedFormat ??= "event";
          supportedRecordCount += 1;
        }
        continue;
      }
      if (legacyShape && detectedFormat !== "event") {
        const sid = record.session_id as string;
        const session = sessionFor(sessions, sid, typeof record.updated_at === "string" ? record.updated_at : new Date().toISOString());
        session.updatedAt = typeof record.updated_at === "string" ? record.updated_at : session.updatedAt;
        session.createdAt = typeof record.created_at === "string" ? record.created_at : session.createdAt;
        session.mode = cleanMode(record.mode);
        session.cwd = typeof record.cwd === "string" && record.cwd.trim() ? record.cwd : null;
        const legacyModel = cleanModel(record.model);
        if (legacyModel) session.model = legacyModel;
        const messages = Array.isArray(record.messages) ? record.messages : [];
        for (const message of messages) {
          const msg = message && typeof message === "object" ? message as Record<string, unknown> : {};
          const tokens = msg.tokens && typeof msg.tokens === "object" ? msg.tokens as Record<string, unknown> : {};
          await writeMetric(session, cleanRole(msg.role), typeof msg.created_at === "string" ? msg.created_at : session.updatedAt, tokens, msg.kind === "telemetry", cleanModel(msg.model));
        }
        const legacyCalls = Array.isArray(record.mcp_calls) ? record.mcp_calls : [];
        for (const call of legacyCalls) {
          const legacyCall = call && typeof call === "object" ? call as Record<string, unknown> : {};
          const server = typeof legacyCall.mcp_server_name === "string" ? legacyCall.mcp_server_name : "";
          const tool = typeof legacyCall.tool_name === "string" ? legacyCall.tool_name : "";
          const name = server && tool ? `mcp__${server.replaceAll("-", "_")}__${tool}` : "";
          const args = legacyCall.args && typeof legacyCall.args === "object" ? legacyCall.args as Record<string, unknown> : {};
          await writeMcpCall(session, typeof legacyCall.occurred_at === "string" ? legacyCall.occurred_at : session.updatedAt, name, args);
        }
        detectedFormat = "legacy";
        supportedRecordCount += 1;
        continue;
      }
      unsupportedCount += 1;
    }

    // Streaming writes records before a later turn_context may determine a model reason. Reconcile
    // only this file's model-less metrics from the final per-session state before committing.
    for (const session of sessions.values()) {
      if (!session.initialized) continue;
      const sessionId = sessionIdFor(session);
      if (session.model !== null) {
        await context.db.run(
          `UPDATE message_metrics
             SET metadata_json = CASE WHEN metadata_json LIKE '%"metric_kind":"telemetry"%' THEN '{"metric_kind":"telemetry"}' ELSE NULL END
           WHERE session_id = ? AND model IS NULL`,
          [sessionId]
        );
      } else if (session.modelReason !== null) {
        const regularMetadata = JSON.stringify({ model_reason: session.modelReason });
        const telemetryMetadata = JSON.stringify({ metric_kind: "telemetry", model_reason: session.modelReason });
        await context.db.run(
          `UPDATE message_metrics
             SET metadata_json = CASE WHEN metadata_json LIKE '%"metric_kind":"telemetry"%' THEN ? ELSE ? END
           WHERE session_id = ? AND model IS NULL`,
          [telemetryMetadata, regularMetadata, sessionId]
        );
      }
    }

    if (invalidCount > 0) warnings.push(sanitizeWarning({ code: "INVALID_JSON_LINE_SKIPPED", message_code: "INVALID_JSON_LINE_SKIPPED", severity: "warning", source_file_id: context.sourceFileId, details: { count: invalidCount } }));
    if (unsupportedCount > 0) warnings.push(sanitizeWarning({ code: "RECORD_SCHEMA_UNSUPPORTED", message_code: "RECORD_SCHEMA_UNSUPPORTED", severity: "warning", source_file_id: context.sourceFileId, details: { count: unsupportedCount } }));
    if (lifecycleCount > 0) warnings.push(sanitizeWarning({ code: "COLLAB_AGENT_LIFECYCLE_DETECTED", message_code: "COLLAB_AGENT_LIFECYCLE_DETECTED", severity: "info", source_file_id: context.sourceFileId, details: { count: lifecycleCount } }));
    if (!detectedFormat || supportedRecordCount === 0) throw new FileImportError("SOURCE_SCHEMA_UNSUPPORTED", "SOURCE_SCHEMA_UNSUPPORTED", warnings);

    await context.db.exec("COMMIT");
  } catch (error) {
    await context.db.exec("ROLLBACK");
    throw error;
  }

  return { counters, warnings };
}

export function codexStorageValidationNote(): string {
  return "Codex Desktop storage candidate roots validated via fixture-driven import; real-machine Windows/Ubuntu verification documented in README.";
}

