import { authTables } from '@convex-dev/auth/server';
import { defineSchema, defineTable } from 'convex/server';
import { v } from 'convex/values';

const applicationTables = {
  /*
   * Below is the users table definition from authTables
   * You can add additional fields here. You can also remove
   * the users table here & create a 'profiles' table if you
   * prefer to keep auth data separate from application data.
   */
  users: defineTable({
    name: v.optional(v.string()),
    image: v.optional(v.string()),
    email: v.optional(v.string()),
    emailVerificationTime: v.optional(v.number()),
    phone: v.optional(v.string()),
    phoneVerificationTime: v.optional(v.number()),
    isAnonymous: v.optional(v.boolean()),
    /* Fields below here are custom & not defined in authTables */
    isAdmin: v.optional(v.boolean()),
    role: v.optional(v.union(v.literal('owner'), v.literal('member'))),
    lastSeenAt: v.optional(v.number()),
    themePreference: v.optional(
      v.union(v.literal('light'), v.literal('dark'), v.literal('system')),
    ),
  })
    .index('email', ['email'])
    .index('phone', ['phone'])
    /* Indexes below here are custom & not defined in authTables */
    .index('name', ['name']),
  gitConnections: defineTable({
    userId: v.id('users'),
    provider: v.union(
      v.literal('github'),
      v.literal('gitea'),
      v.literal('gitlab'),
      v.literal('other'),
    ),
    providerAccountId: v.optional(v.string()),
    displayName: v.string(),
    username: v.optional(v.string()),
    avatarUrl: v.optional(v.string()),
    installationId: v.optional(v.string()),
    scopes: v.optional(v.array(v.string())),
    status: v.union(
      v.literal('active'),
      v.literal('needs_reauth'),
      v.literal('revoked'),
    ),
    connectedAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_user', ['userId'])
    .index('by_user_provider', ['userId', 'provider'])
    .index('by_status', ['status']),
  spoons: defineTable({
    ownerId: v.id('users'),
    name: v.string(),
    description: v.optional(v.string()),
    provider: v.union(
      v.literal('github'),
      v.literal('gitea'),
      v.literal('gitlab'),
      v.literal('other'),
    ),
    upstreamOwner: v.string(),
    upstreamRepo: v.string(),
    upstreamDefaultBranch: v.string(),
    upstreamUrl: v.string(),
    forkOwner: v.optional(v.string()),
    forkRepo: v.optional(v.string()),
    forkDefaultBranch: v.optional(v.string()),
    forkUrl: v.optional(v.string()),
    visibility: v.union(
      v.literal('public'),
      v.literal('private'),
      v.literal('internal'),
      v.literal('unknown'),
    ),
    maintenanceMode: v.union(
      v.literal('watch'),
      v.literal('auto_pr'),
      v.literal('paused'),
    ),
    syncCadence: v.union(
      v.literal('daily'),
      v.literal('weekly'),
      v.literal('manual'),
    ),
    productionRefStrategy: v.union(
      v.literal('default_branch'),
      v.literal('latest_release'),
      v.literal('tag_pattern'),
    ),
    tagPattern: v.optional(v.string()),
    status: v.union(
      v.literal('draft'),
      v.literal('active'),
      v.literal('needs_connection'),
      v.literal('paused'),
      v.literal('archived'),
    ),
    lastCheckedAt: v.optional(v.number()),
    lastUpstreamCommit: v.optional(v.string()),
    lastForkCommit: v.optional(v.string()),
    connectionId: v.optional(v.id('gitConnections')),
    githubInstallationId: v.optional(v.string()),
    githubRepositoryId: v.optional(v.number()),
    upstreamRepositoryId: v.optional(v.number()),
    syncStatus: v.optional(
      v.union(
        v.literal('unknown'),
        v.literal('up_to_date'),
        v.literal('behind'),
        v.literal('ahead'),
        v.literal('diverged'),
        v.literal('checking'),
        v.literal('conflict'),
        v.literal('error'),
      ),
    ),
    upstreamAheadBy: v.optional(v.number()),
    forkAheadBy: v.optional(v.number()),
    lastMergeBaseCommit: v.optional(v.string()),
    lastSyncRunId: v.optional(v.id('syncRuns')),
    lastAiReviewId: v.optional(v.id('aiReviews')),
    lastGithubRefreshAt: v.optional(v.number()),
    lastSuccessfulRefreshAt: v.optional(v.number()),
    lastError: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_owner', ['ownerId'])
    .index('by_owner_status', ['ownerId', 'status'])
    .index('by_owner_provider', ['ownerId', 'provider'])
    .index('by_upstream', ['provider', 'upstreamOwner', 'upstreamRepo']),
  spoonRepositoryStates: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    upstreamFullName: v.string(),
    forkFullName: v.string(),
    upstreamDefaultBranch: v.string(),
    forkDefaultBranch: v.string(),
    upstreamHeadSha: v.optional(v.string()),
    forkHeadSha: v.optional(v.string()),
    mergeBaseSha: v.optional(v.string()),
    upstreamAheadBy: v.number(),
    forkAheadBy: v.number(),
    status: v.union(
      v.literal('up_to_date'),
      v.literal('behind'),
      v.literal('ahead'),
      v.literal('diverged'),
      v.literal('unknown'),
    ),
    openForkPullRequestCount: v.number(),
    openUpstreamPullRequestCount: v.number(),
    lastCommitAt: v.optional(v.number()),
    rawCompareUrl: v.optional(v.string()),
    refreshedAt: v.number(),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId'])
    .index('by_status', ['ownerId', 'status']),
  spoonCommits: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    sha: v.string(),
    side: v.union(v.literal('upstream'), v.literal('fork')),
    message: v.string(),
    authorName: v.optional(v.string()),
    authorEmail: v.optional(v.string()),
    authorLogin: v.optional(v.string()),
    committedAt: v.optional(v.number()),
    htmlUrl: v.optional(v.string()),
    filesChanged: v.optional(v.number()),
    additions: v.optional(v.number()),
    deletions: v.optional(v.number()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_spoon_side', ['spoonId', 'side'])
    .index('by_owner', ['ownerId'])
    .index('by_sha', ['spoonId', 'sha'])
    .index('by_committed', ['spoonId', 'committedAt']),
  spoonPullRequests: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    githubId: v.number(),
    number: v.number(),
    repoFullName: v.string(),
    scope: v.union(
      v.literal('fork'),
      v.literal('upstream'),
      v.literal('from_fork_to_upstream'),
    ),
    title: v.string(),
    state: v.union(v.literal('open'), v.literal('closed'), v.literal('merged')),
    draft: v.boolean(),
    authorLogin: v.optional(v.string()),
    baseRef: v.string(),
    headRef: v.string(),
    headRepoFullName: v.optional(v.string()),
    htmlUrl: v.string(),
    createdAtGithub: v.optional(v.number()),
    updatedAtGithub: v.optional(v.number()),
    mergedAtGithub: v.optional(v.number()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_spoon_scope', ['spoonId', 'scope'])
    .index('by_owner', ['ownerId'])
    .index('by_github_id', ['githubId'])
    .index('by_state', ['spoonId', 'state']),
  syncRuns: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    threadId: v.optional(v.id('threads')),
    kind: v.union(
      v.literal('scheduled_check'),
      v.literal('manual_check'),
      v.literal('upstream_update'),
      v.literal('merge_attempt'),
      v.literal('ai_review'),
    ),
    status: v.union(
      v.literal('queued'),
      v.literal('running'),
      v.literal('clean'),
      v.literal('conflict'),
      v.literal('needs_review'),
      v.literal('failed'),
      v.literal('merged'),
    ),
    upstreamFrom: v.optional(v.string()),
    upstreamTo: v.optional(v.string()),
    summary: v.optional(v.string()),
    aiAssessment: v.optional(v.string()),
    mergeRequestUrl: v.optional(v.string()),
    error: v.optional(v.string()),
    decision: v.optional(
      v.union(
        v.literal('auto_synced'),
        v.literal('thread_created'),
        v.literal('ignored'),
        v.literal('failed'),
      ),
    ),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_owner', ['ownerId'])
    .index('by_spoon', ['spoonId'])
    .index('by_owner_status', ['ownerId', 'status'])
    .index('by_created', ['createdAt']),
  aiReviews: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    syncRunId: v.optional(v.id('syncRuns')),
    model: v.string(),
    status: v.union(
      v.literal('queued'),
      v.literal('running'),
      v.literal('completed'),
      v.literal('failed'),
    ),
    reviewType: v.union(
      v.literal('upstream_update'),
      v.literal('manual_prompt'),
      v.literal('merge_safety'),
    ),
    inputSummary: v.string(),
    outputSummary: v.optional(v.string()),
    risk: v.union(
      v.literal('unknown'),
      v.literal('low'),
      v.literal('medium'),
      v.literal('high'),
    ),
    compatible: v.boolean(),
    requiresHumanReview: v.boolean(),
    recommendedAction: v.union(
      v.literal('sync'),
      v.literal('open_review_pr'),
      v.literal('manual_review'),
      v.literal('do_not_sync'),
      v.literal('unknown'),
    ),
    potentialConflicts: v.optional(v.array(v.string())),
    importantFiles: v.optional(v.array(v.string())),
    reasoningSummary: v.optional(v.string()),
    error: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
    completedAt: v.optional(v.number()),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId'])
    .index('by_status', ['ownerId', 'status'])
    .index('by_sync_run', ['syncRunId'])
    .index('by_created', ['createdAt']),
  spoonSettings: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    autoRefreshEnabled: v.boolean(),
    autoReviewEnabled: v.boolean(),
    autoSyncEnabled: v.boolean(),
    requireAiLowRiskForSync: v.boolean(),
    requireCleanCompareForSync: v.boolean(),
    ignoredFilePatterns: v.optional(v.array(v.string())),
    importantFilePatterns: v.optional(v.array(v.string())),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId']),
  spoonRemotes: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    label: v.string(),
    url: v.string(),
    remoteName: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId']),
  userAiSettings: defineTable({
    userId: v.id('users'),
    provider: v.literal('openai'),
    encryptedApiKey: v.optional(v.string()),
    apiKeyPreview: v.optional(v.string()),
    model: v.string(),
    reasoningEffort: v.union(
      v.literal('none'),
      v.literal('minimal'),
      v.literal('low'),
      v.literal('medium'),
      v.literal('high'),
      v.literal('xhigh'),
    ),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_user', ['userId'])
    .index('by_user_provider', ['userId', 'provider']),
  // Per-user dotfiles: one row per file, materialized into the workspace
  // container's HOME. Content is encrypted at rest (reuses secretCrypto).
  // `path` is relative to HOME, e.g. ".bashrc" or ".config/nvim/init.lua".
  userDotfiles: defineTable({
    ownerId: v.id('users'),
    path: v.string(),
    encryptedContent: v.string(),
    size: v.number(),
    isExecutable: v.optional(v.boolean()),
    updatedAt: v.number(),
  })
    .index('by_owner', ['ownerId'])
    .index('by_owner_path', ['ownerId', 'path']),
  // Per-user environment config: the persistent home username + an optional
  // public dotfiles repo and setup command run in the container.
  userEnvironment: defineTable({
    ownerId: v.id('users'),
    enabled: v.boolean(),
    homeUsername: v.optional(v.string()),
    dotfilesRepoUrl: v.optional(v.string()),
    dotfilesRepoRef: v.optional(v.string()),
    setupCommand: v.optional(v.string()),
    updatedAt: v.number(),
  }).index('by_owner', ['ownerId']),
  aiProviderProfiles: defineTable({
    ownerId: v.id('users'),
    name: v.string(),
    provider: v.union(
      v.literal('openai'),
      v.literal('anthropic'),
      v.literal('google'),
      v.literal('openrouter'),
      v.literal('requesty'),
      v.literal('litellm'),
      v.literal('cloudflare_ai_gateway'),
      v.literal('custom_openai_compatible'),
      v.literal('opencode_openai_login'),
    ),
    authType: v.union(
      v.literal('api_key'),
      v.literal('opencode_auth_json'),
      v.literal('none'),
    ),
    encryptedSecret: v.optional(v.string()),
    secretPreview: v.optional(v.string()),
    baseUrl: v.optional(v.string()),
    defaultModel: v.string(),
    modelOptions: v.optional(v.array(v.string())),
    reasoningEffort: v.union(
      v.literal('none'),
      v.literal('minimal'),
      v.literal('low'),
      v.literal('medium'),
      v.literal('high'),
      v.literal('xhigh'),
    ),
    enabled: v.boolean(),
    isDefault: v.optional(v.boolean()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_owner', ['ownerId'])
    .index('by_owner_provider', ['ownerId', 'provider'])
    .index('by_owner_enabled', ['ownerId', 'enabled']),
  agentRequests: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    agentJobId: v.optional(v.id('agentJobs')),
    prompt: v.string(),
    requestType: v.optional(
      v.union(
        v.literal('manual_prompt'),
        v.literal('upstream_review'),
        v.literal('future_code_change'),
      ),
    ),
    priority: v.optional(
      v.union(v.literal('low'), v.literal('normal'), v.literal('high')),
    ),
    source: v.optional(v.union(v.literal('user'), v.literal('system'))),
    status: v.union(
      v.literal('draft'),
      v.literal('queued'),
      v.literal('running'),
      v.literal('changes_ready'),
      v.literal('merge_request_opened'),
      v.literal('failed'),
      v.literal('cancelled'),
    ),
    targetBranch: v.optional(v.string()),
    selectedSecretIds: v.optional(v.array(v.id('spoonSecrets'))),
    baseBranch: v.optional(v.string()),
    requestedBranchName: v.optional(v.string()),
    mergeRequestUrl: v.optional(v.string()),
    summary: v.optional(v.string()),
    error: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_owner', ['ownerId'])
    .index('by_spoon', ['spoonId'])
    .index('by_owner_status', ['ownerId', 'status'])
    .index('by_created', ['createdAt']),
  spoonSecrets: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    name: v.string(),
    encryptedValue: v.string(),
    valuePreview: v.optional(v.string()),
    description: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId'])
    .index('by_name', ['spoonId', 'name']),
  spoonAgentSettings: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    enabled: v.boolean(),
    // Legacy records may contain openai_direct. New writes use opencode only.
    runtime: v.optional(
      v.union(v.literal('opencode'), v.literal('openai_direct')),
    ),
    defaultBaseBranch: v.optional(v.string()),
    branchPrefix: v.string(),
    installCommand: v.optional(v.string()),
    checkCommand: v.optional(v.string()),
    testCommand: v.optional(v.string()),
    agentModel: v.string(),
    reasoningEffort: v.union(
      v.literal('none'),
      v.literal('minimal'),
      v.literal('low'),
      v.literal('medium'),
      v.literal('high'),
      v.literal('xhigh'),
    ),
    maxJobDurationMs: v.number(),
    maxOutputBytes: v.number(),
    envFilePath: v.optional(
      v.union(
        v.literal('.env'),
        v.literal('.env.local'),
        v.literal('.env.production'),
        v.literal('.env.production.local'),
        v.literal('custom'),
      ),
    ),
    customEnvFilePath: v.optional(v.string()),
    materializeEnvFileByDefault: v.optional(v.boolean()),
    autoDetectCommands: v.optional(v.boolean()),
    allowUserFileEditing: v.optional(v.boolean()),
    aiProviderProfileId: v.optional(v.id('aiProviderProfiles')),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId']),
  agentJobs: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    agentRequestId: v.id('agentRequests'),
    threadId: v.optional(v.id('threads')),
    jobType: v.optional(
      v.union(
        v.literal('user_change'),
        v.literal('maintenance_review'),
        v.literal('conflict_resolution'),
      ),
    ),
    status: v.union(
      v.literal('queued'),
      v.literal('claimed'),
      v.literal('preparing'),
      v.literal('running'),
      v.literal('checks_running'),
      v.literal('changes_ready'),
      v.literal('draft_pr_opened'),
      v.literal('failed'),
      v.literal('cancelled'),
      v.literal('timed_out'),
    ),
    prompt: v.string(),
    // Legacy jobs may contain openai_direct. New jobs use opencode only.
    runtime: v.optional(
      v.union(v.literal('openai_direct'), v.literal('opencode')),
    ),
    workspaceStatus: v.optional(
      v.union(
        v.literal('not_started'),
        v.literal('starting'),
        v.literal('active'),
        v.literal('idle'),
        v.literal('stopped'),
        v.literal('expired'),
        v.literal('failed'),
      ),
    ),
    baseBranch: v.string(),
    workBranch: v.string(),
    opencodeSessionId: v.optional(v.string()),
    codexSessionId: v.optional(v.string()),
    agentRuntimeMode: v.optional(
      v.union(
        v.literal('opencode_server'),
        v.literal('codex_exec'),
        v.literal('legacy_cli'),
      ),
    ),
    containerId: v.optional(v.string()),
    workspaceUrl: v.optional(v.string()),
    workspaceExpiresAt: v.optional(v.number()),
    lastHeartbeatAt: v.optional(v.number()),
    envFilePath: v.optional(v.string()),
    materializeEnvFile: v.optional(v.boolean()),
    githubInstallationId: v.optional(v.string()),
    forkOwner: v.string(),
    forkRepo: v.string(),
    forkUrl: v.string(),
    upstreamOwner: v.string(),
    upstreamRepo: v.string(),
    selectedSecretIds: v.array(v.id('spoonSecrets')),
    aiProviderProfileId: v.optional(v.id('aiProviderProfiles')),
    model: v.string(),
    reasoningEffort: v.union(
      v.literal('none'),
      v.literal('minimal'),
      v.literal('low'),
      v.literal('medium'),
      v.literal('high'),
      v.literal('xhigh'),
    ),
    commitSha: v.optional(v.string()),
    pullRequestUrl: v.optional(v.string()),
    pullRequestNumber: v.optional(v.number()),
    summary: v.optional(v.string()),
    error: v.optional(v.string()),
    claimedBy: v.optional(v.string()),
    claimedAt: v.optional(v.number()),
    startedAt: v.optional(v.number()),
    completedAt: v.optional(v.number()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_owner', ['ownerId'])
    .index('by_spoon', ['spoonId'])
    .index('by_request', ['agentRequestId'])
    .index('by_status', ['status'])
    .index('by_claim', ['status', 'createdAt']),
  agentJobMessages: defineTable({
    jobId: v.id('agentJobs'),
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    role: v.union(
      v.literal('user'),
      v.literal('assistant'),
      v.literal('system'),
      v.literal('tool'),
    ),
    content: v.string(),
    status: v.union(
      v.literal('queued'),
      v.literal('streaming'),
      v.literal('completed'),
      v.literal('failed'),
    ),
    metadata: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_job', ['jobId'])
    .index('by_owner', ['ownerId']),
  agentWorkspaceUiStates: defineTable({
    jobId: v.id('agentJobs'),
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    openFilePaths: v.array(v.string()),
    activeFilePath: v.optional(v.string()),
    vimEnabled: v.boolean(),
    expandedDirectoryPaths: v.array(v.string()),
    agentThreadWidth: v.optional(v.number()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_job', ['jobId'])
    .index('by_owner', ['ownerId']),
  agentInteractionRequests: defineTable({
    jobId: v.id('agentJobs'),
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    runtime: v.union(v.literal('opencode'), v.literal('codex')),
    externalRequestId: v.string(),
    kind: v.union(
      v.literal('question'),
      v.literal('permission'),
      v.literal('tool_confirmation'),
    ),
    title: v.string(),
    body: v.string(),
    options: v.optional(v.array(v.string())),
    status: v.union(
      v.literal('pending'),
      v.literal('answered'),
      v.literal('approved'),
      v.literal('rejected'),
      v.literal('expired'),
    ),
    response: v.optional(v.string()),
    metadata: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_job', ['jobId'])
    .index('by_job_status', ['jobId', 'status'])
    .index('by_owner', ['ownerId']),
  agentWorkspaceChanges: defineTable({
    jobId: v.id('agentJobs'),
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    path: v.string(),
    source: v.union(
      v.literal('user'),
      v.literal('agent'),
      v.literal('command'),
    ),
    changeType: v.union(
      v.literal('added'),
      v.literal('modified'),
      v.literal('deleted'),
      v.literal('renamed'),
    ),
    diff: v.optional(v.string()),
    createdAt: v.number(),
  })
    .index('by_job', ['jobId'])
    .index('by_path', ['jobId', 'path']),
  agentJobEvents: defineTable({
    jobId: v.id('agentJobs'),
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    level: v.union(
      v.literal('debug'),
      v.literal('info'),
      v.literal('warn'),
      v.literal('error'),
    ),
    phase: v.union(
      v.literal('queued'),
      v.literal('clone'),
      v.literal('plan'),
      v.literal('edit'),
      v.literal('install'),
      v.literal('check'),
      v.literal('test'),
      v.literal('commit'),
      v.literal('push'),
      v.literal('pr'),
      v.literal('cleanup'),
    ),
    message: v.string(),
    metadata: v.optional(v.string()),
    createdAt: v.number(),
  })
    .index('by_job', ['jobId'])
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId']),
  agentJobArtifacts: defineTable({
    jobId: v.id('agentJobs'),
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    kind: v.union(
      v.literal('plan'),
      v.literal('diff'),
      v.literal('test_output'),
      v.literal('summary'),
      v.literal('error'),
      v.literal('pr_body'),
    ),
    title: v.string(),
    content: v.string(),
    contentType: v.union(
      v.literal('text/markdown'),
      v.literal('text/plain'),
      v.literal('application/json'),
      v.literal('text/x-diff'),
    ),
    createdAt: v.number(),
  })
    .index('by_job', ['jobId'])
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId']),
  threads: defineTable({
    ownerId: v.id('users'),
    spoonId: v.optional(v.id('spoons')),
    title: v.string(),
    summary: v.optional(v.string()),
    source: v.union(
      v.literal('user_request'),
      v.literal('upstream_update'),
      v.literal('merge_conflict'),
      v.literal('manual_review'),
      v.literal('system'),
    ),
    status: v.union(
      v.literal('open'),
      v.literal('queued'),
      v.literal('running'),
      v.literal('waiting_for_user'),
      v.literal('changes_ready'),
      v.literal('draft_pr_opened'),
      v.literal('resolved'),
      v.literal('ignored'),
      v.literal('failed'),
      v.literal('cancelled'),
    ),
    priority: v.union(v.literal('low'), v.literal('normal'), v.literal('high')),
    upstreamFrom: v.optional(v.string()),
    upstreamTo: v.optional(v.string()),
    forkHeadAtCreation: v.optional(v.string()),
    mergeBaseAtCreation: v.optional(v.string()),
    relatedSyncRunId: v.optional(v.id('syncRuns')),
    relatedAgentRequestId: v.optional(v.id('agentRequests')),
    latestAgentJobId: v.optional(v.id('agentJobs')),
    maintenanceOutcome: v.optional(
      v.union(
        v.literal('auto_synced'),
        v.literal('sync_recommended'),
        v.literal('ignored'),
        v.literal('review_pr_recommended'),
        v.literal('manual_review_required'),
        v.literal('conflict_resolution_required'),
        v.literal('failed'),
        v.literal('unknown'),
      ),
    ),
    ignoredCommitShas: v.optional(v.array(v.string())),
    ignoredReason: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
    resolvedAt: v.optional(v.number()),
  })
    .index('by_owner', ['ownerId'])
    .index('by_owner_status', ['ownerId', 'status'])
    .index('by_spoon', ['spoonId'])
    .index('by_source', ['ownerId', 'source'])
    .index('by_created', ['createdAt']),
  threadMessages: defineTable({
    threadId: v.id('threads'),
    ownerId: v.id('users'),
    spoonId: v.optional(v.id('spoons')),
    role: v.union(
      v.literal('user'),
      v.literal('assistant'),
      v.literal('system'),
      v.literal('tool'),
    ),
    content: v.string(),
    status: v.union(
      v.literal('queued'),
      v.literal('streaming'),
      v.literal('completed'),
      v.literal('failed'),
    ),
    metadata: v.optional(v.string()),
    createdAt: v.number(),
    updatedAt: v.number(),
  })
    .index('by_thread', ['threadId'])
    .index('by_owner', ['ownerId']),
  ignoredUpstreamChanges: defineTable({
    spoonId: v.id('spoons'),
    ownerId: v.id('users'),
    upstreamFrom: v.optional(v.string()),
    upstreamTo: v.string(),
    commitShas: v.array(v.string()),
    reason: v.string(),
    decidedBy: v.union(v.literal('agent'), v.literal('user')),
    threadId: v.optional(v.id('threads')),
    createdAt: v.number(),
  })
    .index('by_spoon', ['spoonId'])
    .index('by_owner', ['ownerId'])
    .index('by_upstream_to', ['spoonId', 'upstreamTo']),
};

export default defineSchema({
  ...authTables,
  ...applicationTables,
});
