import { v } from "convex/values";
import { mutation, query, internalMutation } from "./_generated/server";
import { Doc, Id } from "./_generated/dataModel";
import type { QueryCtx, MutationCtx } from "./_generated/server";
import { internal, api } from "./_generated/api";

// ========== HELPERS ==========

/**
 * Normaliza texto para busca (remove acentos, converte para lowercase)
 */
function normalizarTextoParaBusca(texto: string): string {
  return texto
    .toLowerCase()
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "") // Remove diacríticos
    .trim();
}

/**
 * Helper function para obter usuário autenticado (Better Auth ou Sessão)
 */
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx) {
  // Tentar autenticação via Better Auth primeiro
  const identity = await ctx.auth.getUserIdentity();
  let usuarioAtual = null;

  if (identity && identity.email) {
    usuarioAtual = await ctx.db
      .query("usuarios")
      .withIndex("by_email", (q) => q.eq("email", identity.email!))
      .first();
  }

  // Se não encontrou via Better Auth, tentar via sessão mais recente
  if (!usuarioAtual) {
    const sessaoAtiva = await ctx.db
      .query("sessoes")
      .filter((q) => q.eq(q.field("ativo"), true))
      .order("desc")
      .first();

    if (sessaoAtiva) {
      usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
    }
  }

  return usuarioAtual;
}

/**
 * Helper function para verificar se usuário é administrador de uma sala de reunião
 */
async function verificarPermissaoAdmin(
  ctx: QueryCtx | MutationCtx,
  conversaId: Id<"conversas">,
  usuarioId: Id<"usuarios">
): Promise<boolean> {
  const conversa = await ctx.db.get(conversaId);
  if (!conversa) return false;
  
  // Verificar se é sala de reunião
  if (conversa.tipo !== "sala_reuniao") return false;
  
  // Verificar se tem array de administradores
  if (!conversa.administradores || conversa.administradores.length === 0) {
    // Se não tem administradores definidos, o criador é admin por padrão
    return conversa.criadoPor === usuarioId;
  }
  
  // Verificar se está na lista de administradores
  return conversa.administradores.includes(usuarioId);
}

// ========== MUTATIONS ==========

/**
 * Cria uma nova conversa (individual ou grupo)
 */
export const criarConversa = mutation({
  args: {
    tipo: v.union(v.literal("individual"), v.literal("grupo"), v.literal("sala_reuniao")),
    participantes: v.array(v.id("usuarios")),
    nome: v.optional(v.string()),
    avatar: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    // Validar participantes
    if (!args.participantes.includes(usuarioAtual._id)) {
      args.participantes.push(usuarioAtual._id);
    }

    // Se for conversa individual, verificar se já existe
    if (args.tipo === "individual" && args.participantes.length === 2) {
      const conversaExistente = await ctx.db
        .query("conversas")
        .filter((q) => q.eq(q.field("tipo"), "individual"))
        .collect();

      for (const conversa of conversaExistente) {
        if (
          conversa.participantes.length === 2 &&
          conversa.participantes.every((p) => args.participantes.includes(p))
        ) {
          return conversa._id;
        }
      }
    }

    // Preparar dados da conversa
    const dadosConversa: any = {
      tipo: args.tipo,
      nome: args.nome,
      avatar: args.avatar,
      participantes: args.participantes,
      criadoPor: usuarioAtual._id,
      criadoEm: Date.now(),
    };

    // Se for sala de reunião, adicionar administradores (criador sempre é admin)
    if (args.tipo === "sala_reuniao") {
      dadosConversa.administradores = [usuarioAtual._id];
    }

    // Criar nova conversa
    const conversaId = await ctx.db.insert("conversas", dadosConversa);

    // Criar notificações para outros participantes
    if (args.tipo === "grupo" || args.tipo === "sala_reuniao") {
      const tipoNotificacao = args.tipo === "sala_reuniao" ? "adicionado_grupo" : "adicionado_grupo";
      const tipoTexto = args.tipo === "sala_reuniao" ? "sala de reunião" : "grupo";
      
      for (const participanteId of args.participantes) {
        if (participanteId !== usuarioAtual._id) {
          await ctx.db.insert("notificacoes", {
            usuarioId: participanteId,
            tipo: tipoNotificacao,
            conversaId,
            remetenteId: usuarioAtual._id,
            titulo: args.tipo === "sala_reuniao" ? "Adicionado a sala de reunião" : "Adicionado a grupo",
            descricao: `Você foi adicionado à ${tipoTexto} "${
              args.nome || "Sem nome"
            }" por ${usuarioAtual.nome}`,
            lida: false,
            criadaEm: Date.now(),
          });
        }
      }
    }

    return conversaId;
  },
});

/**
 * Cria uma nova sala de reunião (wrapper específico para facilitar uso)
 */
export const criarSalaReuniao = mutation({
  args: {
    nome: v.string(),
    participantes: v.array(v.id("usuarios")),
    avatar: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    // Validar nome
    if (!args.nome || args.nome.trim().length === 0) {
      throw new Error("O nome da sala de reunião é obrigatório");
    }

    // Validar participantes
    const participantesUnicos = [...new Set(args.participantes)];
    if (!participantesUnicos.includes(usuarioAtual._id)) {
      participantesUnicos.push(usuarioAtual._id);
    }

    // Preparar dados da conversa
    const dadosConversa: any = {
      tipo: "sala_reuniao" as const,
      nome: args.nome.trim(),
      avatar: args.avatar,
      participantes: participantesUnicos,
      criadoPor: usuarioAtual._id,
      criadoEm: Date.now(),
      administradores: [usuarioAtual._id], // Criador sempre é admin
    };

    // Criar nova conversa
    const conversaId = await ctx.db.insert("conversas", dadosConversa);

    // Criar notificações para outros participantes
    const tipoNotificacao = "adicionado_grupo";
    const tipoTexto = "sala de reunião";
    
    for (const participanteId of participantesUnicos) {
      if (participanteId !== usuarioAtual._id) {
        await ctx.db.insert("notificacoes", {
          usuarioId: participanteId,
          tipo: tipoNotificacao,
          conversaId,
          remetenteId: usuarioAtual._id,
          titulo: "Adicionado a sala de reunião",
          descricao: `Você foi adicionado à ${tipoTexto} "${
            args.nome || "Sem nome"
          }" por ${usuarioAtual.nome}`,
          lida: false,
          criadaEm: Date.now(),
        });
      }
    }

    return conversaId;
  },
});

/**
 * Cria ou busca uma conversa individual com outro usuário
 */
export const criarOuBuscarConversaIndividual = mutation({
  args: {
    outroUsuarioId: v.id("usuarios"),
  },
  returns: v.id("conversas"),
  handler: async (ctx, args) => {
    // TENTAR BETTER AUTH PRIMEIRO
    const identity = await ctx.auth.getUserIdentity();

    let usuarioAtual = null;

    if (identity && identity.email) {
      // Buscar por email (Better Auth)
      usuarioAtual = await ctx.db
        .query("usuarios")
        .withIndex("by_email", (q) => q.eq("email", identity.email!))
        .first();
    }

    // SE NÃO ENCONTROU, BUSCAR POR SESSÃO ATIVA (Sistema customizado)
    if (!usuarioAtual) {
      const sessaoAtiva = await ctx.db
        .query("sessoes")
        .filter((q) => q.eq(q.field("ativo"), true))
        .order("desc")
        .first();

      if (sessaoAtiva) {
        usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
      }
    }

    if (!usuarioAtual) throw new Error("Usuário não autenticado");

    // Buscar conversa individual existente entre os dois usuários
    const conversasExistentes = await ctx.db
      .query("conversas")
      .filter((q) => q.eq(q.field("tipo"), "individual"))
      .collect();

    for (const conversa of conversasExistentes) {
      if (
        conversa.participantes.length === 2 &&
        conversa.participantes.includes(usuarioAtual._id) &&
        conversa.participantes.includes(args.outroUsuarioId)
      ) {
        return conversa._id;
      }
    }

    // Se não existe, criar nova conversa individual
    const conversaId = await ctx.db.insert("conversas", {
      tipo: "individual",
      participantes: [usuarioAtual._id, args.outroUsuarioId],
      criadoPor: usuarioAtual._id,
      criadoEm: Date.now(),
    });

    return conversaId;
  },
});

/**
 * Envia uma mensagem em uma conversa
 */
export const enviarMensagem = mutation({
  args: {
    conversaId: v.id("conversas"),
    conteudo: v.string(),
    tipo: v.union(
      v.literal("texto"),
      v.literal("arquivo"),
      v.literal("imagem")
    ),
    arquivoId: v.optional(v.id("_storage")),
    arquivoNome: v.optional(v.string()),
    arquivoTamanho: v.optional(v.number()),
    arquivoTipo: v.optional(v.string()),
    mencoes: v.optional(v.array(v.id("usuarios"))),
    respostaPara: v.optional(v.id("mensagens")), // ID da mensagem que está respondendo
    permitirNotificacaoParaSiMesmo: v.optional(v.boolean()), // ✅ NOVO: Permite criar notificação para si mesmo
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    // Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) throw new Error("Conversa não encontrada");
    if (!conversa.participantes.includes(usuarioAtual._id)) {
      throw new Error("Você não pertence a esta conversa");
    }

    // Normalizar conteúdo para busca (remover acentos, lowercase)
    const conteudoBusca = normalizarTextoParaBusca(args.conteudo);

    // Verificar se é resposta a outra mensagem
    if (args.respostaPara) {
      const mensagemOriginal = await ctx.db.get(args.respostaPara);
      if (!mensagemOriginal || mensagemOriginal.conversaId !== args.conversaId) {
        throw new Error("Mensagem original não encontrada ou não pertence à mesma conversa");
      }
      // SEGURANÇA: Verificar se o remetente da mensagem original é participante da conversa
      if (!conversa.participantes.includes(mensagemOriginal.remetenteId)) {
        throw new Error("Mensagem original inválida");
      }
      if (mensagemOriginal.deletada) {
        throw new Error("Não é possível responder a uma mensagem deletada");
      }
    }

    // Criar mensagem
    const mensagemId = await ctx.db.insert("mensagens", {
      conversaId: args.conversaId,
      remetenteId: usuarioAtual._id,
      tipo: args.tipo,
      conteudo: args.conteudo,
      conteudoBusca,
      arquivoId: args.arquivoId,
      arquivoNome: args.arquivoNome,
      arquivoTamanho: args.arquivoTamanho,
      arquivoTipo: args.arquivoTipo,
      mencoes: args.mencoes,
      respostaPara: args.respostaPara,
      enviadaEm: Date.now(),
      lidaPor: [], // Inicializar como array vazio
    });

    // Detectar URLs no conteúdo e extrair preview (apenas para mensagens de texto, assíncrono)
    if (args.tipo === "texto") {
      const urlRegex = /(https?:\/\/[^\s]+)/g;
      const urls = args.conteudo.match(urlRegex);
      if (urls && urls.length > 0) {
        // Pegar primeira URL encontrada
        const primeiraUrl = urls[0];
        // Agendar processamento de preview via action wrapper
        ctx.scheduler.runAfter(1000, api.actions.linkPreview.processarPreviewLink, {
          mensagemId,
          url: primeiraUrl,
        }).catch((error) => {
          console.error("Erro ao agendar processamento de preview de link:", error);
        });
      }
    }

    // Atualizar última mensagem da conversa
    await ctx.db.patch(args.conversaId, {
      ultimaMensagem: args.conteudo.substring(0, 100),
      ultimaMensagemTimestamp: Date.now(),
      ultimaMensagemRemetenteId: usuarioAtual._id, // Guardar ID do remetente da última mensagem
    });

    // Criar notificações para participantes (com tratamento de erro)
    try {
      for (const participanteId of conversa.participantes) {
        // ✅ MODIFICADO: Permite notificação para si mesmo se flag estiver ativa
        const ehOMesmoUsuario = participanteId === usuarioAtual._id;
        const deveCriarNotificacao =
          !ehOMesmoUsuario || args.permitirNotificacaoParaSiMesmo;

        if (deveCriarNotificacao) {
          const tipoNotificacao = args.mencoes?.includes(participanteId)
            ? "mencao"
            : "nova_mensagem";

          const titulo =
            tipoNotificacao === "mencao"
              ? `${usuarioAtual.nome} mencionou você`
              : `Nova mensagem de ${usuarioAtual.nome}`;
          const descricao = args.conteudo.substring(0, 100);

          // Criar notificação no banco
          await ctx.db.insert("notificacoes", {
            usuarioId: participanteId,
            tipo: tipoNotificacao,
            conversaId: args.conversaId,
            mensagemId,
            remetenteId: usuarioAtual._id,
            titulo,
            descricao,
            lida: false,
            criadaEm: Date.now(),
          });

          // Enviar push notification (assíncrono, não bloqueia)
          ctx.scheduler.runAfter(0, internal.pushNotifications.enviarPushNotification, {
            usuarioId: participanteId,
            titulo,
            corpo: descricao,
            data: {
              conversaId: args.conversaId,
              mensagemId,
              tipo: tipoNotificacao,
            },
          }).catch((error) => {
            console.error(`Erro ao agendar push para usuário ${participanteId}:`, error);
          });

          // Se usuário offline, enviar email (assíncrono)
          const usuarioOnline = await ctx.runQuery(internal.pushNotifications.verificarUsuarioOnline, {
            usuarioId: participanteId,
          });

          if (!usuarioOnline) {
            // Verificar preferências de email para esta conversa
            const preferencias = await ctx.db
              .query("preferenciasNotificacaoConversa")
              .withIndex("by_usuario_conversa", (q) =>
                q.eq("usuarioId", participanteId).eq("conversaId", args.conversaId)
              )
              .first();

            const deveEnviarEmail = !preferencias || preferencias.emailAtivado !== false;

            if (deveEnviarEmail) {
              // Buscar email do usuário
              const usuarioParticipante = await ctx.db.get(participanteId);
              if (usuarioParticipante?.email) {
                // Obter URL do sistema (padrão: localhost para dev)
                const urlSistema = process.env.FRONTEND_URL || "http://localhost:5173";
                
                ctx.scheduler.runAfter(1000, api.email.enviarEmailComTemplate, {
                  destinatario: usuarioParticipante.email,
                  destinatarioId: participanteId,
                  templateCodigo: tipoNotificacao === "mencao" ? "chat_mencao" : "chat_mensagem",
                  variaveis: {
                    remetente: usuarioAtual.nome,
                    mensagem: descricao,
                    conversaId: args.conversaId.toString(),
                    urlSistema,
                  },
                  enviadoPor: usuarioAtual._id,
                }).catch((error) => {
                  console.error(`Erro ao agendar email para usuário ${participanteId}:`, error);
                });
              }
            }
          }
        }
      }
    } catch (error) {
      // Log do erro mas não falhar o envio da mensagem
      console.error("Erro ao criar notificações:", error);
      // A mensagem já foi criada, então retornamos o ID normalmente
    }

    return mensagemId;
  },
});

/**
 * Agenda uma mensagem para envio futuro
 */
export const agendarMensagem = mutation({
  args: {
    conversaId: v.id("conversas"),
    conteudo: v.string(),
    agendadaPara: v.number(), // timestamp
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    // Validar data futura
    if (args.agendadaPara <= Date.now()) {
      throw new Error("Data de agendamento deve ser futura");
    }

    // Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) throw new Error("Conversa não encontrada");
    if (!conversa.participantes.includes(usuarioAtual._id)) {
      throw new Error("Você não pertence a esta conversa");
    }

    // Normalizar conteúdo para busca
    const conteudoBusca = normalizarTextoParaBusca(args.conteudo);

    // Criar mensagem agendada
    const mensagemId = await ctx.db.insert("mensagens", {
      conversaId: args.conversaId,
      remetenteId: usuarioAtual._id,
      tipo: "texto",
      conteudo: args.conteudo,
      conteudoBusca,
      agendadaPara: args.agendadaPara,
      enviadaEm: args.agendadaPara, // Será atualizado quando a mensagem for enviada
      lidaPor: [], // Inicializar como array vazio
    });

    return mensagemId;
  },
});

/**
 * Cancela uma mensagem agendada
 */
export const cancelarMensagemAgendada = mutation({
  args: {
    mensagemId: v.id("mensagens"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args): Promise<{ sucesso: boolean; erro?: string }> => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Usuário não autenticado" };
    }

    const mensagem = await ctx.db.get(args.mensagemId);
    if (!mensagem) {
      return { sucesso: false, erro: "Mensagem não encontrada" };
    }

    // SEGURANÇA: Verificar se a mensagem pertence a uma conversa onde o usuário é participante
    const conversa = await ctx.db.get(mensagem.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      return { sucesso: false, erro: "Você não tem acesso a esta mensagem" };
    }

    // SEGURANÇA: Verificar se o remetente da mensagem é participante da conversa
    if (!conversa.participantes.includes(mensagem.remetenteId)) {
      return { sucesso: false, erro: "Mensagem inválida" };
    }

    if (mensagem.remetenteId !== usuarioAtual._id) {
      return {
        sucesso: false,
        erro: "Você só pode cancelar suas próprias mensagens",
      };
    }

    if (!mensagem.agendadaPara) {
      return { sucesso: false, erro: "Esta mensagem não está agendada" };
    }

    if (mensagem.agendadaPara <= Date.now()) {
      return { sucesso: false, erro: "A data de agendamento já passou" };
    }

    await ctx.db.delete(args.mensagemId);
    return { sucesso: true };
  },
});

/**
 * Adiciona uma reação (emoji) a uma mensagem
 * SEGURANÇA: Usuário só pode reagir a mensagens de conversas onde é participante
 */
export const reagirMensagem = mutation({
  args: {
    mensagemId: v.id("mensagens"),
    emoji: v.string(),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    const mensagem = await ctx.db.get(args.mensagemId);
    if (!mensagem) throw new Error("Mensagem não encontrada");

    // SEGURANÇA: Verificar se a mensagem pertence a uma conversa onde o usuário é participante
    const conversa = await ctx.db.get(mensagem.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      throw new Error("Você não pode reagir a mensagens de conversas onde não participa");
    }

    // SEGURANÇA: Verificar se o remetente da mensagem é participante da conversa
    if (!conversa.participantes.includes(mensagem.remetenteId)) {
      throw new Error("Mensagem inválida");
    }

    const reacoes = mensagem.reagiuPor || [];
    const reacaoExistente = reacoes.find(
      (r) => r.usuarioId === usuarioAtual._id && r.emoji === args.emoji
    );

    if (reacaoExistente) {
      // Remover reação
      await ctx.db.patch(args.mensagemId, {
        reagiuPor: reacoes.filter(
          (r) => !(r.usuarioId === usuarioAtual._id && r.emoji === args.emoji)
        ),
      });
    } else {
      // Adicionar reação
      await ctx.db.patch(args.mensagemId, {
        reagiuPor: [
          ...reacoes,
          { usuarioId: usuarioAtual._id, emoji: args.emoji },
        ],
      });
    }

    return true;
  },
});

/**
 * Marca mensagens de uma conversa como lidas
 * SEGURANÇA: Usuário só pode marcar como lida mensagens de conversas onde é participante
 */
export const marcarComoLida = mutation({
  args: {
    conversaId: v.id("conversas"),
    mensagemId: v.id("mensagens"),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    // SEGURANÇA: Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      throw new Error("Você não pertence a esta conversa");
    }

    // SEGURANÇA: Verificar se a mensagem pertence à conversa e se o remetente é participante
    const mensagem = await ctx.db.get(args.mensagemId);
    if (!mensagem || mensagem.conversaId !== args.conversaId) {
      throw new Error("Mensagem não encontrada nesta conversa");
    }
    if (!conversa.participantes.includes(mensagem.remetenteId)) {
      throw new Error("Mensagem inválida");
    }

    // Buscar registro de leitura existente
    const leituraExistente = await ctx.db
      .query("leituras")
      .withIndex("by_conversa_usuario", (q) =>
        q.eq("conversaId", args.conversaId).eq("usuarioId", usuarioAtual._id)
      )
      .first();

    if (leituraExistente) {
      await ctx.db.patch(leituraExistente._id, {
        ultimaMensagemLida: args.mensagemId,
        lidaEm: Date.now(),
      });
    } else {
      await ctx.db.insert("leituras", {
        conversaId: args.conversaId,
        usuarioId: usuarioAtual._id,
        ultimaMensagemLida: args.mensagemId,
        lidaEm: Date.now(),
      });
    }

    // Atualizar status de leitura nas mensagens
    // Buscar todas as mensagens até a mensagem atual (incluindo ela) na conversa
    const todasMensagens = await ctx.db
      .query("mensagens")
      .withIndex("by_conversa", (q) => q.eq("conversaId", args.conversaId))
      .filter((q) => 
        q.and(
          q.lte(q.field("enviadaEm"), mensagem.enviadaEm),
          q.neq(q.field("remetenteId"), usuarioAtual._id) // Apenas mensagens de outros usuários
        )
      )
      .collect();

    // Atualizar cada mensagem para incluir o usuário atual no array lidaPor (se ainda não estiver)
    for (const msg of todasMensagens) {
      const lidaPor = msg.lidaPor || [];
      if (!lidaPor.includes(usuarioAtual._id)) {
        await ctx.db.patch(msg._id, {
          lidaPor: [...lidaPor, usuarioAtual._id],
        });
      }
    }

    // Marcar notificações desta conversa como lidas
    const notificacoes = await ctx.db
      .query("notificacoes")
      .withIndex("by_usuario_lida", (q) =>
        q.eq("usuarioId", usuarioAtual._id).eq("lida", false)
      )
      .filter((q) => q.eq(q.field("conversaId"), args.conversaId))
      .collect();

    for (const notificacao of notificacoes) {
      await ctx.db.patch(notificacao._id, { lida: true });
    }

    return true;
  },
});

/**
 * Atualiza o status de presença do usuário
 */
export const atualizarStatusPresenca = mutation({
  args: {
    status: v.union(
      v.literal("online"),
      v.literal("offline"),
      v.literal("ausente"),
      v.literal("externo"),
      v.literal("em_reuniao")
    ),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    await ctx.db.patch(usuarioAtual._id, {
      statusPresenca: args.status,
      ultimaAtividade: Date.now(),
    });

    return true;
  },
});

/**
 * Indica que o usuário está digitando em uma conversa
 * SEGURANÇA: Usuário só pode indicar digitação em conversas onde é participante
 */
export const indicarDigitacao = mutation({
  args: {
    conversaId: v.id("conversas"),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    // SEGURANÇA: Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      throw new Error("Você não pertence a esta conversa");
    }

    // Buscar indicador existente
    const indicadorExistente = await ctx.db
      .query("digitando")
      .withIndex("by_usuario", (q) => q.eq("usuarioId", usuarioAtual._id))
      .filter((q) => q.eq(q.field("conversaId"), args.conversaId))
      .first();

    if (indicadorExistente) {
      await ctx.db.patch(indicadorExistente._id, {
        iniciouEm: Date.now(),
      });
    } else {
      await ctx.db.insert("digitando", {
        conversaId: args.conversaId,
        usuarioId: usuarioAtual._id,
        iniciouEm: Date.now(),
      });
    }

    return true;
  },
});

/**
 * Gera URL para upload de arquivo no chat
 */
export const uploadArquivoChat = mutation({
  args: {
    conversaId: v.id("conversas"),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    // Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) throw new Error("Conversa não encontrada");

    if (!conversa.participantes.includes(usuarioAtual._id)) {
      throw new Error("Você não pertence a esta conversa");
    }

    return await ctx.storage.generateUploadUrl();
  },
});

/**
 * Marca uma notificação como lida
 * SEGURANÇA: Usuário só pode marcar como lida suas próprias notificações
 */
export const marcarNotificacaoLida = mutation({
  args: {
    notificacaoId: v.id("notificacoes"),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    const notificacao = await ctx.db.get(args.notificacaoId);
    if (!notificacao) throw new Error("Notificação não encontrada");

    // SEGURANÇA: Verificar se a notificação pertence ao usuário atual
    if (notificacao.usuarioId !== usuarioAtual._id) {
      throw new Error("Você não tem permissão para marcar esta notificação como lida");
    }

    // SEGURANÇA: Se a notificação tem conversaId, verificar se usuário ainda é participante
    if (notificacao.conversaId) {
      const conversa = await ctx.db.get(notificacao.conversaId);
      if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
        throw new Error("Você não tem acesso a esta notificação");
      }
    }

    await ctx.db.patch(args.notificacaoId, { lida: true });
    return true;
  },
});

/**
 * Marca todas as notificações como lidas
 */
export const marcarTodasNotificacoesLidas = mutation({
  args: {},
  handler: async (ctx) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    const notificacoes = await ctx.db
      .query("notificacoes")
      .withIndex("by_usuario_lida", (q) =>
        q.eq("usuarioId", usuarioAtual._id).eq("lida", false)
      )
      .collect();

    for (const notificacao of notificacoes) {
      await ctx.db.patch(notificacao._id, { lida: true });
    }

    return true;
  },
});

/**
 * Deleta todas as notificações do usuário
 * SEGURANÇA: Usuário só pode deletar suas próprias notificações
 */
export const limparTodasNotificacoes = mutation({
  args: {},
  handler: async (ctx) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    const notificacoes = await ctx.db
      .query("notificacoes")
      .withIndex("by_usuario", (q) => q.eq("usuarioId", usuarioAtual._id))
      .collect();

    for (const notificacao of notificacoes) {
      await ctx.db.delete(notificacao._id);
    }

    return { excluidas: notificacoes.length };
  },
});

/**
 * Deleta apenas as notificações não lidas do usuário
 * SEGURANÇA: Usuário só pode deletar suas próprias notificações
 */
export const limparNotificacoesNaoLidas = mutation({
  args: {},
  handler: async (ctx) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    const notificacoes = await ctx.db
      .query("notificacoes")
      .withIndex("by_usuario_lida", (q) =>
        q.eq("usuarioId", usuarioAtual._id).eq("lida", false)
      )
      .collect();

    for (const notificacao of notificacoes) {
      await ctx.db.delete(notificacao._id);
    }

    return { excluidas: notificacoes.length };
  },
});

/**
 * Deleta uma mensagem (soft delete)
 */
/**
 * Editar mensagem enviada
 */
export const editarMensagem = mutation({
  args: {
    mensagemId: v.id("mensagens"),
    novoConteudo: v.string(),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const mensagem = await ctx.db.get(args.mensagemId);
    if (!mensagem) {
      return { sucesso: false, erro: "Mensagem não encontrada" };
    }

    // SEGURANÇA: Verificar se a mensagem pertence a uma conversa onde o usuário é participante
    const conversa = await ctx.db.get(mensagem.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      return { sucesso: false, erro: "Você não tem acesso a esta mensagem" };
    }

    // SEGURANÇA: Verificar se o remetente da mensagem é participante da conversa
    if (!conversa.participantes.includes(mensagem.remetenteId)) {
      return { sucesso: false, erro: "Mensagem inválida" };
    }

    // Verificar se usuário é o remetente
    if (mensagem.remetenteId !== usuarioAtual._id) {
      return { sucesso: false, erro: "Você só pode editar suas próprias mensagens" };
    }

    // Verificar se mensagem não foi deletada
    if (mensagem.deletada) {
      return { sucesso: false, erro: "Não é possível editar uma mensagem deletada" };
    }

    // Verificar se não é mensagem agendada
    if (mensagem.agendadaPara) {
      return { sucesso: false, erro: "Não é possível editar mensagens agendadas" };
    }

    // Validar novo conteúdo
    if (!args.novoConteudo || args.novoConteudo.trim().length === 0) {
      return { sucesso: false, erro: "O conteúdo da mensagem não pode estar vazio" };
    }

    // Normalizar conteúdo para busca
    const conteudoBusca = normalizarTextoParaBusca(args.novoConteudo);

    // Atualizar mensagem
    await ctx.db.patch(args.mensagemId, {
      conteudo: args.novoConteudo.trim(),
      conteudoBusca,
      editadaEm: Date.now(),
    });

    return { sucesso: true };
  },
});

/**
 * Mutation interna para atualizar link preview
 */
export const atualizarLinkPreview = internalMutation({
  args: {
    mensagemId: v.id("mensagens"),
    linkPreview: v.object({
      url: v.string(),
      titulo: v.optional(v.string()),
      descricao: v.optional(v.string()),
      imagem: v.optional(v.string()),
      site: v.optional(v.string()),
    }),
  },
  returns: v.null(),
  handler: async (ctx, args) => {
    await ctx.db.patch(args.mensagemId, {
      linkPreview: args.linkPreview,
    });
    return null;
  },
});

export const deletarMensagem = mutation({
  args: {
    mensagemId: v.id("mensagens"),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) throw new Error("Não autenticado");

    const mensagem = await ctx.db.get(args.mensagemId);
    if (!mensagem) throw new Error("Mensagem não encontrada");

    // SEGURANÇA: Verificar se a mensagem pertence a uma conversa onde o usuário é participante
    const conversa = await ctx.db.get(mensagem.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      throw new Error("Você não tem acesso a esta mensagem");
    }

    // SEGURANÇA: Verificar se o remetente da mensagem é participante da conversa
    if (!conversa.participantes.includes(mensagem.remetenteId)) {
      throw new Error("Mensagem inválida");
    }

    // Verificar se é admin de sala de reunião ou se é o próprio remetente
    const isAdmin = await verificarPermissaoAdmin(ctx, mensagem.conversaId, usuarioAtual._id);
    
    if (mensagem.remetenteId !== usuarioAtual._id && !isAdmin) {
      throw new Error("Você só pode deletar suas próprias mensagens");
    }

    await ctx.db.patch(args.mensagemId, {
      deletada: true,
      conteudo: "Mensagem deletada",
    });

    return true;
  },
});

/**
 * Deleta uma mensagem como administrador (com notificação ao remetente)
 */
export const deletarMensagemComoAdmin = mutation({
  args: {
    mensagemId: v.id("mensagens"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const mensagem = await ctx.db.get(args.mensagemId);
    if (!mensagem) {
      return { sucesso: false, erro: "Mensagem não encontrada" };
    }

    // SEGURANÇA: Verificar se a mensagem pertence a uma conversa onde o usuário é participante
    const conversa = await ctx.db.get(mensagem.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      return { sucesso: false, erro: "Você não tem acesso a esta mensagem" };
    }

    // SEGURANÇA: Verificar se o remetente da mensagem é participante da conversa
    if (!conversa.participantes.includes(mensagem.remetenteId)) {
      return { sucesso: false, erro: "Mensagem inválida" };
    }

    // Verificar se usuário é administrador da sala
    const isAdmin = await verificarPermissaoAdmin(ctx, mensagem.conversaId, usuarioAtual._id);
    if (!isAdmin) {
      return { sucesso: false, erro: "Apenas administradores podem deletar mensagens de outros usuários" };
    }

    // Não permitir deletar mensagem já deletada
    if (mensagem.deletada) {
      return { sucesso: false, erro: "Mensagem já foi deletada" };
    }

    // Deletar mensagem
    await ctx.db.patch(args.mensagemId, {
      deletada: true,
      conteudo: "Mensagem deletada por administrador",
    });

    // Criar notificação para o remetente original (se não for o próprio admin)
    if (mensagem.remetenteId !== usuarioAtual._id) {
      const remetente = await ctx.db.get(mensagem.remetenteId);
      if (remetente) {
        await ctx.db.insert("notificacoes", {
          usuarioId: mensagem.remetenteId,
          tipo: "nova_mensagem",
          conversaId: mensagem.conversaId,
          mensagemId: args.mensagemId,
          remetenteId: usuarioAtual._id,
          titulo: "Mensagem deletada",
          descricao: `Sua mensagem foi deletada por um administrador da sala "${conversa.nome || "Sem nome"}"`,
          lida: false,
          criadaEm: Date.now(),
        });
      }
    }

    return { sucesso: true };
  },
});

/**
 * Adiciona um participante à sala de reunião (apenas administradores)
 */
export const adicionarParticipanteSala = mutation({
  args: {
    conversaId: v.id("conversas"),
    participanteId: v.id("usuarios"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) {
      return { sucesso: false, erro: "Sala de reunião não encontrada" };
    }

    // Verificar se é sala de reunião
    if (conversa.tipo !== "sala_reuniao") {
      return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
    }

    // Verificar se usuário é administrador
    const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
    if (!isAdmin) {
      return { sucesso: false, erro: "Apenas administradores podem adicionar participantes" };
    }

    // Verificar se participante já está na sala
    if (conversa.participantes.includes(args.participanteId)) {
      return { sucesso: false, erro: "Usuário já é participante desta sala" };
    }

    // Verificar se participante existe
    const participante = await ctx.db.get(args.participanteId);
    if (!participante) {
      return { sucesso: false, erro: "Usuário não encontrado" };
    }

    // Adicionar participante
    const novosParticipantes = [...conversa.participantes, args.participanteId];
    await ctx.db.patch(args.conversaId, {
      participantes: novosParticipantes,
    });

    // Criar notificação para o novo participante
    await ctx.db.insert("notificacoes", {
      usuarioId: args.participanteId,
      tipo: "adicionado_grupo",
      conversaId: args.conversaId,
      remetenteId: usuarioAtual._id,
      titulo: "Adicionado a sala de reunião",
      descricao: `Você foi adicionado à sala de reunião "${conversa.nome || "Sem nome"}" por ${usuarioAtual.nome}`,
      lida: false,
      criadaEm: Date.now(),
    });

    return { sucesso: true };
  },
});

/**
 * Remove um participante da sala de reunião (apenas administradores, não pode remover outros admins)
 */
export const removerParticipanteSala = mutation({
  args: {
    conversaId: v.id("conversas"),
    participanteId: v.id("usuarios"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) {
      return { sucesso: false, erro: "Sala de reunião não encontrada" };
    }

    // Verificar se é sala de reunião
    if (conversa.tipo !== "sala_reuniao") {
      return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
    }

    // Verificar se usuário é administrador
    const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
    if (!isAdmin) {
      return { sucesso: false, erro: "Apenas administradores podem remover participantes" };
    }

    // Verificar se participante está na sala
    if (!conversa.participantes.includes(args.participanteId)) {
      return { sucesso: false, erro: "Usuário não é participante desta sala" };
    }

    // Verificar se está tentando remover outro administrador
    const isParticipanteAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, args.participanteId);
    if (isParticipanteAdmin) {
      return { sucesso: false, erro: "Não é possível remover outros administradores" };
    }

    // Remover participante
    const novosParticipantes = conversa.participantes.filter((p) => p !== args.participanteId);
    await ctx.db.patch(args.conversaId, {
      participantes: novosParticipantes,
    });

    // Criar notificação para o participante removido
    const participanteRemovido = await ctx.db.get(args.participanteId);
    if (participanteRemovido) {
      await ctx.db.insert("notificacoes", {
        usuarioId: args.participanteId,
        tipo: "nova_mensagem",
        conversaId: args.conversaId,
        remetenteId: usuarioAtual._id,
        titulo: "Removido da sala de reunião",
        descricao: `Você foi removido da sala de reunião "${conversa.nome || "Sem nome"}" por ${usuarioAtual.nome}`,
        lida: false,
        criadaEm: Date.now(),
      });
    }

    return { sucesso: true };
  },
});

/**
 * Promove um participante a administrador (apenas administradores)
 */
export const promoverAdministrador = mutation({
  args: {
    conversaId: v.id("conversas"),
    participanteId: v.id("usuarios"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) {
      return { sucesso: false, erro: "Sala de reunião não encontrada" };
    }

    // Verificar se é sala de reunião
    if (conversa.tipo !== "sala_reuniao") {
      return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
    }

    // Verificar se usuário é administrador
    const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
    if (!isAdmin) {
      return { sucesso: false, erro: "Apenas administradores podem promover outros administradores" };
    }

    // Verificar se participante está na sala
    if (!conversa.participantes.includes(args.participanteId)) {
      return { sucesso: false, erro: "Usuário não é participante desta sala" };
    }

    // Verificar se já é administrador
    const jaEhAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, args.participanteId);
    if (jaEhAdmin) {
      return { sucesso: false, erro: "Usuário já é administrador desta sala" };
    }

    // Obter lista atual de administradores ou criar nova
    const administradoresAtuais = conversa.administradores || [];
    
    // Se não está na lista, adicionar
    if (!administradoresAtuais.includes(args.participanteId)) {
      const novosAdministradores = [...administradoresAtuais, args.participanteId];
      await ctx.db.patch(args.conversaId, {
        administradores: novosAdministradores,
      });

      // Criar notificação para o novo administrador
      const novoAdmin = await ctx.db.get(args.participanteId);
      if (novoAdmin) {
        await ctx.db.insert("notificacoes", {
          usuarioId: args.participanteId,
          tipo: "nova_mensagem",
          conversaId: args.conversaId,
          remetenteId: usuarioAtual._id,
          titulo: "Promovido a administrador",
          descricao: `Você foi promovido a administrador da sala de reunião "${conversa.nome || "Sem nome"}" por ${usuarioAtual.nome}`,
          lida: false,
          criadaEm: Date.now(),
        });
      }
    }

    return { sucesso: true };
  },
});

/**
 * Rebaixa um administrador a participante (apenas administradores, não pode rebaixar a si mesmo)
 */
export const rebaixarAdministrador = mutation({
  args: {
    conversaId: v.id("conversas"),
    participanteId: v.id("usuarios"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) {
      return { sucesso: false, erro: "Sala de reunião não encontrada" };
    }

    // Verificar se é sala de reunião
    if (conversa.tipo !== "sala_reuniao") {
      return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
    }

    // Verificar se usuário é administrador
    const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
    if (!isAdmin) {
      return { sucesso: false, erro: "Apenas administradores podem rebaixar outros administradores" };
    }

    // Não permitir rebaixar a si mesmo
    if (args.participanteId === usuarioAtual._id) {
      return { sucesso: false, erro: "Você não pode rebaixar a si mesmo" };
    }

    // Verificar se é administrador
    const isParticipanteAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, args.participanteId);
    if (!isParticipanteAdmin) {
      return { sucesso: false, erro: "Usuário não é administrador desta sala" };
    }

    // Não permitir rebaixar o criador da sala
    if (conversa.criadoPor === args.participanteId) {
      return { sucesso: false, erro: "Não é possível rebaixar o criador da sala" };
    }

    // Remover da lista de administradores
    const administradoresAtuais = conversa.administradores || [];
    const novosAdministradores = administradoresAtuais.filter((adminId) => adminId !== args.participanteId);
    
    await ctx.db.patch(args.conversaId, {
      administradores: novosAdministradores.length > 0 ? novosAdministradores : undefined,
    });

    // Criar notificação para o administrador rebaixado
    const adminRebaixado = await ctx.db.get(args.participanteId);
    if (adminRebaixado) {
      await ctx.db.insert("notificacoes", {
        usuarioId: args.participanteId,
        tipo: "nova_mensagem",
        conversaId: args.conversaId,
        remetenteId: usuarioAtual._id,
        titulo: "Rebaixado de administrador",
        descricao: `Você foi rebaixado de administrador da sala de reunião "${conversa.nome || "Sem nome"}" por ${usuarioAtual.nome}`,
        lida: false,
        criadaEm: Date.now(),
      });
    }

    return { sucesso: true };
  },
});

/**
 * Permite que um usuário saia de um grupo ou sala de reunião
 */
export const sairGrupoOuSala = mutation({
  args: {
    conversaId: v.id("conversas"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) {
      return { sucesso: false, erro: "Conversa não encontrada" };
    }

    // Verificar se é grupo ou sala de reunião
    if (conversa.tipo !== "grupo" && conversa.tipo !== "sala_reuniao") {
      return { sucesso: false, erro: "Esta funcionalidade é apenas para grupos e salas de reunião" };
    }

    // Verificar se usuário é participante
    if (!conversa.participantes.includes(usuarioAtual._id)) {
      return { sucesso: false, erro: "Você não é participante desta conversa" };
    }

    // Remover usuário dos participantes
    const novosParticipantes = conversa.participantes.filter((p) => p !== usuarioAtual._id);
    
    // Se for sala de reunião e o usuário for administrador, removê-lo também dos administradores
    let novosAdministradores = conversa.administradores;
    if (conversa.tipo === "sala_reuniao" && conversa.administradores) {
      novosAdministradores = conversa.administradores.filter((adminId) => adminId !== usuarioAtual._id);
    }

    await ctx.db.patch(args.conversaId, {
      participantes: novosParticipantes,
      administradores: novosAdministradores && novosAdministradores.length > 0 ? novosAdministradores : undefined,
    });

    // Criar notificação para outros participantes informando que o usuário saiu
    const tipoTexto = conversa.tipo === "sala_reuniao" ? "sala de reunião" : "grupo";
    for (const participanteId of novosParticipantes) {
      await ctx.db.insert("notificacoes", {
        usuarioId: participanteId,
        tipo: "nova_mensagem",
        conversaId: args.conversaId,
        remetenteId: usuarioAtual._id,
        titulo: "Participante saiu",
        descricao: `${usuarioAtual.nome} saiu da ${tipoTexto} "${conversa.nome || "Sem nome"}"`,
        lida: false,
        criadaEm: Date.now(),
      });
    }

    return { sucesso: true };
  },
});

/**
 * Encerra uma sala de reunião (apenas administradores)
 * Remove todos os participantes e marca a sala como encerrada
 */
export const encerrarReuniao = mutation({
  args: {
    conversaId: v.id("conversas"),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) {
      return { sucesso: false, erro: "Sala de reunião não encontrada" };
    }

    // Verificar se é sala de reunião
    if (conversa.tipo !== "sala_reuniao") {
      return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
    }

    // Verificar se usuário é administrador
    const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
    if (!isAdmin) {
      return { sucesso: false, erro: "Apenas administradores podem encerrar a reunião" };
    }

    // Criar notificação para todos os participantes informando que a reunião foi encerrada
    for (const participanteId of conversa.participantes) {
      if (participanteId !== usuarioAtual._id) {
        await ctx.db.insert("notificacoes", {
          usuarioId: participanteId,
          tipo: "nova_mensagem",
          conversaId: args.conversaId,
          remetenteId: usuarioAtual._id,
          titulo: "Reunião encerrada",
          descricao: `A sala de reunião "${conversa.nome || "Sem nome"}" foi encerrada por ${usuarioAtual.nome}`,
          lida: false,
          criadaEm: Date.now(),
        });
      }
    }

    // Remover todos os participantes (exceto o criador, se necessário manter histórico)
    // Por enquanto, vamos apenas limpar a lista de participantes
    await ctx.db.patch(args.conversaId, {
      participantes: [],
      administradores: undefined,
    });

    return { sucesso: true };
  },
});

/**
 * Envia uma notificação para todos os participantes de uma sala de reunião (apenas administradores)
 */
export const enviarNotificacaoReuniao = mutation({
  args: {
    conversaId: v.id("conversas"),
    titulo: v.string(),
    mensagem: v.string(),
  },
  returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return { sucesso: false, erro: "Não autenticado" };
    }

    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa) {
      return { sucesso: false, erro: "Sala de reunião não encontrada" };
    }

    // Verificar se é sala de reunião
    if (conversa.tipo !== "sala_reuniao") {
      return { sucesso: false, erro: "Esta funcionalidade é apenas para salas de reunião" };
    }

    // Verificar se usuário é administrador
    const isAdmin = await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
    if (!isAdmin) {
      return { sucesso: false, erro: "Apenas administradores podem enviar notificações" };
    }

    // Criar notificação para todos os participantes
    for (const participanteId of conversa.participantes) {
      const tituloNotificacao = args.titulo || "Notificação da sala de reunião";
      const descricaoNotificacao = args.mensagem.substring(0, 100); // Limitar descrição para push
      
      // Criar notificação no banco
      await ctx.db.insert("notificacoes", {
        usuarioId: participanteId,
        tipo: "nova_mensagem",
        conversaId: args.conversaId,
        remetenteId: usuarioAtual._id,
        titulo: tituloNotificacao,
        descricao: args.mensagem,
        lida: false,
        criadaEm: Date.now(),
      });

      // Enviar push notification (assíncrono, não bloqueia)
      ctx.scheduler.runAfter(0, internal.pushNotifications.enviarPushNotification, {
        usuarioId: participanteId,
        titulo: tituloNotificacao,
        corpo: descricaoNotificacao,
        data: {
          conversaId: args.conversaId,
          tipo: "notificacao_reuniao",
        },
      }).catch((error) => {
        console.error(`Erro ao agendar push para usuário ${participanteId}:`, error);
      });
    }

    return { sucesso: true };
  },
});

// ========== QUERIES ==========

/**
 * Verifica se o usuário atual é administrador de uma sala de reunião
 */
export const verificarSeEhAdmin = query({
  args: {
    conversaId: v.id("conversas"),
  },
  returns: v.boolean(),
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return false;

    return await verificarPermissaoAdmin(ctx, args.conversaId, usuarioAtual._id);
  },
});

/**
 * Lista todas as conversas do usuário logado
 * SEGURANÇA: Usuário só vê conversas onde é participante
 */
export const listarConversas = query({
  args: {},
  handler: async (ctx) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    // Buscar todas as conversas do usuário (SEGURANÇA: filtrar por participante)
    const todasConversas = await ctx.db.query("conversas").collect();
    const conversasDoUsuario = todasConversas.filter((c) =>
      c.participantes.includes(usuarioAtual._id)
    );

    // Ordenar por última mensagem
    conversasDoUsuario.sort((a, b) => {
      const timestampA = a.ultimaMensagemTimestamp || a.criadoEm;
      const timestampB = b.ultimaMensagemTimestamp || b.criadoEm;
      return timestampB - timestampA;
    });

    // Enriquecer com informações dos participantes
    const conversasEnriquecidas = await Promise.all(
      conversasDoUsuario.map(async (conversa) => {
        // Buscar participantes
        const participantes = await Promise.all(
          conversa.participantes.map((id) => ctx.db.get(id))
        );

        // Para conversas individuais, pegar o outro usuário
        let outroUsuario = null;
        if (conversa.tipo === "individual") {
          const outroUsuarioRaw = participantes.find(
            (p) => p?._id !== usuarioAtual._id
          );
          if (outroUsuarioRaw) {
            // 🔄 BUSCAR DADOS ATUALIZADOS DO USUÁRIO (não usar snapshot)
            const usuarioAtualizado = await ctx.db.get(outroUsuarioRaw._id);

            if (usuarioAtualizado) {
              // Adicionar URL da foto de perfil
              let fotoPerfilUrl = null;
              if (usuarioAtualizado.fotoPerfil) {
                fotoPerfilUrl = await ctx.storage.getUrl(
                  usuarioAtualizado.fotoPerfil
                );
              }
              outroUsuario = {
                ...usuarioAtualizado,
                fotoPerfilUrl,
              };
            }
          }
        }

        // Contar mensagens não lidas (apenas mensagens NÃO agendadas)
        const leitura = await ctx.db
          .query("leituras")
          .withIndex("by_conversa_usuario", (q) =>
            q.eq("conversaId", conversa._id).eq("usuarioId", usuarioAtual._id)
          )
          .first();

        // CORRIGIDO: Buscar apenas mensagens NÃO agendadas (agendadaPara === undefined)
        // SEGURANÇA: Filtrar apenas mensagens de participantes da conversa
        const todasMensagens = await ctx.db
          .query("mensagens")
          .withIndex("by_conversa", (q) => q.eq("conversaId", conversa._id))
          .collect();

        // Filtrar mensagens agendadas e garantir que remetente é participante
        const mensagens = todasMensagens.filter((m) => {
          if (m.agendadaPara) return false;
          // Garantir que o remetente é participante da conversa
          return conversa.participantes.includes(m.remetenteId);
        });

        let naoLidas = 0;
        if (leitura) {
          naoLidas = mensagens.filter(
            (m) =>
              m.enviadaEm > (leitura.lidaEm || 0) &&
              m.remetenteId !== usuarioAtual._id
          ).length;
        } else {
          naoLidas = mensagens.filter(
            (m) => m.remetenteId !== usuarioAtual._id
          ).length;
        }

        // Verificar se usuário é administrador (apenas para salas de reunião)
        const isAdmin = conversa.tipo === "sala_reuniao" 
          ? await verificarPermissaoAdmin(ctx, conversa._id, usuarioAtual._id)
          : false;

        // Enriquecer participantes com fotoPerfilUrl (para grupos e salas)
        const participantesInfo = await Promise.all(
          participantes
            .filter((p) => p !== null)
            .map(async (participante) => {
              if (!participante) return null;
              
              let fotoPerfilUrl = null;
              if (participante.fotoPerfil) {
                fotoPerfilUrl = await ctx.storage.getUrl(participante.fotoPerfil);
              }
              
              return {
                ...participante,
                fotoPerfilUrl,
              };
            })
        );

        return {
          ...conversa,
          outroUsuario,
          participantesInfo: participantesInfo.filter((p) => p !== null),
          naoLidas,
          isAdmin, // Adicionar flag de admin
        };
      })
    );

    return conversasEnriquecidas;
  },
});

/**
 * Obtém as mensagens de uma conversa com paginação
 * SEGURANÇA: Usuário só vê mensagens de conversas onde é participante
 */
export const obterMensagens = query({
  args: {
    conversaId: v.id("conversas"),
    limit: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    // Verificar se usuário pertence à conversa (SEGURANÇA CRÍTICA)
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      return [];
    }

    // Buscar mensagens (excluir agendadas)
    const mensagens = await ctx.db
      .query("mensagens")
      .withIndex("by_conversa", (q) => q.eq("conversaId", args.conversaId))
      .order("desc")
      .take(args.limit || 50);

    // Filtrar mensagens agendadas e garantir que são da conversa correta
    // SEGURANÇA: Apenas mensagens de participantes da conversa são retornadas
    const mensagensFiltradas = mensagens.filter((m) => {
      // Excluir agendadas
      if (m.agendadaPara) return false;
      
      // Garantir que a mensagem pertence à conversa correta (segurança adicional)
      if (m.conversaId !== args.conversaId) return false;
      
      // SEGURANÇA CRÍTICA: Garantir que o remetente é participante da conversa
      // Isso garante que usuários só veem mensagens de conversas onde participam
      return conversa.participantes.includes(m.remetenteId);
    });

    // Enriquecer com informações do remetente e mensagem respondida
    const mensagensEnriquecidas = await Promise.all(
      mensagensFiltradas.map(async (mensagem) => {
        const remetente = await ctx.db.get(mensagem.remetenteId);
        
        // SEGURANÇA: Não retornar informações de remetente se não for participante
        if (!remetente || !conversa.participantes.includes(remetente._id)) {
          return null;
        }
        
        let arquivoUrl = null;
        if (mensagem.arquivoId) {
          arquivoUrl = await ctx.storage.getUrl(mensagem.arquivoId);
        }

        // Buscar mensagem original se for resposta
        let mensagemOriginal = null;
        if (mensagem.respostaPara) {
          const original = await ctx.db.get(mensagem.respostaPara);
          if (original && conversa.participantes.includes(original.remetenteId)) {
            const remetenteOriginal = await ctx.db.get(original.remetenteId);
            mensagemOriginal = {
              _id: original._id,
              conteudo: original.conteudo.substring(0, 100), // Limitar tamanho
              remetente: remetenteOriginal ? {
                _id: remetenteOriginal._id,
                nome: remetenteOriginal.nome,
              } : null,
              deletada: original.deletada || false,
            };
          }
        }

        return {
          ...mensagem,
          remetente,
          arquivoUrl,
          mensagemOriginal,
        };
      })
    );

    // Filtrar nulls (caso alguma mensagem tenha sido rejeitada por segurança)
    return mensagensEnriquecidas.filter((m) => m !== null).reverse();
  },
});

/**
 * Obtém mensagens agendadas de uma conversa
 * SEGURANÇA: Usuário só vê suas próprias mensagens agendadas de conversas onde é participante
 */
export const obterMensagensAgendadas = query({
  args: {
    conversaId: v.id("conversas"),
  },
  handler: async (ctx, args): Promise<Doc<"mensagens">[]> => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    // SEGURANÇA: Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      return [];
    }

    // Buscar mensagens agendadas
    const todasMensagens = await ctx.db
      .query("mensagens")
      .withIndex("by_conversa", (q) => q.eq("conversaId", args.conversaId))
      .collect();

    // Filtrar apenas as agendadas do usuário atual (SEGURANÇA: só suas próprias mensagens)
    const minhasMensagensAgendadas = todasMensagens.filter(
      (m) =>
        m.remetenteId === usuarioAtual._id &&
        m.agendadaPara !== undefined &&
        m.agendadaPara > Date.now() &&
        m.conversaId === args.conversaId // Garantir que pertence à conversa correta
    );

    return minhasMensagensAgendadas.sort(
      (a, b) => (a.agendadaPara ?? 0) - (b.agendadaPara ?? 0)
    );
  },
});

/**
 * Listar todas as mensagens agendadas do usuário atual (para página de notificações)
 * SEGURANÇA: Usuário só vê suas próprias mensagens agendadas de conversas onde ainda é participante
 */
export const listarAgendamentosChat = query({
  args: {},
  handler: async (
    ctx
  ): Promise<
    Array<
      Doc<"mensagens"> & {
        conversaInfo: Doc<"conversas"> | null;
        destinatarioInfo: Doc<"usuarios"> | null;
      }
    >
  > => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) {
      return [];
    }

    // Buscar todas as mensagens agendadas do usuário
    const todasMensagens = await ctx.db
      .query("mensagens")
      .withIndex("by_remetente", (q) => q.eq("remetenteId", usuarioAtual._id))
      .collect();

    // Filtrar apenas as que têm agendamento (passadas ou futuras)
    const mensagensAgendadas = todasMensagens.filter(
      (m) => m.agendadaPara !== undefined
    );

    // Enriquecer com informações da conversa e destinatário
    const mensagensEnriquecidas = await Promise.all(
      mensagensAgendadas.map(async (mensagem) => {
        const conversaInfo = await ctx.db.get(mensagem.conversaId);

        // SEGURANÇA: Verificar se usuário ainda é participante da conversa
        if (!conversaInfo || !conversaInfo.participantes.includes(usuarioAtual._id)) {
          return null; // Usuário não é mais participante, não mostrar mensagem
        }

        // SEGURANÇA: Verificar se o remetente (que deve ser o usuário atual) é participante
        if (!conversaInfo.participantes.includes(mensagem.remetenteId)) {
          return null; // Remetente não é participante, mensagem inválida
        }

        let destinatarioInfo: Doc<"usuarios"> | null = null;

        // Se for conversa individual, encontrar o outro participante
        if (conversaInfo.tipo === "individual") {
          const outroParticipanteId = conversaInfo.participantes.find(
            (p) => p !== usuarioAtual._id
          );
          if (outroParticipanteId) {
            destinatarioInfo = await ctx.db.get(outroParticipanteId);
          }
        }

        return {
          ...mensagem,
          conversaInfo,
          destinatarioInfo,
        };
      })
    );

    // Filtrar nulls e ordenar por data de agendamento (mais próximos primeiro)
    return mensagensEnriquecidas
      .filter((m): m is NonNullable<typeof m> => m !== null)
      .sort((a, b) => {
      const dataA = a.agendadaPara ?? 0;
      const dataB = b.agendadaPara ?? 0;
      return dataA - dataB;
    });
  },
});

/**
 * Obtém as notificações do usuário
 * SEGURANÇA: Usuário só vê notificações de conversas onde ainda é participante
 */
export const obterNotificacoes = query({
  args: {
    apenasPendentes: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    let query = ctx.db
      .query("notificacoes")
      .withIndex("by_usuario", (q) => q.eq("usuarioId", usuarioAtual._id));

    if (args.apenasPendentes) {
      query = ctx.db
        .query("notificacoes")
        .withIndex("by_usuario_lida", (q) =>
          q.eq("usuarioId", usuarioAtual._id).eq("lida", false)
        );
    }

    const notificacoes = await query.order("desc").take(50);

    // Enriquecer com informações do remetente e validar acesso
    const notificacoesEnriquecidas = await Promise.all(
      notificacoes.map(async (notificacao) => {
        // SEGURANÇA: Se a notificação tem conversaId, verificar se usuário ainda é participante
        if (notificacao.conversaId) {
          const conversa = await ctx.db.get(notificacao.conversaId);
          if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
            return null; // Usuário não é mais participante, não mostrar notificação
          }

          // SEGURANÇA: Se tem remetenteId, verificar se é participante da conversa
          if (notificacao.remetenteId && !conversa.participantes.includes(notificacao.remetenteId)) {
            return null; // Remetente não é participante, notificação inválida
          }
        }

        let remetente = null;
        if (notificacao.remetenteId) {
          remetente = await ctx.db.get(notificacao.remetenteId);
        }
        return {
          ...notificacao,
          remetente,
        };
      })
    );

    // Filtrar nulls antes de retornar
    return notificacoesEnriquecidas.filter((n): n is NonNullable<typeof n> => n !== null);
  },
});

/**
 * Conta o número de notificações não lidas
 */
export const contarNotificacoesNaoLidas = query({
  args: {},
  handler: async (ctx) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return 0;

    const notificacoes = await ctx.db
      .query("notificacoes")
      .withIndex("by_usuario_lida", (q) =>
        q.eq("usuarioId", usuarioAtual._id).eq("lida", false)
      )
      .collect();

    return notificacoes.length;
  },
});

/**
 * Obtém usuários online
 */
export const obterUsuariosOnline = query({
  args: {},
  handler: async (ctx) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    const usuarios = await ctx.db
      .query("usuarios")
      .withIndex("by_status_presenca", (q) => q.eq("statusPresenca", "online"))
      .collect();

    return usuarios.map((u) => ({
      _id: u._id,
      nome: u.nome,
      email: u.email,
      avatar: u.avatar,
      fotoPerfil: u.fotoPerfil,
      statusPresenca: u.statusPresenca,
      statusMensagem: u.statusMensagem,
      setor: u.setor,
    }));
  },
});

/**
 * Lista todos os usuários (para criar nova conversa)
 */
export const listarTodosUsuarios = query({
  args: {},
  handler: async (ctx) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    const usuarios = await ctx.db
      .query("usuarios")
      .withIndex("by_ativo", (q) => q.eq("ativo", true))
      .collect();

    // Excluir o usuário atual e buscar matrículas
    const usuariosComMatricula = await Promise.all(
      usuarios
        .filter((u) => u._id !== usuarioAtual._id)
        .map(async (u) => {
          let matricula: string | undefined = undefined;
          if (u.funcionarioId) {
            const funcionario = await ctx.db.get(u.funcionarioId);
            matricula = funcionario?.matricula;
          }
          
          // Buscar URL da foto de perfil se existir
          let fotoPerfilUrl: string | null = null;
          if (u.fotoPerfil) {
            fotoPerfilUrl = await ctx.storage.getUrl(u.fotoPerfil);
          }
          
          return {
            _id: u._id,
            nome: u.nome,
            email: u.email,
            matricula,
            avatar: u.avatar,
            fotoPerfil: u.fotoPerfil,
            fotoPerfilUrl,
            statusPresenca: u.statusPresenca,
            statusMensagem: u.statusMensagem,
            setor: u.setor,
          };
        })
    );

    return usuariosComMatricula;
  },
});

/**
 * Busca mensagens em conversas com filtros avançados
 * SEGURANÇA: Usuário só vê mensagens de conversas onde é participante e onde o remetente também é participante
 */
export const buscarMensagens = query({
  args: {
    query: v.string(),
    conversaId: v.optional(v.id("conversas")),
    remetenteId: v.optional(v.id("usuarios")),
    tipo: v.optional(v.union(v.literal("texto"), v.literal("arquivo"), v.literal("imagem"))),
    dataInicio: v.optional(v.number()),
    dataFim: v.optional(v.number()),
    limite: v.optional(v.number()),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    // Normalizar query para busca
    const queryNormalizada = normalizarTextoParaBusca(args.query);

    // Buscar em todas as conversas do usuário
    const todasConversas = await ctx.db.query("conversas").collect();
    const conversasDoUsuario = todasConversas.filter((c) =>
      c.participantes.includes(usuarioAtual._id)
    );

    // SEGURANÇA: Se filtrar por remetente, verificar se ele é participante de alguma conversa do usuário
    if (args.remetenteId) {
      const remetenteEParticipante = conversasDoUsuario.some(c => 
        c.participantes.includes(args.remetenteId!)
      );
      if (!remetenteEParticipante) {
        return []; // Remetente não é participante de nenhuma conversa do usuário
      }
    }

    let mensagens: Doc<"mensagens">[] = [];

    if (args.conversaId !== undefined) {
      // Verificar se usuário pertence à conversa
      const conversa = await ctx.db.get(args.conversaId);
      if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
        return [];
      }

      // Buscar em conversa específica
      const mensagensConversa = await ctx.db
        .query("mensagens")
        .withIndex("by_conversa", (q) => q.eq("conversaId", args.conversaId!))
        .collect();
      
      // SEGURANÇA: Filtrar apenas mensagens cujo remetente é participante desta conversa específica
      mensagens = mensagensConversa.filter(m => 
        conversa.participantes.includes(m.remetenteId)
      );
    } else {
      // Buscar em todas as conversas do usuário
      for (const conversa of conversasDoUsuario) {
        const mensagensConversa = await ctx.db
          .query("mensagens")
          .withIndex("by_conversa", (q) => q.eq("conversaId", conversa._id))
          .collect();
        
        // SEGURANÇA: Filtrar apenas mensagens cujo remetente é participante desta conversa específica
        const mensagensValidas = mensagensConversa.filter(m => 
          conversa.participantes.includes(m.remetenteId)
        );
        mensagens.push(...mensagensValidas);
      }
    }

    // Aplicar filtros
    let mensagensFiltradas = mensagens.filter((m) => {
      // Excluir deletadas e agendadas
      if (m.deletada || m.agendadaPara) {
        return false;
      }

      // SEGURANÇA CRÍTICA: Garantir que a mensagem pertence a uma conversa do usuário
      // e que o remetente é participante dessa conversa específica
      const conversaDaMensagem = conversasDoUsuario.find(c => c._id === m.conversaId);
      if (!conversaDaMensagem) {
        return false;
      }
      
      // SEGURANÇA CRÍTICA: Garantir que o remetente é participante da conversa específica da mensagem
      if (!conversaDaMensagem.participantes.includes(m.remetenteId)) {
        return false;
      }

      // Filtrar por query (busca no conteúdo normalizado)
      if (queryNormalizada && queryNormalizada.length > 0) {
        const conteudoBusca = m.conteudoBusca || normalizarTextoParaBusca(m.conteudo);
        if (!conteudoBusca.includes(queryNormalizada)) {
          return false;
        }
      }

      // Filtrar por remetente (já verificado acima, mas garantir novamente)
      if (args.remetenteId) {
        if (m.remetenteId !== args.remetenteId) {
        return false;
        }
        // Verificar novamente se o remetente é participante da conversa específica desta mensagem
        if (!conversaDaMensagem.participantes.includes(args.remetenteId)) {
          return false;
        }
      }

      // Filtrar por tipo
      if (args.tipo && m.tipo !== args.tipo) {
        return false;
      }

      // Filtrar por data
      if (args.dataInicio && m.enviadaEm < args.dataInicio) {
        return false;
      }
      if (args.dataFim && m.enviadaEm > args.dataFim) {
        return false;
      }

      return true;
    });

    // Ordenar por data (mais recentes primeiro)
    mensagensFiltradas.sort((a, b) => b.enviadaEm - a.enviadaEm);

    // Limitar resultados
    if (args.limite) {
      mensagensFiltradas = mensagensFiltradas.slice(0, args.limite);
    }

    // Enriquecer com informações (apenas para mensagens válidas)
    const mensagensEnriquecidas = await Promise.all(
      mensagensFiltradas.map(async (mensagem) => {
        const conversaDaMensagem = conversasDoUsuario.find(c => c._id === mensagem.conversaId);
        
        // SEGURANÇA: Validar novamente antes de enriquecer
        if (!conversaDaMensagem || !conversaDaMensagem.participantes.includes(mensagem.remetenteId)) {
          return null;
        }

        const remetente = await ctx.db.get(mensagem.remetenteId);
        
        // SEGURANÇA: Só retornar se remetente for participante
        if (!remetente || !conversaDaMensagem.participantes.includes(remetente._id)) {
          return null;
        }

        return {
          ...mensagem,
          remetente,
          conversa: conversaDaMensagem,
        };
      })
    );

    // Filtrar nulls antes de retornar
    return mensagensEnriquecidas
      .filter((m): m is NonNullable<typeof m> => m !== null)
      .sort((a, b) => b.enviadaEm - a.enviadaEm)
      .slice(0, 50);
  },
});

/**
 * Obtém quem está digitando em uma conversa
 * SEGURANÇA: Usuário só vê digitação de conversas onde é participante
 */
export const obterDigitando = query({
  args: {
    conversaId: v.id("conversas"),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return [];

    // SEGURANÇA: Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      return [];
    }

    // Buscar indicadores de digitação (últimos 10 segundos)
    const dezSegundosAtras = Date.now() - 10000;
    const digitando = await ctx.db
      .query("digitando")
      .withIndex("by_conversa", (q) => q.eq("conversaId", args.conversaId))
      .filter((q) => q.gte(q.field("iniciouEm"), dezSegundosAtras))
      .collect();

    // Filtrar usuário atual e garantir que são participantes da conversa
    const digitandoFiltrado = digitando.filter(
      (d) => {
        if (d.usuarioId === usuarioAtual._id) return false;
        // Garantir que o usuário digitando é participante da conversa
        return conversa.participantes.includes(d.usuarioId);
      }
    );

    const usuarios = await Promise.all(
      digitandoFiltrado.map(async (d) => {
        const usuario = await ctx.db.get(d.usuarioId);
        // SEGURANÇA: Só retornar se for participante
        if (!usuario || !conversa.participantes.includes(usuario._id)) {
          return null;
        }
        return usuario;
      })
    );

    return usuarios.filter((u) => u !== null);
  },
});

/**
 * Conta mensagens não lidas de uma conversa
 * SEGURANÇA: Usuário só conta mensagens de conversas onde é participante
 */
export const contarNaoLidas = query({
  args: {
    conversaId: v.id("conversas"),
  },
  handler: async (ctx, args) => {
    const usuarioAtual = await getUsuarioAutenticado(ctx);
    if (!usuarioAtual) return 0;

    // SEGURANÇA: Verificar se usuário pertence à conversa
    const conversa = await ctx.db.get(args.conversaId);
    if (!conversa || !conversa.participantes.includes(usuarioAtual._id)) {
      return 0;
    }

    const leitura = await ctx.db
      .query("leituras")
      .withIndex("by_conversa_usuario", (q) =>
        q.eq("conversaId", args.conversaId).eq("usuarioId", usuarioAtual._id)
      )
      .first();

    const todasMensagens = await ctx.db
      .query("mensagens")
      .withIndex("by_conversa", (q) => q.eq("conversaId", args.conversaId))
      .filter((q) => q.eq(q.field("agendadaPara"), undefined))
      .collect();

    // SEGURANÇA: Filtrar apenas mensagens de participantes da conversa
    const mensagens = todasMensagens.filter((m) => 
      conversa.participantes.includes(m.remetenteId)
    );

    if (leitura) {
      return mensagens.filter(
        (m) =>
          m.enviadaEm > (leitura.lidaEm || 0) &&
          m.remetenteId !== usuarioAtual._id
      ).length;
    }

    return mensagens.filter((m) => m.remetenteId !== usuarioAtual._id).length;
  },
});

// ========== INTERNAL MUTATIONS (para crons) ==========

/**
 * Envia mensagens agendadas (chamado pelo cron)
 */
export const enviarMensagensAgendadas = internalMutation({
  args: {},
  handler: async (ctx) => {
    const agora = Date.now();

    // Buscar mensagens que deveriam ser enviadas
    // Como o índice by_agendamento indexa por agendadaPara, podemos usar range query
    // Buscar mensagens com agendadaPara entre 0 e agora (mensagens agendadas que já devem ser enviadas)
    // Valores undefined não aparecem no índice, então só buscamos mensagens realmente agendadas
    const mensagensAgendadas = await ctx.db
      .query("mensagens")
      .withIndex("by_agendamento", (q) => q.gte("agendadaPara", 0).lte("agendadaPara", agora))
      .collect();

    for (const mensagem of mensagensAgendadas) {
      // Normalizar conteúdo para busca (se ainda não foi feito)
      const conteudoBusca = mensagem.conteudoBusca || normalizarTextoParaBusca(mensagem.conteudo);
      
      // Atualizar mensagem para "enviada"
      await ctx.db.patch(mensagem._id, {
        agendadaPara: undefined,
        enviadaEm: agora,
        conteudoBusca: conteudoBusca, // Garantir que tem conteúdo de busca
      });

      // Atualizar última mensagem da conversa
      const conversa = await ctx.db.get(mensagem.conversaId);
      if (conversa) {
        await ctx.db.patch(mensagem.conversaId, {
          ultimaMensagem: mensagem.conteudo.substring(0, 100),
          ultimaMensagemTimestamp: agora,
          ultimaMensagemRemetenteId: mensagem.remetenteId, // Guardar ID do remetente
        });

        // Criar notificações para outros participantes
        const remetente = await ctx.db.get(mensagem.remetenteId);
        if (remetente) {
          // Determinar tipo de notificação (se há menções)
          const tipoNotificacao = mensagem.mencoes && mensagem.mencoes.length > 0 ? "mencao" : "nova_mensagem";
          const titulo = tipoNotificacao === "mencao"
            ? `${remetente.nome} mencionou você`
            : `Nova mensagem de ${remetente.nome}`;
          const descricao = mensagem.conteudo.substring(0, 100);

          for (const participanteId of conversa.participantes) {
            if (participanteId !== mensagem.remetenteId) {
              // Criar notificação no banco
              await ctx.db.insert("notificacoes", {
                usuarioId: participanteId,
                tipo: tipoNotificacao,
                conversaId: mensagem.conversaId,
                mensagemId: mensagem._id,
                remetenteId: mensagem.remetenteId,
                titulo,
                descricao,
                lida: false,
                criadaEm: agora,
              });

              // Enviar push notification (assíncrono, não bloqueia)
              ctx.scheduler.runAfter(0, internal.pushNotifications.enviarPushNotification, {
                usuarioId: participanteId,
                titulo,
                corpo: descricao,
                data: {
                  conversaId: mensagem.conversaId,
                  mensagemId: mensagem._id,
                  tipo: tipoNotificacao,
                },
              }).catch((error) => {
                console.error(`Erro ao agendar push para usuário ${participanteId}:`, error);
              });
            }
          }
        }
      }
    }

    return mensagensAgendadas.length;
  },
});

/**
 * Limpa indicadores de digitação antigos (chamado pelo cron)
 */
export const limparIndicadoresDigitacao = internalMutation({
  args: {},
  handler: async (ctx) => {
    const dezSegundosAtras = Date.now() - 10000;

    const indicadoresAntigos = await ctx.db
      .query("digitando")
      .filter((q) => q.lt(q.field("iniciouEm"), dezSegundosAtras))
      .collect();

    for (const indicador of indicadoresAntigos) {
      await ctx.db.delete(indicador._id);
    }

    return indicadoresAntigos.length;
  },
});
