File size: 22,185 Bytes
65d68f5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
/**
 * Classifies a user message into a response mode so the chat can:
 * - route greetings/casual messages to chat AI without requiring sources
 * - answer generally for topic questions (API without source IDs)
 * - show a clean upload CTA for doc-specific requests with no source
 * - show clarify options for genuinely ambiguous short messages
 * - ground answers in selected sources when present
 */

import type { ApiDocument } from "@/lib/api/types";
import { defaultStudyIntent, type StudyIntent } from "@/lib/study-intents";
import type { ChatMessage } from "./types";

// ── Types ─────────────────────────────────────────────────────────────────────

export type ResponseMode =
  | "casual"
  | "general_answer"
  | "needs_source"
  | "source_grounded"
  | "tool_ready"
  | "clarify";

export type AnswerMode =
  | "casual_chat"
  | "general_ai_answer"
  | "web_search_answer"
  | "source_grounded_answer"
  | "source_required_upload_prompt"
  | "tool_generation"
  | "clarification";

export type AnswerModeResult = {
  mode: AnswerMode;
  intent: string;
  requiresSource: boolean;
  shouldUseWeb: boolean;
  canAnswerGenerally: boolean;
  topic: string | null;
  toolType: StudyIntent | null;
  userFacingNextStep: string | null;
};

export type AnswerModeSource = {
  title?: string | null;
  file_name?: string | null;
};

export type ChatIntentResult = {
  /** Best-guess StudyIntent for downstream routing. */
  intent: StudyIntent;
  responseMode: ResponseMode;
  answerMode?: AnswerMode;
  shouldUseWeb?: boolean;
  userFacingNextStep?: string | null;
  requiresSource: boolean;
  canAnswerGenerally: boolean;
  /**
   * Pre-built reply text for local UX prompts such as needs_source.
   * Null means the API should generate the answer.
   */
  immediateReply: string | null;
  topic: string | null;
};

// ── Patterns ──────────────────────────────────────────────────────────────────

const GREETING_RE =
  /^\s*(hi+|hey+|hello+|yo+|sup|bro|howdy|hola|namaste|good\s+(morning|evening|afternoon|day|night)|start|begin|let'?s\s+go|ready)\W*$/i;

// Matches "hi + something" β€” user starts with a greeting word then asks a question
const GREETING_PREFIX_RE =
  /^\s*(hi+|hey+|hello+|yo+|bro|sup)\b[\s,!.]*/i;

const CAPABILITY_RE =
  /\b(what\s+can\s+you\s+do|what\s+do\s+you\s+do|who\s+are\s+you|what(?:'s|\s+is)\s+your\s+name|are\s+you\s+(an?\s+)?(ai|bot|chatbot|robot|assistant)|what\s+are\s+you|how\s+(do|to|did|does|can|should|would|could|might|shall)\s+(i\s+)?use|how\s+(does|do|would|should)\s+(this|it|docdoe|the\s+app)\s+work|help\s+me\s+study|what\s+is\s+this\s+(app|tool)|your\s+features?|your\s+capabilities?|introduce\s+yourself|how\s+does\s+docdoe\s+work)\b/i;

const CASUAL_INTRO_RE =
  /^\s*(hi+|hey+|hello+|yo+|bro)\b[\s,!.]*(my\s+name\s+is|i'?m|i\s+am)\b/i;

// Patterns that explicitly reference an already-uploaded or "this" document.
// Does NOT match generic notes/quiz/explain requests that happen to lack a source.
const DOC_REF_RE =
  /\b(this|that|the|my|uploaded|selected)\s+(pdf|doc(?:ument)?|file|chapter|notes?|textbook|paper|source|material|page)\b|\b(from|based\s+on|in|using|explain|summarize|summarise|extract\s+from)\s+(this|that|my|the\s+uploaded|the\s+selected)\b/i;

const PYQ_RE =
  /\b(pyq|previous[\s-]year|previous\s+question\s+papers?|past\s+papers?|10[\s-]year\s+(?:pyq|pattern|paper)|analyze\s+papers?|paper\s+signal|exam\s+pattern|repeated?\s+questions?|predict\s+questions?)\b/i;

const UNCLEAR_RE =
  /^\s*(ok(?:ay)?|yeah|yep|yes|no|hmm?|uh|sure|got\s*it|that\s+one|do\s+it|go\s+ahead|continue|next|done|fine|proceed|thanks?|thank\s+you|thx|ty|cool|nice|great|good)\s*[.!?]?\s*$/i;

const NOTES_RE =
  // allow any adjective(s) between the verb and "notes": exam notes, study
  // notes, scoring notes, short notes, chapter notes, etc.
  /\b(make|generate|create|write|give\s+me|build|prepare)\s+(me\s+)?(some\s+|a\s+)?(?:[a-z-]+\s+){0,3}notes?\b|\bnotes?\s+(on|for|about|of)\b/i;

const QUIZ_RE =
  /\b(make|create|generate|give\s+me|build)\s+(a\s+|some\s+)?(quiz|practice\s+(quiz|test)|test)\b|\b(test\s+me|quiz\s+me|ask\s+me\s+questions?)\b/i;

const FLASHCARD_RE =
  /\b(make|create|generate|give\s+me)\s+(some\s+)?(flash\s?cards?|memory\s+cards?|revision\s+cards?)\b|\b(flash\s?cards?|memory\s+cards?|revision\s+cards?)\s+(on|for|about|of)\b/i;

const VIDEO_RE =
  /\b(make|create|build|generate)\s+(a\s+)?(study\s+)?video\b|(video\s+(plan|for|on|about|of))\b/i;

const STUDY_PLAN_RE =
  /\b(study\s+plan|revision\s+plan|last\s+night|one\s+night|overnight|tonight|tomorrow|cram|crash\s+course|schedule|timetable|what\s+should\s+i\s+study|learn\b.*\bnight|finish\b.*\bnight|complete\b.*\bnight)\b/i;

const EXAM_ANSWER_RE =
  /\b(exam\s+mode|exam\s+answer|board\s+style|marks?\s+answer|[1-9]\s*[- ]?mark|five\s+mark|answer\s+format)\b/i;

const EXPLICIT_WEB_REQUEST_RE =
  /\b(?:search|browse|look\s+up|check)\s+(?:(?:the|on)\s+)?(?:internet|web|online)\b|\b(?:internet|web|online)\s+search\b/i;
const CURRENT_INFO_TOPIC_RE =
  /\b(?:latest|current|today(?:'s)?|recent|updated|new)\b.{0,80}\b(?:news|notices?|announcements?|results?|exam\s+dates?|(?:official|board|exam)\s+(?:timetables?|schedules?)|syllabus|prices?|rates?|status|weather|scores?|rankings?|releases?|deadlines?|admissions?|scholarships?|polic(?:y|ies)|rules?|guidelines?)\b|\b(?:news|notices?|announcements?|results?|exam\s+dates?|(?:official|board|exam)\s+(?:timetables?|schedules?)|syllabus|prices?|rates?|status|weather|scores?|rankings?|releases?|deadlines?|admissions?|scholarships?|polic(?:y|ies)|rules?|guidelines?)\b.{0,80}\b(?:today|latest|current|recent|updated)\b/i;
const CURRENT_ROLE_RE =
  /\b(?:who\s+is|who's|name)\s+(?:the\s+)?(?:current|latest)\s+(?:president|prime\s+minister|minister|chief\s+minister|governor|chief\s+executive|ceo|chairperson|head)\b/i;
const YEARLY_UPDATE_RE =
  /\b20\d{2}\s+(?:update|notice|announcement|result|syllabus|timetable|schedule)\b/i;

function asksForLiveWebInformation(message: string, hasSource: boolean) {
  if (EXPLICIT_WEB_REQUEST_RE.test(message)) return true;
  if (hasSource) return false;
  return CURRENT_INFO_TOPIC_RE.test(message) || CURRENT_ROLE_RE.test(message) || YEARLY_UPDATE_RE.test(message);
}

const PLACEHOLDER_TOPIC_RE =
  /\b(topic\s+i\s+type|topic\s+you\s+type|topic\s+first|the\s+topic|break\s+it\s+into\s+scenes|matched\s+to\s+your\s+material)\b/i;

// ── Helpers ───────────────────────────────────────────────────────────────────

function cleanTopicCandidate(value: string) {
  return value
    .replace(/[?.!,]+$/g, "")
    // strip leading request filler ("make a", "give me some", "please a", etc.)
    .replace(/^((?:please|make|create|generate|build|write|prepare|give\s+me|a|an|the|some)\s+)+/gi, "")
    .replace(/\b(fully|completely|properly|fast|quickly|from\s+scratch|from\s+zero)\b/gi, " ")
    .replace(/\b(?:in|within|by|before|for)\s+(?:one\s+night|tonight|tomorrow|the\s+exam|my\s+exam|exam)\b.*$/i, " ")
    .replace(/\b(?:one\s+night|overnight|tonight|tomorrow|cram|crash\s+course)\b.*$/i, " ")
    .replace(/\s+/g, " ")
    .trim();
}

function extractTopic(message: string): string | null {
  const patterns: RegExp[] = [
    // Tool requests with a preposition: "<tool words> ... on/for/about <topic>".
    // Runs first so "study video for calculus" β†’ "calculus" (not "video for calculus").
    /(?:notes?|quiz(?:zes)?|flash\s?cards?|memory\s+cards?|revision\s+cards?|video(?:\s+plan)?|answer|questions?|test|summary)\s+(?:me\s+)?(?:on|for|about|of)\s+(.+)/i,
    // Direct teach/learn verbs.
    /(?:i\s+(?:want|need|have)\s+to\s+learn\s+|i\s+must\s+learn\s+|help\s+me\s+(?:learn|understand)\s+|teach\s+(?:me\s+)?|explain\s+(?:me\s+)?|what\s+is\s+|what\s+are\s+|define\s+|tell\s+me\s+about\s+|about\s+|learn\s+|finish\s+|complete\s+|revise\s+|study\s+|summarize\s+|summarise\s+|prove\s+|derive\s+|derivation\s+of\s+)(.+)/i,
    // Trailing form: "<topic> notes/quiz/flashcards/...".
    /^(.+?)\s+(?:notes?|quiz|flash\s?cards?|explained?|summary|definition|meaning)\s*$/i,
  ];
  const STOP_WORDS = new Set(["make", "create", "generate", "build", "write", "give", "me", "some", "a", "an", "study", "video", "memory", "card", "cards"]);

  for (const p of patterns) {
    const m = message.match(p);
    if (m?.[1]) {
      const topic = cleanTopicCandidate(m[1]);
      const lc = topic.toLowerCase();
      if (
        topic.length > 2 &&
        topic.length < 120 &&
        !STOP_WORDS.has(lc) &&
        !/^(this|that|my|the|it|everything|all of it)$/i.test(topic)
      ) {
        return topic;
      }
    }
  }
  return null;
}

function isDocRef(message: string): boolean {
  return DOC_REF_RE.test(message);
}

function sourceTitleMentioned(message: string, selectedSources: readonly AnswerModeSource[]): boolean {
  const lower = message.toLowerCase();
  return selectedSources.some((source) => {
    const title = (source.title || source.file_name || "")
      .toLowerCase()
      .replace(/\.[a-z0-9]+$/i, "")
      .trim();
    return title.length >= 4 && lower.includes(title);
  });
}

function toolTypeForMessage(lower: string): StudyIntent | null {
  if (PYQ_RE.test(lower)) return "analyze_pyq";
  if (NOTES_RE.test(lower)) return "generate_notes";
  if (QUIZ_RE.test(lower)) return "generate_quiz";
  if (FLASHCARD_RE.test(lower)) return "generate_flashcards";
  if (VIDEO_RE.test(lower)) return "create_video_plan";
  if (STUDY_PLAN_RE.test(lower)) return "create_revision_plan";
  if (EXAM_ANSWER_RE.test(lower)) return "generate_exam_answer";
  return null;
}

function uploadPromptForTool(toolType: StudyIntent | null): string {
  if (toolType === "analyze_pyq" || toolType === "predict_questions") {
    return "Upload previous question papers first. I'll find repeated topics, marks weightage, trends, and likely questions from those papers.";
  }
  if (toolType === "generate_notes" || toolType === "summarize_source") {
    return "Tell me the topic, paste text, or upload a chapter. I can create scoring notes from any of those.";
  }
  if (toolType === "create_video_plan" || toolType === "render_video") {
    return "Tell me the topic first, or select a source if you want the video matched to your material.";
  }
  return "Tell me the topic, goal, or time left. Uploading is optional unless you want source-matched output.";
}

function mapAnswerModeToResponseMode(mode: AnswerMode): ResponseMode {
  switch (mode) {
    case "casual_chat":
      return "casual";
    case "source_required_upload_prompt":
      return "needs_source";
    case "source_grounded_answer":
      return "source_grounded";
    case "tool_generation":
      return "tool_ready";
    case "clarification":
      return "clarify";
    case "web_search_answer":
    case "general_ai_answer":
    default:
      return "general_answer";
  }
}

function promptForMode(result: AnswerModeResult): string | null {
  if (result.mode === "source_required_upload_prompt") {
    return result.userFacingNextStep;
  }
  return null;
}

export function detectAnswerMode({
  message,
  selectedSources = [],
  activeTool = defaultStudyIntent,
  conversationContext = [],
  webSearchEnabled = false,
}: {
  message: string;
  selectedSources?: readonly AnswerModeSource[];
  activeTool?: StudyIntent;
  conversationContext?: readonly ChatMessage[];
  webSearchEnabled?: boolean;
}): AnswerModeResult {
  const trimmed = message.trim();

  // Pure greeting ("hi", "hello bro") or "Hi, my name is X" stay casual.
  if (GREETING_RE.test(trimmed) || CASUAL_INTRO_RE.test(trimmed)) {
    return {
      mode: "casual_chat",
      intent: "casual",
      requiresSource: false,
      shouldUseWeb: false,
      canAnswerGenerally: false,
      topic: null,
      toolType: null,
      userFacingNextStep: null,
    };
  }

  // "Hi teach me +2 matrix maths" β€” a greeting prefix in front of a REAL ask.
  // Strip the greeting and process the actual request, otherwise a friendly
  // "Hi" would downgrade a genuine study question to the casual welcome card.
  let working = trimmed;
  const greetingPrefixMatch = GREETING_PREFIX_RE.exec(trimmed);
  if (greetingPrefixMatch) {
    const rest = trimmed.slice(greetingPrefixMatch[0].length).trim();
    if (rest.length >= 2 && !GREETING_RE.test(rest)) {
      working = rest;
    }
  }

  const lower = working.toLowerCase();
  const hasSource = selectedSources.length > 0;
  const topic = extractTopic(working);
  const explicitSourceRequest = isDocRef(working) || sourceTitleMentioned(working, selectedSources);
  const toolType = toolTypeForMessage(lower);
  const asksForCurrentInfo = asksForLiveWebInformation(lower, hasSource);

  if (CAPABILITY_RE.test(working)) {
    return {
      mode: "casual_chat",
      intent: "help",
      requiresSource: false,
      shouldUseWeb: false,
      canAnswerGenerally: false,
      topic: null,
      toolType: null,
      userFacingNextStep: null,
    };
  }

  if (UNCLEAR_RE.test(working)) {
    const lastAssistant = [...conversationContext].reverse().find(
      (m) => m.role === "assistant" && m.card && m.card.kind !== "loading" && m.card.kind !== "error",
    );
    if (lastAssistant && /^(yes|yep|yeah|sure|do it|go ahead|continue|next|proceed)$/i.test(working)) {
      return {
        mode: "general_ai_answer",
        intent: "continuation",
        requiresSource: false,
        shouldUseWeb: false,
        canAnswerGenerally: true,
        topic: null,
        toolType: activeTool,
        userFacingNextStep: null,
      };
    }
    if (/^(ok(?:ay)?|thanks?|thank you|cool|nice|got it|fine)$/i.test(working)) {
      return {
        mode: "casual_chat",
        intent: "casual",
        requiresSource: false,
        shouldUseWeb: false,
        canAnswerGenerally: false,
        topic: null,
        toolType: null,
        userFacingNextStep: null,
      };
    }
    return {
      mode: "clarification",
      intent: "ambiguous",
      requiresSource: false,
      shouldUseWeb: false,
      canAnswerGenerally: false,
      topic: null,
      toolType: activeTool,
      userFacingNextStep: "Tell me the topic, or upload/select a source if you want textbook-matched output.",
    };
  }

  if (explicitSourceRequest && !hasSource) {
    return {
      mode: "source_required_upload_prompt",
      intent: toolType ? "tool_request" : "source_specific",
      requiresSource: true,
      shouldUseWeb: false,
      canAnswerGenerally: false,
      topic,
      toolType,
      userFacingNextStep: toolType ? uploadPromptForTool(toolType) : "Upload/select the PDF or source first, then I'll explain it clearly.",
    };
  }

  // A live/current-information request is an evidence route, not a study-plan
  // generator merely because the sentence also contains "timetable" or
  // "schedule". Put it ahead of tool detection so official notices and exam
  // dates reach web retrieval. Personal requests such as "update my plan" and
  // "what should I study today?" do not satisfy the narrow web predicate.
  if (asksForCurrentInfo) {
    return {
      mode: "web_search_answer",
      intent: "current_info",
      requiresSource: false,
      shouldUseWeb: webSearchEnabled,
      canAnswerGenerally: true,
      topic: topic ?? working.slice(0, 80),
      toolType: null,
      userFacingNextStep: webSearchEnabled ? null : "I can answer generally, but live internet search is not connected yet.",
    };
  }

  if (toolType) {
    if (toolType === "create_video_plan" && topic && PLACEHOLDER_TOPIC_RE.test(topic) && !hasSource) {
      return {
        mode: "clarification",
        intent: "tool_request",
        requiresSource: false,
        shouldUseWeb: false,
        canAnswerGenerally: false,
        topic: null,
        toolType,
        userFacingNextStep: uploadPromptForTool(toolType),
      };
    }

    // PYQ: always run the tool β€” it returns REAL downloadable Kerala previous
    // papers (no source needed). If a source IS attached it also analyzes it.
    if (toolType === "analyze_pyq" || toolType === "predict_questions") {
      return {
        mode: "tool_generation",
        intent: "tool_request",
        requiresSource: false,
        shouldUseWeb: false,
        canAnswerGenerally: true,
        topic,
        toolType,
        userFacingNextStep: null,
      };
    }

    if (!topic && !hasSource && !asksForCurrentInfo) {
      if (toolType === "create_revision_plan") {
        return {
          mode: "tool_generation",
          intent: "tool_request",
          requiresSource: false,
          shouldUseWeb: false,
          canAnswerGenerally: true,
          topic: "your topic",
          toolType,
          userFacingNextStep: null,
        };
      }
      return {
        mode: "clarification",
        intent: "tool_request",
        requiresSource: false,
        shouldUseWeb: false,
        canAnswerGenerally: false,
        topic: null,
        toolType,
        userFacingNextStep: "Tell me the topic first. You can type it, paste text, or upload a source if you want textbook-matched output.",
      };
    }

    return {
      mode: "tool_generation",
      intent: "tool_request",
      requiresSource: explicitSourceRequest || (!topic && hasSource),
      shouldUseWeb: asksForCurrentInfo && webSearchEnabled,
      canAnswerGenerally: Boolean(topic) || !explicitSourceRequest,
      topic: topic ?? working.slice(0, 80),
      toolType,
      userFacingNextStep: asksForCurrentInfo && !webSearchEnabled
        ? "I can answer generally, but live internet search is not connected yet."
        : null,
    };
  }

  if (explicitSourceRequest && hasSource) {
    return {
      mode: "source_grounded_answer",
      intent: "source_specific",
      requiresSource: true,
      shouldUseWeb: false,
      canAnswerGenerally: false,
      topic,
      toolType: null,
      userFacingNextStep: null,
    };
  }

  return {
    mode: "general_ai_answer",
    intent: "general_topic",
    requiresSource: false,
    shouldUseWeb: false,
    canAnswerGenerally: true,
    topic: topic ?? working.slice(0, 80),
    toolType: activeTool === defaultStudyIntent ? "teach_topic" : activeTool,
    userFacingNextStep: null,
  };
}

// ── Main export ───────────────────────────────────────────────────────────────

function buildCasualReply(
  intent: string,
  selectedSources: ApiDocument[],
  message: string,
): string {
  const hasSource = selectedSources.length > 0;
  const sourceHint = hasSource
    ? `You have ${selectedSources.length} source${selectedSources.length > 1 ? "s" : ""} ready.`
    : "Upload a textbook, notes, or question paper, or just ask me any topic.";

  // Identity / name question
  if (
    /\b(what(?:'s|\s+is)\s+your\s+name|who\s+are\s+you|are\s+you\s+(an?\s+)?(ai|bot|chatbot|robot)|what\s+are\s+you)\b/i.test(message)
  ) {
    return `I'm **DocDoe Knowledge Brain** β€” an exam intelligence system for Indian board and competitive exams.\n\nI retrieve from your uploaded sources, apply mark-scheme logic, and generate explanations, notes, quizzes, flashcards, PYQ analysis, study paths, and video plans.\n\n${sourceHint}`;
  }

  // General greeting
  if (/^\s*(hi+|hey+|hello+|yo+|sup|bro|howdy|hola|ready|start)\b/i.test(message)) {
    return hasSource
      ? `${selectedSources.length} source${selectedSources.length > 1 ? "s are" : " is"} active. Ask a question, or generate notes, a quiz, flashcards, a revision plan, or a video plan from your material.`
      : "DocDoe Knowledge Brain is ready. Ask any topic for a grounded explanation, or upload a textbook, notes, or previous paper for source-matched exam output.";
  }

  // Capability / help
  if (intent === "help") {
    return `DocDoe provides seven study workflows: chapter explanations, scoring notes, practice quizzes, flashcards, PYQ pattern analysis, revision plans, and study video plans.\n\nAttach a source for textbook-matched answers, or ask a topic for general exam intelligence.`;
  }

  // Generic acknowledgement (ok, thanks, etc.)
  return `Understood. What should we work on next β€” explain a topic, generate notes, run a quiz, or attach a source?`;
}

export function detectStudyChatIntent(
  message: string,
  selectedSources: ApiDocument[],
  activeTool: StudyIntent,
  conversationContext: ChatMessage[],
): ChatIntentResult {
  const detection = detectAnswerMode({
    message,
    selectedSources,
    activeTool,
    conversationContext,
    webSearchEnabled: false,
  });
  const responseMode = mapAnswerModeToResponseMode(detection.mode);

  // For casual mode: always build an immediateReply so the welcome card renders
  // without hitting the backend. For source prompts, use the structured CTA.
  let immediateReply: string | null = null;
  if (detection.mode === "casual_chat") {
    immediateReply = buildCasualReply(detection.intent, selectedSources, message);
  } else {
    immediateReply = promptForMode(detection);
  }

  // When no tool is detected, a fresh general/source answer must NOT inherit a
  // stale tool from the previous turn (e.g. asking a topic right after a PYQ
  // request). Default to teach_topic / explain_source instead of activeTool.
  const intent =
    detection.toolType ??
    (detection.mode === "source_grounded_answer" || detection.mode === "source_required_upload_prompt"
      ? "explain_source"
      : "teach_topic");

  return {
    intent,
    responseMode,
    answerMode: detection.mode,
    shouldUseWeb: detection.shouldUseWeb,
    userFacingNextStep: detection.userFacingNextStep,
    requiresSource: detection.requiresSource,
    canAnswerGenerally: detection.canAnswerGenerally,
    immediateReply,
    topic: detection.topic,
  };
}