/**
 * Backend API contract types — shared between Electron main/renderer and the backend.
 *
 * Co-locates Zod schemas with inferred TypeScript types so the same definitions
 * serve both compile-time type-safety and runtime validation of API responses.
 *
 * @see AI_REFACTOR_PLAN.md — Phase 1, Step 1.1
 */
import { z } from 'zod';

// ---------------------------------------------------------------------------
// Billing & Auth
// ---------------------------------------------------------------------------

export const BillingTierSchema = z.enum(['free', 'pro', 'power', 'team']);
export type BillingTier = z.infer<typeof BillingTierSchema>;

export const AuthTokensSchema = z.object({
  accessToken: z.string(),
  refreshToken: z.string(),
  /** Unix epoch milliseconds (backend returns `exp * 1000`). */
  expiresAt: z.number().int(),
});
export type AuthTokens = z.infer<typeof AuthTokensSchema>;

export const UserProfileSchema = z.object({
  id: z.string(),
  email: z.string().email(),
  name: z.string().nullable().optional(),
  surname: z.string().nullable().optional(),
  tier: BillingTierSchema,
  avatarUrl: z.string().url().nullable().optional(),
  hasPassword: z.boolean().default(true),
  onboardingCompletedAt: z.number().int().nullable().optional(),
  memory: z.record(z.string(), z.string()).default({}),
});
export type UserProfile = z.infer<typeof UserProfileSchema>;

// ---------------------------------------------------------------------------
// WebSocket Frames — Bidirectional Protocol
// ---------------------------------------------------------------------------

/** Actions the backend can request the Electron client to execute via Drizzle. */
export const ToolCallActionSchema = z.enum([
  'select',
  'count',
  'get',
  'insert',
  'update',
  'delete',
  'propose_note_edit',
  'list_directory',
  'read_file_content',
  'get_file_metadata',
  'read_project_folder_manifest',
  'read_project_folder_file',
  'list_projects_with_folder_manifests',
]);
export type ToolCallAction = z.infer<typeof ToolCallActionSchema>;

// --- Client → Server frames ------------------------------------------------

export const WsToolResultSchema = z.object({
  type: z.literal('tool_result'),
  id: z.string(),
  row: z.record(z.string(), z.unknown()).optional(),
  rows: z.array(z.record(z.string(), z.unknown())).optional(),
  results: z
    .array(z.object({ id: z.string(), content: z.string(), score: z.number() }))
    .optional(),
  deleted: z.boolean().optional(),
  ok: z.boolean().optional(),
  error: z.string().optional(),
});
export type WsToolResult = z.infer<typeof WsToolResultSchema>;

/**
 * First frame sent by Electron on the persistent device WS connection.
 * Identifies the device and the agent configs it owns.
 */
export const WsDeviceHelloSchema = z.object({
  type: z.literal('device_hello'),
  deviceId: z.string(),
  agentIds: z.array(z.string()),
});
export type WsDeviceHello = z.infer<typeof WsDeviceHelloSchema>;

// --- V3 Chat frames — Client → Server ----------------------------------------

export const WsHomeRequestSchema = z.object({
  type: z.literal('home_request'),
  message: z.string(),
  conversationHistory: z
    .array(z.object({ role: z.enum(['user', 'assistant', 'system']), content: z.string() }))
    .optional(),
});
export type WsHomeRequest = z.infer<typeof WsHomeRequestSchema>;

export const WsFloatingRequestSchema = z.object({
  type: z.literal('floating_request'),
  message: z.string(),
  scope: z.object({
    type: z.enum(['task', 'project', 'note', 'timeline']),
    id: z.string().optional(),
  }),
  conversationHistory: z
    .array(z.object({ role: z.enum(['user', 'assistant', 'system']), content: z.string() }))
    .optional(),
});
export type WsFloatingRequest = z.infer<typeof WsFloatingRequestSchema>;

// --- Journey frames — Client → Server ----------------------------------------

/** Start a setup journey for custom prompt creation. */
export const WsJourneyStartSchema = z.object({
  type: z.literal('journey_start'),
  sessionId: z.string(),
  agentType: z.enum(['local_directory', 'gmail', 'teams', 'outlook']),
  directory: z.string().optional(),
  dataTypes: z.array(z.string()),
  existingConfig: z.string().nullable().optional(),
});
export type WsJourneyStart = z.infer<typeof WsJourneyStartSchema>;

/** Send a user reply during a journey conversation. */
export const WsJourneyMessageSchema = z.object({
  type: z.literal('journey_message'),
  sessionId: z.string(),
  message: z.string(),
});
export type WsJourneyMessage = z.infer<typeof WsJourneyMessageSchema>;

export const WsBriefRequestSchema = z.object({
  type: z.literal('brief_request'),
  requestId: z.string().optional(),
  sessionId: z.string().optional(),
  mode: z.enum(['home', 'project']),
  projectId: z.string().nullable().optional(),
});
export type WsBriefRequest = z.infer<typeof WsBriefRequestSchema>;

export const WsTaskBriefRequestSchema = z.object({
  type: z.literal('task_brief_request'),
  requestId: z.string().optional(),
  sessionId: z.string().optional(),
  taskId: z.string(),
});
export type WsTaskBriefRequest = z.infer<typeof WsTaskBriefRequestSchema>;

export const WsClientFrameSchema = z.discriminatedUnion('type', [
  WsToolResultSchema,
  WsDeviceHelloSchema,
  WsHomeRequestSchema,
  WsFloatingRequestSchema,
  WsBriefRequestSchema,
  WsTaskBriefRequestSchema,
  WsJourneyStartSchema,
  WsJourneyMessageSchema,
]);
export type WsClientFrame = z.infer<typeof WsClientFrameSchema>;

// --- Server → Client frames ------------------------------------------------

export const RunContextSchema = z.object({
  type: z.literal('agent_batch'),
  runId: z.string(),
  agentId: z.string(),
});
export type RunContext = z.infer<typeof RunContextSchema>;

export const WsToolCallSchema = z.object({
  type: z.literal('tool_call'),
  id: z.string(),
  action: ToolCallActionSchema,
  table: z.string().optional(),
  data: z.record(z.string(), z.unknown()).optional(),
  filters: z.record(z.string(), z.unknown()).optional(),
  vector: z.array(z.number()).optional(),
  limit: z.number().int().optional(),
  /** Present on tool calls originating from a scheduled agent batch run. */
  runContext: RunContextSchema.optional(),
});
export type WsToolCall = z.infer<typeof WsToolCallSchema>;

export const WsPingSchema = z.object({
  type: z.literal('ping'),
});
export type WsPing = z.infer<typeof WsPingSchema>;

// --- V3 Chat frames — Server → Client ----------------------------------------

export const WsStreamStartSchema = z.object({
  type: z.literal('stream_start'),
  requestId: z.string(),
});
export type WsStreamStart = z.infer<typeof WsStreamStartSchema>;

export const WsStreamTextSchema = z.object({
  type: z.literal('stream_text'),
  requestId: z.string(),
  chunk: z.string(),
});
export type WsStreamText = z.infer<typeof WsStreamTextSchema>;

export const WsStreamEndSchema = z.object({
  type: z.literal('stream_end'),
  requestId: z.string(),
  mutations: z.union([
    z.record(z.string(), z.unknown()),
    z.array(z.unknown()),
  ]).nullish(),
  error: z.string().nullish(),
});
export type WsStreamEnd = z.infer<typeof WsStreamEndSchema>;

export const WsFloatingDomainSchema = z.object({
  type: z.literal('floating_domain'),
  requestId: z.string(),
  domain: z.union([
    z.enum(['tasks', 'notes', 'timelines', 'projects']),
    z.object({
      type: z.enum(['task', 'timeline', 'project', 'note', 'node']),
      id: z.string().nullable().optional(),
      section: z.enum(['task', 'timeline', 'note']).nullable().optional(),
    }),
  ]),
});
export type WsFloatingDomain = z.infer<typeof WsFloatingDomainSchema>;

// --- Journey frames — Server → Client ----------------------------------------

/** Server reply during a setup journey conversation. */
export const WsJourneyReplySchema = z.object({
  type: z.literal('journey_reply'),
  sessionId: z.string(),
  message: z.string(),
  done: z.boolean(),
  /** Serialised AgentConfig JSON string produced by the journey when done=true. */
  agentConfig: z.string().nullable().optional(),
});
export type WsJourneyReply = z.infer<typeof WsJourneyReplySchema>;

// --- V3 Block data interfaces ------------------------------------------------

export interface ChartBlockData {
  chartType: 'area' | 'bar' | 'line' | 'pie' | 'radar' | 'radial';
  title: string;
  data: Record<string, unknown>[];
  config: Record<string, { label: string; color: string }>;
}

export interface EntityRefBlockData {
  entity: 'task' | 'project' | 'note' | 'timeline' | 'timelineEvent';
  /** Live IDs — component fetches real data from local DB. */
  ids: string[];
  /** @deprecated Legacy inline items — prefer `ids` for live data. */
  items?: Record<string, unknown>[];
}

export interface TableBlockData {
  headers: string[];
  rows: string[][];
}

export interface TimelineBlockData {
  events: {
    id: string;
    title: string;
    date: number;
    endDate?: number;
    projectId?: string | null;
    isCompleted?: number;
    isAiSuggested?: number;
  }[];
}

/** Sent by the backend when a scheduled agent batch run finishes. */
export const WsRunCompleteSchema = z.object({
  type: z.literal('run_complete'),
  runContext: RunContextSchema,
  status: z.enum(['success', 'error', 'partial']),
});
export type WsRunComplete = z.infer<typeof WsRunCompleteSchema>;

export const WsIndexFileResultSchema = z.object({
  type: z.literal('index_file_result'),
  sessionId: z.string(),
  relPath: z.string(),
  summary: z.string().nullable().optional(),
  tokensUsed: z.number().int().nonnegative().default(0),
  error: z.string().optional(),
});
export type WsIndexFileResult = z.infer<typeof WsIndexFileResultSchema>;

export const WsIndexSessionProgressSchema = z.object({
  type: z.literal('index_session_progress'),
  sessionId: z.string(),
  processed: z.number().int().nonnegative(),
  total: z.number().int().nonnegative(),
});
export type WsIndexSessionProgress = z.infer<typeof WsIndexSessionProgressSchema>;

export const WsIndexSessionDoneSchema = z.object({
  type: z.literal('index_session_done'),
  sessionId: z.string(),
  status: z.enum(['completed', 'cancelled', 'quota_exceeded', 'error']),
});
export type WsIndexSessionDone = z.infer<typeof WsIndexSessionDoneSchema>;

export const WsServerFrameSchema = z.discriminatedUnion('type', [
  WsToolCallSchema,
  WsPingSchema,
  WsStreamStartSchema,
  WsStreamTextSchema,
  WsStreamEndSchema,
  WsFloatingDomainSchema,
  WsJourneyReplySchema,
  WsRunCompleteSchema,
  WsIndexFileResultSchema,
  WsIndexSessionProgressSchema,
  WsIndexSessionDoneSchema,
]);
export type WsServerFrame = z.infer<typeof WsServerFrameSchema>;

// ---------------------------------------------------------------------------
// Agent Manifests & Permissions
// ---------------------------------------------------------------------------

export const AgentManifestSchema = z.object({
  name: z.string(),
  description: z.string(),
  permissions: z.array(z.string()),
  schedule: z.string().optional(),
});
export type AgentManifest = z.infer<typeof AgentManifestSchema>;

// ---------------------------------------------------------------------------
// Agent REST API — response types (Phase 3, Step 3.4)
// ---------------------------------------------------------------------------

/** An item in the agent catalog returned by GET /api/v1/agents/catalog. */
export const AgentCatalogItemSchema = z.object({
  id: z.string(),
  name: z.string(),
  type: z.enum(['local_directory', 'gmail', 'teams', 'outlook']),
  description: z.string(),
  /** Cloud provider identifier (e.g. 'gmail', 'teams', 'outlook'). */
  provider: z.string().optional(),
  supportedDataTypes: z.array(z.string()),
});
export type AgentCatalogItem = z.infer<typeof AgentCatalogItemSchema>;

/** A configured local directory agent stored on the backend. */
export const LocalAgentConfigSchema = z.object({
  id: z.string(),
  userId: z.string(),
  deviceId: z.string(),
  name: z.string(),
  directoryPaths: z.array(z.string()),
  dataTypes: z.array(z.string()),
  agentConfig: z.record(z.string(), z.unknown()).nullable(),
  scheduleCron: z.string(),
  enabled: z.boolean(),
  lastRunAt: z.number().int().nullable().optional(),
  createdAt: z.number().int(),
  updatedAt: z.number().int(),
});
export type LocalAgentConfig = z.infer<typeof LocalAgentConfigSchema>;

/** A configured cloud connector agent stored on the backend. */
export const CloudAgentConfigSchema = z.object({
  id: z.string(),
  userId: z.string(),
  provider: z.enum(['gmail', 'teams', 'outlook']),
  name: z.string(),
  dataTypes: z.array(z.string()),
  promptTemplate: z.string(),
  scheduleCron: z.string(),
  filterConfig: z.record(z.string(), z.unknown()).optional(),
  enabled: z.boolean(),
  lastRunAt: z.number().int().nullable().optional(),
  createdAt: z.number().int(),
  updatedAt: z.number().int(),
});
export type CloudAgentConfig = z.infer<typeof CloudAgentConfigSchema>;

/** A single agent run log entry returned by GET /api/v1/agents/runs. */
export const AgentRunLogSchema = z.object({
  id: z.string(),
  agentId: z.string(),
  agentType: z.enum(['local', 'cloud']),
  userId: z.string(),
  status: z.enum(['running', 'success', 'error', 'partial']),
  itemsProcessed: z.number().int(),
  itemsCreated: z.number().int(),
  errors: z.array(z.string()),
  startedAt: z.number().int(),
  completedAt: z.number().int().nullable().optional(),
});
export type AgentRunLog = z.infer<typeof AgentRunLogSchema>;

// ---------------------------------------------------------------------------
// Note Edits — AI-proposed changes pending human approval
// ---------------------------------------------------------------------------

export const NoteEditSchema = z.object({
  id: z.string(),
  noteId: z.string(),
  type: z.enum(['append', 'insert', 'replace']),
  anchorBefore: z.string().nullable().optional(),
  anchorText: z.string().nullable().optional(),
  proposedContent: z.string(),
  status: z.enum(['pending', 'approved', 'rejected']),
  agentId: z.string().nullable().optional(),
  runId: z.string().nullable().optional(),
  reasoning: z.string().nullable().optional(),
  createdAt: z.number().int(),
  resolvedAt: z.number().int().nullable().optional(),
});
export type NoteEdit = z.infer<typeof NoteEditSchema>;

/** Response from POST /api/v1/agents/journey/start or /journey/message. */
export const JourneyMessageSchema = z.object({
  sessionId: z.string(),
  message: z.string(),
  done: z.boolean(),
  /** Serialised AgentConfig JSON string — present on the final message when `done === true`. */
  agentConfig: z.string().optional(),
});
export type JourneyMessage = z.infer<typeof JourneyMessageSchema>;
