import React, { createContext, useCallback, useEffect, useMemo, useState, } from "react"; import toast from "react-hot-toast"; import medicoService, { type Medico } from "../services/medicoService"; import authService, { type UserInfoFullResponse, } from "../services/authService"; // tokens + user-info // tokenManager removido no modelo somente Supabase (sem usuário técnico) // Tipos de roles suportados export type UserRole = | "secretaria" | "medico" | "paciente" | "admin" | "gestor" | "user"; // Role genérica para pacientes export interface SessionUserBase { id: string; nome: string; email?: string; role: UserRole; roles?: UserRole[]; permissions?: { [k: string]: boolean | undefined }; } export interface SecretariaUser extends SessionUserBase { role: "secretaria"; } export interface MedicoUser extends SessionUserBase { role: "medico"; crm?: string; especialidade?: string; } export interface PacienteUser extends SessionUserBase { role: "paciente"; pacienteId?: string; } export interface AdminUser extends SessionUserBase { role: "admin"; } export type SessionUser = | SecretariaUser | MedicoUser | PacienteUser | AdminUser | (SessionUserBase & { role: "gestor" }); interface AuthContextValue { user: SessionUser | null; isAuthenticated: boolean; loading: boolean; loginSecretaria: (email: string, senha: string) => Promise; loginMedico: (email: string, senha: string) => Promise; loginComEmailSenha: (email: string, senha: string) => Promise; // fluxo unificado real loginPaciente: (paciente: { id: string; nome: string; email?: string; }) => Promise; logout: () => void; role: UserRole | null; roles: UserRole[]; permissions: Record; refreshSession: () => Promise; } const AuthContext = createContext(undefined); const STORAGE_KEY = "appSession"; interface PersistedSession { user: SessionUser; token?: string; // para quando integrar authService real refreshToken?: string; savedAt: string; } export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children, }) => { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); // Restaurar sessão do localStorage e verificar token useEffect(() => { const restoreSession = async () => { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) { const parsed = JSON.parse(raw) as PersistedSession; if (parsed?.user?.role) { console.log("[AuthContext] Restaurando sessão:", parsed.user); // Verificar se há tokens válidos salvos if (parsed.token) { console.log("[AuthContext] Restaurando tokens no tokenStore"); // Restaurar tokens no tokenStore const tokenStore = (await import("../services/tokenStore")) .default; tokenStore.setTokens(parsed.token, parsed.refreshToken); } else { console.warn("[AuthContext] Sessão encontrada mas sem token. Verificando tokenStore..."); // Verificar se há token no tokenStore (pode ter sido salvo diretamente) const tokenStore = (await import("../services/tokenStore")) .default; const existingToken = tokenStore.getAccessToken(); if (existingToken) { console.log("[AuthContext] Token encontrado no tokenStore, mantendo sessão"); } else { console.warn("[AuthContext] Nenhum token encontrado. Sessão pode estar inválida."); } } setUser(parsed.user); } } else { console.log("[AuthContext] Nenhuma sessão salva encontrada"); } } catch (error) { console.error("[AuthContext] Erro ao restaurar sessão:", error); } finally { setLoading(false); } }; void restoreSession(); }, []); const persist = useCallback((session: PersistedSession) => { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(session)); } catch { /* ignore */ } }, []); const clearPersisted = useCallback(() => { try { localStorage.removeItem(STORAGE_KEY); } catch { /* ignore */ } }, []); const normalizeRole = (r: string | undefined): UserRole | undefined => { if (!r) return undefined; const map: Record = { medico: "medico", doctor: "medico", secretaria: "secretaria", assistant: "secretaria", paciente: "paciente", patient: "paciente", user: "paciente", // Role genérica mapeada para paciente admin: "admin", gestor: "gestor", manager: "gestor", }; return map[r.toLowerCase()] || undefined; }; const pickPrimaryRole = (rolesArr: UserRole[]): UserRole => { const priority: UserRole[] = [ "admin", "gestor", "medico", "secretaria", "paciente", ]; for (const p of priority) if (rolesArr.includes(p)) return p; return rolesArr[0] || "paciente"; }; const buildSessionUser = React.useCallback( (info: UserInfoFullResponse): SessionUser => { console.log( "[buildSessionUser] info recebido:", JSON.stringify(info, null, 2) ); const rolesNormalized = (info.roles || []) .map(normalizeRole) .filter(Boolean) as UserRole[]; console.log("[buildSessionUser] roles normalizadas:", rolesNormalized); const permissions = info.permissions || {}; const primaryRole = pickPrimaryRole( rolesNormalized.length ? rolesNormalized : [normalizeRole((info.roles || [])[0]) || "paciente"] ); console.log("[buildSessionUser] primaryRole escolhida:", primaryRole); const base = { id: info.user?.id || "", nome: info.profile?.full_name || info.user?.email?.split("@")[0] || "Usuário", email: info.user?.email, role: primaryRole, roles: rolesNormalized, permissions, } as SessionUserBase; console.log("[buildSessionUser] SessionUser final:", base); if (primaryRole === "medico") { return { ...base, role: "medico" } as MedicoUser; } if (primaryRole === "secretaria") { return { ...base, role: "secretaria" } as SecretariaUser; } if (primaryRole === "admin") { return { ...base, role: "admin" } as AdminUser; } if (primaryRole === "gestor") { return { ...base, role: "gestor" } as SessionUser; } return { ...base, role: "paciente" } as PacienteUser; }, [] ); // LEGADO: manter até que todos os usuários passem a existir no auth real const loginSecretaria = useCallback( async (email: string, senha: string) => { // Mock atual: validar contra credenciais fixas (pode evoluir para authService.login) if (email === "secretaria@clinica.com" && senha === "secretaria123") { const newUser: SecretariaUser = { id: "sec-1", nome: "Secretária", email, role: "secretaria", roles: ["secretaria"], permissions: {}, }; setUser(newUser); persist({ user: newUser, savedAt: new Date().toISOString() }); toast.success("Login realizado"); return true; } toast.error("Credenciais inválidas"); return false; }, [persist] ); // LEGADO: usa service de médicos sem validar senha real (apenas existência) const loginMedico = useCallback( async (email: string, senha: string) => { const resp = await medicoService.loginMedico(email, senha); if (!resp.success || !resp.data) { toast.error(resp.error || "Erro ao autenticar médico"); return false; } const m: Medico = resp.data; const newUser: MedicoUser = { id: m.id, nome: m.nome, email: m.email, role: "medico", crm: m.crm, especialidade: m.especialidade, roles: ["medico"], permissions: {}, }; setUser(newUser); persist({ user: newUser, savedAt: new Date().toISOString() }); toast.success(`Bem-vindo(a) Dr(a). ${m.nome}`); return true; }, [persist] ); // Fluxo unificado real usando authService + endpoint user-info para mapear role dinâmica const loginComEmailSenha = useCallback( async (email: string, senha: string) => { console.log("[AuthContext] Iniciando login para:", email); const loginResp = await authService.login({ email, password: senha }); console.log("[AuthContext] Resposta login:", loginResp); if (!loginResp.success || !loginResp.data) { console.error("[AuthContext] Login falhou:", loginResp.error); toast.error(loginResp.error || "Falha no login"); return false; } console.log("[AuthContext] Token recebido, buscando user-info..."); // Buscar user-info para descobrir papel const infoResp = await authService.getUserInfo(); console.log("[AuthContext] Resposta user-info:", infoResp); if (!infoResp.success || !infoResp.data) { console.error( "[AuthContext] Falha ao obter user-info:", infoResp.error ); toast.error(infoResp.error || "Falha ao obter user-info"); return false; } const sessionUser = buildSessionUser(infoResp.data); console.log("[AuthContext] Usuário da sessão criado:", sessionUser); setUser(sessionUser); persist({ user: sessionUser, savedAt: new Date().toISOString(), token: loginResp.data.access_token, refreshToken: loginResp.data.refresh_token, }); console.log("[AuthContext] Login completo!"); toast.success("Login realizado"); return true; }, [persist, buildSessionUser] ); // Para paciente, aproveitamos fluxo existente: quando o paciente já foi validado externamente no loginPaciente const loginPaciente = useCallback( async (paciente: { id: string; nome: string; email?: string }) => { console.log("[AuthContext] loginPaciente chamado com:", paciente); const newUser: PacienteUser = { id: paciente.id, nome: paciente.nome, email: paciente.email, role: "paciente", pacienteId: paciente.id, roles: ["paciente"], permissions: {}, }; console.log("[AuthContext] Usuário criado:", newUser); setUser(newUser); persist({ user: newUser, savedAt: new Date().toISOString() }); // Bridge para páginas que ainda leem localStorage("pacienteLogado") try { const legacy = { _id: paciente.id, nome: paciente.nome, email: paciente.email ?? "", cpf: "", telefone: "", }; localStorage.setItem("pacienteLogado", JSON.stringify(legacy)); } catch { // ignore } console.log("[AuthContext] Usuário persistido no localStorage"); toast.success(`Bem-vindo(a), ${paciente.nome}`); return true; }, [persist] ); const logout = useCallback(async () => { try { const resp = await authService.logout(); // chama /auth/v1/logout (204 esperado) if (!resp.success && resp.error) { toast.error(`Falha no logout remoto: ${resp.error}`); } else { toast.success("Sessão encerrada no servidor"); } } catch (e) { console.warn("Erro inesperado ao executar logout remoto", e); toast("Logout local (falha remota)"); } finally { // Limpa contexto local setUser(null); clearPersisted(); authService.clearLocalAuth(); try { localStorage.removeItem("pacienteLogado"); } catch { // ignore } // Modelo somente Supabase: nenhum token técnico para invalidar } }, [clearPersisted]); const refreshSession = useCallback(async () => { // Futuro: usar refresh token real. Agora apenas revalida estrutura. try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return; const parsed = JSON.parse(raw) as PersistedSession; if (!parsed?.user?.role) return; setUser(parsed.user); } catch { // ignorar } }, []); const value: AuthContextValue = useMemo( () => ({ user, role: user?.role ?? null, roles: user?.roles || (user?.role ? [user.role] : []), permissions: user?.permissions || {}, isAuthenticated: !!user, loading, loginSecretaria, loginMedico, loginComEmailSenha, loginPaciente, logout, refreshSession, }), [ user, loading, loginSecretaria, loginMedico, loginComEmailSenha, loginPaciente, logout, refreshSession, ] ); return {children}; }; export default AuthContext;