import { useMutation, useQueryClient } from "@tanstack/react-query" import { canUseWritingCheck, recordWritingCheckUsage } from "@/utils/rate-limiter" import { useAuthStore } from "@/store/auth-store" import { saveWritingSubmission, countTodayWritingSubmissions } from "@/lib/progress-service" import type { WritingFeedback } from "@/types" const AUTH_DAILY_LIMIT = 10 const GUEST_DAILY_LIMIT = 3 // Resolve env at runtime — production injects window.__ENV__ via docker/entrypoint.sh, // dev reads from Vite's import.meta.env. Must match src/lib/supabase.ts. function resolveSupabaseEnv() { const runtime = (window as unknown as { __ENV__?: Record }).__ENV__ ?? {} const url = runtime.VITE_SUPABASE_URL || import.meta.env.VITE_SUPABASE_URL const key = runtime.VITE_SUPABASE_ANON_KEY || runtime.VITE_SUPABASE_PUBLISHABLE_KEY || import.meta.env.VITE_SUPABASE_ANON_KEY || import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY return { url: url as string | undefined, key: key as string | undefined } } // Calls the writing-check-dbiz Supabase edge function. // SSE format emitted by the function: data: {"text":"..."} | data: [DONE] async function callEdgeFunction( content: string, onChunk?: (text: string) => void, ): Promise { const { url, key } = resolveSupabaseEnv() if (!url || !key) { throw new Error("Supabase chưa được cấu hình. Vui lòng kiểm tra biến môi trường.") } const res = await fetch(`${url}/functions/v1/writing-check-dbiz`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${key}`, apikey: key, }, body: JSON.stringify({ content }), }) if (!res.ok) { const body = await res.json().catch(() => ({})) throw new Error(body?.error ?? "Đã có lỗi khi chấm bài. Vui lòng thử lại.") } const reader = res.body!.getReader() const decoder = new TextDecoder() let buffer = "" let accumulated = "" while (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const lines = buffer.split("\n") buffer = lines.pop() ?? "" for (const line of lines) { if (!line.startsWith("data: ")) continue const payload = line.slice(6).trim() if (payload === "[DONE]") continue let chunk: { text?: string; error?: string } try { chunk = JSON.parse(payload) } catch { continue } if (chunk.error) throw new Error(chunk.error) const text = chunk.text ?? "" if (text) { accumulated += text onChunk?.(text) } } } const start = accumulated.indexOf("{") const end = accumulated.lastIndexOf("}") if (start === -1 || end === -1) { throw new Error("Phản hồi từ AI không hợp lệ. Vui lòng thử lại.") } const raw = JSON.parse(accumulated.slice(start, end + 1)) const toArray = (v: unknown): string[] => { if (Array.isArray(v)) return v if (typeof v === "string" && v.length > 0) return [v] return [] } return { ...raw, grammar: toArray(raw.grammar), vocabulary: toArray(raw.vocabulary), improvedVersion: raw.improved_version ?? raw.improvedVersion ?? "", } as WritingFeedback } export function useWritingCheck() { const queryClient = useQueryClient() return useMutation({ mutationFn: async ({ content, onChunk, }: { content: string onChunk?: (text: string) => void }): Promise => { const user = useAuthStore.getState().user if (user) { const usedToday = await countTodayWritingSubmissions(user.id) if (usedToday >= AUTH_DAILY_LIMIT) { throw new Error(`Bạn đã dùng hết ${AUTH_DAILY_LIMIT} lần kiểm tra hôm nay. Quay lại vào ngày mai!`) } } else { if (!canUseWritingCheck()) { throw new Error(`Bạn đã dùng hết ${GUEST_DAILY_LIMIT} lần kiểm tra hôm nay. Đăng ký để được 10 lần/ngày!`) } } const feedback = await callEdgeFunction(content, onChunk) if (user) { await saveWritingSubmission(user.id, content, feedback) queryClient.invalidateQueries({ queryKey: ["writing-history"] }) } else { recordWritingCheckUsage() } return feedback }, }) }