// Shims dos services do motor sobre a ext API do satélite (/api/nw/v1/*). // Mantém a MESMA interface que o chatApiService.ts do motor expõe, normalizando // os shapes da ext API para o formato que os stores/hooks portados esperam. import { nw } from './nwClient'; // ─── Instâncias ── (ext /sessions) ────────────────────────────────────────── export const instanceApi = { list: () => nw.get('/sessions'), create: (name: string) => nw.post('/sessions', { name }), connect: (id: string) => nw.post(`/sessions/${id}/connect`), disconnect: (id: string) => nw.post(`/sessions/${id}/disconnect`), getQr: (id: string) => nw.get(`/sessions/${id}/qr`), delete: (id: string) => nw.delete(`/sessions/${id}`), }; // ─── Avatar self-heal ── (ext /contacts/:jid/avatar/refresh) ───────────────── // A URL do CDN do WhatsApp expira e passa a 404; aqui pedimos ao motor para // re-buscar a foto atual pela conexão viva. Retorna a URL nova ou null. export const avatarApi = { refresh: async (instanceId: string, jid: string): Promise => { try { const r = await nw.get(`/contacts/${encodeURIComponent(jid)}/avatar/refresh?session=${encodeURIComponent(instanceId)}`); return r?.avatar ?? null; } catch { return null; } }, }; // ─── Chats ── (ext /inbox → shape achatado; normaliza p/ mapBackendChat) ──── export const chatApi = { list: async (instanceId: string, opts: { search?: string; archived?: boolean; limit?: number } = {}) => { const q = new URLSearchParams({ session: instanceId }); if (opts.search) q.set('search', opts.search); if (opts.limit) q.set('limit', String(opts.limit)); const raw = await nw.get(`/inbox?${q.toString()}`); const arr = Array.isArray(raw) ? raw : []; // ext: { id, jid, displayName, avatar, phone, unreadCount, lastMessageAt, // isPinned, isArchived, lastMessage } → shape esperado pelo chatStore. return arr.map((c: any) => ({ id: c.id, jid: c.jid, instanceId, name: c.displayName ?? null, contactName: c.displayName ?? null, contactPhone: c.phone ?? null, contactAvatarUrl: c.avatar ?? null, unreadCount: c.unreadCount ?? 0, lastMessageAt: c.lastMessageAt ?? null, createdAt: c.lastMessageAt ?? null, isPinned: c.isPinned ?? false, isArchived: c.isArchived ?? false, lastMessage: c.lastMessage ?? null, })); }, markRead: (chatId: string) => nw.post(`/inbox/${chatId}/read`), archive: (chatId: string, archived: boolean) => nw.patch(`/inbox/${chatId}/archive`, { archived }), pin: (chatId: string, pinned: boolean) => nw.patch(`/inbox/${chatId}/pin`, { pinned }), delete: (chatId: string) => nw.delete(`/inbox/${chatId}`), }; // ─── Mensagens ── (ext devolve array; envolve em {messages, hasMore}) ─────── export const messageApi = { list: async (chatId: string, opts: { before?: string; after?: string; limit?: number } = {}) => { const q = new URLSearchParams(); if (opts.before) q.set('before', opts.before); if (opts.limit) q.set('limit', String(opts.limit)); const qs = q.toString(); const raw = await nw.get(`/inbox/${chatId}/messages${qs ? `?${qs}` : ''}`); const arr = Array.isArray(raw) ? raw : (raw?.messages ?? []); const limit = opts.limit ?? 50; return { messages: arr.map((m: any) => ({ ...m, chatId, replyToId: m.replyToId ?? null, replyTo: m.replyTo ?? null })), hasMore: arr.length >= limit, }; }, search: (instanceId: string, q: string, limit?: number) => { const p = new URLSearchParams({ session: instanceId, q }); if (limit) p.set('limit', String(limit)); return nw.get(`/inbox/messages/search?${p.toString()}`); }, // Envio por chatId (a ext resolve jid/instância). O motor chamava por // (instanceId, jid, ...); o chatStore foi adaptado p/ passar (chatId, text, replyTo). send: (chatId: string, text: string, replyToMessageId?: string) => nw.post(`/inbox/${chatId}/send`, { text, ...(replyToMessageId ? { replyToMessageId } : {}) }), }; // ─── Mídia ── (ext /inbox/:chatId/send-media | send-audio) ────────────────── export const mediaApi = { sendMedia: (chatId: string, file: File, caption?: string, replyToMessageId?: string) => { const fd = new FormData(); fd.append('file', file); if (caption) fd.append('caption', caption); if (replyToMessageId) fd.append('replyToMessageId', replyToMessageId); return nw.post(`/inbox/${chatId}/send-media`, fd); }, sendAudio: (chatId: string, blob: Blob) => { const fd = new FormData(); fd.append('audio', blob, 'audio.ogg'); return nw.post(`/inbox/${chatId}/send-audio`, fd); }, // Sticker: reaproveita o endpoint de mídia (a ext detecta image/webp → sticker). sendSticker: (chatId: string, blob: Blob) => { const fd = new FormData(); fd.append('file', new File([blob], 'sticker.webp', { type: 'image/webp' })); return nw.post(`/inbox/${chatId}/send-media`, fd); }, // Re-download não existe na ext (mídia já vem resolvida). No-op gracioso. redownload: (_instanceId: string, _messageDbId: string): Promise<{ mediaUrl: string }> => Promise.resolve({ mediaUrl: '' }), }; // ─── Grupos (ext não expõe participantes — stub gracioso) ─────────────────── export const groupApi = { getParticipants: (_instanceId: string, groupJid: string) => Promise.resolve({ groupJid, subject: '', participants: [] as any[] }), }; // ─── Protocolo (ext ainda não expõe CRUD por chat — stub; task follow-up) ─── export const protocolApi = { list: (_chatId: string) => Promise.resolve([] as any[]), open: (_chatId: string, _data: { sectorId?: string; teamId?: string }) => Promise.resolve(null as any), update: (_chatId: string, _protocolId: string, _data: { status?: string; summary?: string }) => Promise.resolve(null as any), }; // ─── Reconciliação (ext não expõe — no-op) ────────────────────────────────── export const reconcileApi = { reconcileNames: (_instanceId: string) => Promise.resolve({ updated: 0 }), }; // ─── Tipos (compat com o motor; usados como type-only pelos componentes) ──── export interface GroupParticipant { jid: string; admin: string | null; name: string | null; phone: string } export interface Sector { id: string; tenantId: string; name: string; isActive: boolean; createdAt: string; updatedAt: string; _count?: { teams: number; protocols: number } } export interface Team { id: string; tenantId: string; sectorId: string; name: string; isActive: boolean; createdAt: string; updatedAt: string; _count?: { protocols: number } } export type TemplateType = 'TEXT' | 'BUTTONS' | 'INTERACTIVE' | 'LIST' | 'POLL' | 'CAROUSEL' export interface MessageTemplate { id: string; tenantId: string; name: string; description: string | null; type: TemplateType; payload: Record; tags: string[]; usageCount: number; createdAt: string; updatedAt: string } export interface StickerRecord { id: string; sha256: string; wasabiPath: string; isAnimated: boolean; isFavorite: boolean; useCount?: number; lastSeenAt?: string } export interface RichPayload { text?: string; footer?: string; nativeButtons?: any[]; nativeList?: any; nativeCarousel?: any; poll?: any } // ─── Setores/Equipes (ext não expõe protocolo/setores — stubs) ────────────── export const sectorApi = { list: () => Promise.resolve([] as Sector[]), create: (_name: string) => Promise.resolve(null as any), update: (_id: string, _d: any) => Promise.resolve(null as any), delete: (_id: string) => Promise.resolve({} as any), listTeams: (_sectorId: string) => Promise.resolve([] as Team[]), createTeam: (_sectorId: string, _name: string) => Promise.resolve(null as any), updateTeam: (_sectorId: string, _teamId: string, _d: any) => Promise.resolve(null as any), deleteTeam: (_sectorId: string, _teamId: string) => Promise.resolve({} as any), }; // ─── Templates (stub simples) ─────────────────────────────────────────────── export const templateApi = { list: (_opts: any = {}) => Promise.resolve([] as MessageTemplate[]), get: (_id: string) => Promise.resolve(null as any), create: (_d: any) => Promise.resolve(null as any), update: (_id: string, _d: any) => Promise.resolve(null as any), delete: (_id: string) => Promise.resolve({} as any), markUsed: (_id: string) => Promise.resolve({} as any), }; // ─── Stickers (ext não expõe catálogo — stub) ─────────────────────────────── export const stickerApi = { list: () => Promise.resolve({ listVersion: '', stickers: [] as StickerRecord[] }), recent: () => Promise.resolve({ stickers: [] as StickerRecord[] }), toggleFavorite: (id: string) => Promise.resolve({ id, isFavorite: false }), }; // ─── Rich message (composer passa instanceId/jid; sem chatId não mapeia na // ext → stub que rejeita graciosamente) ───────────────────────────────────── export const richMessageApi = { send: (_instanceId: string, _jid: string, _payload: RichPayload) => Promise.reject(new Error('Envio rico indisponível no satélite')), }; // ─── Demais exports do chatApiService do motor (stubs — a ext não os expõe; // mantidos para os named imports da árvore não quebrarem em runtime) ───────── export interface ApiKeyRecord { id: string; name: string; keyPreview: string; isActive: boolean; createdAt: string; expiresAt: string | null } export interface BackendContact { id: string; instanceId: string; jid: string; name: string | null; verifiedName: string | null; notify: string | null; phone: string | null; avatarUrl: string | null; scoreReputacao: number; flagRestricao: boolean; isBlocked: boolean; createdAt: string; updatedAt: string } export type EventType = 'BIRTHDAY' | 'ANNIVERSARY' | 'SIGNUP_DAYS' export type RecipientMode = 'MANUAL' | 'ALL_CONTACTS' | 'TAG_FILTER' export type ScheduleType = 'ONCE' | 'RECURRING' | 'EVENT' export type ScheduleStatus = 'ACTIVE' | 'PAUSED' | 'DONE' | 'CANCELLED' export interface ScheduleRecipient { jid: string; name?: string; birthday?: string; anniversary?: string; signupDate?: string } export interface ScheduledMessage { id: string; tenantId: string; instanceId: string; name: string; templateId: string | null; payload: Record; recipientMode: RecipientMode; recipients: ScheduleRecipient[]; scheduleType: ScheduleType; cronExpr: string | null; scheduledAt: string | null; eventType: EventType | null; timezone: string; status: ScheduleStatus; lastRunAt: string | null; nextRunAt: string | null; runCount: number; createdAt: string; updatedAt: string; template?: any; _count?: { logs: number } } export interface ScheduleLog { id: string; scheduleId: string; status: string; sentCount: number; failedCount: number; error: string | null; runAt: string } // Cliente axios-like genérico sobre o nw (alguns arquivos importam `api` direto). export const api = { get: (u: string) => nw.get(u).then((data: any) => ({ data })), post: (u: string, body?: any) => nw.post(u, body).then((data: any) => ({ data })), put: (u: string, body?: any) => nw.put(u, body).then((data: any) => ({ data })), patch: (u: string, body?: any) => nw.patch(u, body).then((data: any) => ({ data })), delete: (u: string) => nw.delete(u).then((data: any) => ({ data })), }; export const authApi = { register: (_d: any) => Promise.resolve({} as any), login: (_d: any) => Promise.resolve({} as any), logout: () => Promise.resolve({} as any), me: () => Promise.resolve({} as any), changePassword: (_d: any) => Promise.resolve({} as any), }; export const apiKeyApi = { list: () => Promise.resolve([] as ApiKeyRecord[]), create: (_name: string, _e?: string) => Promise.resolve({} as any), toggle: (_id: string, _a: boolean) => Promise.resolve({} as any), delete: (_id: string) => Promise.resolve({} as any), }; export const contactApi = { list: (_opts: any = {}) => Promise.resolve({ total: 0, contacts: [] as BackendContact[] }), get: (_id: string) => Promise.resolve(null as any), update: (_id: string, _d: any) => Promise.resolve(null as any), delete: (_id: string) => Promise.resolve({} as any), stats: (_instanceId?: string) => Promise.resolve({ total: 0, blocked: 0, flagged: 0, active: 0 }), }; export const broadcastApi = { create: (_instanceId: string, _d: any) => Promise.resolve({ jobId: '', total: 0 }), list: (_instanceId: string, _limit?: number) => Promise.resolve([] as any[]), get: (_instanceId: string, _broadcastId: string) => Promise.resolve(null as any), }; export const planApi = { status: () => Promise.resolve({ daysRemaining: null as number | null, planName: '' }), }; export const chatbotApi = { getCredentials: (_instanceId: string) => Promise.resolve([] as any[]), createCredential: (_instanceId: string, _d: any) => Promise.resolve({} as any), deleteCredential: (_instanceId: string, _credId: string) => Promise.resolve({} as any), getBots: (_instanceId: string) => Promise.resolve([] as any[]), createBot: (_instanceId: string, _d: any) => Promise.resolve({} as any), updateBot: (_instanceId: string, _botId: string, _d: any) => Promise.resolve({} as any), deleteBot: (_instanceId: string, _botId: string) => Promise.resolve({} as any), pauseChat: (_instanceId: string, _chatId: string) => Promise.resolve({} as any), resumeChat: (_instanceId: string, _chatId: string) => Promise.resolve({} as any), }; export const scheduleApi = { list: (_opts: any = {}) => Promise.resolve([] as ScheduledMessage[]), get: (_id: string) => Promise.resolve(null as any), create: (_d: any) => Promise.resolve(null as any), update: (_id: string, _d: any) => Promise.resolve(null as any), delete: (_id: string) => Promise.resolve({} as any), pause: (_id: string) => Promise.resolve({} as any), resume: (_id: string) => Promise.resolve({} as any), runNow: (_id: string) => Promise.resolve({} as any), logs: (_id: string) => Promise.resolve([] as ScheduleLog[]), };