# Thread domain types (PRD-01): Thread, ThreadLabel,
# ThreadAttachment and related enums/inputs.

enum ThreadStatus {
  BACKLOG
  TODO
  IN_PROGRESS
  IN_REVIEW
  BLOCKED
  DONE
  CANCELLED
}

# Derived lifecycle status — computed per-request from thread_turns.
# RUNNING is emitted when a fresh (≤ 5 min) queued or running turn exists;
# AWAITING_USER is reserved for future user-input-awaiting signal and is
# not emitted by v1 — clients should treat it as unreachable.
enum ThreadLifecycleStatus {
  RUNNING
  COMPLETED
  CANCELLED
  FAILED
  IDLE
  AWAITING_USER
}

enum ThreadChannel {
  CHAT
  EMAIL
  SCHEDULE
  MANUAL
  WEBHOOK
  API
  TASK
  CONNECTOR
  SLACK
}

type Thread {
  id: ID!
  tenantId: ID!
  agentId: ID
  computerId: ID
  spaceId: ID!
  userId: ID
  agent: Agent
  computer: Computer
  space: Space
  user: User
  participants: [ThreadParticipant!]!
  messages(limit: Int, cursor: String): MessageConnection!
  number: Int!
  identifier: String
  title: String!
  status: ThreadStatus!
  channel: ThreadChannel!
  assigneeType: String
  assigneeId: ID
  assignee: User
  reporterId: ID
  reporter: User
  checkoutRunId: String
  checkoutVersion: Int!
  billingCode: String
  labels: AWSJSON
  metadata: AWSJSON
  dueAt: AWSDateTime
  startedAt: AWSDateTime
  completedAt: AWSDateTime
  cancelledAt: AWSDateTime
  closedAt: AWSDateTime
  archivedAt: AWSDateTime
  lastTurnCompletedAt: AWSDateTime
  lastResponsePreview: String
  lastReadAt: AWSDateTime
  createdByType: String
  createdById: String
  attachments: [ThreadAttachment!]!
  costSummary: Float
  lastActivityAt: AWSDateTime
  # Nullable so DB-level loader failures surface as `lifecycleStatus: null`
  # with a field error rather than null-propagating the entire Thread.
  lifecycleStatus: ThreadLifecycleStatus
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
}

type ThreadsPage {
  items: [Thread!]!
  totalCount: Int!
}

type ThreadLabel {
  id: ID!
  tenantId: ID!
  name: String!
  color: String
  description: String
  createdAt: AWSDateTime!
}

type ThreadAttachment {
  id: ID!
  threadId: ID!
  tenantId: ID!
  name: String
  mimeType: String
  sizeBytes: Int
  uploadedBy: ID
  createdAt: AWSDateTime!
}

type ThreadLabelAssignment {
  id: ID!
  threadId: ID!
  labelId: ID!
  tenantId: ID!
  label: ThreadLabel
  createdAt: AWSDateTime!
}

# --- Inputs ---

input CreateThreadInput {
  tenantId: ID!
  agentId: ID
  computerId: ID
  spaceId: ID
  title: String!
  channel: ThreadChannel
  assigneeType: String
  assigneeId: ID
  billingCode: String
  labels: AWSJSON
  metadata: AWSJSON
  dueAt: AWSDateTime
  createdByType: String
  createdById: String
  # Optional opening user message. When set, the resolver atomically
  # creates the thread and the first message in a single transaction so
  # hosts can avoid the two-round-trip "create then send" footgun.
  firstMessage: String
}

input UpdateThreadInput {
  title: String
  status: ThreadStatus
  channel: ThreadChannel
  assigneeType: String
  assigneeId: ID
  billingCode: String
  labels: AWSJSON
  metadata: AWSJSON
  dueAt: AWSDateTime
  archivedAt: AWSDateTime
  lastReadAt: AWSDateTime
}

input CreateThreadLabelInput {
  tenantId: ID!
  name: String!
  color: String
  description: String
}

input UpdateThreadLabelInput {
  name: String
  color: String
  description: String
}

input CheckoutThreadInput {
  runId: String!
}

input ReleaseThreadInput {
  runId: String!
  status: ThreadStatus
}

# --- Query / Mutation extensions ---

extend type Query {
  threads(
    tenantId: ID!
    status: ThreadStatus
    channel: ThreadChannel
    agentId: ID
    computerId: ID
    assigneeId: ID
    search: String
    limit: Int
    cursor: String
  ): [Thread!]!
  threadsPaged(
    tenantId: ID!
    statuses: [String!]
    search: String
    showArchived: Boolean
    sortField: String
    sortDir: String
    limit: Int
    offset: Int
    unreadOnly: Boolean
    """
    Optional scope to a single Computer. When set, results are filtered to
    threads where `computer_id = $computerId`. Tenant scoping still applies
    on top, so a cross-tenant computerId returns empty rather than leaking.
    Consumed by the admin Computer Detail Dashboard's shared ThreadsTable
    (plan 2026-05-13-005 U7).
    """
    computerId: ID
    spaceId: ID
  ): ThreadsPage!
  thread(id: ID!): Thread
  threadByNumber(tenantId: ID!, number: Int!): Thread
  threadLabels(tenantId: ID!): [ThreadLabel!]!
  unreadThreadCount(tenantId: ID!, agentId: ID): Int!
}

enum ThreadParticipantType {
  USER
  AGENT
}

enum ThreadParticipantNotificationPreference {
  SUBSCRIBED
  MENTIONS
  MUTED
}

type ThreadParticipant {
  id: ID!
  tenantId: ID!
  threadId: ID!
  spaceId: ID!
  participantType: ThreadParticipantType!
  userId: ID
  agentId: ID
  user: User
  agent: Agent
  role: String!
  source: String!
  notificationPreference: ThreadParticipantNotificationPreference!
  lastReadAt: AWSDateTime
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
}

type ThreadMentionTarget {
  id: ID!
  targetType: MessageMentionTargetType!
  targetId: ID!
  displayName: String!
  avatarUrl: String
  role: String
}

extend type Query {
  threadMentionTargets(threadId: ID!): [ThreadMentionTarget!]!
}

extend type Mutation {
  createThread(input: CreateThreadInput!): Thread!
  updateThread(id: ID!, input: UpdateThreadInput!): Thread!
  deleteThread(id: ID!): Boolean!

  checkoutThread(id: ID!, input: CheckoutThreadInput!): Thread!
  releaseThread(id: ID!, input: ReleaseThreadInput!): Thread!

  createThreadLabel(input: CreateThreadLabelInput!): ThreadLabel!
  updateThreadLabel(id: ID!, input: UpdateThreadLabelInput!): ThreadLabel!
  deleteThreadLabel(id: ID!): Boolean!

  assignThreadLabel(threadId: ID!, labelId: ID!): ThreadLabelAssignment!
  removeThreadLabel(threadId: ID!, labelId: ID!): Boolean!

  escalateThread(input: EscalateThreadInput!): Thread!
  delegateThread(input: DelegateThreadInput!): Thread!
}

input EscalateThreadInput {
  threadId: ID!
  reason: String!
  agentId: ID!
}

input DelegateThreadInput {
  threadId: ID!
  assigneeId: ID!
  reason: String
  agentId: ID!
}
