import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

/* eslint-disable sort-keys */

const attemptClassification = v.union(
  v.literal("Succeeded"),
  v.literal("RetryableFailure"),
  v.literal("NeedsInput"),
  v.literal("Blocked"),
  v.literal("VerificationFailed"),
  v.literal("BudgetExhausted"),
  v.literal("Cancelled"),
  v.literal("PermanentFailure")
);
const workStatus = v.union(
  v.literal("proposed"),
  v.literal("defining"),
  v.literal("awaiting-definition-approval"),
  v.literal("designing"),
  v.literal("awaiting-design-approval"),
  v.literal("ready"),
  v.literal("executing"),
  v.literal("needs-input"),
  v.literal("blocked"),
  v.literal("completed"),
  v.literal("failed"),
  v.literal("cancelled")
);

export default defineSchema({
  organizations: defineTable({
    createdAt: v.number(),
    createdBy: v.string(),
    kind: v.union(v.literal("personal"), v.literal("team")),
    name: v.string(),
  }).index("by_createdBy_and_kind", ["createdBy", "kind"]),
  organizationMembers: defineTable({
    createdAt: v.number(),
    organizationId: v.id("organizations"),
    role: v.union(v.literal("owner"), v.literal("member")),
    userId: v.string(),
  })
    .index("by_userId", ["userId"])
    .index("by_organizationId", ["organizationId"])
    .index("by_organizationId_and_userId", ["organizationId", "userId"]),
  gitConnections: defineTable({
    connectedAt: v.number(),
    credentialCiphertext: v.string(),
    credentialIv: v.string(),
    credentialKind: v.union(v.literal("oauth"), v.literal("token")),
    organizationId: v.id("organizations"),
    provider: v.union(v.literal("github"), v.literal("gitea")),
    serverUrl: v.string(),
    updatedAt: v.number(),
    username: v.optional(v.string()),
  })
    .index("by_organizationId", ["organizationId"])
    .index("by_organizationId_and_provider_and_serverUrl", [
      "organizationId",
      "provider",
      "serverUrl",
    ]),
  projects: defineTable({
    createdAt: v.number(),
    defaultBranch: v.optional(v.string()),
    description: v.optional(v.string()),
    gitConnectionId: v.optional(v.id("gitConnections")),
    name: v.string(),
    normalizedSourceUrl: v.string(),
    organizationId: v.id("organizations"),
    repositoryPath: v.string(),
    sourceHost: v.string(),
    sourceUrl: v.string(),
    updatedAt: v.number(),
  })
    .index("by_organizationId_and_createdAt", ["organizationId", "createdAt"])
    .index("by_organizationId_and_normalizedSourceUrl", [
      "organizationId",
      "normalizedSourceUrl",
    ]),
  projectContextDocuments: defineTable({
    content: v.string(),
    createdAt: v.number(),
    kind: v.union(
      v.literal("readme"),
      v.literal("agents"),
      v.literal("product"),
      v.literal("business"),
      v.literal("design"),
      v.literal("tech")
    ),
    origin: v.literal("repository"),
    path: v.string(),
    projectId: v.id("projects"),
    sourceUrl: v.string(),
    updatedAt: v.number(),
  }).index("by_projectId_and_path", ["projectId", "path"]),
  conversations: defineTable({
    createdAt: v.number(),
    organizationId: v.id("organizations"),
  }).index("by_organizationId", ["organizationId"]),
  conversationTurns: defineTable({
    attemptNumber: v.optional(v.number()),
    clientRequestId: v.string(),
    completedAt: v.optional(v.number()),
    conversationId: v.id("conversations"),
    createdAt: v.number(),
    error: v.optional(v.string()),
    leaseExpiresAt: v.optional(v.number()),
    leaseOwner: v.optional(v.string()),
    status: v.union(
      v.literal("queued"),
      v.literal("processing"),
      v.literal("completed"),
      v.literal("failed")
    ),
    submissionId: v.optional(v.string()),
  })
    .index("by_conversationId_and_clientRequestId", [
      "conversationId",
      "clientRequestId",
    ])
    .index("by_status_and_leaseExpiresAt", ["status", "leaseExpiresAt"]),
  conversationMessages: defineTable({
    content: v.string(),
    conversationId: v.id("conversations"),
    createdAt: v.number(),
    ordinal: v.number(),
    role: v.union(v.literal("user"), v.literal("assistant")),
    turnId: v.id("conversationTurns"),
  })
    .index("by_conversationId_and_ordinal", ["conversationId", "ordinal"])
    .index("by_turnId_and_role", ["turnId", "role"]),
  conversationAttachments: defineTable({
    createdAt: v.number(),
    filename: v.optional(v.string()),
    messageId: v.id("conversationMessages"),
    mimeType: v.string(),
    storageId: v.id("_storage"),
  }).index("by_messageId", ["messageId"]),
  signals: defineTable({
    conversationId: v.id("conversations"),
    createdAt: v.number(),
    desiredOutcome: v.string(),
    organizationId: v.id("organizations"),
    processedByAgentInstanceId: v.string(),
    processedByAgentName: v.string(),
    projectId: v.id("projects"),
    sourceKey: v.string(),
    summary: v.string(),
    title: v.string(),
  })
    .index("by_organization_and_createdAt", ["organizationId", "createdAt"])
    .index("by_organization_and_sourceKey", ["organizationId", "sourceKey"])
    .index("by_project_and_createdAt", ["projectId", "createdAt"]),
  signalConstraints: defineTable({
    ordinal: v.number(),
    signalId: v.id("signals"),
    value: v.string(),
  }).index("by_signalId_and_ordinal", ["signalId", "ordinal"]),
  signalSources: defineTable({
    messageId: v.id("conversationMessages"),
    ordinal: v.number(),
    rawTextSnapshot: v.string(),
    signalId: v.id("signals"),
    sourceCreatedAt: v.number(),
  })
    .index("by_signalId_and_ordinal", ["signalId", "ordinal"])
    .index("by_messageId", ["messageId"]),
  works: defineTable({
    createdAt: v.number(),
    definitionApprovalVersion: v.optional(v.number()),
    definitionVersion: v.optional(v.number()),
    designApprovalVersion: v.optional(v.number()),
    designVersion: v.optional(v.number()),
    objective: v.string(),
    organizationId: v.id("organizations"),
    projectId: v.id("projects"),
    status: workStatus,
    title: v.string(),
    updatedAt: v.number(),
  })
    .index("by_project_and_createdAt", ["projectId", "createdAt"])
    .index("by_organization_and_createdAt", ["organizationId", "createdAt"]),

  signalWorkAttachments: defineTable({
    createdAt: v.number(),
    signalId: v.id("signals"),
    workId: v.id("works"),
  })
    .index("by_signal", ["signalId"])
    .index("by_work", ["workId"])
    .index("by_signal_and_work", ["signalId", "workId"]),

  workEvents: defineTable({
    createdAt: v.number(),
    idempotencyKey: v.string(),
    kind: v.union(
      v.literal("work.proposed"),
      v.literal("signal.attached"),
      v.literal("definition.requested"),
      v.literal("definition.saved"),
      v.literal("definition.revised"),
      v.literal("definition.approved"),
      v.literal("definition.invalidated"),
      v.literal("question.created"),
      v.literal("question.answered"),
      v.literal("question.withdrawn"),
      v.literal("design.requested"),
      v.literal("design.saved"),
      v.literal("design.revised"),
      v.literal("design.approved"),
      v.literal("design.invalidated"),
      v.literal("planner.failed"),
      v.literal("slice.started"),
      v.literal("slice.completed"),
      v.literal("slice.ready"),
      v.literal("run.started"),
      v.literal("run.completed"),
      v.literal("run.cancelled"),
      v.literal("attempt.claimed"),
      v.literal("attempt.event"),
      v.literal("attempt.completed"),
      v.literal("attempt.reconciled"),
      v.literal("resolver.decided"),
      v.literal("artifact.recorded"),
      v.literal("delivery.recorded"),
      v.literal("delivery.updated")
    ),
    payloadJson: v.optional(v.string()),
    referenceId: v.optional(v.string()),
    signalId: v.optional(v.id("signals")),
    workId: v.id("works"),
  })
    .index("by_work_and_createdAt", ["workId", "createdAt"])
    .index("by_work_and_idempotencyKey", ["workId", "idempotencyKey"]),

  workDefinitions: defineTable({
    createdAt: v.number(),
    createdBy: v.string(),
    payloadJson: v.string(),
    risk: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
    status: v.union(
      v.literal("proposed"),
      v.literal("current"),
      v.literal("superseded")
    ),
    version: v.number(),
    workId: v.id("works"),
  })
    .index("by_work_and_version", ["workId", "version"])
    .index("by_work_and_status", ["workId", "status"]),

  workQuestions: defineTable({
    alternativesJson: v.string(),
    answer: v.optional(v.string()),
    createdAt: v.number(),
    definitionVersion: v.number(),
    impact: v.union(v.literal("low"), v.literal("medium"), v.literal("high")),
    prompt: v.string(),
    questionId: v.string(),
    recommendation: v.optional(v.string()),
    status: v.union(
      v.literal("open"),
      v.literal("answered"),
      v.literal("withdrawn")
    ),
    workId: v.id("works"),
  })
    .index("by_workId_and_definitionVersion", ["workId", "definitionVersion"])
    .index("by_workId_and_definitionVersion_and_questionId", [
      "workId",
      "definitionVersion",
      "questionId",
    ]),

  workApprovals: defineTable({
    approvedAt: v.number(),
    approvedBy: v.string(),
    definitionVersion: v.number(),
    designVersion: v.optional(v.number()),
    kind: v.union(v.literal("definition"), v.literal("design")),
    status: v.union(v.literal("active"), v.literal("invalidated")),
    workId: v.id("works"),
  })
    .index("by_workId_and_kind", ["workId", "kind"])
    .index("by_workId_and_kind_and_definitionVersion_and_designVersion", [
      "workId",
      "kind",
      "definitionVersion",
      "designVersion",
    ]),

  designPackets: defineTable({
    createdAt: v.number(),
    createdBy: v.string(),
    definitionVersion: v.number(),
    payloadJson: v.string(),
    status: v.union(
      v.literal("proposed"),
      v.literal("current"),
      v.literal("superseded")
    ),
    version: v.number(),
    workId: v.id("works"),
  }).index("by_work_and_version", ["workId", "version"]),

  workSlices: defineTable({
    createdAt: v.optional(v.number()),
    designVersion: v.number(),
    objective: v.string(),
    observableBehavior: v.string(),
    ordinal: v.number(),
    payloadJson: v.string(),
    sliceId: v.string(),
    status: v.union(
      v.literal("planned"),
      v.literal("ready"),
      v.literal("running"),
      v.literal("completed"),
      v.literal("blocked")
    ),
    title: v.string(),
    workId: v.id("works"),
  })
    .index("by_workId_and_designVersion", ["workId", "designVersion"])
    .index("by_workId_and_designVersion_and_sliceId", [
      "workId",
      "designVersion",
      "sliceId",
    ]),

  workRuns: defineTable({
    baseRevision: v.optional(v.string()),
    candidateRevision: v.optional(v.string()),
    createdAt: v.number(),
    designVersion: v.optional(v.number()),
    endedAt: v.optional(v.number()),
    kitId: v.string(),
    kitVersion: v.string(),
    environmentId: v.optional(v.string()),
    executionKind: v.optional(
      v.union(v.literal("simulated"), v.literal("real"))
    ),
    scenario: v.union(
      v.literal("success"),
      v.literal("transient-failure-then-success"),
      v.literal("needs-input"),
      v.literal("permanent-failure"),
      v.literal("cancelled")
    ),
    sliceId: v.optional(v.string()),
    sliceRowId: v.optional(v.id("workSlices")),
    startedAt: v.optional(v.number()),
    status: v.union(
      v.literal("ready"),
      v.literal("running"),
      v.literal("terminal"),
      v.literal("cancelled")
    ),
    terminalClassification: v.optional(attemptClassification),
    terminalSummary: v.optional(v.string()),
    workflowId: v.optional(v.string()),
    workId: v.id("works"),
  }).index("by_work_and_createdAt", ["workId", "createdAt"]),

  workAttempts: defineTable({
    classification: v.optional(attemptClassification),
    failureReason: v.optional(
      v.union(
        v.literal("Authentication"),
        v.literal("Cancelled"),
        v.literal("HarnessFailed"),
        v.literal("InvalidInput"),
        v.literal("ProviderUnavailable"),
        v.literal("RepositoryFailed"),
        v.literal("Timeout")
      )
    ),
    endedAt: v.optional(v.number()),
    leaseExpiresAt: v.optional(v.number()),
    leaseOwner: v.optional(v.string()),
    number: v.number(),
    runId: v.id("workRuns"),
    startedAt: v.optional(v.number()),
    status: v.union(
      v.literal("queued"),
      v.literal("claimed"),
      v.literal("running"),
      v.literal("terminal")
    ),
    summary: v.optional(v.string()),
    workId: v.id("works"),
    workspaceKey: v.optional(v.string()),
  })
    .index("by_runId_and_number", ["runId", "number"])
    .index("by_status_and_leaseExpiresAt", ["status", "leaseExpiresAt"]),

  workAttemptEvents: defineTable({
    attemptId: v.id("workAttempts"),
    kind: v.string(),
    message: v.string(),
    metadataJson: v.string(),
    occurredAt: v.number(),
    sequence: v.number(),
  }).index("by_attempt_and_sequence", ["attemptId", "sequence"]),

  resolverDecisions: defineTable({
    attemptId: v.id("workAttempts"),
    attemptNumber: v.number(),
    classification: attemptClassification,
    createdAt: v.number(),
    decision: v.union(v.literal("retry"), v.literal("terminal")),
    resultingWorkStatus: v.optional(workStatus),
    runId: v.id("workRuns"),
    summary: v.string(),
    workId: v.id("works"),
  })
    .index("by_runId_and_attemptNumber", ["runId", "attemptNumber"])
    .index("by_attemptId", ["attemptId"]),

  workArtifacts: defineTable({
    attemptId: v.optional(v.id("workAttempts")),
    contentHash: v.optional(v.string()),
    createdAt: v.number(),
    designVersion: v.optional(v.number()),
    environmentId: v.optional(v.string()),
    idempotencyKey: v.string(),
    kind: v.union(
      v.literal("definition"),
      v.literal("design"),
      v.literal("diff"),
      v.literal("test-report"),
      v.literal("verification-report"),
      v.literal("runtime-log"),
      v.literal("screenshot"),
      v.literal("video"),
      v.literal("commit"),
      v.literal("branch"),
      v.literal("pull-request"),
      v.literal("preview"),
      v.literal("deployment"),
      v.literal("other")
    ),
    metadataJson: v.string(),
    organizationId: v.id("organizations"),
    producer: v.string(),
    projectId: v.id("projects"),
    provenanceJson: v.string(),
    runId: v.optional(v.id("workRuns")),
    sliceId: v.optional(v.string()),
    sourceRevision: v.optional(v.string()),
    title: v.string(),
    uri: v.optional(v.string()),
    verificationStatus: v.union(
      v.literal("unverified"),
      v.literal("verified"),
      v.literal("rejected")
    ),
    workId: v.id("works"),
  })
    .index("by_workId_and_createdAt", ["workId", "createdAt"])
    .index("by_workId_and_idempotencyKey", ["workId", "idempotencyKey"])
    .index("by_runId_and_createdAt", ["runId", "createdAt"]),

  workDeliveries: defineTable({
    artifactId: v.optional(v.id("workArtifacts")),
    createdAt: v.number(),
    externalId: v.string(),
    idempotencyKey: v.string(),
    kind: v.union(
      v.literal("branch"),
      v.literal("commit"),
      v.literal("pull-request"),
      v.literal("preview"),
      v.literal("deployment")
    ),
    metadataJson: v.string(),
    organizationId: v.id("organizations"),
    projectId: v.id("projects"),
    provider: v.string(),
    sourceRevision: v.string(),
    status: v.union(
      v.literal("recorded"),
      v.literal("ready"),
      v.literal("approved"),
      v.literal("delivered"),
      v.literal("failed"),
      v.literal("cancelled")
    ),
    target: v.string(),
    updatedAt: v.number(),
    url: v.optional(v.string()),
    workId: v.id("works"),
  })
    .index("by_workId_and_createdAt", ["workId", "createdAt"])
    .index("by_workId_and_idempotencyKey", ["workId", "idempotencyKey"]),

  // -----------------------------------------------------------------
  // Flue persistence stores (schema/format version 4).
  // -----------------------------------------------------------------

  // One-row key/value metadata table, keyed with `schema_version` for the
  // persisted-store versioning obligation.
  flueMeta: defineTable({
    key: v.string(),
    value: v.string(),
  }).index("by_key", ["key"]),

  // Durable agent-submission lifecycle: one row per submission. Settlement
  // obligations live on the same row so reserve/finalize are atomic with the
  // status transition they gate.
  flueSubmissions: defineTable({
    submissionId: v.string(),
    sessionKey: v.string(),
    sequence: v.number(),
    kind: v.union(v.literal("dispatch"), v.literal("direct")),
    // Client-supplied canonical input/chunks encoded by Flue helpers; exact
    // string compared on idempotent replay.
    inputJson: v.string(),
    chunksJson: v.string(),
    traceCarrierJson: v.optional(v.string()),
    // Admit-time canonical record (Flue ConversationCreatedRecord) for the
    // default session of the default harness.
    recordJson: v.optional(v.string()),
    status: v.union(
      v.literal("queued"),
      v.literal("running"),
      v.literal("terminalizing"),
      v.literal("settled")
    ),
    acceptedAt: v.number(),
    canonicalReadyAt: v.optional(v.number()),
    attemptId: v.optional(v.string()),
    inputAppliedAt: v.optional(v.number()),
    recoveryRequestedAt: v.optional(v.number()),
    abortRequestedAt: v.optional(v.number()),
    startedAt: v.optional(v.number()),
    error: v.optional(v.string()),
    attemptCount: v.number(),
    maxRetry: v.number(),
    timeoutAt: v.number(),
    ownerId: v.optional(v.string()),
    leaseExpiresAt: v.number(),
    // Settlement obligation (reserved/finalized on this row).
    settlementRecordId: v.optional(v.string()),
    settlementRecordJson: v.optional(v.string()),
    settledOutcome: v.optional(
      v.union(v.literal("completed"), v.literal("failed"), v.literal("aborted"))
    ),
    updatedAt: v.number(),
  })
    .index("by_submissionId", ["submissionId"])
    .index("by_sessionKey_and_sequence", ["sessionKey", "sequence"])
    .index("by_status_and_sequence", ["status", "sequence"])
    .index("by_status_and_leaseExpiresAt", ["status", "leaseExpiresAt"])
    .index("by_status_and_settlementRecordId", [
      "status",
      "settlementRecordId",
    ]),

  // Durable evidence that a submission attempt started and has not yet
  // settled. Append-once by (submissionId, attemptId).
  flueAttemptMarkers: defineTable({
    attemptId: v.string(),
    createdAt: v.number(),
    submissionId: v.string(),
  })
    .index("by_submissionId_and_attemptId", ["submissionId", "attemptId"])
    .index("by_submissionId", ["submissionId"]),

  // Conversation-stream metadata: one row per stream path.
  flueConversationStreams: defineTable({
    closed: v.boolean(),
    createdAt: v.number(),
    identityJson: v.string(),
    incarnation: v.string(),
    nextOffset: v.number(),
    nextProducerSequence: v.number(),
    path: v.string(),
    producerEpoch: v.number(),
    producerId: v.optional(v.string()),
  }).index("by_path", ["path"]),

  // Conversation-stream batches: one row per appended batch. Offset is
  // 0-based; `seq` is the row's position in the stream.
  flueConversationBatches: defineTable({
    appendedAt: v.number(),
    attemptId: v.optional(v.string()),
    path: v.string(),
    producerEpoch: v.number(),
    producerId: v.string(),
    producerSequence: v.number(),
    recordsJson: v.string(),
    seq: v.number(),
    submissionId: v.optional(v.string()),
  })
    .index("by_path_and_seq", ["path", "seq"])
    .index("by_path_producer_epoch_producerSequence", [
      "path",
      "producerId",
      "producerEpoch",
      "producerSequence",
    ]),

  // Event-stream metadata: one row per stream path.
  flueEventStreams: defineTable({
    closed: v.boolean(),
    createdAt: v.number(),
    nextSeq: v.number(),
    path: v.string(),
  }).index("by_path", ["path"]),

  // Event-stream entries: one row per appended event. `onceKey` carries the
  // idempotency key for `appendEventOnce` and is null for plain appends.
  flueEventEntries: defineTable({
    appendedAt: v.number(),
    dataJson: v.string(),
    onceKey: v.optional(v.string()),
    path: v.string(),
    seq: v.number(),
  })
    .index("by_path_and_seq", ["path", "seq"])
    .index("by_path_and_onceKey", ["path", "onceKey"]),

  // Workflow run records.
  flueRuns: defineTable({
    durationMs: v.optional(v.number()),
    endedAt: v.optional(v.string()),
    errorJson: v.optional(v.string()),
    inputJson: v.optional(v.string()),
    isError: v.optional(v.boolean()),
    resultJson: v.optional(v.string()),
    runId: v.string(),
    startedAt: v.string(),
    status: v.union(
      v.literal("active"),
      v.literal("completed"),
      v.literal("errored")
    ),
    traceCarrierJson: v.optional(v.string()),
    workflowName: v.string(),
  })
    .index("by_runId", ["runId"])
    .index("by_startedAt_and_runId", ["startedAt", "runId"])
    .index("by_workflowName_and_startedAt_and_runId", [
      "workflowName",
      "startedAt",
      "runId",
    ])
    .index("by_status_startedAt_runId", ["status", "startedAt", "runId"])
    .index("by_status_workflowName_startedAt_runId", [
      "status",
      "workflowName",
      "startedAt",
      "runId",
    ]),

  // Immutable attachment bytes. Identity is (streamPath, attachmentId); reads
  // are additionally scoped by conversationId.
  flueAttachments: defineTable({
    attachmentId: v.string(),
    bytes: v.bytes(),
    conversationId: v.string(),
    createdAt: v.number(),
    digest: v.string(),
    filename: v.optional(v.string()),
    mimeType: v.string(),
    size: v.number(),
    streamPath: v.string(),
  })
    .index("by_streamPath", ["streamPath"])
    .index("by_streamPath_and_attachmentId", ["streamPath", "attachmentId"])
    .index("by_streamPath_and_conversationId_and_attachmentId", [
      "streamPath",
      "conversationId",
      "attachmentId",
    ]),
});
