# student-bff GraphQL Schema (v1)
#
# 负责人: ai04
# 仲裁依据: coord-final-decisions §2 B1-B8 + president-final-rulings §2.2
# 存放路径: packages/shared-ts/contracts/graphql/student-bff.schema.graphql (president §2.2.1)
# 起草与仲裁流程: ai04 起草 → coord 在批次 2 启动前仲裁第一版 → ai14 (student-portal) 消费
#
# 设计规范 (president §2.2.5):
#   - Query/Mutation 用 camelCase
#   - 分页采用 Relay Cursor Connections 规范 ({ edges, pageInfo, totalCount })
#   - 错误响应: GraphQL errors 数组 + extensions.code + extensions.traceId
#   - 权限点标注: # @permission: <RESOURCE>_<ACTION>
#   - DataScope 标注: # @dataScope: OWN (学生数据隔离 SELF)
#   - Type 用 PascalCase, 字段用 camelCase, 枚举用 UPPER_SNAKE_CASE
#   - 必填字段用 !, 可空字段不标 ! (避免破坏性变更)
#
# 降级模式 (president §2.6 方案 B):
#   - 下游不可用时 success=true + error=null + data 内 degraded=true
#   - 降级字段返回 null, 父对象加 degraded/degradedReason/degradedFields

scalar DateTime
scalar JSON

# ============================================================================
# Relay Cursor Connections 规范
# ============================================================================

interface Node {
  id: ID!
}

type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}

# ============================================================================
# 错误扩展 (GraphQL errors 数组 + extensions)
# ============================================================================

"""
GraphQL errors 数组中 extensions 字段规范 (president §2.2.3 + G8 裁决).
由 GlobalErrorFilter 注入, 不在 schema 中显式暴露.
"""
type GraphQLErrorExtension {
  code: String!        # BFF_STUDENT_* 前缀 (B5 裁决)
  traceId: String!     # 全链路追踪 ID (Gateway 注入 X-Request-Id)
  i18nKey: String!     # i18n key: error.bffStudent.<code_snake> (F4 裁决)
  severity: String!    # error / warning / info
}

# ============================================================================
# 通用类型
# ============================================================================

"""
降级标记 (president §2.6 方案 B).
当下游服务不可用但需返回部分数据时, 父对象包含此接口字段.
"""
interface Degradable {
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

type DegradationInfo {
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 用户与权限 (下游: iam)
# ============================================================================

type UserProfile {
  id: ID!
  email: String!
  name: String!
  avatar: String
  roles: [String!]!
  permissions: [String!]!
}

type ViewportItem {
  key: String!
  label: String!
  route: String!
  icon: String
  sortOrder: Int!
  requiredPermission: String
}

type ViewportConfig {
  navigation: [ViewportItem!]!
  dataScope: StudentDataScope!
}

type StudentDataScope {
  showHistoryGrades: Boolean!
  showClassRanking: Boolean!
  enableAIChat: Boolean!
}

type CurrentUserPayload implements Degradable {
  user: UserProfile
  viewports: ViewportConfig
  effectivePermissions: [String!]!
  dataScope: String           # SELF (学生固定)
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 班级 (下游: core-edu ClassService)
# ============================================================================

type StudentClass {
  id: ID!
  name: String!
  gradeId: String!
  gradeName: String
  subjects: [String!]!
  homeroomTeacher: TeacherBrief
}

type TeacherBrief {
  id: ID!
  name: String!
  avatar: String
}

type StudentClassConnection {
  edges: [StudentClassEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type StudentClassEdge {
  node: StudentClass!
  cursor: String!
}

type StudentClassPayload implements Degradable {
  classes: [StudentClass!]!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 考试 (下游: core-edu ExamService)
# ============================================================================

enum ExamStatus {
  DRAFT
  PUBLISHED
  IN_PROGRESS
  COMPLETED
  CANCELLED
  ARCHIVED
}

type Exam {
  id: ID!
  classId: ID!
  title: String!
  description: String
  examDate: DateTime!
  duration: Int!             # 考试时长(分钟)
  totalScore: Float!
  status: ExamStatus!
  createdBy: ID!
  createdAt: DateTime!
  updatedAt: DateTime!
  daysLeft: Int              # 距考试天数(负数表示已过)
}

type ExamConnection {
  edges: [ExamEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type ExamEdge {
  node: Exam!
  cursor: String!
}

type ExamListPayload implements Degradable {
  exams: [Exam!]!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 作业 (下游: core-edu HomeworkService)
# ============================================================================

enum HomeworkStatus {
  ASSIGNED
  SUBMITTED
  GRADED
  OVERDUE
  RETURNED
}

type Homework {
  id: ID!
  classId: ID!
  title: String!
  description: String
  dueDate: DateTime!
  status: HomeworkStatus!
  createdBy: ID!
  createdAt: DateTime!
  updatedAt: DateTime!
  submission: HomeworkSubmission  # 当前学生的提交
}

type HomeworkSubmission {
  id: ID!
  homeworkId: ID!
  studentId: ID!
  status: HomeworkStatus!
  submittedAt: DateTime
  gradedAt: DateTime
  score: Float
  feedback: String
}

type HomeworkConnection {
  edges: [HomeworkEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type HomeworkEdge {
  node: Homework!
  cursor: String!
}

type HomeworkListPayload implements Degradable {
  homework: [Homework!]!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 成绩 (下游: core-edu GradeService)
# ============================================================================

type Grade {
  id: ID!
  studentId: ID!
  examId: ID
  homeworkId: ID
  examTitle: String
  homeworkTitle: String
  subject: String
  score: Float!
  totalScore: Float!
  feedback: String
  gradedBy: ID!
  gradedAt: DateTime!
  createdAt: DateTime!
  updatedAt: DateTime!
}

type GradeConnection {
  edges: [GradeEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type GradeEdge {
  node: Grade!
  cursor: String!
}

type GradeListPayload implements Degradable {
  grades: [Grade!]!
  averageScore: Float
  totalCount: Int!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 考勤 (下游: core-edu AttendanceService, P3 预留)
# ============================================================================

enum AttendanceStatus {
  PRESENT
  ABSENT
  LATE
  EARLY_LEAVE
  EXCUSED
}

type AttendanceRecord {
  id: ID!
  studentId: ID!
  classId: ID!
  date: DateTime!
  status: AttendanceStatus!
  remark: String
  recordedBy: ID!
  createdAt: DateTime!
}

type AttendanceConnection {
  edges: [AttendanceEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type AttendanceEdge {
  node: AttendanceRecord!
  cursor: String!
}

type AttendanceListPayload implements Degradable {
  records: [AttendanceRecord!]!
  presentCount: Int!
  absentCount: Int!
  lateCount: Int!
  totalCount: Int!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 教材与章节 (下游: content, P4)
# ============================================================================

type Textbook {
  id: ID!
  title: String!
  subjectId: ID!
  subjectName: String
  gradeId: ID!
  gradeName: String
  version: String!
  coverImage: String
  chapters: [Chapter!]!
}

type Chapter {
  id: ID!
  textbookId: ID!
  title: String!
  description: String
  sortOrder: Int!
  parentId: ID             # 父章节(支持章节树)
  knowledgePoints: [KnowledgePoint!]!
}

type KnowledgePoint {
  id: ID!
  title: String!
  description: String
  mastery: Float           # 0-1 掌握度
}

type TextbookConnection {
  edges: [TextbookEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type TextbookEdge {
  node: Textbook!
  cursor: String!
}

type TextbookListPayload implements Degradable {
  textbooks: [Textbook!]!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

type ChapterListPayload implements Degradable {
  chapters: [Chapter!]!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 学习路径 (下游: content KnowledgeGraphService, P4)
# ============================================================================

type LearningPath {
  studentId: ID!
  subjectId: ID!
  points: [KnowledgePoint!]!
  recommendedOrder: [String!]!   # 知识点 id 顺序
  estimatedHours: Float
}

type LearningPathPayload implements Degradable {
  path: LearningPath
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 学情分析 (下游: data-ana, P4)
# ============================================================================

type StudentDashboard {
  studentId: ID!
  averageScore: Float
  classRank: Int
  totalStudents: Int
  pendingHomeworkCount: Int!
  upcomingExamCount: Int!
  unreadNotificationCount: Int!
  lastGrade: Grade
  weakness: WeakPointSummary    # P4 data-ana 启用后填充
  trend: LearningTrendSummary   # P4 data-ana 启用后填充
}

type WeakPointSummary {
  knowledgePointId: ID!
  title: String!
  mastery: Float!
  subject: String
}

type StudentWeakness {
  studentId: ID!
  subjectId: ID
  weakPoints: [WeakPoint!]!
}

type WeakPoint {
  knowledgePointId: ID!
  title: String!
  mastery: Float!
  subject: String
  lastAssessedAt: DateTime
}

type LearningTrendSummary {
  studentId: ID!
  subjectId: ID
  points: [TrendPoint!]!
}

type LearningTrend {
  studentId: ID!
  subjectId: ID
  points: [TrendPoint!]!
}

type TrendPoint {
  date: DateTime!
  score: Float!
  subject: String
}

type StudentDashboardPayload implements Degradable {
  dashboard: StudentDashboard
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

type WeaknessPayload implements Degradable {
  weakness: StudentWeakness
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

type TrendPayload implements Degradable {
  trend: LearningTrend
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# 通知 (下游: msg, P5)
# ============================================================================

enum NotificationType {
  HOMEWORK_ASSIGNED
  HOMEWORK_GRADED
  EXAM_PUBLISHED
  EXAM_UPDATED
  GRADE_RECORDED
  SYSTEM
  ANNOUNCEMENT
}

enum NotificationChannel {
  IN_APP
  EMAIL
  SMS
  PUSH
}

type Notification {
  id: ID!
  userId: ID!
  type: NotificationType!
  title: String!
  content: String!
  channel: NotificationChannel!
  isRead: Boolean!
  createdAt: DateTime!
  readAt: DateTime
  metadata: JSON
}

type NotificationConnection {
  edges: [NotificationEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type NotificationEdge {
  node: Notification!
  cursor: String!
}

type NotificationListPayload implements Degradable {
  notifications: [Notification!]!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

type UnreadCountPayload implements Degradable {
  count: Int!
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

# ============================================================================
# AI 答疑 (下游: ai, P5)
# ============================================================================

type AIChatResponse {
  content: String!
  model: String!
  usage: AIUsage!
}

type AIUsage {
  promptTokens: Int!
  completionTokens: Int!
  totalTokens: Int!
}

type AIChatPayload implements Degradable {
  response: AIChatResponse
  degraded: Boolean!
  degradedReason: String
  degradedFields: [String!]
}

input AIChatInput {
  messages: [AIChatMessageInput!]!
  model: String = "gpt-4o-mini"
  context: AIChatContextInput
}

# ============================================================================
# Mutation 结果
# ============================================================================

type SubmitHomeworkResult {
  success: Boolean!
  submissionId: ID
  homeworkId: ID!
  submittedAt: DateTime
  status: HomeworkStatus
  error: MutationError
}

type MarkNotificationReadResult {
  success: Boolean!
  notificationId: ID!
  readAt: DateTime
  error: MutationError
}

type MutationError {
  code: String!              # BFF_STUDENT_* 前缀
  message: String!
  traceId: String
  i18nKey: String
}

# ============================================================================
# Query 根类型
# ============================================================================

type Query {
  """当前学生信息 + 权限 + 视口 (聚合 iam.GetUserInfo + GetEffectivePermissions + GetViewports)"""
  # @permission: AUTH_READ
  # @dataScope: OWN
  currentUser: CurrentUserPayload!

  """我的班级列表 (core-edu.ClassService.GetClass + ListStudentsByClass)"""
  # @permission: CLASS_READ
  # @dataScope: OWN
  myClasses(
    after: String
    first: Int = 20
    before: String
    last: Int
  ): StudentClassConnection!

  """我的考试列表 (core-edu.ExamService.ListExamsByClass)"""
  # @permission: EXAM_READ
  # @dataScope: OWN
  myExams(
    after: String
    first: Int = 20
    before: String
    last: Int
    status: ExamStatus
  ): ExamConnection!

  """我的作业列表 (core-edu.HomeworkService.ListHomeworkByClass)"""
  # @permission: HOMEWORK_READ
  # @dataScope: OWN
  myHomework(
    after: String
    first: Int = 20
    before: String
    last: Int
    status: HomeworkStatus
  ): HomeworkConnection!

  """我的成绩列表 (core-edu.GradeService.ListGradesByStudent, B4 强制 userId 比对)"""
  # @permission: GRADE_READ
  # @dataScope: OWN
  myGrades(
    after: String
    first: Int = 20
    before: String
    last: Int
    subject: String
    startDate: DateTime
    endDate: DateTime
  ): GradeConnection!

  """我的考勤记录 (core-edu.AttendanceService.ListAttendanceByStudent)"""
  # @permission: ATTENDANCE_READ
  # @dataScope: OWN
  myAttendance(
    after: String
    first: Int = 20
    before: String
    last: Int
    startDate: DateTime
    endDate: DateTime
  ): AttendanceConnection!

  """教材列表 (content.TextbookService.ListTextbooks)"""
  # @permission: TEXTBOOK_READ
  # @dataScope: OWN
  textbooks(
    after: String
    first: Int = 20
    before: String
    last: Int
    subjectId: ID
    gradeId: ID
  ): TextbookConnection!

  """章节列表 (content.ChapterService.ListChapters)"""
  # @permission: CHAPTER_READ
  # @dataScope: OWN
  chapters(
    after: String
    first: Int = 20
    before: String
    last: Int
    textbookId: ID!
  ): ChapterListPayload!

  """学习路径推荐 (content.KnowledgeGraphService.GetLearningPath)"""
  # @permission: LEARNING_PATH_READ
  # @dataScope: OWN
  learningPath(subjectId: ID!): LearningPathPayload!

  """学生仪表盘 (data-ana.AnalyticsService.GetStudentDashboard)"""
  # @permission: DASHBOARD_VIEW
  # @dataScope: OWN
  studentDashboard: StudentDashboardPayload!

  """我的薄弱点 (data-ana.AnalyticsService.GetStudentWeakness)"""
  # @permission: WEAKNESS_READ
  # @dataScope: OWN
  myWeakness(subjectId: ID): WeaknessPayload!

  """学习趋势 (data-ana.AnalyticsService.GetLearningTrend)"""
  # @permission: TREND_READ
  # @dataScope: OWN
  myTrend(
    subjectId: ID
    startDate: DateTime
    endDate: DateTime
  ): TrendPayload!

  """我的通知列表 (msg.NotificationService.ListNotifications)"""
  # @permission: NOTIFICATION_READ
  # @dataScope: OWN
  myNotifications(
    after: String
    first: Int = 20
    before: String
    last: Int
    onlyUnread: Boolean = false
  ): NotificationConnection!

  """通知未读数 (msg.NotificationService.GetUnreadCount)"""
  # @permission: NOTIFICATION_READ
  # @dataScope: OWN
  myNotificationUnreadCount: UnreadCountPayload!

  """AI 答疑 (同步, ai.Chat). 实际为写操作但 schema 设计为 Query 便于前端 GET 缓存"""
  # @permission: STUDENT_AI_CHAT
  # @dataScope: OWN
  aiChat(input: AIChatInput!): AIChatPayload!
}

# ============================================================================
# Mutation 根类型
# ============================================================================

type Mutation {
  """提交作业 (core-edu.HomeworkService.SubmitHomework, B4 强制 userId 比对)"""
  # @permission: HOMEWORK_SUBMIT
  # @dataScope: OWN
  submitHomework(
    input: SubmitHomeworkInput!
  ): SubmitHomeworkResult!

  """标记通知已读 (msg.NotificationService.MarkAsRead)"""
  # @permission: NOTIFICATION_UPDATE
  # @dataScope: OWN
  markNotificationAsRead(
    input: MarkNotificationReadInput!
  ): MarkNotificationReadResult!
}

# ============================================================================
# Input 类型
# ============================================================================

input SubmitHomeworkInput {
  homeworkId: ID!
  studentId: ID!              # 必须与 x-user-id 一致 (B4 越权防御)
  answers: [HomeworkAnswerInput!]!
}

input HomeworkAnswerInput {
  questionId: ID!
  content: String!            # 最大 5000 字符
  attachments: [String!]      # URL 列表, 最多 5 个
}

input MarkNotificationReadInput {
  notificationId: ID!
  userId: ID!                 # 必须与 x-user-id 一致 (B4 越权防御)
}

# ============================================================================
# AI 流式答疑 (SSE Subscription, P5)
# ============================================================================

type AIStreamChunk {
  content: String!            # 本次分块内容
  done: Boolean!              # 是否结束
  model: String
  usage: AIUsage              # done=true 时填充
}

input AIStreamChatInput {
  messages: [AIChatMessageInput!]!
  model: String = "gpt-4o-mini"
  context: AIChatContextInput
}

input AIChatMessageInput {
  role: String!               # user / assistant
  content: String!
}

input AIChatContextInput {
  subject: String
  knowledgePointId: ID
}

# ============================================================================
# Subscription 根类型 (SSE 传输, GraphQL Yoga 原生支持)
# ============================================================================

type Subscription {
  """AI 答疑流式响应 (ai.StreamChat, SSE 传输)"""
  # @permission: STUDENT_AI_CHAT
  # @dataScope: OWN
  aiStreamChat(input: AIStreamChatInput!): AIStreamChunk!
}
