// ABOUTME: Type definitions for thread events and thread management
// ABOUTME: Events include user messages, agent messages, tool calls, and tool results

import type { ToolCall, ToolResult } from '@lace/agent/tools/types';
import type { ApprovalDecision } from '@lace/agent/tools/types';
import type { CombinedTokenUsage } from '@lace/agent/token-management/types';
import type { MCPServerConfig } from '@lace/agent/config/mcp-types';

// Single source of truth for all event types
export const EVENT_TYPES = [
  // Persisted events
  'USER_MESSAGE',
  'AGENT_MESSAGE',
  'TOOL_CALL',
  'TOOL_APPROVAL_REQUEST',
  'TOOL_APPROVAL_RESPONSE',
  'TOOL_RESULT',
  'LOCAL_SYSTEM_MESSAGE',
  'SYSTEM_PROMPT',
  'USER_SYSTEM_PROMPT',
  // Transient events (not persisted to database)
  'AGENT_TOKEN',
  'AGENT_STREAMING',
  'AGENT_STATE_CHANGE',
  'TOKEN_USAGE_UPDATE',
  'COMPACTION_START',
  'COMPACTION_COMPLETE',
  // Agent lifecycle events (transient)
  'AGENT_SPAWNED',
  'AGENT_SUMMARY_UPDATED',
  // Project events (transient)
  'PROJECT_CREATED',
  'PROJECT_UPDATED',
  'PROJECT_DELETED',
  // System events (transient)
  'SYSTEM_NOTIFICATION',
  // Session events (transient)
  'SESSION_INFO',
  'CONTEXT_WINDOW',
  // Error events (transient)
  'AGENT_ERROR',
  // Event visibility updates (transient)
  'EVENT_UPDATED',
  // MCP events (transient)
  'MCP_CONFIG_CHANGED', // When project MCP config changes
  'MCP_SERVER_STATUS_CHANGED', // When MCP server starts/stops/fails
] as const;

// Derive LaceEventType union from the array
export type LaceEventType = (typeof EVENT_TYPES)[number];

// Helper to check if an event type is transient (not persisted to database)
export function isTransientEventType(type: LaceEventType): boolean {
  // These event types are always transient, even if they have a threadId
  return [
    'AGENT_TOKEN',
    'AGENT_STREAMING',
    'AGENT_STATE_CHANGE',
    'TOKEN_USAGE_UPDATE',
    'COMPACTION_START',
    'COMPACTION_COMPLETE',
    // Agent lifecycle events
    'AGENT_SPAWNED',
    'AGENT_SUMMARY_UPDATED',
    // Project events
    'PROJECT_CREATED',
    'PROJECT_UPDATED',
    'PROJECT_DELETED',
    // System events
    'SYSTEM_NOTIFICATION',
    // Session events
    'SESSION_INFO',
    'CONTEXT_WINDOW',
    // Error events
    'AGENT_ERROR',
    // Event visibility updates
    'EVENT_UPDATED',
    // MCP events
    'MCP_CONFIG_CHANGED',
    'MCP_SERVER_STATUS_CHANGED',
  ].includes(type);
}

/**
 * Event Category Helpers
 *
 * Lace events fall into three semantic categories:
 *
 * 1. CONVERSATION EVENTS: Persisted and shown in timeline
 *    - USER_MESSAGE, AGENT_MESSAGE, TOOL_CALL, TOOL_RESULT, etc.
 *    - These form the core conversation history
 *
 * 2. TRANSIENT EVENTS: Not persisted, used for real-time updates
 *    - AGENT_TOKEN, AGENT_STREAMING, AGENT_STATE_CHANGE, etc.
 *    - These provide live feedback but aren't part of history
 *
 * 3. INTERNAL WORKFLOW EVENTS: Persisted but not shown in timeline
 *    - TOOL_APPROVAL_REQUEST, TOOL_APPROVAL_RESPONSE
 *    - These support features like resumable approvals after restart
 */

/**
 * Check if an event is part of internal workflow (persisted but not shown)
 * These events are persisted for system functionality but hidden from users
 */
export function isInternalWorkflowEvent(type: LaceEventType): boolean {
  return ['TOOL_APPROVAL_REQUEST', 'TOOL_APPROVAL_RESPONSE'].includes(type);
}

/**
 * Check if an event should be shown in the conversation timeline
 * These are the events that form the visible conversation history
 */
export function isConversationEvent(type: LaceEventType): boolean {
  // Conversation events are those that are:
  // 1. Not transient (persisted to database)
  // 2. Not internal workflow events (hidden from timeline)
  return !isTransientEventType(type) && !isInternalWorkflowEvent(type);
}

// Tool approval event data types
export interface ToolApprovalRequestData {
  toolCallId: string;
}

export interface ToolApprovalResponseData {
  toolCallId: string;
  decision: ApprovalDecision;
}

// Base interface for common properties
interface BaseLaceEvent {
  id?: string; // Optional - generated by ThreadManager if not provided
  timestamp?: Date; // Optional - generated by ThreadManager if not provided
  // New fields for unified event system
  transient?: boolean; // If true, don't persist to database
  visibleToModel?: boolean; // If false, event is not sent to model (compacted away)
  context?: {
    sessionId?: string;
    projectId?: string;
    taskId?: string;
    threadId?: string; // For agent/conversational events - replaces top-level threadId
    systemMessage?: boolean; // For system-level messages that don't belong to any specific thread
  };
}

// Agent message data with optional token usage
export interface AgentMessageData {
  content: string;
  tokenUsage?: CombinedTokenUsage;
}

// Data types for transient events
interface AgentTokenData {
  token: string;
}

interface AgentStreamingData {
  content: string;
}

export interface AgentStateChangeData {
  agentId: ThreadId;
  from: string;
  to: string;
}

export interface TokenUsageUpdateData {
  tokenUsage: CombinedTokenUsage;
}

interface CompactionStartData {
  auto: boolean;
}

interface CompactionCompleteData {
  success: boolean;
  error?: string;
  summary?: string;
  originalEventCount?: number;
  compactedEventCount?: number;
}

// Agent lifecycle event data
interface AgentSpawnedData {
  type: 'agent:spawned'; // For compatibility with existing handlers
  taskId?: string;
  agentThreadId: string;
  providerInstanceId: string;
  modelId: string;
  context: {
    actor: string;
    isHuman: boolean;
  };
  timestamp: Date;
}

export interface AgentSummaryUpdatedData {
  summary: string;
  agentThreadId: string;
  timestamp: Date;
}

export function isAgentSummaryUpdatedData(value: unknown): value is AgentSummaryUpdatedData {
  if (!value || typeof value !== 'object') return false;
  const obj = value as Record<string, unknown>;
  return (
    typeof obj.summary === 'string' &&
    typeof obj.agentThreadId === 'string' &&
    (obj.timestamp instanceof Date || typeof obj.timestamp === 'string')
  );
}

// Project event data types
interface ProjectCreatedData {
  projectId: string;
  project: {
    id: string;
    name: string;
    description?: string;
    path: string;
  };
  context: {
    actor: string;
    isHuman: boolean;
  };
  timestamp: Date;
  type: 'project:created'; // For compatibility
}

interface ProjectUpdatedData {
  projectId: string;
  project: {
    id: string;
    name: string;
    description?: string;
    path: string;
  };
  context: {
    actor: string;
    isHuman: boolean;
  };
  timestamp: Date;
  type: 'project:updated'; // For compatibility
}

interface ProjectDeletedData {
  projectId: string;
  project: {
    id: string;
    name: string;
    description?: string;
    path: string;
  };
  context: {
    actor: string;
    isHuman: boolean;
  };
  timestamp: Date;
  type: 'project:deleted'; // For compatibility
}

// System event data
interface SystemNotificationData {
  message: string;
  severity: 'info' | 'warning' | 'error';
  context: {
    actor: string;
    isHuman: boolean;
  };
  timestamp: Date;
  type: 'system:notification'; // For compatibility
}

// Session event data
export interface SessionInfoData {
  title: string;
  updatedAt?: Date;
  _meta?: Record<string, unknown>;
}

export interface ContextWindowData {
  used: number;
  size: number;
}

// MCP event data types
interface MCPConfigChangedData {
  serverId: string;
  action: 'created' | 'updated' | 'deleted';
  serverConfig?: MCPServerConfig;
}

interface MCPServerStatusChangedData {
  serverId: string;
  status: 'starting' | 'running' | 'stopped' | 'failed';
  error?: string;
}

// Error type definitions - centralized for reuse
export type ErrorType = 'provider_failure' | 'tool_execution' | 'processing_error' | 'timeout';
export type ErrorPhase =
  | 'provider_response'
  | 'tool_execution'
  | 'conversation_processing'
  | 'initialization';

// Runtime type guards
export function isErrorType(value: unknown): value is ErrorType {
  return (
    typeof value === 'string' &&
    [
      'provider_failure',
      'tool_execution',
      'processing_error',
      'timeout',
      'streaming_error',
    ].includes(value)
  );
}

export function isErrorPhase(value: unknown): value is ErrorPhase {
  return (
    typeof value === 'string' &&
    ['provider_response', 'tool_execution', 'conversation_processing', 'initialization'].includes(
      value
    )
  );
}

// Agent error data
export interface AgentErrorData {
  errorType: ErrorType;
  message: string;
  stack?: string;
  fullError?: unknown; // Full error object for structured access
  context: {
    phase: ErrorPhase;
    // Available from Agent
    providerName?: string; // From this.providerInstance?.providerName
    providerInstanceId?: string; // From thread metadata
    modelId?: string; // From thread metadata
    // For tool-related errors
    toolName?: string; // When phase === 'tool_execution'
    toolCallId?: string; // When phase === 'tool_execution'
    // Additional context
    workingDirectory?: string;
    retryAttempt?: number;
  };
  isRetryable: boolean;
  retryCount?: number;
}

// Event visibility update data
export interface EventUpdatedData {
  eventId: string;
  visibleToModel: boolean;
}

// Discriminated union for type-safe event handling
export type LaceEvent =
  | (BaseLaceEvent & {
      type: 'USER_MESSAGE';
      data: string;
    })
  | (BaseLaceEvent & {
      type: 'AGENT_MESSAGE';
      data: AgentMessageData;
    })
  | (BaseLaceEvent & {
      type: 'TOOL_CALL';
      data: ToolCall;
    })
  | (BaseLaceEvent & {
      type: 'TOOL_RESULT';
      data: ToolResult;
    })
  | (BaseLaceEvent & {
      type: 'TOOL_APPROVAL_REQUEST';
      data: ToolApprovalRequestData;
    })
  | (BaseLaceEvent & {
      type: 'TOOL_APPROVAL_RESPONSE';
      data: ToolApprovalResponseData;
    })
  | (BaseLaceEvent & {
      type: 'LOCAL_SYSTEM_MESSAGE';
      data: string;
    })
  | (BaseLaceEvent & {
      type: 'SYSTEM_PROMPT';
      data: string;
    })
  | (BaseLaceEvent & {
      type: 'USER_SYSTEM_PROMPT';
      data: string;
    })
  | (BaseLaceEvent & {
      type: 'AGENT_TOKEN';
      data: AgentTokenData;
    })
  | (BaseLaceEvent & {
      type: 'AGENT_STREAMING';
      data: AgentStreamingData;
    })
  | (BaseLaceEvent & {
      type: 'AGENT_STATE_CHANGE';
      data: AgentStateChangeData;
    })
  | (BaseLaceEvent & {
      type: 'TOKEN_USAGE_UPDATE';
      data: TokenUsageUpdateData;
    })
  | (BaseLaceEvent & {
      type: 'COMPACTION_START';
      data: CompactionStartData;
    })
  | (BaseLaceEvent & {
      type: 'COMPACTION_COMPLETE';
      data: CompactionCompleteData;
    })
  | (BaseLaceEvent & {
      type: 'AGENT_SPAWNED';
      data: AgentSpawnedData;
    })
  | (BaseLaceEvent & {
      type: 'AGENT_SUMMARY_UPDATED';
      data: AgentSummaryUpdatedData;
    })
  | (BaseLaceEvent & {
      type: 'PROJECT_CREATED';
      data: ProjectCreatedData;
    })
  | (BaseLaceEvent & {
      type: 'PROJECT_UPDATED';
      data: ProjectUpdatedData;
    })
  | (BaseLaceEvent & {
      type: 'PROJECT_DELETED';
      data: ProjectDeletedData;
    })
  | (BaseLaceEvent & {
      type: 'SYSTEM_NOTIFICATION';
      data: SystemNotificationData;
    })
  | (BaseLaceEvent & {
      type: 'SESSION_INFO';
      data: SessionInfoData;
    })
  | (BaseLaceEvent & {
      type: 'CONTEXT_WINDOW';
      data: ContextWindowData;
    })
  | (BaseLaceEvent & {
      type: 'AGENT_ERROR';
      data: AgentErrorData;
    })
  | (BaseLaceEvent & {
      type: 'EVENT_UPDATED';
      data: EventUpdatedData;
    })
  | (BaseLaceEvent & {
      type: 'MCP_CONFIG_CHANGED';
      data: MCPConfigChangedData;
    })
  | (BaseLaceEvent & {
      type: 'MCP_SERVER_STATUS_CHANGED';
      data: MCPServerStatusChangedData;
    });

export interface Thread {
  id: string;
  sessionId?: string;
  projectId?: string;
  createdAt: Date;
  updatedAt: Date;
  events: LaceEvent[];
  metadata?: {
    name?: string;
    isSession?: boolean;
    provider?: string;
    model?: string;
    previousResponseId?: string; // Last response id from the previous provider request (OpenAI Responses API conversation chaining; Anthropic cache-diagnosis)
    [key: string]: unknown;
  };
}

// Branded type for thread IDs
export type ThreadId = string & { readonly __brand: 'ThreadId' };

// Type guard
export function isThreadId(value: string): value is ThreadId {
  return /^lace_\d{8}_[a-z0-9]{6}(\.\d+)*$/.test(value);
}

// Constructor with validation
export function asThreadId(value: string): ThreadId {
  if (!isThreadId(value)) {
    throw new Error(`Invalid thread ID format: ${value}`);
  }
  return value;
}

// For new agent specifications
// Format: "new:persona" | "new:persona;fast" | "new:persona;smart" | "new:persona;provider:model"
export type NewAgentSpec = string & { readonly __brand: 'NewAgentSpec' };

export function isNewAgentSpec(value: string): value is NewAgentSpec {
  // Must start with "new:" followed by persona, optionally followed by ;modelSpec
  // Persona cannot contain forward slashes (to reject old format)
  return /^new:[^;/]+(;.*)?$/.test(value);
}

export function createNewAgentSpec(persona: string, modelSpec?: string): NewAgentSpec {
  if (!modelSpec) {
    return `new:${persona}` as NewAgentSpec;
  }
  return `new:${persona};${modelSpec}` as NewAgentSpec;
}

export interface ParsedNewAgentSpec {
  persona: string;
  modelSpec?: string; // undefined | 'fast' | 'smart' | 'instanceId:modelId'
}

export function parseNewAgentSpec(spec: NewAgentSpec): ParsedNewAgentSpec {
  const match = spec.match(/^new:([^;]+)(;(.*))?$/);
  if (!match) {
    throw new Error(
      `Invalid NewAgentSpec format: ${spec}. Expected format: new:persona[;modelSpec]`
    );
  }

  const persona = match[1];
  const modelSpec = match[3]; // The part after semicolon, if any

  return {
    persona,
    modelSpec,
  };
}

// Unsafe cast for internal use only (e.g., when we know format is correct)
export function asNewAgentSpec(value: string): NewAgentSpec {
  return value as NewAgentSpec;
}

// Union type for task assignment
export type AssigneeId = ThreadId | NewAgentSpec | 'human';

export function isAssigneeId(value: string): value is AssigneeId {
  return isThreadId(value) || isNewAgentSpec(value) || value === 'human';
}

// Unsafe cast for internal use only (e.g., when we know format is correct)
export function asAssigneeId(value: string): AssigneeId {
  return value as AssigneeId;
}
