'use strict'

/**
 * context-builder.js
 * Constrói o contexto local do projeto Alemão Conveniências para enriquecer
 * cada chamada à Secretária IA no motor NewWhats.
 *
 * O objeto retornado é injetado no system prompt do ProtocolEngine como
 * "=== CONTEXTO DO CLIENTE (dados reais do projeto) ===".
 *
 * Se nada for encontrado, retorna null (sem overhead no motor).
 */

const { queryOne, query } = require('../../database/postgres')

/**
 * Normaliza um número de telefone para 10–11 dígitos (sem código de país).
 * Entrada: JID whatsapp (556799138794@s.whatsapp.net) ou número bruto.
 */
function normalizePhone(raw) {
  if (!raw) return ''
  // Remove tudo que não for dígito e código de país BR
  const digits = raw.replace(/\D/g, '')
  if (digits.startsWith('55') && digits.length >= 12) return digits.slice(2)
  return digits
}

/**
 * Tenta localizar um usuário pelo telefone normalizado.
 * Retorna o registro ou null se não encontrado.
 */
const escapeLike = s => s.replace(/[%_\\]/g, c => '\\' + c)

async function findUserByPhone(phone) {
  if (!phone || phone.length < 8) return null
  try {
    // Tenta match exato e com variações de comprimento (com/sem 9 extra)
    const variants = [phone]
    if (phone.length === 11) variants.push(phone.slice(0, 2) + phone.slice(3)) // remove 9 extra
    if (phone.length === 10) variants.push(phone.slice(0, 2) + '9' + phone.slice(2)) // adiciona 9 extra

    for (const v of variants) {
      const user = await queryOne(
        `SELECT id, nome, cpf, email, telefone, created_at
         FROM users
         WHERE REGEXP_REPLACE(telefone, '[^0-9]', '', 'g') LIKE $1
         LIMIT 1`,
        [`%${escapeLike(v)}`],
      )
      if (user) return user
    }
  } catch (e) {
    // Tabela pode ter nome diferente — tenta customers
    try {
      const user = await queryOne(
        `SELECT id, nome, cpf, email, telefone, created_at FROM customers WHERE telefone LIKE $1 LIMIT 1`,
        [`%${escapeLike(phone)}`],
      )
      if (user) return user
    } catch { /* sem tabela customers */ }
  }
  return null
}

/**
 * Busca o clube e plano ativo do usuário.
 */
async function findActivePlan(userCpf) {
  if (!userCpf) return null
  try {
    return await queryOne(
      `SELECT cm.status, cm.started_at AS member_since,
              bc.name AS club_name,
              cp.name AS plan_name, cp.price_monthly AS price
       FROM club_members cm
       JOIN benefit_clubs bc ON bc.id = cm.club_id
       JOIN club_plans    cp ON cp.id = cm.plan_id
       WHERE cm.user_cpf = $1 AND cm.status IN ('active','pending_payment')
       ORDER BY cm.started_at DESC
       LIMIT 1`,
      [userCpf.replace(/\D/g, '')],
    )
  } catch { return null }
}

/**
 * Busca os últimos pedidos do usuário (tabela pedidos, por user_id).
 */
async function findRecentOrders(userId, limit = 3) {
  if (!userId) return []
  try {
    return await query(
      `SELECT id, protocolo, status, forma_pagto, tipo_entrega, total, financeiro_status, created_at
       FROM pedidos
       WHERE user_id = $1
       ORDER BY created_at DESC
       LIMIT $2`,
      [userId, limit],
    )
  } catch { return [] }
}

/**
 * Busca promoções ativas do dia (schema correto do projeto).
 */
async function findActivePromotions(tenantId = 1) {
  try {
    return await query(
      `SELECT nome, descricao, preco_original, preco_promocional
       FROM promotions
       WHERE ativo = TRUE AND tenant_id = $1
       ORDER BY data_criacao DESC
       LIMIT 5`,
      [tenantId],
    )
  } catch { return [] }
}

/**
 * Localiza um funcionário/colaborador pelo telefone na tabela accounts.
 * Retorna o registro ou null. Usado para detectar quando o remetente é
 * um agente interno — evita que a IA trate o funcionário como cliente externo.
 */
async function findAccountByPhone(phone) {
  if (!phone || phone.length < 8) return null
  const variants = [phone]
  if (phone.length === 11) variants.push(phone.slice(0, 2) + phone.slice(3))
  if (phone.length === 10) variants.push(phone.slice(0, 2) + '9' + phone.slice(2))
  try {
    for (const v of variants) {
      const row = await queryOne(
        `SELECT id, name, role, availability
         FROM accounts
         WHERE REGEXP_REPLACE(phone, '[^0-9]', '', 'g') LIKE $1
           AND active = true
         LIMIT 1`,
        [`%${escapeLike(v)}`],
      )
      if (row) return row
    }
  } catch { /* tabela accounts pode não existir em todos os projetos */ }
  return null
}

/**
 * WPP-02 — Localiza cliente PF/PJ na tabela clientes pelo telefone.
 */
async function findClienteByPhone(phone, tenantId = 1) {
  if (!phone || phone.length < 8) return null
  try {
    const variants = [phone]
    if (phone.length === 11) variants.push(phone.slice(0, 2) + phone.slice(3))
    if (phone.length === 10) variants.push(phone.slice(0, 2) + '9' + phone.slice(2))

    for (const v of variants) {
      const row = await queryOne(
        `SELECT c.id, c.nome, c.tipo, c.cpf_cnpj, c.email, c.status
         FROM clientes c
         JOIN cliente_telefones ct ON ct.cliente_id = c.id
         WHERE c.tenant_id = $1
           AND REGEXP_REPLACE(ct.numero, '[^0-9]', '', 'g') LIKE $2
         LIMIT 1`,
        [tenantId, `%${escapeLike(v)}`],
      )
      if (row) return row
    }
  } catch { /* tabela pode não existir ainda */ }
  return null
}

// Mapeamento de status para descrição amigável
const STATUS_COTACAO = {
  rascunho:    'rascunho (carrinho ainda não confirmado)',
  salva:       'salva (aguardando confirmação do cliente)',
  enviada:     'enviada (aguardando análise)',
  processando: 'em processamento',
  concluida:   'concluída',
  finalizada:  'finalizada (pedido gerado)',
  cancelada:   'cancelada',
}
const STATUS_PEDIDO = {
  aguardando: 'aguardando (recebido, aguardando separação)',
  novo:       'novo (recebido, aguardando separação)',
  separacao:  'em separação (itens sendo separados no estoque)',
  embalagem:  'em embalagem (sendo embalado)',
  expedicao:  'saiu para entrega / expedição',
  entregue:   'entregue',
  cancelado:  'cancelado',
  auditoria:  'em auditoria interna',
  fiscal:     'em processamento fiscal',
}
const FINANCEIRO_STATUS = {
  pendente:  'pagamento pendente',
  aprovado:  'pagamento aprovado',
  recusado:  'pagamento recusado',
  reembolsado: 'reembolsado',
}

function descStatus(val, map) {
  if (!val) return null
  return map[val] ?? val
}

/**
 * Extrai menções de cotação ou pedido ID/protocolo da mensagem do usuário.
 * Ex: "cotação #1", "cotação 1", "pedido #3", "PED-20260426-3"
 */
function extractMentions(msg) {
  if (!msg) return []
  const results = []
  // Protocolo completo: PED-XXXXXXXX-N
  const protos = msg.match(/PED-\d{8}-\d+/gi) ?? []
  protos.forEach(p => results.push({ type: 'protocolo', value: p.toUpperCase() }))
  // cotação #N ou cotacao N
  const cots = msg.match(/cota[çc][aã]o\s*[#nº]?\s*(\d+)/gi) ?? []
  cots.forEach(m => {
    const id = m.match(/\d+/)?.[0]
    if (id) results.push({ type: 'cotacao_id', value: id })
  })
  // pedido #N
  const peds = msg.match(/pedido\s*[#nº]?\s*(\d+)/gi) ?? []
  peds.forEach(m => {
    const id = m.match(/\d+/)?.[0]
    if (id) results.push({ type: 'pedido_id', value: id })
  })
  return results
}

/**
 * Busca cotação + pedido vinculado diretamente por ID (qualquer usuário).
 */
async function findCotacaoById(id, tenantId = 1) {
  try {
    const c = await queryOne(
      `SELECT c.id, c.status AS cotacao_status, c.total,
              p.id AS pedido_id, p.protocolo, p.status AS pedido_status,
              p.forma_pagto, p.tipo_entrega, p.financeiro_status,
              p.created_at AS pedido_criado_em
       FROM cotacoes c
       LEFT JOIN pedidos p ON p.cotacao_id = c.id AND p.tenant_id = c.tenant_id
       WHERE c.id = $1 AND c.tenant_id = $2
       ORDER BY p.id DESC
       LIMIT 1`,
      [id, tenantId],
    )
    return c
  } catch { return null }
}

/**
 * Busca pedido por protocolo (qualquer usuário).
 */
async function findPedidoByProtocolo(protocolo, tenantId = 1) {
  try {
    return await queryOne(
      `SELECT id, protocolo, status, forma_pagto, tipo_entrega,
              financeiro_status, total, created_at
       FROM pedidos
       WHERE protocolo ILIKE $1 AND tenant_id = $2
       LIMIT 1`,
      [protocolo, tenantId],
    )
  } catch { return null }
}

/**
 * WPP-04 — Busca cotação aberta do cliente.
 */
async function findCotacaoAberta(clienteId) {
  if (!clienteId) return null
  try {
    return await queryOne(
      `SELECT c.id, c.status, c.total,
              COUNT(ci.id) AS total_itens
       FROM cotacoes c
       LEFT JOIN cotacao_itens ci ON ci.cotacao_id = c.id
       WHERE c.cliente_id = $1 AND c.status IN ('rascunho','salva')
       GROUP BY c.id
       ORDER BY c.updated_at DESC
       LIMIT 1`,
      [clienteId],
    )
  } catch { return null }
}

/**
 * WPP-04 — Busca últimos pedidos do cliente no módulo de cotação.
 */
async function findPedidosCliente(clienteId, limit = 3) {
  if (!clienteId) return []
  try {
    return await query(
      `SELECT id, status, total, tipo_entrega, created_at
       FROM pedidos
       WHERE cliente_id = $1
       ORDER BY created_at DESC
       LIMIT $2`,
      [clienteId, limit],
    )
  } catch { return [] }
}

/**
 * WPP-03 — WPP-04 — Produtos em destaque por categoria para sugestão rápida.
 */
async function findProdutosDestaque(tenantId = 1) {
  try {
    return await query(
      `SELECT nome, sku, preco, preco_promocional AS preco_promo, unidade, estoque, categoria
       FROM produtos p
       WHERE tenant_id = $1 AND ativo = TRUE AND estoque > 0
       ORDER BY categoria, nome
       LIMIT 12`,
      [tenantId],
    )
  } catch { return [] }
}

// ── Saudação contextual por horário (America/Sao_Paulo) ─────────────────────
function saudacaoPorHorario(date = new Date()) {
  // Ajusta para fuso de SP (-03:00) sem dependência externa
  const utc = date.getUTCHours() * 60 + date.getUTCMinutes()
  const minSP = (utc - 180 + 1440) % 1440
  const h = Math.floor(minSP / 60)
  const dow = (date.getUTCDay() + (utc - 180 < 0 ? -1 : 0) + 7) % 7
  const periodo = h < 5 ? 'madrugada' : h < 12 ? 'manhã' : h < 18 ? 'tarde' : 'noite'
  const saudacoes = {
    madrugada: 'Boa madrugada',
    'manhã':   'Bom dia',
    tarde:     'Boa tarde',
    noite:     'Boa noite',
  }
  // Verifica se a loja está aberta conforme brain (Seg-Qui 8-23, Sex-Sab 8-2, Dom 10-22)
  let aberto
  if (dow === 0)         aberto = h >= 10 && h < 22                   // domingo
  else if (dow >= 1 && dow <= 4) aberto = h >= 8  && h < 23           // seg-qui
  else if (dow === 5)    aberto = h >= 8  || h < 2                    // sexta (atravessa madrugada)
  else                   aberto = h >= 8  || h < 2                    // sábado
  return {
    saudacao:  saudacoes[periodo],
    periodo,
    hora_local:`${String(h).padStart(2, '0')}h`,
    loja_aberta: aberto,
    dia_semana: ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'][dow],
  }
}

// ── Detecção de intenções na mensagem ───────────────────────────────────────
const STOPWORDS = new Set([
  'o','a','os','as','um','uma','de','da','do','das','dos','em','no','na','nos','nas',
  'e','ou','que','para','pra','por','com','sem','meu','minha','seu','sua','tem','ter',
  'tinha','vou','quero','queria','gostaria','poderia','pode','podem','é','são','foi',
  'voce','você','vc','ai','aí','la','lá','aqui','tudo','bem','oi','ola','olá','bom',
  'boa','dia','tarde','noite','obrigado','obrigada','valeu','flw','ok','sim','não','nao',
])

function tokenize(msg) {
  return (msg || '')
    .toLowerCase()
    .normalize('NFD').replace(/[̀-ͯ]/g, '')
    .replace(/[^a-z0-9\s]/g, ' ')
    .split(/\s+/)
    .filter(t => t.length >= 3 && !STOPWORDS.has(t))
}

function detectIntents(msg) {
  const m = (msg || '').toLowerCase()
  return {
    pergunta_horario:    /\b(abert[oa]s?|fechad[oa]s?|hor[áa]rio|funcion|que\s+horas)\b/i.test(m),
    pergunta_endereco:   /\b(endere[çc]o|onde\s+(fica|esta|est[áa])|localiza|como\s+chego)\b/i.test(m),
    pergunta_entrega:    /\b(entrega|entregar|delivery|tele|leva\s+at[ée]|taxa\s+de\s+entrega|frete|prazo)\b/i.test(m),
    pergunta_pagamento:  /\b(pagamento|pagar|pix|cart[ãa]o|d[ée]bito|cr[ée]dito|dinheiro|boleto|forma\s+de\s+pag)\b/i.test(m),
    listar_meus_pedidos: /\b(meus\s+pedidos|minhas\s+compras|hist[óo]rico|o\s+que\s+(j[áa]\s+)?comprei)\b/i.test(m),
    pergunta_sorteio:    /\b(sorteio|rifa|ticket|sorteios|premia[çc][aã]o|ganhei)\b/i.test(m),
    pergunta_clube:      /\b(clube|fideli|assinatura|plano|membro|benef[íi]cio)\b/i.test(m),
    urgencia:            /\b(urgente|urg[ée]ncia|r[áa]pido|imediato|j[áa]\s+(j[áa]|ja)|tem\s+pressa|t[ôo]\s+com\s+pressa|estou\s+com\s+pressa|emerg[êe]ncia)\b/i.test(m),
    frustracao:          /\b(absurdo|p[ée]ssimo|horr[ií]vel|nunca\s+mais|cad[êe]\s+meu|reclama[çc][aã]o|cancela|inaceit[áa]vel|demor[oa]u\s+demais|n[ãa]o\s+aguento|t[ôo]\s+puto)\b/i.test(m),
    despedida:           /^(obrigad[oa]|valeu|flw|tchau|at[ée]|brigad[oa]|t[áa]\s+bom|ok\s+obrigad|nada\s+mais)[\s!.\,]*$/i.test(m.trim()),
    saudacao_simples:    /^(oi|ol[áa]|opa|eai|e\s+a[íi]|bom\s+dia|boa\s+(tarde|noite|madrugada)|alguem\s+a[íi])[\s!.\,?]*$/i.test(m.trim()),
    msg_curta_ambigua:   m.trim().length > 0 && m.trim().length < 4,  // "?", "ok", "..."
  }
}

// ── Busca de produtos por palavras-chave da mensagem ────────────────────────
async function findProdutosByKeywords(tokens, tenantId = 1, limit = 8) {
  if (!tokens?.length) return []
  try {
    // Cria pattern ILIKE para cada token e busca em nome/categoria/descrição
    const conditions = tokens.map((_, i) => `(
      p.nome ILIKE $${i + 2}
      OR p.categoria ILIKE $${i + 2}
      OR COALESCE(p.descricao, '') ILIKE $${i + 2}
    )`).join(' OR ')
    const params = [tenantId, ...tokens.map(t => `%${escapeLike(t)}%`)]
    return await query(
      `SELECT nome, sku, preco, preco_promocional AS preco_promo, unidade, estoque, categoria
       FROM produtos p
       WHERE tenant_id = $1 AND ativo = TRUE AND (${conditions})
       ORDER BY (estoque > 0) DESC, preco ASC
       LIMIT ${Number(limit)}`,
      params,
    )
  } catch { return [] }
}

// ── Mídias recentes processadas (PDF / imagem) ───────────────────────────────

/**
 * Busca as últimas mídias processadas neste chat (PDFs e imagens) nas últimas
 * `lookbackMinutes` minutos. O texto extraído fica em nw_auto_replies.user_msg
 * com prefixo "[documento PDF]" ou "[imagem]".
 *
 * Útil para que o LLM saiba que o cliente enviou um comprovante/cupom mesmo
 * que a mensagem atual seja apenas texto ou um ping de follow-up.
 */
async function findRecentMedia(chatId, lookbackMinutes = 120) {
  if (!chatId) return []
  try {
    const rows = await query(
      `SELECT user_msg, created_at
       FROM nw_auto_replies
       WHERE chat_id = $1
         AND created_at > NOW() - ($2 || ' minutes')::interval
         AND status IN ('sent','sent_local')
         AND (user_msg ILIKE '[documento PDF]%' OR user_msg ILIKE '[imagem]%')
       ORDER BY created_at DESC
       LIMIT 3`,
      [chatId, String(lookbackMinutes)],
    )
    return rows.map(r => {
      const ageMs  = Date.now() - new Date(r.created_at).getTime()
      const ageMin = Math.round(ageMs / 60_000)
      const age    = ageMin < 60 ? `há ${ageMin}min` : `há ${Math.round(ageMin / 60)}h`
      const tipo   = r.user_msg.startsWith('[documento PDF]') ? 'PDF' : 'imagem'
      return { tipo, conteudo: r.user_msg, enviado: age }
    })
  } catch { return [] }
}

// ── Sorteios ativos ─────────────────────────────────────────────────────────
async function findSorteiosAtivos(tenantId = 1) {
  try {
    return await query(
      `SELECT id, titulo, descricao, data_sorteio, premio
       FROM sorteios
       WHERE tenant_id = $1 AND status = 'ativo' AND data_sorteio > NOW()
       ORDER BY data_sorteio ASC
       LIMIT 5`,
      [tenantId],
    )
  } catch { return [] }
}

/**
 * Ponto de entrada principal.
 * @param {string} rawPhone — número do remetente (com ou sem código de país)
 * @param {number} tenantId
 * @param {string} userMsg — mensagem do usuário (para detectar menções de cotação/pedido)
 * @param {string|null} chatId — chat_id do WhatsApp (ex: 5567…@s.whatsapp.net); habilita histórico de mídias
 * @returns {object|null} contexto para injeção no motor, ou null se nada encontrado
 */
async function buildContext(rawPhone, tenantId = 1, userMsg = '', chatId = null) {
  const phone = normalizePhone(rawPhone)
  const result = {}
  let hasData = false

  // ── Saudação contextual e estado da loja ─────────────────────────────────
  result.contexto_temporal = saudacaoPorHorario()
  hasData = true  // sempre injeta, custo zero

  // ── Detecção de intenções da mensagem ──────────────────────────────────
  const intents = detectIntents(userMsg)
  const intentsAtivas = Object.entries(intents).filter(([, v]) => v).map(([k]) => k)
  if (intentsAtivas.length > 0) result.intencoes_detectadas = intentsAtivas

  // ── Detecção de funcionário/colaborador interno ───────────────────────────
  // Se o remetente for um agente cadastrado em accounts, injeta aviso para a IA
  // não tratar como cliente externo — evita paradoxos como "como falo com [eu mesmo]?"
  const account = await findAccountByPhone(phone)
  if (account) {
    hasData = true
    result.eh_funcionario = true
    result.funcionario = {
      nome:         account.name,
      cargo:        account.role,
      disponivel:   account.availability === 'online',
    }
  }

  // ── WPP-02: Cliente do módulo Cotação (PF/PJ) ─────────────────────────────
  const cliente = await findClienteByPhone(phone, tenantId)
  if (cliente) {
    hasData = true
    result.cliente_cadastro = {
      nome:     cliente.nome,
      tipo:     cliente.tipo,          // PF ou PJ
      cpf_cnpj: cliente.cpf_cnpj,
      email:    cliente.email ?? null,
      status:   cliente.status,
    }

    // WPP-04: cotação aberta do cliente
    const cotacao = await findCotacaoAberta(cliente.id)
    if (cotacao) {
      result.cotacao_aberta = {
        id:          cotacao.id,
        status:      cotacao.status,
        total:       cotacao.total ? `R$ ${parseFloat(cotacao.total).toFixed(2)}` : 'R$ 0,00',
        total_itens: cotacao.total_itens,
      }
    }

    // WPP-04: pedidos recentes do cliente (módulo cotação)
    const pedidos = await findPedidosCliente(cliente.id)
    if (pedidos.length > 0) {
      result.pedidos_cotacao = pedidos.map(p => ({
        id:           p.id,
        status:       descStatus(p.status, STATUS_PEDIDO),
        valor:        p.total ? `R$ ${parseFloat(p.total).toFixed(2)}` : null,
        tipo_entrega: p.tipo_entrega,
        data:         p.created_at ? new Date(p.created_at).toLocaleDateString('pt-BR') : null,
      }))
    }
  }

  // ── Cliente legacy (tabela users / hotspot) ───────────────────────────────
  const user = await findUserByPhone(phone)
  if (user) {
    hasData = true
    if (!result.cliente_cadastro) {
      result.cliente = {
        nome:          user.nome ?? 'Não identificado',
        email:         user.email ?? null,
        telefone:      user.telefone ?? phone,
        cliente_desde: user.created_at ? new Date(user.created_at).toLocaleDateString('pt-BR') : null,
      }
    }

    // Plano/Clube
    const plan = await findActivePlan(user.cpf)
    if (plan) {
      result.plano = {
        clube:        plan.club_name,
        plano:        plan.plan_name ?? 'Sem plano específico',
        status:       plan.status,
        membro_desde: plan.member_since ? new Date(plan.member_since).toLocaleDateString('pt-BR') : null,
        preco:        plan.price ? `R$ ${parseFloat(plan.price).toFixed(2)}` : null,
      }
    }

    // Pedidos do cliente (app de cotação, via user_id)
    const orders = await findRecentOrders(user.id)
    if (orders.length > 0) {
      result.pedidos_recentes = orders.map(o => ({
        protocolo:    o.protocolo || `#${o.id}`,
        status:       o.status,
        forma_pagto:  o.forma_pagto || null,
        tipo_entrega: o.tipo_entrega || null,
        valor:        o.total ? `R$ ${parseFloat(o.total).toFixed(2)}` : null,
        financeiro:   o.financeiro_status || null,
        data:         o.created_at ? new Date(o.created_at).toLocaleDateString('pt-BR') : null,
      }))
    }
  }

  // ── Promoções/produtos: gate por intenção (economia de tokens) ──────────
  // Carrega promoções e destaques só se o cliente está em modo "explorar":
  //   - saudação simples (mostrar o que tem de novo)
  //   - intent de promoção/clube/produto
  //   - mensagem com tokens significativos (busca de produto)
  const querUmCatalogo = intents.saudacao_simples ||
                          intents.pergunta_clube ||
                          /\b(promo[çc][aã]o|oferta|desconto|tem\s+(o\s+que|algo)|cat[áa]logo|destaque)\b/i.test(userMsg)

  if (querUmCatalogo) {
    const promos = await findActivePromotions(tenantId)
    if (promos.length > 0) {
      hasData = true
      result.promocoes_ativas = promos.slice(0, 3).map(p => ({
        produto:           p.nome,
        preco_promocional: `R$ ${parseFloat(p.preco_promocional).toFixed(2)}`,
        desconto_pct:      p.preco_original
          ? `${Math.round((1 - p.preco_promocional / p.preco_original) * 100)}%`
          : null,
      }))
    }
  }

  // ── Busca de produtos por palavras-chave da mensagem ────────────────────
  // Só dispara se a mensagem tiver tokens significativos (não só saudação)
  if (!intents.saudacao_simples && !intents.despedida && !intents.msg_curta_ambigua) {
    const tokens = tokenize(userMsg)
    if (tokens.length > 0) {
      const buscados = await findProdutosByKeywords(tokens, tenantId, 8)
      if (buscados.length > 0) {
        hasData = true
        result.produtos_buscados = buscados.map(p => ({
          nome:      p.nome,
          sku:       p.sku,
          preco:     p.preco_promo
            ? `R$ ${parseFloat(p.preco_promo).toFixed(2)} (promo, era R$ ${parseFloat(p.preco).toFixed(2)})`
            : `R$ ${parseFloat(p.preco).toFixed(2)}`,
          unidade:   p.unidade,
          estoque:   Number(p.estoque) > 0 ? 'disponível' : 'indisponível',
          categoria: p.categoria,
        }))
      }
    }
  }

  // ── Sorteios ativos (somente se houver intenção/menção) ────────────────
  if (intents.pergunta_sorteio) {
    const sorteios = await findSorteiosAtivos(tenantId)
    if (sorteios.length > 0) {
      hasData = true
      result.sorteios_ativos = sorteios.map(s => ({
        id:           s.id,
        titulo:       s.titulo,
        premio:       s.premio,
        data_sorteio: s.data_sorteio ? new Date(s.data_sorteio).toLocaleDateString('pt-BR') : null,
      }))
    }
  }

  // ── Mídias recentes (PDF / imagem processados neste chat) ─────────────────
  // Carrega sempre que houver chatId e a mensagem atual não for uma saudação
  // simples sem contexto — evita ruído em "oi" de primeiro contato.
  if (chatId && !intents.saudacao_simples) {
    const midias = await findRecentMedia(chatId)
    if (midias.length > 0) {
      result.midias_recentes = midias
      hasData = true
    }
  }

  // ── Carrinho abandonado (cotação salva sem finalizar há > 4h) ──────────
  // Só checa se temos cliente ou user identificado
  if (cliente || user) {
    try {
      const ownerCol = cliente ? 'cliente_id' : 'user_id'
      const ownerVal = cliente ? cliente.id : user.id
      const carrinho = await queryOne(
        `SELECT id, total, updated_at,
                (SELECT COUNT(*) FROM cotacao_itens ci WHERE ci.cotacao_id = c.id) AS total_itens
         FROM cotacoes c
         WHERE ${ownerCol} = $1 AND tenant_id = $2
           AND status IN ('rascunho','salva')
           AND updated_at < NOW() - INTERVAL '4 hours'
           AND updated_at > NOW() - INTERVAL '7 days'
         ORDER BY updated_at DESC LIMIT 1`,
        [ownerVal, tenantId],
      )
      if (carrinho && Number(carrinho.total_itens) > 0) {
        result.carrinho_abandonado = {
          id:          carrinho.id,
          total_itens: Number(carrinho.total_itens),
          valor:       carrinho.total ? `R$ ${parseFloat(carrinho.total).toFixed(2)}` : null,
          desde:       carrinho.updated_at ? new Date(carrinho.updated_at).toLocaleDateString('pt-BR') : null,
        }
      }
    } catch { /* tabelas podem variar */ }
  }

  // ── Lookup direto por menção na mensagem (cotação #N, pedido #N, PED-…) ──
  const mentions = extractMentions(userMsg)
  // Busca itens de um pedido para incluir no contexto da IA
  async function itensResumidosPedido(pedidoId) {
    try {
      const rows = await query(
        `SELECT nome_produto, quantidade, observacao FROM pedido_itens WHERE pedido_id=$1 ORDER BY id LIMIT 20`,
        [pedidoId]
      )
      return rows.map(i => {
        const obs = i.observacao ? ` (obs: ${i.observacao})` : ''
        return `${Number(i.quantidade)}x ${i.nome_produto}${obs}`
      })
    } catch { return [] }
  }

  async function itensResumidosCotacao(cotacaoId) {
    try {
      const rows = await query(
        `SELECT nome_produto, quantidade, observacao FROM cotacao_itens WHERE cotacao_id=$1 ORDER BY id LIMIT 20`,
        [cotacaoId]
      )
      return rows.map(i => {
        const obs = i.observacao ? ` (obs: ${i.observacao})` : ''
        return `${Number(i.quantidade)}x ${i.nome_produto}${obs}`
      })
    } catch { return [] }
  }

  if (mentions.length > 0) {
    const consultados = []
    for (const m of mentions) {
      if (m.type === 'cotacao_id') {
        const row = await findCotacaoById(m.value, tenantId)
        if (row) {
          const jaEPedido = row.cotacao_status === 'finalizada' && row.pedido_id
          if (jaEPedido) {
            // Cotação virou pedido — a IA deve falar do pedido, não da cotação
            const itens = await itensResumidosPedido(row.pedido_id)
            consultados.push({
              nota_ia:          `O cliente se referiu a "cotação #${m.value}" mas esta já foi confirmada e gerou um PEDIDO. Responda sempre sobre o PEDIDO abaixo, nunca diga "cotação finalizada".`,
              pedido_protocolo: row.protocolo ?? `#${row.pedido_id}`,
              pedido_status:    descStatus(row.pedido_status, STATUS_PEDIDO),
              forma_pagto:      row.forma_pagto ?? null,
              tipo_entrega:     row.tipo_entrega ?? null,
              financeiro:       descStatus(row.financeiro_status, FINANCEIRO_STATUS),
              valor:            row.total ? `R$ ${parseFloat(row.total).toFixed(2)}` : null,
              criado_em:        row.pedido_criado_em ? new Date(row.pedido_criado_em).toLocaleDateString('pt-BR') : null,
              itens:            itens.length > 0 ? itens : undefined,
            })
          } else {
            // Ainda é cotação (rascunho ou salva, sem pedido gerado)
            const itens = await itensResumidosCotacao(row.id)
            consultados.push({
              cotacao_id:     row.id,
              cotacao_status: descStatus(row.cotacao_status, STATUS_COTACAO),
              total:          row.total ? `R$ ${parseFloat(row.total).toFixed(2)}` : null,
              itens:          itens.length > 0 ? itens : undefined,
            })
          }
          hasData = true
        }
      } else if (m.type === 'protocolo') {
        const row = await findPedidoByProtocolo(m.value, tenantId)
        if (row) {
          const itens = await itensResumidosPedido(row.id)
          consultados.push({
            pedido_protocolo: row.protocolo,
            pedido_status:    descStatus(row.status, STATUS_PEDIDO),
            forma_pagto:      row.forma_pagto ?? null,
            tipo_entrega:     row.tipo_entrega ?? null,
            financeiro:       descStatus(row.financeiro_status, FINANCEIRO_STATUS),
            valor:            row.total ? `R$ ${parseFloat(row.total).toFixed(2)}` : null,
            criado_em:        row.created_at ? new Date(row.created_at).toLocaleDateString('pt-BR') : null,
            itens:            itens.length > 0 ? itens : undefined,
          })
          hasData = true
        }
      } else if (m.type === 'pedido_id') {
        // 1) tenta protocolo terminando com o número (ex: PED-…-1)
        let pedRow = await findPedidoByProtocolo(`%${m.value}`, tenantId).catch(() => null)
        // 2) tenta pedido por id numérico direto
        if (!pedRow) {
          pedRow = await queryOne(
            `SELECT id, protocolo, status, forma_pagto, tipo_entrega, financeiro_status, total, created_at
             FROM pedidos WHERE id = $1 AND tenant_id = $2 LIMIT 1`,
            [m.value, tenantId]
          ).catch(() => null)
        }
        if (pedRow) {
          const itens = await itensResumidosPedido(pedRow.id)
          consultados.push({
            pedido_protocolo: pedRow.protocolo ?? `#${pedRow.id}`,
            pedido_status:    descStatus(pedRow.status, STATUS_PEDIDO),
            forma_pagto:      pedRow.forma_pagto ?? null,
            tipo_entrega:     pedRow.tipo_entrega ?? null,
            financeiro:       descStatus(pedRow.financeiro_status, FINANCEIRO_STATUS),
            valor:            pedRow.total ? `R$ ${parseFloat(pedRow.total).toFixed(2)}` : null,
            criado_em:        pedRow.created_at ? new Date(pedRow.created_at).toLocaleDateString('pt-BR') : null,
            itens:            itens.length > 0 ? itens : undefined,
          })
          hasData = true
        } else {
          // 3) último fallback: o cliente pode estar referindo-se ao número da cotação
          //    (ex: "pedido #1" → cotação #1 → PED-20260426-3)
          const cot = await findCotacaoById(m.value, tenantId)
          if (cot) {
            const jaEPedido = cot.cotacao_status === 'finalizada' && cot.pedido_id
            if (jaEPedido) {
              const itens = await itensResumidosPedido(cot.pedido_id)
              consultados.push({
                nota_ia:          `O cliente disse "pedido #${m.value}" mas este número é o da cotação original. O pedido real é ${cot.protocolo ?? '#' + cot.pedido_id}. Informe o protocolo correto ao cliente.`,
                pedido_protocolo: cot.protocolo ?? `#${cot.pedido_id}`,
                pedido_status:    descStatus(cot.pedido_status, STATUS_PEDIDO),
                forma_pagto:      cot.forma_pagto ?? null,
                tipo_entrega:     cot.tipo_entrega ?? null,
                financeiro:       descStatus(cot.financeiro_status, FINANCEIRO_STATUS),
                valor:            cot.total ? `R$ ${parseFloat(cot.total).toFixed(2)}` : null,
                criado_em:        cot.pedido_criado_em ? new Date(cot.pedido_criado_em).toLocaleDateString('pt-BR') : null,
                itens:            itens.length > 0 ? itens : undefined,
              })
            } else {
              const itens = await itensResumidosCotacao(cot.id)
              consultados.push({
                cotacao_id:     cot.id,
                cotacao_status: descStatus(cot.cotacao_status, STATUS_COTACAO),
                total:          cot.total ? `R$ ${parseFloat(cot.total).toFixed(2)}` : null,
                itens:          itens.length > 0 ? itens : undefined,
              })
            }
            hasData = true
          }
        }
      }
    }
    if (consultados.length > 0) result.consulta_mencionada = consultados
  }

  if (!hasData) return null

  // ── SystemExtra: header de grounding + sinais em tempo real ──────────────
  // O brain (sector_brain_nodes) já carrega persona, regras, conhecimento.
  // Aqui adicionamos só o que muda por mensagem: rotular DADOS_REAIS + flags.
  const sinais = []

  // Grounding: nomeia o JSON do contexto como DADOS_REAIS (alinhado com brain)
  sinais.push(
    '[DADOS_REAIS DESTA CONVERSA]\n' +
    'O JSON de "context" abaixo é a única fonte da verdade para fatos do cliente, pedidos e produtos. ' +
    'NUNCA afirme valores, status ou prazos que não estejam nele. ' +
    'Se o dado não está em DADOS_REAIS: pergunte ao cliente OU chame escalar_humano.'
  )

  // Sinais em tempo real (não estão em CONHECIMENTO estático)
  const flags = []
  if (!result.contexto_temporal.loja_aberta) {
    flags.push(`LOJA_FECHADA (${result.contexto_temporal.dia_semana} ${result.contexto_temporal.hora_local})`)
  }
  if (result.consulta_mencionada?.length > 0) flags.push('PERGUNTA_SOBRE_PEDIDO_ESPECIFICO')
  if (result.carrinho_abandonado) flags.push(`CARRINHO_ABANDONADO_${result.carrinho_abandonado.total_itens}_itens_desde_${result.carrinho_abandonado.desde}`)
  if (intents.urgencia)    flags.push('URGENCIA')
  if (intents.frustracao)  flags.push('FRUSTRACAO')
  if (intents.despedida)   flags.push('DESPEDIDA')
  if (intents.msg_curta_ambigua) flags.push('MSG_AMBIGUA')
  if (!user && !cliente)   flags.push('CLIENTE_NAO_IDENTIFICADO')
  if (cliente?.tipo === 'PJ') flags.push('CLIENTE_PJ')
  if (result.midias_recentes?.length > 0) {
    const tipos = result.midias_recentes.map(m => `${m.tipo}(${m.enviado})`).join(',')
    flags.push(`MIDIA_RECENTE[${tipos}]`)
  }

  if (flags.length > 0) sinais.push('[SINAIS]\n' + flags.join(' | '))

  result._systemExtra = sinais.join('\n\n')

  return result
}

module.exports = { buildContext, extractMentions, findCotacaoById, findClienteByPhone, normalizePhone }
