/**
 * Assistant entity types
 *
 * Assistants represent model + manually assembled context configurations.
 * They store inference parameters, tool references, and context source toggles.
 */

import * as z from 'zod'

import { UniqueModelIdSchema } from './model'
import { TagSchema } from './tag'

// ============================================================================
// Sub-Schemas
// ============================================================================

/** MCP server interaction mode */
export const McpModeSchema = z.enum(['disabled', 'auto', 'manual'])
export type McpMode = z.infer<typeof McpModeSchema>

/**
 * Assistant settings — inference parameters + context source toggles.
 * Stored as a single JSON column in the database.
 *
 * Default values are centralized in `DEFAULT_ASSISTANT_SETTINGS`.
 *
 * `enable*` flags control whether the corresponding parameter is sent to the model.
 * When `enable* = false`, the value is still stored but not used — the model's own
 * default takes effect. This removes the need for nullable value fields.
 */
export const AssistantSettingsSchema = z.object({
  // -- Inference parameters --
  /** from DEFAULT_TEMPERATURE */
  temperature: z.number().min(0).max(2),
  /** disabled = use model's own default */
  enableTemperature: z.boolean(),
  topP: z.number().min(0).max(1),
  enableTopP: z.boolean(),
  /** from DEFAULT_MAX_TOKENS */
  maxTokens: z.number().int().positive(),
  /** disabled = use model's own default */
  enableMaxTokens: z.boolean(),
  /** streaming provides better UX */
  streamOutput: z.boolean(),
  /** let model decide.
   *  String (not enum) because providers define custom values (e.g. 'xlow', 'high-reasoning'). */
  reasoning_effort: z.string(),
  // -- Tool use --
  mcpMode: McpModeSchema,
  maxToolCalls: z.number().int().positive(),
  enableMaxToolCalls: z.boolean(),

  // -- Context sources --
  enableWebSearch: z.boolean(),

  /** User-defined model parameters (e.g. {"top_k": 40, "repetition_penalty": 1.1}).
   *  Discriminated union on `type` ensures `value` is type-safe:
   *  - `string` → string value, rendered as text input
   *  - `number` → number value, rendered as number spinner
   *  - `boolean` → boolean value, rendered as toggle
   *  - `json` → arbitrary JSON value, rendered as JSON editor */
  customParameters: z.array(
    z.discriminatedUnion('type', [
      z.object({ name: z.string(), type: z.literal('string'), value: z.string() }),
      z.object({ name: z.string(), type: z.literal('number'), value: z.number() }),
      z.object({ name: z.string(), type: z.literal('boolean'), value: z.boolean() }),
      z.object({ name: z.string(), type: z.literal('json'), value: z.unknown() })
    ])
  )
})
export type AssistantSettings = z.infer<typeof AssistantSettingsSchema>

/** Pre-computed default settings object — avoids runtime parse() on every row conversion */
export const DEFAULT_ASSISTANT_SETTINGS: AssistantSettings = {
  temperature: 1.0,
  enableTemperature: false,
  topP: 1,
  enableTopP: false,
  maxTokens: 4096,
  enableMaxTokens: false,
  streamOutput: true,
  reasoning_effort: 'default',
  mcpMode: 'auto',
  maxToolCalls: 20,
  enableMaxToolCalls: true,
  enableWebSearch: false,
  customParameters: []
}

// ============================================================================
// Assistant Entity
// ============================================================================

/** Assistant entity ID — UUID v4, generated by the database on insert. */
export const AssistantIdSchema = z.uuidv4()

/**
 * Complete Assistant entity as returned by the API.
 *
 * Row mappers normalize DB-nullable fields before returning this entity.
 * `modelId` is explicitly `.nullable()` because an assistant may have no model set.
 */
export const AssistantSchema = z.strictObject({
  /** Assistant ID — UUID v4, generated by the database on insert. */
  id: AssistantIdSchema,
  /** Display name */
  name: z.string().min(1),
  /** System prompt text or prompt template ID reference */
  prompt: z.string(),
  /** Emoji icon for UI display */
  emoji: z.emoji(),
  /** Long-form description */
  description: z.string(),
  /** Inference settings — model params + context toggles */
  settings: AssistantSettingsSchema,
  /** Default/primary model ID in UniqueModelId format ("providerId::modelId") */
  modelId: UniqueModelIdSchema.nullable(),
  /** Persistent ordering key. Read-only; modified only through order endpoints. */
  orderKey: z.string(),
  /** Ordered MCP server IDs linked through assistant_mcp_server */
  mcpServerIds: z.array(z.string()),
  /** Ordered knowledge base IDs linked through assistant_knowledge_base */
  knowledgeBaseIds: z.array(z.string()),
  /** Creation timestamp (ISO string).
   *  DB column is nullable (SQLite integer), but rowToAssistant always provides a
   *  fallback so the API-level type is non-optional. */
  createdAt: z.iso.datetime(),
  /** Last update timestamp (ISO string). Same nullable-at-DB / non-null-at-API pattern. */
  updatedAt: z.iso.datetime(),
  /** Tags associated with this assistant (embedded via inline join in list/get endpoints) */
  tags: z.array(TagSchema),
  /**
   * Human-readable model name resolved from `user_model.name` at read time.
   * Read-only embedded field — edits go through `modelId`. Renderer consumers
   * (list / grid / card) display this directly instead of re-resolving the
   * `providerId::modelId` unique id against provider state.
   * `null` when the model row is missing (e.g. user removed the model after
   * binding).
   */
  modelName: z.string().nullable()
})
export type Assistant = z.infer<typeof AssistantSchema>
