/**
 * EventStore — Durable event storage and domain projection for AirCoding V1.0.0 Alpha
 *
 * Implements: append(event), append_many(events), query(filter).
 * Per system-detailed-design.md §5.3 and event-registry-v1.md §2.
 *
 * INV-1: project() is the ONLY place status columns are written
 * INV-2: project() never opens external DB/file
 * INV-5: publish is post-commit transport (EventBus.publish called AFTER commit)
 *
 * @module packages/runtime/src/events/EventStore
 */

import type {
  RuntimeEvent,
  EventFilter,
  TransactionHandle,
  TaskID,
  AgentID,
  ToolRunID,
  CommandRunID,
} from '@aircoding/contracts'

import { eventSchemaRegistry } from './EventSchemaRegistry.js'
import { eventBus } from './EventBus.js'
import { EventRepository, type EventInsert, type EventFilter as RepoEventFilter } from '../storage/repositories/EventRepository.js'
import type { DatabaseHandle } from '../storage/MigrationRunner.js'

// =============================================================================
// Types - event payload shapes from event-registry-v1.md
// =============================================================================

interface SessionCreatedPayload {
  session_id: string
  project_id: string
  project_root: string
  title?: string
  model_provider_id?: string
  model_id?: string
  metadata?: Record<string, unknown>
}

interface SessionArchivedPayload { session_id: string; reason?: string }
interface SessionDeletedPayload { session_id: string; reason?: string }
interface UserMessageCreatedPayload {
  message_id: string
  canonical_format: string
  content_json: unknown
  parent_message_id?: string
  token_estimate?: number
  metadata?: Record<string, unknown>
}
interface AssistantMessageStartedPayload {
  message_id: string
  canonical_format: string
  parent_message_id?: string
  route?: string[]
  metadata?: Record<string, unknown>
  content_json?: unknown
}
interface AssistantMessageCreatedPayload {
  message_id: string
  canonical_format: string
  content_json: unknown
  parent_message_id?: string
  route?: string[]
  token_estimate?: number
  metadata?: Record<string, unknown>
}
interface AssistantMessageFailedPayload {
  message_id: string
  partial_content_json?: unknown
  error: Record<string, unknown>
  evidence_refs?: Record<string, unknown>[]
  metadata?: Record<string, unknown>
}
interface AgentStartedPayload {
  agent_id: string
  agent_type: string
  task_id?: string
  pid?: number
  model_provider_id?: string
  model_id?: string
  workspace_id?: string
  metadata?: Record<string, unknown>
}
interface AgentCompletedPayload {
  agent_id: string
  task_id?: string
  summary: string
  worker_result_ref?: string
  metadata?: Record<string, unknown>
}
interface AgentFailedPayload {
  agent_id: string
  task_id?: string
  error: Record<string, unknown>
  evidence_refs?: Record<string, unknown>[]
  metadata?: Record<string, unknown>
}
interface AgentLostPayload {
  agent_id: string
  task_id?: string
  last_heartbeat_at?: string
  detection_reason: 'heartbeat_timeout' | 'process_exit_without_result' | 'ipc_broken'
}
interface AgentCancelledPayload { agent_id: string; task_id?: string; reason: string }
interface TaskCreatedPayload {
  task_id: string
  type: string
  title: string
  task_spec_json: unknown
  dependencies?: Record<string, unknown>[]
  metadata?: Record<string, unknown>
}
interface TaskStartedPayload {
  task_id: string
  agent_id: string
  attempt_id: string
  attempt_index: number
  workspace_id?: string
}
interface TaskCompletedPayload {
  task_id: string
  agent_id?: string
  attempt_id?: string
  worker_result_json: unknown
  summary: string
  changed_files?: string[]
  evidence_refs?: Record<string, unknown>[]
}
interface TaskBlockedPayload {
  task_id: string
  agent_id?: string
  reason: string
  blocker_kind: string
  evidence_refs?: Record<string, unknown>[]
  suggested_next_step?: string
}
interface TaskFailedPayload {
  task_id: string
  agent_id?: string
  attempt_id?: string
  error: Record<string, unknown>
  evidence_refs?: Record<string, unknown>[]
  metadata?: Record<string, unknown>
}
interface TaskCancelledPayload { task_id: string; reason: string; cancelled_by: string }
interface TaskInterruptedPayload { task_id: string; reason: string; resumable: boolean; resume_ref?: string }
interface ToolStartedPayload {
  tool_run_id: string
  tool_name: string
  task_id?: string
  agent_id?: string
  origin_message_id?: string
  input_json: unknown
  metadata?: Record<string, unknown>
}
interface ToolCompletedPayload {
  tool_run_id: string
  output_json?: unknown
  duration_ms?: number
  artifact_ids?: string[]
  evidence_refs?: Record<string, unknown>[]
  metadata?: Record<string, unknown>
}
interface ToolFailedPayload {
  tool_run_id: string
  duration_ms?: number
  error: Record<string, unknown>
  evidence_refs?: Record<string, unknown>[]
  metadata?: Record<string, unknown>
}
interface ToolCancelledPayload { tool_run_id: string; reason: string }
interface CommandStartedPayload {
  command_run_id: string
  task_id?: string
  agent_id?: string
  origin_message_id?: string
  tool_run_id?: string
  command: string
  cwd: string
  metadata?: Record<string, unknown>
}
interface CommandCompletedPayload {
  command_run_id: string
  exit_code: number
  duration_ms?: number
  stdout_artifact_id?: string
  stderr_artifact_id?: string
  combined_artifact_id?: string
  diagnostic_ids?: string[]
  parsed_diagnostics_json?: unknown
  metadata?: Record<string, unknown>
}
interface CommandFailedPayload {
  command_run_id: string
  exit_code?: number
  duration_ms?: number
  stdout_artifact_id?: string
  stderr_artifact_id?: string
  combined_artifact_id?: string
  error: Record<string, unknown>
  evidence_refs?: Record<string, unknown>[]
  metadata?: Record<string, unknown>
}
interface ArtifactCreatedPayload {
  artifact_id: string
  type: string
  uri: string
  path: string
  original_name?: string
  size_bytes?: number
  sha256?: string
  task_id?: string
  agent_id?: string
  tool_run_id?: string
  command_run_id?: string
  associated_entity_type?: string
  associated_entity_id?: string
  metadata?: Record<string, unknown>
}
interface DiagnosticCreatedPayload {
  diagnostic_id: string
  task_id?: string
  agent_id?: string
  command_run_id?: string
  artifact_id?: string
  language?: string
  toolchain?: string
  severity?: string
  file?: string
  line?: number
  column?: number
  code?: string
  message: string
  semantic_signature: string
  metadata?: Record<string, unknown>
}
interface EvidenceCreatedPayload {
  evidence_ref_id: string
  kind: string
  ref: string
  location_json?: unknown
  claim: string
  task_id?: string
  agent_id?: string
  tool_run_id?: string
  command_run_id?: string
  artifact_id?: string
  diagnostic_id?: string
  message_id?: string
}
interface SummaryCreatedPayload {
  summary_id: string
  type: string
  range_start_message_id?: string
  range_end_message_id?: string
  content_json: unknown
  metadata?: Record<string, unknown>
}
interface WorkspaceCreatedPayload {
  workspace_id: string
  task_id?: string
  agent_id?: string
  path: string
  strategy: string
  base_ref?: string
  branch_name?: string
}
interface WorkspaceMergeStartedPayload { workspace_id: string; task_id?: string; strategy: string; target_ref?: string }
interface WorkspaceMergeCompletedPayload { workspace_id: string; task_id?: string; merged_ref?: string; diff_artifact_id?: string }
interface WorkspaceMergeConflictedPayload {
  workspace_id: string
  task_id?: string
  conflict_files: string[]
  conflict_artifact_id?: string
  suggested_resolution?: string
}
interface WorkspaceCleanedPayload { workspace_id: string; reason: string }

// =============================================================================
// EventStore
// =============================================================================

/**
 * Transaction function type for database operations
 */
type TransactionFn<T> = (tx: TransactionHandle) => Promise<T>

/**
 * EventStore implements durable event persistence and domain projection.
 */
export class EventStore {
  private eventRepo: EventRepository
  private txManager: { transaction<T>(fn: TransactionFn<T>): Promise<T> } | null = null

  // Repository placeholders for domain projection
  private sessionRepo: any = null
  private messageRepo: any = null
  private messageDraftRepo: any = null
  private taskRepo: any = null
  private taskAttemptRepo: any = null
  private taskDepRepo: any = null
  private agentRepo: any = null
  private toolRunRepo: any = null
  private commandRunRepo: any = null
  private artifactRepo: any = null
  private diagnosticRepo: any = null
  private evidenceRepo: any = null
  private workspaceRepo: any = null
  private summaryRepo: any = null

  constructor(db: DatabaseHandle) {
    this.eventRepo = new EventRepository(db)
  }

  /**
   * Set the transaction manager (DatabaseManager) for this store.
   */
  setTransactionManager(txManager: { transaction<T>(fn: TransactionFn<T>): Promise<T> }): void {
    this.txManager = txManager
  }

  /**
   * Set repositories for domain projection.
   */
  setRepositories(repos: {
    sessionRepo?: any
    messageRepo?: any
    messageDraftRepo?: any
    taskRepo?: any
    taskAttemptRepo?: any
    taskDepRepo?: any
    agentRepo?: any
    toolRunRepo?: any
    commandRunRepo?: any
    artifactRepo?: any
    diagnosticRepo?: any
    evidenceRepo?: any
    workspaceRepo?: any
    summaryRepo?: any
  }): void {
    Object.assign(this, repos)
  }

  /**
   * Append a single durable event.
   */
  async append<T>(event: RuntimeEvent<T>): Promise<void> {
    // Validate event schema
    const isValid = eventSchemaRegistry.validate(event.type, event.version, event.payload)
    if (!isValid) {
      throw new Error(`Invalid event: ${event.type}@v${event.version} not registered`)
    }

    const persistence = eventSchemaRegistry.getPersistence(event.type, event.version)
    if (persistence !== 'durable') {
      throw new Error(`Event ${event.type} is not durable, use EventBus.publish for ephemeral`)
    }

    const record = this.toRecord(event)

    // Use transaction if available, otherwise simple insert
    if (this.txManager) {
      await this.txManager.transaction(async (tx) => {
        await this.eventRepo.insert_in_transaction(record, tx)
        this.project(event as RuntimeEvent<unknown>, tx)
      })
    } else {
      // Fallback: simple insert without full transaction
      await this.eventRepo.insert(record)
      this.project(event as RuntimeEvent<unknown>, { id: 'no-tx' })
    }

    // Post-commit: publish to EventBus (INV-5)
    eventBus.publish(event)
  }

  /**
   * Append multiple events in a single transaction.
   */
  async append_many<T>(events: RuntimeEvent<T>[]): Promise<void> {
    if (events.length === 0) return

    // Validate all events
    for (const event of events) {
      const isValid = eventSchemaRegistry.validate(event.type, event.version, event.payload)
      if (!isValid) {
        throw new Error(`Invalid event: ${event.type}@v${event.version} not registered`)
      }
      const persistence = eventSchemaRegistry.getPersistence(event.type, event.version)
      if (persistence !== 'durable') {
        throw new Error(`Event ${event.type} is not durable`)
      }
    }

    const records = events.map((e) => this.toRecord(e))

    if (this.txManager) {
      await this.txManager.transaction(async (tx) => {
        for (const record of records) {
          await this.eventRepo.insert_in_transaction(record, tx)
        }
        for (const event of events) {
          this.project(event as RuntimeEvent<unknown>, tx)
        }
      })
    } else {
      for (const record of records) {
        await this.eventRepo.insert(record)
      }
      for (const event of events) {
        this.project(event as RuntimeEvent<unknown>, { id: 'no-tx' })
      }
    }

    // Post-commit: publish all events
    for (const event of events) {
      eventBus.publish(event)
    }
  }

  /**
   * Query events with filters.
   */
  async query(filter: EventFilter): Promise<RuntimeEvent[]> {
    const repoFilter: RepoEventFilter = {
      session_id: filter.session_id,
      types: filter.types,
      task_id: filter.task_id,
      agent_id: filter.agent_id,
      tool_run_id: filter.tool_run_id,
      command_run_id: filter.command_run_id,
      route_prefix: filter.route_prefix,
      since: filter.since,
    }

    const records = await this.eventRepo.query(repoFilter)
    return records.map(this.fromRecord)
  }

  /**
   * Convert RuntimeEvent to PersistedEventRecord format.
   */
  private toRecord<T>(event: RuntimeEvent<T>): EventInsert {
    const payload = event.payload as Record<string, unknown>
    return {
      id: event.id,
      session_id: event.session_id,
      type: event.type,
      version: event.version,
      timestamp: event.timestamp,
      source_kind: event.source.kind,
      source_id: event.source.id,
      agent_type: event.source.agent_type,
      task_id: payload.task_id as TaskID | undefined,
      agent_id: payload.agent_id as AgentID | undefined,
      tool_run_id: payload.tool_run_id as ToolRunID | undefined,
      command_run_id: payload.command_run_id as CommandRunID | undefined,
      route_json: JSON.stringify(event.route),
      route_text: event.route.join('/'),
      payload_json: JSON.stringify(event.payload),
    }
  }

  /**
   * Convert PersistedEventRecord back to RuntimeEvent.
   */
  private fromRecord(record: any): RuntimeEvent {
    return {
      id: record.id,
      type: record.type,
      version: record.version,
      timestamp: record.timestamp,
      session_id: record.session_id,
      source: {
        kind: record.source_kind,
        id: record.source_id,
        agent_type: record.agent_type,
      },
      route: JSON.parse(record.route_json),
      payload: JSON.parse(record.payload_json),
    }
  }

  /**
   * Project event to domain tables per DD §5.4 Table A.
   * INV-1: This is the ONLY place status columns are written.
   * INV-2: This method never opens external DB/file.
   */
  private project<T>(event: RuntimeEvent<T>, _tx: TransactionHandle): void {
    const payload = event.payload as Record<string, unknown>
    const now = event.timestamp

    switch (event.type) {
      // Session Events
      case 'session.created': {
        const p = payload as unknown as SessionCreatedPayload
        this.sessionRepo?.insert({
          id: p.session_id,
          project_id: p.project_id,
          project_root: p.project_root,
          title: p.title,
          status: 'active',
          created_at: now,
          updated_at: now,
          model_provider_id: p.model_provider_id,
          model_id: p.model_id,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'session.archived': {
        const p = payload as unknown as SessionArchivedPayload
        this.sessionRepo?.update(p.session_id, { status: 'archived', updated_at: now }, _tx)
        break
      }
      case 'session.deleted': {
        const p = payload as unknown as SessionDeletedPayload
        this.sessionRepo?.update(p.session_id, { status: 'deleted', updated_at: now }, _tx)
        break
      }

      // Message Events
      case 'user.message.created': {
        const p = payload as unknown as UserMessageCreatedPayload
        this.messageRepo?.insert({
          id: p.message_id,
          session_id: event.session_id,
          role: 'user',
          canonical_format: p.canonical_format,
          content_json: JSON.stringify(p.content_json),
          parent_message_id: p.parent_message_id,
          route_json: JSON.stringify([]),
          created_at: now,
          token_estimate: p.token_estimate,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'assistant.message.started': {
        const p = payload as unknown as AssistantMessageStartedPayload
        this.messageDraftRepo?.upsert({
          message_id: p.message_id,
          session_id: event.session_id,
          role: 'assistant',
          canonical_format: p.canonical_format,
          partial_content_json: JSON.stringify(p.content_json ?? {}),
          status: 'streaming',
          created_at: now,
          updated_at: now,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'assistant.message.created': {
        const p = payload as unknown as AssistantMessageCreatedPayload
        this.messageRepo?.insert({
          id: p.message_id,
          session_id: event.session_id,
          role: 'assistant',
          canonical_format: p.canonical_format,
          content_json: JSON.stringify(p.content_json),
          parent_message_id: p.parent_message_id,
          route_json: JSON.stringify(p.route ?? []),
          created_at: now,
          token_estimate: p.token_estimate,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        this.messageDraftRepo?.delete_for_message(p.message_id, _tx)
        break
      }
      case 'assistant.message.failed': {
        const p = payload as unknown as AssistantMessageFailedPayload
        this.messageDraftRepo?.update(p.message_id, { status: 'error', updated_at: now }, _tx)
        break
      }

      // Agent Events
      case 'agent.started': {
        const p = payload as unknown as AgentStartedPayload
        this.agentRepo?.insert({
          id: p.agent_id,
          session_id: event.session_id,
          type: p.agent_type,
          status: 'running',
          pid: p.pid,
          task_id: p.task_id,
          model_provider_id: p.model_provider_id,
          model_id: p.model_id,
          started_at: now,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'agent.completed': {
        const p = payload as unknown as AgentCompletedPayload
        this.agentRepo?.update(p.agent_id, { status: 'completed', completed_at: now }, _tx)
        break
      }
      case 'agent.failed': {
        const p = payload as unknown as AgentFailedPayload
        this.agentRepo?.update(p.agent_id, { status: 'failed', completed_at: now }, _tx)
        break
      }
      case 'agent.lost': {
        const p = payload as unknown as AgentLostPayload
        this.agentRepo?.update(p.agent_id, {
          status: 'lost',
          last_heartbeat_at: p.last_heartbeat_at,
          completed_at: now,
        }, _tx)
        break
      }
      case 'agent.cancelled': {
        const p = payload as unknown as AgentCancelledPayload
        this.agentRepo?.update(p.agent_id, { status: 'cancelled', completed_at: now }, _tx)
        break
      }

      // Task Events
      case 'task.created': {
        const p = payload as unknown as TaskCreatedPayload
        this.taskRepo?.insert({
          id: p.task_id,
          session_id: event.session_id,
          type: p.type,
          status: 'pending',
          title: p.title,
          task_spec_json: JSON.stringify(p.task_spec_json),
          created_at: now,
        }, _tx)
        if (p.dependencies && p.dependencies.length > 0) {
          for (const dep of p.dependencies) {
            // Generate UUID without using self.crypto
            const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
              const r = Math.random() * 16 | 0
              const v = c === 'x' ? r : (r & 0x3 | 0x8)
              return v.toString(16)
            })
            this.taskDepRepo?.insert({
              id: uuid,
              session_id: event.session_id,
              task_id: p.task_id,
              depends_on_task_id: dep.depends_on_task_id,
              dependency_type: dep.dependency_type,
              reason: dep.reason,
              created_at: now,
            }, _tx)
          }
        }
        break
      }
      case 'task.started': {
        const p = payload as unknown as TaskStartedPayload
        this.taskRepo?.update(p.task_id, {
          status: 'running',
          started_at: now,
          assigned_agent_id: p.agent_id,
          workspace_id: p.workspace_id,
        }, _tx)
        this.taskAttemptRepo?.insert({
          id: p.attempt_id,
          session_id: event.session_id,
          task_id: p.task_id,
          attempt_index: p.attempt_index,
          agent_id: p.agent_id,
          status: 'running',
          started_at: now,
        }, _tx)
        break
      }
      case 'task.completed': {
        const p = payload as unknown as TaskCompletedPayload
        this.taskRepo?.update(p.task_id, {
          status: 'completed',
          completed_at: now,
          worker_result_json: JSON.stringify(p.worker_result_json),
        }, _tx)
        if (p.attempt_id) {
          this.taskAttemptRepo?.update(p.attempt_id, {
            status: 'completed',
            completed_at: now,
            worker_result_json: JSON.stringify(p.worker_result_json),
          }, _tx)
        }
        break
      }
      case 'task.blocked': {
        const p = payload as unknown as TaskBlockedPayload
        this.taskRepo?.update(p.task_id, { status: 'blocked' }, _tx)
        break
      }
      case 'task.failed': {
        const p = payload as unknown as TaskFailedPayload
        this.taskRepo?.update(p.task_id, { status: 'failed', completed_at: now }, _tx)
        if (p.attempt_id) {
          this.taskAttemptRepo?.update(p.attempt_id, {
            status: 'failed',
            completed_at: now,
            failure_summary: (p.error.message as string) ?? 'Unknown error',
          }, _tx)
        }
        break
      }
      case 'task.cancelled': {
        const p = payload as unknown as TaskCancelledPayload
        this.taskRepo?.update(p.task_id, { status: 'cancelled', completed_at: now }, _tx)
        break
      }
      case 'task.interrupted': {
        const p = payload as unknown as TaskInterruptedPayload
        this.taskRepo?.update(p.task_id, { status: 'interrupted', completed_at: now }, _tx)
        break
      }

      // Tool Events
      case 'tool.started': {
        const p = payload as unknown as ToolStartedPayload
        this.toolRunRepo?.insert({
          id: p.tool_run_id,
          session_id: event.session_id,
          task_id: p.task_id,
          agent_id: p.agent_id,
          origin_message_id: p.origin_message_id,
          tool_name: p.tool_name,
          status: 'running',
          input_json: JSON.stringify(p.input_json),
          started_at: now,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'tool.completed': {
        const p = payload as unknown as ToolCompletedPayload
        this.toolRunRepo?.update(p.tool_run_id, {
          status: 'ok',
          output_json: p.output_json ? JSON.stringify(p.output_json) : undefined,
          duration_ms: p.duration_ms,
          artifacts_json: p.artifact_ids ? JSON.stringify(p.artifact_ids) : undefined,
          evidence_refs_json: p.evidence_refs ? JSON.stringify(p.evidence_refs) : undefined,
          completed_at: now,
        }, _tx)
        break
      }
      case 'tool.failed': {
        const p = payload as unknown as ToolFailedPayload
        this.toolRunRepo?.update(p.tool_run_id, {
          status: 'error',
          error_json: JSON.stringify(p.error),
          duration_ms: p.duration_ms,
          completed_at: now,
        }, _tx)
        break
      }
      case 'tool.cancelled': {
        const p = payload as unknown as ToolCancelledPayload
        this.toolRunRepo?.update(p.tool_run_id, { status: 'cancelled', completed_at: now }, _tx)
        break
      }

      // Command Events
      case 'command.started': {
        const p = payload as unknown as CommandStartedPayload
        this.commandRunRepo?.insert({
          id: p.command_run_id,
          session_id: event.session_id,
          task_id: p.task_id,
          agent_id: p.agent_id,
          origin_message_id: p.origin_message_id,
          tool_run_id: p.tool_run_id,
          command: p.command,
          cwd: p.cwd,
          started_at: now,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'command.completed': {
        const p = payload as unknown as CommandCompletedPayload
        this.commandRunRepo?.update(p.command_run_id, {
          exit_code: p.exit_code,
          duration_ms: p.duration_ms,
          stdout_artifact_id: p.stdout_artifact_id,
          stderr_artifact_id: p.stderr_artifact_id,
          combined_artifact_id: p.combined_artifact_id,
          diagnostic_ids: p.diagnostic_ids ? JSON.stringify(p.diagnostic_ids) : undefined,
          parsed_diagnostics_json: p.parsed_diagnostics_json ? JSON.stringify(p.parsed_diagnostics_json) : undefined,
          completed_at: now,
        }, _tx)
        break
      }
      case 'command.failed': {
        const p = payload as unknown as CommandFailedPayload
        this.commandRunRepo?.update(p.command_run_id, {
          exit_code: p.exit_code,
          duration_ms: p.duration_ms,
          stdout_artifact_id: p.stdout_artifact_id,
          stderr_artifact_id: p.stderr_artifact_id,
          combined_artifact_id: p.combined_artifact_id,
          completed_at: now,
        }, _tx)
        break
      }

      // Artifact, Diagnostic, Evidence Events
      case 'artifact.created': {
        const p = payload as unknown as ArtifactCreatedPayload
        this.artifactRepo?.insert({
          id: p.artifact_id,
          session_id: event.session_id,
          type: p.type,
          uri: p.uri,
          path: p.path,
          original_name: p.original_name,
          size_bytes: p.size_bytes,
          sha256: p.sha256,
          task_id: p.task_id,
          agent_id: p.agent_id,
          tool_run_id: p.tool_run_id,
          command_run_id: p.command_run_id,
          associated_entity_type: p.associated_entity_type,
          associated_entity_id: p.associated_entity_id,
          created_at: now,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'diagnostic.created': {
        const p = payload as unknown as DiagnosticCreatedPayload
        this.diagnosticRepo?.insert({
          id: p.diagnostic_id,
          session_id: event.session_id,
          task_id: p.task_id,
          agent_id: p.agent_id,
          command_run_id: p.command_run_id,
          artifact_id: p.artifact_id,
          language: p.language,
          toolchain: p.toolchain,
          severity: p.severity,
          file: p.file,
          line: p.line,
          column: p.column,
          code: p.code,
          message: p.message,
          semantic_signature: p.semantic_signature,
          created_at: now,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }
      case 'evidence.created': {
        const p = payload as unknown as EvidenceCreatedPayload
        this.evidenceRepo?.insert({
          id: p.evidence_ref_id,
          session_id: event.session_id,
          task_id: p.task_id,
          agent_id: p.agent_id,
          tool_run_id: p.tool_run_id,
          command_run_id: p.command_run_id,
          artifact_id: p.artifact_id,
          diagnostic_id: p.diagnostic_id,
          message_id: p.message_id,
          kind: p.kind,
          ref: p.ref,
          location_json: p.location_json ? JSON.stringify(p.location_json) : undefined,
          claim: p.claim,
          created_at: now,
        }, _tx)
        break
      }

      // Summary Events
      case 'summary.created': {
        const p = payload as unknown as SummaryCreatedPayload
        this.summaryRepo?.insert({
          id: p.summary_id,
          session_id: event.session_id,
          type: p.type,
          range_start_message_id: p.range_start_message_id,
          range_end_message_id: p.range_end_message_id,
          content_json: JSON.stringify(p.content_json),
          created_at: now,
          metadata_json: p.metadata ? JSON.stringify(p.metadata) : undefined,
        }, _tx)
        break
      }

      // Workspace Events
      case 'workspace.created': {
        const p = payload as unknown as WorkspaceCreatedPayload
        this.workspaceRepo?.insert({
          id: p.workspace_id,
          session_id: event.session_id,
          task_id: p.task_id,
          agent_id: p.agent_id,
          path: p.path,
          strategy: p.strategy,
          status: 'active',
          base_ref: p.base_ref,
          branch_name: p.branch_name,
          created_at: now,
        }, _tx)
        break
      }
      case 'workspace.merge.started': {
        const p = payload as unknown as WorkspaceMergeStartedPayload
        this.workspaceRepo?.update(p.workspace_id, {
          metadata_json: JSON.stringify({ merge_in_progress: true, strategy: p.strategy, target_ref: p.target_ref }),
        }, _tx)
        break
      }
      case 'workspace.merge.completed': {
        const p = payload as unknown as WorkspaceMergeCompletedPayload
        this.workspaceRepo?.update(p.workspace_id, { status: 'merged', merged_at: now }, _tx)
        break
      }
      case 'workspace.merge.conflicted': {
        const p = payload as unknown as WorkspaceMergeConflictedPayload
        this.workspaceRepo?.update(p.workspace_id, { status: 'conflicted' }, _tx)
        break
      }
      case 'workspace.cleaned': {
        const p = payload as unknown as WorkspaceCleanedPayload
        this.workspaceRepo?.update(p.workspace_id, { status: 'cleaned' }, _tx)
        break
      }

      // Context compaction, permission, doctor, requirement, architecture,
      // memory, debug events - append only for V1
      default:
        break
    }
  }
}

/**
 * Default singleton instance for global use.
 * Note: Requires setTransactionManager() and setRepositories() to be fully functional.
 */
export const eventStore = new EventStore({} as DatabaseHandle)