require('dotenv').config();
const express = require('express');
const cookieParser = require('cookie-parser');
const path = require('path');
const multer = require('multer');
const { MsEdgeTTS, OUTPUT_FORMAT } = require('msedge-tts');
const { YoutubeTranscript } = require('youtube-transcript');

const app = express();
const PORT = process.env.PORT || 3000;
const ACCESS_PASSWORD = process.env.ACCESS_PASSWORD || 'puglindo26';

// Configuração do multer para uploads de áudio
const multerUpload = multer({
  storage: multer.memoryStorage(),
  limits: { fileSize: 25 * 1024 * 1024 } // 25MB max
});

// ============================================================================
// HELPER: chamada à API MiniMax (texto / classificação / reescrita de prompt)
// Substitui OpenRouter para todas as funções de TEXTO a partir de jun/2026.
// Imagem, música e vídeo continuam usando endpoints nativos MiniMax em /v1/
// ============================================================================
async function callMinimax({ messages, temperature = 0.5, max_tokens = 1500, system = null, model = null }) {
  const baseUrl = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
  const chosenModel = model || process.env.MINIMAX_MODEL || 'MiniMax-M3';
  const finalMessages = system
    ? [{ role: 'system', content: system }, ...messages]
    : messages;

  // Tentativa 1: MiniMax M3 com timeout de 45s
  try {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 45000);

    const res = await fetch(`${baseUrl}/anthropic/v1/messages`, {
      method: 'POST',
      headers: {
        'X-Api-Key': process.env.MINIMAX_API_KEY,
        'Content-Type': 'application/json',
        'anthropic-version': '2023-06-01'
      },
      body: JSON.stringify({
        model: chosenModel,
        max_tokens,
        temperature,
        messages: finalMessages
      }),
      signal: controller.signal
    });

    clearTimeout(timeoutId);

    if (res.ok) {
      const data = await res.json();
      const text = (data.content || [])
        .filter(b => b.type === 'text')
        .map(b => b.text)
        .join('')
        .trim();
      if (text) return { text, raw: data, provider: 'minimax' };
    } else {
      const txt = await res.text();
      console.warn(`[AI Text] MiniMax respondeu ${res.status}: ${txt}. Acionando fallback...`);
    }
  } catch (err) {
    console.warn('[AI Text] Falha/Timeout no MiniMax (' + err.message + '). Acionando fallback...');
  }

  // Fallback 1: Groq (Llama 3.3 70b / Llama 3.1 8b)
  if (process.env.GROQ_API_KEY) {
    try {
      console.log('[AI Text Fallback] Acionando Groq...');
      const groqMessages = system ? [{ role: 'system', content: system }, ...messages] : messages;
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 30000);

      const res = await fetch('https://api.groq.com/openai/v1/chat/completions', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model: 'llama-3.3-70b-versatile',
          messages: groqMessages,
          temperature,
          max_tokens
        }),
        signal: controller.signal
      });

      clearTimeout(timeoutId);

      if (res.ok) {
        const data = await res.json();
        const text = data.choices?.[0]?.message?.content?.trim();
        if (text) return { text, raw: data, provider: 'groq' };
      }
    } catch (groqErr) {
      console.warn('[AI Text Fallback] Falha no Groq:', groqErr.message);
    }
  }

  // Fallback 2: Google AI Gemini
  if (process.env.GOOGLE_AI_API_KEY) {
    try {
      console.log('[AI Text Fallback] Acionando Google AI Gemini...');
      const geminiModel = process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash';
      const promptText = (system ? `System: ${system}\n\n` : '') + messages.map(m => `${m.role}: ${m.content}`).join('\n');
      
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 30000);

      const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${geminiModel}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          contents: [{ parts: [{ text: promptText }] }],
          generationConfig: { temperature, maxOutputTokens: max_tokens }
        }),
        signal: controller.signal
      });

      clearTimeout(timeoutId);

      if (res.ok) {
        const data = await res.json();
        const text = data.candidates?.[0]?.content?.parts?.[0]?.text?.trim();
        if (text) return { text, raw: data, provider: 'gemini' };
      }
    } catch (gemErr) {
      console.warn('[AI Text Fallback] Falha no Gemini:', gemErr.message);
    }
  }

  throw new Error('Todas as IAs de geração de texto estão indisponíveis no momento. Por favor, tente novamente.');
}

app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb', extended: true }));
app.use(cookieParser(process.env.SESSION_SECRET || 'pedagog-secret'));

// Middleware para verificar autenticação
const requireAuth = (req, res, next) => {
  const session = req.signedCookies.pedagog_session || req.cookies.pedagog_session || req.signedCookies.camila_session || req.cookies.camila_session;
  if (session === 'authenticated') {
    next();
  } else {
    if (req.xhr || req.path.startsWith('/api/')) {
      res.status(401).json({ error: 'Não autorizado' });
    } else {
      res.redirect('/login');
    }
  }
};

// Handlers globais para evitar crash do processo Node.js por falhas transitórias de conexão
process.on('uncaughtException', (err) => {
  console.error('[CRITICAL] Exceção não capturada no processo (evitado crash):', err);
});

process.on('unhandledRejection', (reason, promise) => {
  console.error('[CRITICAL] Rejeição de Promise não tratada (evitado crash):', reason);
});

const fs = require('fs');
const { Pool } = require('pg');
const dbPool = new Pool({
  connectionString: process.env.DATABASE_URL,
  connectionTimeoutMillis: 10000,
  idleTimeoutMillis: 30000,
  max: 25
});

dbPool.on('error', (err) => {
  console.error('[Database Pool Error] Erro inesperado em cliente de banco (não fatal):', err.message);
});

async function backupMediaFile(filePath, buffer) {
  try {
    const filename = path.basename(filePath);
    let contentType = 'application/octet-stream';
    const ext = path.extname(filename).toLowerCase();
    if (ext === '.jpg' || ext === '.jpeg') contentType = 'image/jpeg';
    else if (ext === '.png') contentType = 'image/png';
    else if (ext === '.gif') contentType = 'image/gif';
    else if (ext === '.mp3') contentType = 'audio/mpeg';
    else if (ext === '.mp4') contentType = 'video/mp4';
    else if (ext === '.wav') contentType = 'audio/wav';
    
    // Timeout para não travar em caso de lentidão temporária do banco
    const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Timeout no backup de mídia')), 5000));
    const queryPromise = dbPool.query(
      `INSERT INTO escola.arquivos (filename, content_type, dados)
       VALUES ($1, $2, $3)
       ON CONFLICT (filename) DO UPDATE SET dados = $3, content_type = $2`,
      [filename, contentType, buffer]
    );

    await Promise.race([queryPromise, timeoutPromise]);
    console.log(`[Media Backup] Backup do arquivo ${filename} salvo no banco escola.arquivos.`);
  } catch (err) {
    console.warn(`[Media Backup] Aviso ao fazer backup do arquivo ${filePath} no banco:`, err.message);
  }
}

const CONFIG_FILE_PATH = path.join(__dirname, 'agent_config.json');
const KNOWLEDGE_FILE_PATH = path.join(__dirname, 'conhecimento_adquirido.json');
const TEMPERATURE_PRESETS = {
  tecnico: { value: 0.2, label: 'Técnico e Direto', description: 'Respostas curtas, objetivas, sem rodeios. Ideal para quando precisa só do essencial.' },
  equilibrado: { value: 0.6, label: 'Equilibrado', description: 'Bom para uso geral — nem muito curto, nem muito longo.' },
  detalhista: { value: 1.0, label: 'Detalhista e Falante', description: 'Respostas extensas, explicações completas,contextualização máxima. Ideal para estudo e revisão.' }
};

let agentConfigCache = {
  agentName: "PedagogIA",
  iaAvatarUrl: "assets/pedagog_ia.png",
  userAvatarUrl: "assets/pedagog_user.png",
  temperaturePreset: "equilibrado"
};

function readAgentConfig() {
  return agentConfigCache;
}

async function writeAgentConfig(config) {
  agentConfigCache = {
    agentName: config.agentName || "PedagogIA",
    iaAvatarUrl: config.iaAvatarUrl || config.kemilyAvatarUrl || "assets/pedagog_ia.png",
    userAvatarUrl: config.userAvatarUrl || config.camilaAvatarUrl || "assets/pedagog_user.png",
    temperaturePreset: config.temperaturePreset || 'equilibrado'
  };
  try {
    await dbPool.query(
      `INSERT INTO escola.config (key, agent_name, ia_avatar_url, user_avatar_url, temperature_preset, updated_at)
       VALUES ('current', $1, $2, $3, $4, NOW())
       ON CONFLICT (key) DO UPDATE SET
         agent_name = EXCLUDED.agent_name,
         ia_avatar_url = EXCLUDED.ia_avatar_url,
         user_avatar_url = EXCLUDED.user_avatar_url,
         temperature_preset = EXCLUDED.temperature_preset,
         updated_at = NOW();`,
      [agentConfigCache.agentName, agentConfigCache.iaAvatarUrl, agentConfigCache.userAvatarUrl, agentConfigCache.temperaturePreset]
    );
    fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(agentConfigCache, null, 2), 'utf8');
    const botConfigDir = path.join(__dirname, '..', 'BotVPS', 'data');
    if (fs.existsSync(botConfigDir)) {
      fs.writeFileSync(path.join(botConfigDir, 'agent_config.json'), JSON.stringify(agentConfigCache, null, 2), 'utf8');
    }
  } catch (e) {
    console.error('Erro ao salvar agent_config no banco:', e);
  }
}

// Leitura/escrita do conhecimento adquirido
function readConhecimento() {
  // Mantido apenas para compatibilidade de migração na inicialização
  try {
    if (fs.existsSync(KNOWLEDGE_FILE_PATH)) {
      return JSON.parse(fs.readFileSync(KNOWLEDGE_FILE_PATH, 'utf8'));
    }
  } catch (e) {
    console.error('Erro ao ler conhecimento_adquirido.json:', e);
  }
  return { facts: [], lastUpdated: null };
}

async function addKnowledgeFact(fact, source = 'conversa') {
  try {
    const res = await dbPool.query("SELECT content FROM escola.conhecimento;");
    const exists = res.rows.some(f => 
      f.content.toLowerCase().includes(fact.toLowerCase()) || 
      fact.toLowerCase().includes(f.content.toLowerCase())
    );
    if (!exists) {
      await dbPool.query(
        "INSERT INTO escola.conhecimento (content, source, added_at) VALUES ($1, $2, NOW());",
        [fact, source]
      );
    }
    return !exists;
  } catch (err) {
    console.error('Erro ao adicionar fato no banco:', err);
    return false;
  }
}

async function removeKnowledgeFact(id) {
  try {
    await dbPool.query("DELETE FROM escola.conhecimento WHERE id = $1;", [id]);
  } catch (err) {
    console.error('Erro ao remover fato do banco:', err);
  }
}

async function getKnowledgeAsText() {
  try {
    const res = await dbPool.query("SELECT content, source FROM escola.conhecimento ORDER BY added_at DESC;");
    if (!res.rows.length) return '';
    return '\n\n════════════════════════════════════════\nCONHECIMENTO ADQUIRIDO (Aprendido com a Camila):\n════════════════════════════════════════\n' + 
      res.rows.map(f => `[${f.source}] ${f.content}`).join('\n') +
      '\n════════════════════════════════════════\n';
  } catch (err) {
    console.error('Erro ao ler conhecimento do banco:', err);
    return '';
  }
}

// Rota POST para sintetizar fala com MsEdgeTTS e retornar áudio
app.post('/api/tts', requireAuth, async (req, res) => {
  const { text } = req.body;
  if (!text) return res.status(400).json({ error: 'Texto é obrigatório' });

  try {
    const tts = new MsEdgeTTS();
    await tts.setMetadata("pt-BR-FranciscaNeural", OUTPUT_FORMAT.AUDIO_24KHZ_48KBITRATE_MONO_MP3);
    const { audioStream } = tts.toStream(text);

    res.setHeader('Content-Type', 'audio/mpeg');
    audioStream.pipe(res);

    audioStream.on('error', (err) => {
      console.error('Erro na stream do MsEdgeTTS:', err);
      if (!res.headersSent) {
        res.status(500).json({ error: 'Erro ao processar áudio' });
      }
    });
  } catch (error) {
    console.error('Erro ao gerar TTS:', error);
    if (!res.headersSent) {
      res.status(500).json({ error: error.message || 'Erro ao gerar áudio' });
    }
  }
});

// Rota POST para gerar música customizada (Estúdio Musical)
app.post('/api/music/generate', requireAuth, async (req, res) => {
  const { tema, voz, ritmo, duracao } = req.body;
  if (!tema) {
    return res.status(400).json({ error: 'Tema da música é obrigatório' });
  }

  try {
    // 1. Determinar o prompt de estilo baseado nos inputs
    let voiceDesc = 'young female vocals, singing in clear native brazilian portuguese accent, no european accent, sweet and warm voice';
    if (voz === 'homem') {
      voiceDesc = 'male vocals, singing in clear native brazilian portuguese accent, warm and gentle singing voice';
    } else if (voz === 'crianca') {
      voiceDesc = 'real young kid vocals, little child singing, boy or girl child voice, playful children voice, native brazilian portuguese kid accent, definitely no adult voices';
    }

    let rhythmDesc = 'happy acoustic children song, simple melody, acoustic guitar';
    if (ritmo === 'roda') {
      rhythmDesc = 'traditional Brazilian cantiga de roda, folk children rhythm, acoustic guitar, percussion';
    } else if (ritmo === 'pop') {
      rhythmDesc = 'upbeat pop for children, modern synth, happy rhythm, claps';
    } else if (ritmo === 'ninar') {
      rhythmDesc = 'gentle lullaby for children, music box, soft strings, calm, sleeping mood';
    } else if (ritmo === 'marcha') {
      rhythmDesc = 'marching children rhythm, playful snare drums, acoustic guitar, brass accent';
    }

    const stylePrompt = `${rhythmDesc}, ${voiceDesc}, educational children song style, singing exclusively in native Brazilian Portuguese (PT-BR) accent, authentic Brazilian vocals, clear pronunciation`;

    // 2. Determinar o tamanho das letras baseado na duração (duracao)
    let lengthInstructions = 'curta, de apenas 6 a 8 versos';
    if (duracao === '1min') {
      lengthInstructions = 'média, de 12 a 16 versos (com verso 1, refrão, verso 2, refrão)';
    } else if (duracao === '2min') {
      lengthInstructions = 'completa e longa, de 24 a 32 versos (com estrutura completa: verso 1, refrão, verso 2, refrão, ponte e refrão final)';
    }

    // 3. Chamar a IA (MiniMax-M3) para compor a letra
    const lyricsPrompt = `Gere uma letra de cantiga escolar infantil sobre o tema: "${tema}".
A letra deve ser escrita para o público infantil (linguagem lúdica, alegre e educativa).
A estrutura de versos deve ser: ${lengthInstructions}.
Retorne APENAS a letra formatada em versos com quebras de linha. Não adicione notas, comentários ou títulos.`;

    const aiResponse = await callMinimax({
      system: "Você é um compositor pedagógico infantil que escreve letras de músicas educativas em português brasileiro. Retorne estritamente a letra da música, sem introduções ou observações extras.",
      messages: [{ role: 'user', content: lyricsPrompt }],
      temperature: 0.7,
      max_tokens: 1000
    });

    const lyrics = aiResponse.text ? aiResponse.text.trim() : '';
    if (!lyrics) {
      throw new Error('Falha ao compor a letra da música.');
    }

    // 4. Chamar a API da MiniMax para gerar a música (canto + melodia)
    const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
    const musicResp = await fetch(`${minmBase}/v1/music_generation`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        model: 'music-2.0',
        prompt: stylePrompt,
        lyrics: lyrics,
        audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' }
      })
    });

    if (!musicResp.ok) {
      const errText = await musicResp.text();
      throw new Error(`MiniMax music-2.0 respondeu erro: ${errText}`);
    }

    const musicData = await musicResp.json();
    const audioHex = musicData.data?.audio;
    if (!audioHex) {
      throw new Error('Nenhum dado de áudio retornado pela MiniMax.');
    }

    // 5. Salvar o arquivo localmente
    const fs = require('fs');
    const path = require('path');
    const mediaDir = path.join(__dirname, 'public', 'generated-media');
    if (!fs.existsSync(mediaDir)) {
      fs.mkdirSync(mediaDir, { recursive: true });
    }
    const fileName = `estudio_musica_${Date.now()}.mp3`;
    const filePath = path.join(mediaDir, fileName);
    const audioBuffer = Buffer.from(audioHex, 'hex');
    fs.writeFileSync(filePath, audioBuffer);
    await backupMediaFile(filePath, audioBuffer);
    const audioUrl = `/generated-media/${fileName}`;

    // 6. Retornar dados para o frontend
    const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
    const dbRes = await dbPool.query(
      `INSERT INTO escola.estudio_musicas (usuario_id, voz, ritmo, tema, letra, audio_url) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,
      [usuarioId, voz, ritmo, tema, lyrics, audioUrl]
    );

    res.json({
      success: true,
      id: dbRes.rows[0].id,
      lyrics: lyrics,
      audioUrl: audioUrl
    });

  } catch (error) {
    console.error('Erro no Estúdio Musical:', error);
    res.status(500).json({ error: error.message || 'Ocorreu um erro ao gerar a música' });
  }
});

// ============================================================
// ROTAS DE HISTÓRICO DO ESTÚDIO MUSICAL
// ============================================================
app.get('/api/music/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`SELECT id, voz, ritmo, tema, created_at FROM escola.estudio_musicas WHERE usuario_id = $1 ORDER BY created_at DESC`, [usuarioId]);
    res.json(dbRes.rows);
  } catch (err) { res.status(500).json({ error: 'Erro ao listar histórico.' }); }
});

app.get('/api/music/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`SELECT * FROM escola.estudio_musicas WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Não encontrada.' });
    res.json(dbRes.rows[0]);
  } catch (err) { res.status(500).json({ error: 'Erro ao buscar.' }); }
});

app.delete('/api/music/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    // Apagar também o arquivo físico se quiser
    const fileRes = await dbPool.query(`SELECT audio_url FROM escola.estudio_musicas WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    if (fileRes.rows.length > 0 && fileRes.rows[0].audio_url) {
      const fs = require('fs');
      const path = require('path');
      const filePath = path.join(__dirname, 'public', fileRes.rows[0].audio_url);
      if (fs.existsSync(filePath)) {
        fs.unlinkSync(filePath);
      }
    }

    await dbPool.query(`DELETE FROM escola.estudio_musicas WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    res.json({ success: true });
  } catch (err) { res.status(500).json({ error: 'Erro ao deletar.' }); }
});

// ============================================================
// ROTAS DO ESTÚDIO: FÁBRICA DE QUADRINHOS
// ============================================================

// 1. Gerar o roteiro (script) dos quadrinhos usando MiniMax-M3
app.post('/api/comics/generate-script', requireAuth, async (req, res) => {
  const { 
    tema, 
    quantidade, 
    baloes, 
    proporcao, 
    personagens, 
    cenario,
    existingPanels,
    character_description,
    continuationType 
  } = req.body;

  if (!tema) {
    return res.status(400).json({ error: 'O tema dos quadrinhos é obrigatório.' });
  }

  try {
    let prompt = '';
    if (existingPanels && Array.isArray(existingPanels) && existingPanels.length > 0) {
      const startPanelNum = continuationType === 'extend' ? existingPanels.length + 1 : 1;
      
      prompt = `Você está continuando ou se baseando em um projeto de história em quadrinhos existente.
DADOS DO PROJETO EXISTENTE:
- Descrição visual física e de estilo constante dos personagens (em inglês): "${character_description || ''}"
- Painéis já existentes (roteiro anterior):
${existingPanels.map(p => `  * Quadrinho ${p.panel_number || p.panelNumber}: [Visual] ${p.image_prompt || p.imagePrompt || 'N/A'} | [Fala/Legenda] ${p.dialogue}`).join('\n')}

INSTRUÇÕES DE GERAÇÃO:
Tipo de continuidade: ${continuationType === 'extend' ? 'Estender a mesma história (continuação cronológica direta, criando novos painéis sequenciais)' : 'Criar uma nova história separada (spin-off com os mesmos personagens, recomeçando do painel 1)'}.
Tema/Objetivo da nova fase: "${tema}".
Gere exatamente ${quantidade} novos quadrinhos.
Baloes de falas: ${baloes ? 'Sim, cada quadrinho deve conter um diálogo/fala extremamente conciso e curto, contendo no máximo 10 palavras no total.' : 'Não, a história é silenciosa, sem falas, apenas a descrição da cena'}.
Proporção visual: ${proporcao}.
Cenário/Local principal da nova fase: ${cenario}.

Gere um JSON perfeitamente válido contendo:
{
  "title": "${continuationType === 'extend' ? 'Manter o título original ou propor um subtítulo condizente' : 'Novo Título da História'}",
  "character_description": "${character_description || 'Descrição visual constante dos personagens em inglês'}",
  "panels": [
    {
      "panel_number": ${startPanelNum},
      "image_prompt": "Prompt visual detalhado em inglês para o gerador de imagens da MiniMax descrevendo a cena deste quadrinho. IMPORTANTE PARA CONSISTÊNCIA: Substitua TODOS os nomes de personagens por sua descrição física minuciosa em inglês (obtida de character_description) em cada quadrinho que aparecerem (ex: em vez de apenas escrever 'Lucas', escreva a descrição detalhada dele com roupas e cabelos específicos, etc). O estilo deve ser de ilustração de livro infantil de alta qualidade, 3D Pixar, traço limpo.",
      "dialogue": "Falas do quadrinho em português (ex: 'Lucas: Veja aquilo!') com NO MÁXIMO 10 palavras, ou narração curta caso não tenha balões."
    },
    ...
  ]
}

Responda APENAS o JSON válido, sem qualquer texto explicativo antes ou depois.`;
    } else {
      prompt = `Crie uma história em quadrinhos pedagógica infantil com o tema: "${tema}".
A história deve conter exatamente ${quantidade} quadrinhos.
Baloes de falas: ${baloes ? 'Sim, cada quadrinho deve conter um diálogo/fala extremamente conciso e curto, contendo no máximo 10 palavras no total.' : 'Não, a história é silenciosa, sem falas, apenas a descrição da cena'}.
Proporção visual: ${proporcao}.
Personagens participantes: ${JSON.stringify(personagens)}.
Cenário/Local principal: ${cenario}.

Gere um JSON perfeitamente válido contendo:
{
  "title": "Título da História",
  "character_description": "Descrição visual constante dos personagens em inglês (para usar em geradores de imagens, detalhando roupas específicas, cores, características físicas de forma fixa para manter a consistência).",
  "panels": [
    {
      "panel_number": 1,
      "image_prompt": "Prompt visual detalhado em inglês para o gerador de imagens da MiniMax descrevendo a cena deste quadrinho. IMPORTANTE PARA CONSISTÊNCIA: Substitua TODOS os nomes de personagens por sua descrição física minuciosa em inglês (do character_description) em cada quadrinho que aparecerem (ex: não escreva apenas 'Mariana', escreva a descrição dela detalhando cabelo e roupas de forma fixa e idêntica em todos os quadrinhos). O estilo deve ser de ilustração de livro infantil de alta qualidade, 3D Pixar, traço limpo.",
      "dialogue": "Falas do quadrinho em português (ex: 'Lucas: Veja aquilo!') com NO MÁXIMO 10 palavras, ou narração curta caso não tenha balões."
    },
    ...
  ]
}

Responda APENAS o JSON válido, sem qualquer texto explicativo antes ou depois.`;
    }

    const r = await callMinimax({
      system: "Você é um roteirista pedagógico especialista em histórias em quadrinhos infantis para educação básica. Você responde apenas com estruturas JSON válidas.",
      messages: [{ role: 'user', content: prompt }],
      temperature: 0.3,
      max_tokens: 3000
    });

    let jsonText = r.text;
    let scriptData;
    try {
      const firstBrace = jsonText.indexOf('{');
      const lastBrace = jsonText.lastIndexOf('}');
      if (firstBrace !== -1 && lastBrace !== -1) {
        const jsonString = jsonText.substring(firstBrace, lastBrace + 1);
        scriptData = JSON.parse(jsonString);
      } else {
        jsonText = jsonText.replace(/```json/gi, '').replace(/```/g, '').trim();
        scriptData = JSON.parse(jsonText);
      }
    } catch (parseErr) {
      console.error('Falha ao parsear JSON do roteiro de quadrinhos. Resposta original:', jsonText);
      throw new Error('Formato de resposta inválido retornado pela IA: ' + parseErr.message);
    }

    res.json(scriptData);
  } catch (error) {
    console.error('Erro ao gerar roteiro dos quadrinhos:', error);
    res.status(500).json({ error: 'Erro ao compor a história em quadrinhos: ' + error.message });
  }
});

// 2. Gerar uma imagem individual para um quadrinho usando MiniMax image-01 com Fallback Automático
app.post('/api/comics/generate-frame', requireAuth, async (req, res) => {
  const { prompt, proporcao } = req.body;

  if (!prompt) {
    return res.status(400).json({ error: 'O prompt de imagem é obrigatório.' });
  }

  const fs = require('fs');
  const path = require('path');
  const mediaDir = path.join(__dirname, 'public', 'generated-media');
  if (!fs.existsSync(mediaDir)) {
    fs.mkdirSync(mediaDir, { recursive: true });
  }

  const fileName = `quadrinho_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`;
  const filePath = path.join(mediaDir, fileName);

  let imageBuffer = null;
  let providerUsed = 'minimax';

  // Tentativa 1: MiniMax image-01 com timeout de 38s
  try {
    const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
    const apiKey = process.env.MINIMAX_API_KEY;

    if (apiKey) {
      const controller = new AbortController();
      const timeoutId = setTimeout(() => controller.abort(), 38000);

      const genResp = await fetch(`${minmBase}/v1/image_generation`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${apiKey}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model: 'image-01',
          prompt: prompt,
          n: 1
        }),
        signal: controller.signal
      });

      clearTimeout(timeoutId);

      if (genResp.ok) {
        const genData = await genResp.json();
        const imageUrl = genData.data?.image_urls?.[0];
        if (imageUrl) {
          const imgFetch = await fetch(imageUrl);
          if (imgFetch.ok) {
            imageBuffer = Buffer.from(await imgFetch.arrayBuffer());
          }
        }
      } else {
        const errText = await genResp.text();
        console.warn('[Comics Image] MiniMax retornou erro, acionando fallback inteligente:', errText);
      }
    }
  } catch (minimaxErr) {
    console.warn('[Comics Image] Falha/Timeout no MiniMax image-01 (' + minimaxErr.message + '), acionando gerador fallback...');
  }

  // Tentativa 2 (Fallback Resiliente Multi-Engine com Retries):
  if (!imageBuffer) {
    const is169 = proporcao === '16:9' || (prompt && prompt.includes('16:9'));
    const width = is169 ? 1280 : 1024;
    const height = is169 ? 720 : 768;
    const cleanPrompt = (prompt || 'cute children book illustration').replace(/[^\w\s,.-]/gi, '');
    const enhancedPrompt = encodeURIComponent(`${cleanPrompt}, 3d pixar disney style, digital children book illustration, vibrant colors, masterpiece, cinematic lighting`);
    
    const engines = ['flux', 'turbo'];

    for (const engine of engines) {
      if (imageBuffer) break;
      for (let attempt = 1; attempt <= 2; attempt++) {
        try {
          providerUsed = `pollinations-${engine}`;
          const seed = Math.floor(Math.random() * 1000000);
          const fallbackUrl = `https://image.pollinations.ai/prompt/${enhancedPrompt}?width=${width}&height=${height}&model=${engine}&nologo=true&seed=${seed}`;

          const controller = new AbortController();
          const timeoutId = setTimeout(() => controller.abort(), 25000);

          const fbResp = await fetch(fallbackUrl, { signal: controller.signal });
          clearTimeout(timeoutId);

          if (fbResp.ok) {
            imageBuffer = Buffer.from(await fbResp.arrayBuffer());
            console.log(`[Comics Image] Imagem gerada com sucesso via Fallback (${providerUsed}, tentativa ${attempt}).`);
            break;
          } else {
            console.warn(`[Comics Image] Fallback ${providerUsed} tentativa ${attempt} respondeu ${fbResp.status}`);
            if (fbResp.status === 429) {
              await new Promise(r => setTimeout(r, 700 * attempt));
            }
          }
        } catch (fbErr) {
          console.warn(`[Comics Image] Erro no fallback ${engine} tentativa ${attempt}:`, fbErr.message);
        }
      }
    }
  }

  if (!imageBuffer) {
    return res.status(500).json({ error: 'Não foi possível gerar a ilustração do quadro no momento. Por favor, tente novamente.' });
  }

  try {
    fs.writeFileSync(filePath, imageBuffer);
    
    // Backup assíncrono não bloqueante
    backupMediaFile(filePath, imageBuffer).catch(err => {
      console.warn('[Comics Backup] Falha em background no backup da imagem:', err.message);
    });

    res.json({ 
      imageUrl: `/generated-media/${fileName}`,
      provider: providerUsed
    });
  } catch (saveErr) {
    console.error('Erro ao salvar imagem localmente:', saveErr);
    res.status(500).json({ error: 'Erro ao persistir imagem gerada: ' + saveErr.message });
  }
});

// 3. Compilar a apresentação de vídeo usando FFmpeg a partir dos painéis de quadrinhos
app.post('/api/comics/generate-video', requireAuth, async (req, res) => {
  const { frames, musicSelection, frameDuration } = req.body;

  if (!frames || !Array.isArray(frames) || frames.length === 0) {
    return res.status(400).json({ error: 'Nenhum painel enviado para geração de vídeo.' });
  }

  const duration = frameDuration ? parseInt(frameDuration) : 5;
  const musicFile = musicSelection || 'alegre';

  try {
    const fs = require('fs');
    const path = require('path');
    const { exec } = require('child_process');

    const mediaDir = path.join(__dirname, 'public', 'generated-media');
    if (!fs.existsSync(mediaDir)) {
      fs.mkdirSync(mediaDir, { recursive: true });
    }

    // Mapear trilha musical
    let bgMusicPath = path.join(__dirname, 'public', 'assets', 'audio', 'alegre.mp3');
    if (musicFile === 'calma') {
      bgMusicPath = path.join(__dirname, 'public', 'assets', 'audio', 'calma.mp3');
    } else if (musicFile === 'aventura') {
      bgMusicPath = path.join(__dirname, 'public', 'assets', 'audio', 'aventura.mp3');
    }

    // Criar arquivo de input para o FFmpeg Concat
    const tempFileId = `${Date.now()}_${Math.floor(Math.random() * 1000)}`;
    const concatFilePath = path.join(__dirname, `concat_${tempFileId}.txt`);
    
    let concatContent = '';
    frames.forEach(frame => {
      const localFilePath = path.join(__dirname, 'public', frame.imageUrl);
      concatContent += `file '${localFilePath}'\nduration ${duration}\n`;
    });
    // O concat do FFmpeg requer repetir o último arquivo no final
    const lastLocalFilePath = path.join(__dirname, 'public', frames[frames.length - 1].imageUrl);
    concatContent += `file '${lastLocalFilePath}'\n`;

    fs.writeFileSync(concatFilePath, concatContent);

    const outputFileName = `video_quadrinhos_${tempFileId}.mp4`;
    const outputFilePath = path.join(mediaDir, outputFileName);
    const totalDuration = frames.length * duration;

    // Comando FFmpeg: concatena as imagens, adiciona o áudio, ajusta o codec para h264/aac e encerra na duração final
    const ffmpegCommand = `ffmpeg -y -f concat -safe 0 -i "${concatFilePath}" -i "${bgMusicPath}" -c:v libx264 -pix_fmt yuv420p -c:a aac -shortest -t ${totalDuration} "${outputFilePath}"`;

    console.log('[FFmpeg] Executando comando de vídeo:', ffmpegCommand);

    exec(ffmpegCommand, (error, stdout, stderr) => {
      // Remove o arquivo concat temporário
      try {
        fs.unlinkSync(concatFilePath);
      } catch (e) {
        console.error('Falha ao remover arquivo concat temporário:', e);
      }

      if (error) {
        console.error('Erro ao processar FFmpeg:', error, stderr);
        return res.status(500).json({ error: 'Erro ao gerar o vídeo da apresentação. Como o servidor foi atualizado e reiniciado recentemente, por favor crie/gere a história em quadrinhos novamente para recriar as imagens temporárias antes de exportar o vídeo.' });
      }

      // Backup do vídeo gerado no banco de dados em background
      (async () => {
        try {
          const videoBuffer = fs.readFileSync(outputFilePath);
          await backupMediaFile(outputFilePath, videoBuffer);
        } catch (backupErr) {
          console.error('[Media Backup] Erro ao fazer backup do vídeo do FFmpeg:', backupErr);
        }
      })();

      res.json({ videoUrl: `/generated-media/${outputFileName}` });
    });

  } catch (error) {
    console.error('Erro ao compilar vídeo de apresentação:', error);
    res.status(500).json({ error: 'Erro ao gerar o vídeo: ' + error.message });
  }
});

// --- ROTAS DE PROJETOS DE HQ (SALVAR, LISTAR, CARREGAR, EXCLUIR) ---

// 1. Listar projetos
app.get('/api/comics/projects', requireAuth, async (req, res) => {
  const usuarioId = req.userId || '00000000-0000-0000-0000-000000000000';
  try {
    const result = await dbPool.query(
      'SELECT id, titulo, tema, cenario, proporcao, character_description_global, character_description_english, created_at, updated_at FROM escola.projetos_comics WHERE usuario_id = $1 ORDER BY updated_at DESC;',
      [usuarioId]
    );
    res.json(result.rows || []);
  } catch (error) {
    console.error('Erro ao listar projetos de quadrinhos:', error.message);
    res.json([]);
  }
});

// 2. Carregar projeto específico
app.get('/api/comics/projects/:id', requireAuth, async (req, res) => {
  const usuarioId = req.userId || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;
  try {
    const projectRes = await dbPool.query(
      'SELECT * FROM escola.projetos_comics WHERE id = $1 AND usuario_id = $2;',
      [id, usuarioId]
    );
    if (projectRes.rows.length === 0) {
      return res.status(404).json({ error: 'Projeto não encontrado.' });
    }

    const panelsRes = await dbPool.query(
      'SELECT id, panel_number, image_url, image_prompt, dialogue FROM escola.comics_panels WHERE projeto_id = $1 ORDER BY panel_number ASC;',
      [id]
    );

    res.json({
      project: projectRes.rows[0],
      panels: panelsRes.rows || []
    });
  } catch (error) {
    console.error('Erro ao obter projeto de quadrinhos:', error);
    res.status(500).json({ error: 'Erro ao obter projeto de quadrinhos.' });
  }
});

// 3. Salvar / Atualizar projeto
app.post('/api/comics/projects', requireAuth, async (req, res) => {
  const usuarioId = req.userId || '00000000-0000-0000-0000-000000000000';
  const { id, titulo, tema, cenario, proporcao, character_description_global, character_description_english, panels } = req.body;

  if (!titulo) {
    return res.status(400).json({ error: 'O título do projeto é obrigatório.' });
  }

  let client = null;
  try {
    client = await dbPool.connect();
    await client.query('BEGIN');

    let projetoId = id;
    if (projetoId) {
      // Verificar propriedade
      const check = await client.query(
        'SELECT id FROM escola.projetos_comics WHERE id = $1 AND usuario_id = $2;',
        [projetoId, usuarioId]
      );
      if (check.rows.length === 0) {
        throw new Error('Projeto não encontrado para edição.');
      }

      // Atualizar metadados
      await client.query(
        `UPDATE escola.projetos_comics 
         SET titulo = $1, tema = $2, cenario = $3, proporcao = $4, character_description_global = $5, character_description_english = $6, updated_at = NOW() 
         WHERE id = $7;`,
        [titulo, tema, cenario, proporcao, character_description_global, character_description_english, projetoId]
      );
    } else {
      // Criar novo projeto
      const insertProj = await client.query(
        `INSERT INTO escola.projetos_comics (usuario_id, titulo, tema, cenario, proporcao, character_description_global, character_description_english) 
         VALUES ($1, $2, $3, $4, $5, $6, $7) 
         RETURNING id;`,
        [usuarioId, titulo, tema, cenario, proporcao, character_description_global, character_description_english]
      );
      projetoId = insertProj.rows[0].id;
    }

    // Atualizar painéis: remover antigos se existiam e salvar novos
    await client.query('DELETE FROM escola.comics_panels WHERE projeto_id = $1;', [projetoId]);

    if (panels && Array.isArray(panels)) {
      for (const p of panels) {
        await client.query(
          `INSERT INTO escola.comics_panels (projeto_id, panel_number, image_url, image_prompt, dialogue) 
           VALUES ($1, $2, $3, $4, $5);`,
          [projetoId, p.panel_number, p.image_url, p.image_prompt, p.dialogue]
        );
      }
    }

    await client.query('COMMIT');
    res.json({ success: true, id: projetoId, message: 'Projeto salvo com sucesso!' });
  } catch (error) {
    if (client) {
      try {
        await client.query('ROLLBACK');
      } catch (rbErr) {
        console.warn('Erro ao executar ROLLBACK:', rbErr.message);
      }
    }
    console.error('Erro ao salvar projeto de quadrinhos:', error);
    res.status(500).json({ error: 'Erro ao salvar projeto: ' + error.message });
  } finally {
    if (client) {
      client.release();
    }
  }
});

// 4. Excluir projeto
app.delete('/api/comics/projects/:id', requireAuth, async (req, res) => {
  const usuarioId = req.userId || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;
  try {
    const result = await dbPool.query(
      'DELETE FROM escola.projetos_comics WHERE id = $1 AND usuario_id = $2 RETURNING id;',
      [id, usuarioId]
    );
    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'Projeto não encontrado ou sem permissão para exclusão.' });
    }
    res.json({ success: true, message: 'Projeto excluído com sucesso!' });
  } catch (error) {
    console.error('Erro ao excluir projeto de quadrinhos:', error);
    res.status(500).json({ error: 'Erro ao excluir projeto de quadrinhos.' });
  }
});

// ============================================================
// ROTAS DO VIDEOMIND (TRANSCRIÇÃO E ANÁLISE DE VÍDEO)
// ============================================================
function getYouTubeId(url) {
  const regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=|live\/|shorts\/)([^#\&\?]*).*/;
  const match = url.match(regExp);
  return (match && match[2].length === 11) ? match[2] : null;
}

app.post('/api/videomind/analyze', requireAuth, async (req, res) => {
  const { url, lines, pastedTranscript } = req.body;
  
  if (!url) {
    return res.status(400).json({ error: 'A URL do vídeo é obrigatória.' });
  }
  
  const videoId = getYouTubeId(url);
  if (!videoId) {
    return res.status(400).json({ error: 'A URL inserida não é uma URL válida do YouTube.' });
  }
  
  const numLines = parseInt(lines) || 20;
  let transcriptText = '';
  let metadata = { 
    title: 'Vídeo do YouTube', 
    author: 'Desconhecido', 
    thumbnail: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg` 
  };
  
  try {
    // Tenta obter metadados via oEmbed oficial do YouTube
    const oEmbedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
    const oEmbedResp = await fetch(oEmbedUrl);
    if (oEmbedResp.ok) {
      const oEmbedData = await oEmbedResp.json();
      metadata.title = oEmbedData.title || metadata.title;
      metadata.author = oEmbedData.author_name || metadata.author;
      metadata.thumbnail = oEmbedData.thumbnail_url || metadata.thumbnail;
    }
  } catch (metaErr) {
    console.error('Erro ao buscar metadados do oEmbed:', metaErr);
  }
  
  let isFallbackMode = false;
  let systemNotice = '';
  
  if (pastedTranscript && pastedTranscript.trim().length > 10) {
    // Se o usuário colou a transcrição, ignorar a extração automática
    transcriptText = pastedTranscript.trim();
    systemNotice = `> 📝 **Nota do Sistema**: A análise abaixo foi elaborada com base na transcrição manual que você forneceu.\n\n`;
  } else {
    try {
      // Tenta obter legenda
      const transcriptObj = await YoutubeTranscript.fetchTranscript(videoId);
      transcriptText = transcriptObj.map(t => t.text).join(' ');
    } catch (transcErr) {
      console.warn(`[VideoMind] Erro ao obter legenda para o vídeo ${videoId}:`, transcErr.message);
      isFallbackMode = true;
      
      if (transcErr.message && transcErr.message.toLowerCase().includes('disabled')) {
        transcriptText = `[Aviso: O autor deste vídeo desativou as legendas ou elas ainda não foram geradas pelo YouTube. Por favor, analise e descreva os objetivos pedagógicos com base estritamente no título do vídeo, canal do autor e no seu vasto conhecimento pedagógico sobre o tema indicado no título]`;
        systemNotice = `> ⚠️ **Nota do Sistema**: Este vídeo não possui legendas disponíveis (foram desativadas pelo autor ou ainda não geradas pelo YouTube). A análise e o resumo abaixo foram elaborados a partir do título do vídeo, nome do canal e fundamentação pedagógica correspondente.\n\n`;
      } else {
        transcriptText = `[Aviso: Devido a restrições de conexão do YouTube, a legenda de áudio não pôde ser lida diretamente. Por favor, analise e descreva os objetivos pedagógicos com base estritamente no título do vídeo, canal do autor e no seu vasto conhecimento pedagógico sobre o tema indicado no título]`;
        systemNotice = `> ⚠️ **Nota do Sistema**: Devido a restrições de conexão do YouTube ou indisponibilidade temporária, a transcrição não pôde ser lida. A análise e o resumo abaixo foram elaborados a partir do título do vídeo, canal do autor e fundamentação psicopedagógica correspondente.\n\n`;
      }
    }
  }
  
  try {
    const prompt = `Analise este vídeo do YouTube intitulado "${metadata.title}" (Canal: ${metadata.author}):
ID do Vídeo no YouTube: ${videoId}
Link do vídeo: https://www.youtube.com/watch?v=${videoId}

Conteúdo ou descrição da transcrição:
"""
${transcriptText}
"""

Instruções para o conteúdo de saída:
1. Escreva um RESUMO detalhado do vídeo. Esse resumo precisa ter EXATAMENTE ou APROXIMADAMENTE ${numLines} linhas de texto. Se a transcrição direta não estiver disponível, faça um resumo conceitual com base no título e no tema geral do vídeo com a mesma quantidade de linhas.
2. Faça uma ANÁLISE PEDAGÓGICA COMPLEMENTAR detalhando:
   - Relevância do conteúdo para fins educacionais.
   - Indicação de faixa etária/segmento dos alunos recomendado.
   - Conexão direta com a BNCC (mencione os códigos de competências, habilidades ou campos de experiência correspondentes).
   - Sugestões práticas de aplicação em sala de aula (atividades pedagógicas, dinâmicas de grupo ou perguntas para debate com os alunos).

Retorne a resposta estruturada estritamente em Markdown. Separe as seções usando títulos claros:
# Resumo Pedagógico (${numLines} Linhas)
[Insira o resumo aqui, ocupando aproximadamente ${numLines} linhas de texto]

# Análise Pedagógica e Sugestões Práticas
[Insira a análise pedagógica, os pontos da BNCC e as sugestões de atividades práticas aqui]`;

    let reportText = '';
    const systemInstruction = "Você é um pedagogo experiente e especialista em planejamento de aulas no Brasil, alinhado à Base Nacional Comum Curricular (BNCC).";

    // Se estiver em fallback, damos preferência ao Gemini pela capacidade de grounding e busca
    if (isFallbackMode && process.env.GOOGLE_AI_API_KEY) {
      try {
        console.log('[VideoMind] Usando fallback com Google Gemini...');
        const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
        const response = await fetch(geminiUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            contents: [{ parts: [{ text: `${systemInstruction}\n\nTarefa:\n${prompt}` }] }],
            generationConfig: { temperature: 0.5, maxOutputTokens: 4000 }
          })
        });
        
        if (response.ok) {
          const data = await response.json();
          reportText = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
        } else {
          const errTxt = await response.text();
          console.warn('[VideoMind] Fallback do Gemini respondeu com erro:', errTxt);
        }
      } catch (geminiErr) {
        console.error('[VideoMind] Erro no fallback do Gemini:', geminiErr.message);
      }
    }

    // Se o Gemini não foi usado ou falhou, usamos o callMinimax padrão
    if (!reportText) {
      const aiResponse = await callMinimax({
        system: systemInstruction,
        messages: [{ role: 'user', content: prompt }],
        temperature: 0.5,
        max_tokens: 3000
      });
      reportText = aiResponse.text || '';
    }
    
    res.json({
      success: true,
      metadata,
      report: systemNotice + reportText
    });
  } catch (err) {
    console.error('Erro ao processar análise do VideoMind:', err);
    res.status(500).json({ error: 'Erro ao gerar análise com a IA: ' + err.message });
  }
});

// ============================================================
// ROTAS DO GARATUJAS (ANALISADOR DE TRAÇADO INFANTIL)
// ============================================================
app.post('/api/garatujas/analyze', requireAuth, multerUpload.single('image'), async (req, res) => {
  const { alunoName, alunoIdade } = req.body;
  const file = req.file;
  
  if (!alunoName) {
    return res.status(400).json({ error: 'O nome do aluno é obrigatório.' });
  }
  if (!file) {
    return res.status(400).json({ error: 'A foto do desenho é obrigatória.' });
  }
  
  try {
    const fs = require('fs');
    const mediaDir = path.join(__dirname, 'public', 'generated-media');
    if (!fs.existsSync(mediaDir)) {
      fs.mkdirSync(mediaDir, { recursive: true });
    }
    
    const ext = path.extname(file.originalname) || '.png';
    const fileName = `garatuja_${Date.now()}${ext}`;
    const filePath = path.join(mediaDir, fileName);
    fs.writeFileSync(filePath, file.buffer);
    await backupMediaFile(filePath, file.buffer);
    const imageUrl = `/generated-media/${fileName}`;
    
    const base64Image = file.buffer.toString('base64');
    const mimeType = file.mimetype || 'image/png';
    
    let analise = '';
    
    if (process.env.GOOGLE_AI_API_KEY) {
      console.log('[Garatujas] Usando API direta do Google Gemini para análise de imagem...');
      const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
      
      const idadeContexto = alunoIdade ? `de ${alunoIdade} de idade` : '(até 6 anos de idade)';

      const promptText = `Você é um psicopedagogo e especialista em desenvolvimento infantil no Brasil.
Analise a imagem anexada, que é um desenho/rabisco feito pelo aluno(a) "${alunoName}" ${idadeContexto}.

Identifique visualmente os padrões dos traços, uso do espaço, pressão do lápis (se perceptível) e formas criadas. Descreva com base na literatura científica (como Piaget, Luquet, Lowenfeld):
1. O estágio exato do desenho infantil em que a criança se encontra (ex: Garatuja Desordenada, Garatuja Ordenada, Pré-Esquematismo, etc.) justificando com os elementos visuais que você está vendo na imagem. Lembre-se que a criança tem ${idadeContexto}.
2. A análise do desenvolvimento cognitivo e da coordenação motora fina expressa nos traços vistos na imagem.
3. Propostas práticas de estimulação pedagógica personalizadas para o professor aplicar com o aluno "${alunoName}" em sala de aula para avançar para o próximo estágio.

Retorne a resposta estruturada em Markdown, usando títulos claros:
# Estágio do Desenho e Análise Psicomotora
[Sua análise baseada na imagem aqui]

# Diagnóstico de Desenvolvimento Cognitivo
[Seu diagnóstico aqui]

# Sugestões de Atividades para Estimular ${alunoName}
[Suas propostas aqui]`;

      const response = await fetch(geminiUrl, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          contents: [
            {
              parts: [
                { text: promptText },
                {
                  inlineData: {
                    mimeType: mimeType,
                    data: base64Image
                  }
                }
              ]
            }
          ],
          generationConfig: {
            temperature: 0.4,
            maxOutputTokens: 8192
          }
        })
      });

      if (!response.ok) {
        const errText = await response.text();
        throw new Error(`Erro na API oficial do Gemini: ${errText}`);
      }

      const resData = await response.json();
      analise = resData.candidates?.[0]?.content?.parts?.[0]?.text || '';
    } else {
      throw new Error('Chave GOOGLE_AI_API_KEY não configurada no servidor.');
    }
    
    const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
    const dbRes = await dbPool.query(
      `INSERT INTO escola.garatujas (usuario_id, aluno_name, image_url, analise) 
       VALUES ($1, $2, $3, $4) RETURNING id, created_at`,
      [usuarioId, alunoName, imageUrl, analise]
    );
    
    res.json({
      success: true,
      id: dbRes.rows[0].id,
      alunoName,
      imageUrl,
      analise,
      createdAt: dbRes.rows[0].created_at
    });
    
  } catch (err) {
    console.error('Erro na análise de garatujas:', err);
    res.status(500).json({ error: 'Erro ao analisar imagem: ' + err.message });
  }
});

app.get('/api/garatujas/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, aluno_name as "alunoName", image_url as "imageUrl", created_at as "createdAt" 
       FROM escola.garatujas 
       WHERE usuario_id = $1 
       ORDER BY created_at DESC`,
      [usuarioId]
    );
    res.json(dbRes.rows);
  } catch (err) {
    console.error('Erro ao listar garatujas:', err);
    res.status(500).json({ error: 'Erro ao obter histórico de desenhos.' });
  }
});

app.get('/api/garatujas/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, aluno_name as "alunoName", image_url as "imageUrl", analise, created_at as "createdAt" 
       FROM escola.garatujas 
       WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rows.length === 0) {
      return res.status(404).json({ error: 'Análise não encontrada.' });
    }
    res.json(dbRes.rows[0]);
  } catch (err) {
    console.error('Erro ao buscar garatuja:', err);
    res.status(500).json({ error: 'Erro ao buscar análise.' });
  }
});

app.delete('/api/garatujas/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const imageRes = await dbPool.query(
      `SELECT image_url FROM escola.garatujas WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );

    const dbRes = await dbPool.query(
      `DELETE FROM escola.garatujas WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rowCount === 0) {
      return res.status(404).json({ error: 'Análise não encontrada ou não pertence ao usuário.' });
    }

    if (imageRes.rows.length > 0 && imageRes.rows[0].image_url) {
      const imgUrl = imageRes.rows[0].image_url;
      if (imgUrl.startsWith('/generated-media/')) {
        const fs = require('fs');
        const filename = imgUrl.replace('/generated-media/', '');
        const filepath = path.join(__dirname, 'public', 'generated-media', filename);
        if (fs.existsSync(filepath)) {
          try {
            fs.unlinkSync(filepath);
          } catch (unlinkErr) {
            console.error('Erro ao deletar arquivo de imagem fisicamente:', unlinkErr);
          }
        }
      }
    }

    res.json({ success: true, message: 'Análise de desenho deletada com sucesso.' });
  } catch (err) {
    console.error('Erro ao deletar garatuja:', err);
    res.status(500).json({ error: 'Erro ao deletar análise.' });
  }
});


// ============================================================
// ROTAS DO BRINCARATIVO (CIRCUITOS PSICOMOTORES E BRINCADEIRAS)
// ============================================================
app.post('/api/brincar-ativo/generate', requireAuth, async (req, res) => {
  const { faixaEtaria, recursos } = req.body;
  
  if (!faixaEtaria) {
    return res.status(400).json({ error: 'A faixa etária é obrigatória.' });
  }
  if (!recursos) {
    return res.status(400).json({ error: 'A descrição dos recursos é obrigatória.' });
  }
  
  try {
    const prompt = `Você é um educador físico e pedagogo especializado na Educação Infantil (crianças de 0 a 6 anos) de acordo com a BNCC no Brasil.
Gere uma proposta de circuito psicomotor ou brincadeira ativa adequada para a seguinte faixa etária e com os recursos disponíveis:

Faixa Etária: ${faixaEtaria}
Recursos e Materiais Disponíveis: ${recursos}

Instruções para o conteúdo:
1. Dê um título lúdico e atraente para a atividade.
2. Especifique os Objetivos de Aprendizagem e Desenvolvimento da BNCC (incluindo códigos, como EI01CG02, EI02CG05, etc.).
3. Descreva o Passo a Passo detalhado de como montar o circuito/brincadeira no espaço e como guiar as crianças.
4. Diga o que o professor deve observar durante a atividade para avaliar o desenvolvimento psicomotor dos alunos.

Retorne a resposta estritamente em Markdown estruturado:
# Atividade: [Nome Lúdico]
**Faixa Etária:** ${faixaEtaria}
**Campos de Experiência (BNCC):** [Campos e Códigos]

## 📋 Objetivos
[Objetivos]

## 🛠️ Como Montar e Conduzir
[Passo a passo]

## 🔍 O que Observar (Avaliação Formativa)
[Dicas de observação]`;

    const aiResponse = await callMinimax({
      system: "Você é um especialista em educação física e psicomotricidade na educação infantil brasileira.",
      messages: [{ role: 'user', content: prompt }],
      temperature: 0.5,
      max_tokens: 3000
    });
    
    const atividade = aiResponse.text || '';
    
    const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
    const dbRes = await dbPool.query(
      `INSERT INTO escola.brincar_ativo (usuario_id, faixa_etaria, recursos, atividade) 
       VALUES ($1, $2, $3, $4) RETURNING id, created_at`,
      [usuarioId, faixaEtaria, recursos, atividade]
    );
    
    res.json({
      success: true,
      id: dbRes.rows[0].id,
      faixaEtaria,
      recursos,
      atividade,
      createdAt: dbRes.rows[0].created_at
    });
  } catch (err) {
    console.error('Erro no BrincarAtivo:', err);
    res.status(500).json({ error: 'Erro ao gerar atividade com a IA: ' + err.message });
  }
});

app.get('/api/brincar-ativo/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, faixa_etaria as "faixaEtaria", recursos, created_at as "createdAt" 
       FROM escola.brincar_ativo 
       WHERE usuario_id = $1 
       ORDER BY created_at DESC`,
      [usuarioId]
    );
    res.json(dbRes.rows);
  } catch (err) {
    console.error('Erro ao listar atividades:', err);
    res.status(500).json({ error: 'Erro ao obter lista de atividades.' });
  }
});

app.get('/api/brincar-ativo/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, faixa_etaria as "faixaEtaria", recursos, atividade, created_at as "createdAt" 
       FROM escola.brincar_ativo 
       WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rows.length === 0) {
      return res.status(404).json({ error: 'Atividade não encontrada.' });
    }
    res.json(dbRes.rows[0]);
  } catch (err) {
    console.error('Erro ao buscar atividade:', err);
    res.status(500).json({ error: 'Erro ao buscar atividade.' });
  }
});

app.delete('/api/brincar-ativo/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `DELETE FROM escola.brincar_ativo WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rowCount === 0) {
      return res.status(404).json({ error: 'Atividade não encontrada ou não pertence ao usuário.' });
    }
    res.json({ success: true, message: 'Atividade deletada com sucesso.' });
  } catch (err) {
    console.error('Erro ao deletar brincar-ativo:', err);
    res.status(500).json({ error: 'Erro ao deletar atividade.' });
  }
});

// ============================================================
// ============================================================
// ROTAS DO ESTÚDIO DE POESIAS (ESTÚDIO PROFISSIONAL)
// ============================================================
app.post('/api/poesias/generate', requireAuth, async (req, res) => {
  const { faixaEtaria, estrofes, tema, estrutura, estilo, voz, turmaId, gerarAudio } = req.body;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';

  if (!tema) {
    return res.status(400).json({ error: 'O tema da poesia é obrigatório.' });
  }

  try {
    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";

    const promptPoesia = `Você é a ${agentName}, poetisa, linguista e assistente pedagógica da professora Camila Martella Gasparini Reifonas.
A professora Camila deseja compor uma poesia pedagógica para seus alunos na Educação Infantil.

PARÂMETROS DA POESIA:
- Tema / Ideia Central: "${tema}"
- Faixa Etária: "${faixaEtaria || 'Crianças pequenas (4 a 5 anos)'}"
- Quantidade de Estrofes: ${estrofes || 3}
- Estrutura Poética: "${estrutura || 'Quadras (Rimas A-B-A-B)'}"
- Estilo Pedagógico: "${estilo || 'Lúdico-cantado e sonoro'}"

INSTRUÇÕES DE COMPOSIÇÃO:
1. Componha uma poesia envolvente, com ritmo marcante, musicalidade e rimas expressivas adequadas para a idade informada.
2. Realize uma análise literária detalhada com estimativas percentuais (0-100%) para:
   - Rima (precisão das terminações sonoras)
   - Ritmo (cadência e compasso dos versos)
   - Aliteração (repetição harmoniosa de sons consonantais)
   - Musicalidade (sonoridade geral)
3. Crie uma versão com guia fonético / marcação de leitura (com acentos tônicos em negrito e marcações de [pausa] para a leitura em voz alta).
4. Mapeie o campo de experiência da BNCC e as habilidades trabalhadas (ex: EI02EF01 / EI03EF02 - Escuta, Fala, Pensamento e Imaginação).
5. Forneça uma vivência pedagógica prática (dica para a professora Camila conduzir sarau, gestos ou dinâmicas).

Retorne ESTRITAMENTE um JSON válido (sem texto fora do JSON):
{
  "titulo": "Título Criativo do Poema",
  "poesia": "Texto completo da poesia com quebras de linha e separação de estrofes.",
  "esquemaRima": "A-B-A-B",
  "analiseLiteraria": {
    "rima": 85,
    "ritmo": 80,
    "aliteracao": 65,
    "musicalidade": 90,
    "explicacaoMetrica": "Versos regulares com cadência agradável, rimas cruzadas e sonoridade envolvente para contação de histórias."
  },
  "versaoFonetica": "Versão da poesia com sílabas tônicas marcadas e [pausa] para orientação de leitura em sala de aula.",
  "bnccCampos": "EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação",
  "dicasPedagogicas": "Sugestão de 2 a 3 linhas para a professora aplicar com as crianças em sala."
}`;

    let jsonResponseText = '';

    // 1. Tentar MiniMax M3
    try {
      console.log('[Poesia] Compondo poesia com MiniMax M3...');
      const r = await callMinimax({
        system: "Você é uma poetisa infantil e linguista pedagógica de excelência no Brasil. Responda estritamente em formato JSON válido.",
        messages: [{ role: 'user', content: promptPoesia }],
        temperature: 0.7,
        max_tokens: 1800
      });
      jsonResponseText = r.text;
    } catch (err) {
      console.error('[Poesia] Erro MiniMax M3:', err.message);
    }

    // 2. Fallback Groq
    if (!jsonResponseText && process.env.GROQ_API_KEY) {
      try {
        console.log('[Poesia] Fallback Groq...');
        const r = await fetch('https://api.groq.com/openai/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GROQ_MODEL || 'openai/gpt-oss-120b',
            messages: [{ role: 'user', content: promptPoesia }],
            temperature: 0.7,
            max_tokens: 1800,
            response_format: { type: 'json_object' }
          })
        });
        if (r.ok) {
          const data = await r.json();
          jsonResponseText = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Poesia] Erro Groq:', err.message);
      }
    }

    // 3. Fallback OpenRouter
    if (!jsonResponseText && process.env.OPENROUTER_API_KEY) {
      try {
        console.log('[Poesia] Fallback OpenRouter...');
        const r = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
            messages: [{ role: 'user', content: promptPoesia }],
            temperature: 0.7,
            max_tokens: 1800
          })
        });
        if (r.ok) {
          const data = await r.json();
          jsonResponseText = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Poesia] Erro OpenRouter:', err.message);
      }
    }

    // 4. Fallback Google Gemini
    if (!jsonResponseText && process.env.GOOGLE_AI_API_KEY) {
      try {
        console.log('[Poesia] Fallback Gemini...');
        const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
        const response = await fetch(geminiUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            contents: [{ parts: [{ text: promptPoesia }] }],
            generationConfig: { temperature: 0.7, maxOutputTokens: 2000 }
          })
        });
        if (response.ok) {
          const resData = await response.json();
          jsonResponseText = resData.candidates?.[0]?.content?.parts?.[0]?.text || '';
        }
      } catch (err) {
        console.error('[Poesia] Erro Gemini:', err.message);
      }
    }

    // Parse JSON
    let poesiaData = {};
    try {
      const cleanJson = jsonResponseText.replace(/```json|```/g, '').trim();
      poesiaData = JSON.parse(cleanJson);
    } catch (parseErr) {
      console.warn('[Poesia] Erro parse JSON, usando fallback texto:', parseErr.message);
      poesiaData = {
        titulo: `Poema: ${tema}`,
        poesia: jsonResponseText || `Na escola a gente brinca,\nNa escola a gente aprende,\nCom ${tema} todo dia,\nNosso coração se acende!`,
        esquemaRima: "A-B-C-B",
        analiseLiteraria: {
          rima: 80,
          ritmo: 75,
          aliteracao: 60,
          musicalidade: 85,
          explicacaoMetrica: "Métrica ritmada ideal para contação infantil e memorização em roda."
        },
        versaoFonetica: `Na es**co**la a gente **brin**ca, [pausa]\nNa es**co**la a gente a**pren**de, [pausa]\nCom ${tema} todo **di**a, [pausa]\nNosso cora**ção** se a**cen**de!`,
        bnccCampos: "EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação",
        dicasPedagogicas: "Faça gestos de mãos no peito e palmas suaves ao recitar as rimas finais."
      };
    }

    // GERAÇÃO DE ÁUDIO TTS / RECITADO (MiniMax)
    let audioUrl = null;
    if (gerarAudio !== false && process.env.MINIMAX_API_KEY) {
      try {
        console.log('[Poesia] Gerando áudio recitado com MiniMax...');
        let voiceDesc = 'sweet gentle native brazilian female teacher voice reciting warm nursery poem, calm clear tempo';
        if (voz === 'homem') {
          voiceDesc = 'warm friendly native brazilian male educator voice storytelling poem, soft and clear';
        } else if (voz === 'crianca') {
          voiceDesc = 'cute enthusiastic young brazilian child voice reciting poetry, joyful';
        }

        const audioPrompt = `Spoken poetry recitation, ${voiceDesc}, clear pronunciation, acoustic ambient background`;
        const lyricsToRecite = (poesiaData.poesia || tema).replace(/[*#]/g, '').trim();

        const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
        const musicResp = await fetch(`${minmBase}/v1/music_generation`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'music-2.0',
            prompt: audioPrompt,
            lyrics: lyricsToRecite.substring(0, 500),
            audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' }
          })
        });

        if (musicResp.ok) {
          const mData = await musicResp.json();
          const audioHex = mData.data?.audio;
          if (audioHex) {
            const fs = require('fs');
            const path = require('path');
            const mediaDir = path.join(__dirname, 'public', 'generated-media');
            if (!fs.existsSync(mediaDir)) {
              fs.mkdirSync(mediaDir, { recursive: true });
            }
            const fileName = `poesia_${Date.now()}.mp3`;
            const filePath = path.join(mediaDir, fileName);
            const audioBuffer = Buffer.from(audioHex, 'hex');
            fs.writeFileSync(filePath, audioBuffer);
            await backupMediaFile(filePath, audioBuffer);
            audioUrl = `/generated-media/${fileName}`;
          }
        }
      } catch (audioErr) {
        console.error('[Poesia] Erro ao gerar áudio recitado:', audioErr.message);
      }
    }

    // Salvar no Banco PostgreSQL
    let savedId = null;
    try {
      const dbRes = await dbPool.query(
        `INSERT INTO escola.poesias (
          usuario_id, faixa_etaria, estrofes, tema, poesia, 
          estrutura, estilo, audio_url, analise_metrica, versao_fonetica, bncc, turma_id, dicas
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id`,
        [
          usuarioId,
          faixaEtaria || 'Crianças pequenas',
          parseInt(estrofes) || 3,
          tema,
          poesiaData.poesia || '',
          estrutura || 'Quadras',
          estilo || 'Lúdico-cantado',
          audioUrl,
          JSON.stringify(poesiaData.analiseLiteraria || {}),
          poesiaData.versaoFonetica || '',
          poesiaData.bnccCampos || '',
          turmaId || null,
          poesiaData.dicasPedagogicas || ''
        ]
      );
      savedId = dbRes.rows[0]?.id;
    } catch (dbErr) {
      console.warn('[Poesia] Erro ao salvar colunas estendidas, tentando salvar básico:', dbErr.message);
      try {
        const dbRes2 = await dbPool.query(
          `INSERT INTO escola.poesias (usuario_id, faixa_etaria, estrofes, tema, poesia) VALUES ($1, $2, $3, $4, $5) RETURNING id`,
          [usuarioId, faixaEtaria || 'Crianças', parseInt(estrofes) || 3, tema, poesiaData.poesia || '']
        );
        savedId = dbRes2.rows[0]?.id;
      } catch (err2) {
        console.error('[Poesia] Erro ao salvar poesia:', err2.message);
      }
    }

    res.json({
      success: true,
      id: savedId,
      titulo: poesiaData.titulo || `Poema: ${tema}`,
      poesia: poesiaData.poesia || '',
      esquemaRima: poesiaData.esquemaRima || 'A-B-A-B',
      analiseLiteraria: poesiaData.analiseLiteraria || { rima: 80, ritmo: 80, aliteracao: 60, musicalidade: 85 },
      versaoFonetica: poesiaData.versaoFonetica || '',
      bnccCampos: poesiaData.bnccCampos || 'EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação',
      dicasPedagogicas: poesiaData.dicasPedagogicas || '',
      audioUrl: audioUrl,
      tema: tema,
      faixaEtaria: faixaEtaria,
      estrutura: estrutura,
      estilo: estilo
    });

  } catch (err) {
    console.error('Erro geral no Estúdio de Poesia:', err);
    res.status(500).json({ error: 'Erro ao compor poesia: ' + err.message });
  }
});

app.get('/api/poesias/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, faixa_etaria, estrofes, tema, audio_url, created_at FROM escola.poesias WHERE usuario_id = $1 ORDER BY created_at DESC`,
      [usuarioId]
    );
    res.json(dbRes.rows);
  } catch (err) {
    try {
      const dbRes2 = await dbPool.query(
        `SELECT id, faixa_etaria, estrofes, tema, created_at FROM escola.poesias WHERE usuario_id = $1 ORDER BY created_at DESC`,
        [usuarioId]
      );
      res.json(dbRes2.rows);
    } catch (err2) {
      res.status(500).json({ error: 'Erro ao listar histórico de poesias.' });
    }
  }
});

app.get('/api/poesias/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT * FROM escola.poesias WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rows.length === 0) {
      return res.status(404).json({ error: 'Poesia não encontrada.' });
    }
    res.json(dbRes.rows[0]);
  } catch (err) {
    res.status(500).json({ error: 'Erro ao buscar poesia.' });
  }
});

app.delete('/api/poesias/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `DELETE FROM escola.poesias WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rowCount === 0) {
      return res.status(404).json({ error: 'Poesia não encontrada.' });
    }
    res.json({ success: true, message: 'Poesia deletada com sucesso.' });
  } catch (err) {
    res.status(500).json({ error: 'Erro ao deletar poesia.' });
  }
});

// ============================================================
// ROTAS DO CRIAR CARTAZES
// ============================================================
app.post('/api/cartazes/generate', requireAuth, async (req, res) => {
  const { titulo, tema, corFundo, corTexto, layout, promptImagem, tamanho, orientacao } = req.body;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';

  if (!titulo || !tema || !layout) {
    return res.status(400).json({ error: 'Título, tema e layout são obrigatórios.' });
  }

  try {
    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";

    // 1. Gerar Conteúdo do Cartaz
    const posterPrompt = `Você é a ${agentName}, assistente pedagógica da professora Camila Martella Gasparini Reifonas.
A professora Camila quer criar um Cartaz para sala de aula da Educação Infantil.

Detalhes do Cartaz:
- Título: "${titulo}"
- Tema / Foco Pedagógico: "${tema}"
- Tipo de Cartaz: "${layout}" (educativo, informativo, conscientizacao, cultural)
- Tamanho: "${tamanho || 'A4'}"
- Orientação: "${orientacao === 'paisagem' ? 'Paisagem/Horizontal' : 'Retrato/Vertical'}"

Por favor, gere o CONTEÚDO textual e estrutural do cartaz em HTML. 

REGRAS DE IDIOMA E LOCALIZAÇÃO:
- O conteúdo gerado deve ser IMPRETERIVELMENTE em português do Brasil (pt-BR). Não use termos em outros idiomas de forma desnecessária.

REGRAS FUNDAMENTAIS E ESTÉTICAS DE DIAGRAMAÇÃO:
- O cartaz deve caber ESTRITAMENTE em UMA ÚNICA PÁGINA FÍSICA (Tamanho ${tamanho || 'A4'}, orientação ${orientacao === 'paisagem' ? 'Horizontal/Paisagem' : 'Vertical/Retrato'}). Não extrapole este espaço de forma alguma.
- Limite a quantidade de conteúdo: gere no máximo 2 ou 3 cards (divs de conteúdo) no total. Cada card deve ter pouquíssimo texto (no máximo 3 ou 4 itens curtos por card).
- Distribua a informação em CARDS (pequenos cartões ou blocos curtos) bem espaçados, parecidos com pôsteres infantis modernos, onde o texto NÃO ocupa uma folha corrida.
- Os textos devem ser extremamente curtos, diretos, visuais e fáceis de ler.
- Use emojis apropriados para crianças.
- Se for "educativo", estruture o conteúdo pedagógico de forma visual (ex: caixas para alfabeto, tabuada, regras gramaticais).
- Se for "informativo", destaque avisos gerais e orientações (ex: horários, cardápio, provas) em caixas curtas e legíveis.
- Se for "conscientizacao", use frases curtas de impacto sobre campanhas (ex: prevenção, saúde, coleta seletiva), acompanhadas de dicas em pequenos blocos.
- Se for "cultural", promova a atividade de lazer, esporte ou feira destacando o evento de forma animada e pontual.
- Considere a orientação ${orientacao === 'paisagem' ? 'Paisagem (horizontal)' : 'Retrato (vertical)'} ao organizar o fluxo: para paisagem, os cards devem ficar lado a lado (2 colunas horizontais); para retrato, eles se empilham verticalmente.
- Retorne apenas o formato HTML (use <div>, <p>, <ul>, <li>, <strong>, <span>). Não adicione <html> ou <body>, retorne apenas o conteúdo interno. Não inclua o título <h1> pois ele já é colocado no topo pela interface.`;

    let conteudoHtml = '';
    
    // Tentar MiniMax M3
    try {
      console.log('[Cartazes] Gerando conteúdo com MiniMax...');
      const result = await callMinimax({
        messages: [{ role: 'user', content: posterPrompt }],
        temperature: 0.7,
        max_tokens: 1500
      });
      conteudoHtml = result.text;
    } catch (err) {
      console.error('[Cartazes] Erro MiniMax:', err.message);
    }

    // Fallback OpenRouter
    if (!conteudoHtml && process.env.OPENROUTER_API_KEY) {
      try {
        console.log('[Cartazes] Fallback OpenRouter...');
        const r = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
            messages: [{ role: 'user', content: posterPrompt }],
            temperature: 0.7,
            max_tokens: 1500
          })
        });
        if (r.ok) {
          const data = await r.json();
          conteudoHtml = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Cartazes] Erro OpenRouter:', err.message);
      }
    }

    if (!conteudoHtml) {
      conteudoHtml = `<h2>${titulo}</h2><p>Tema: ${tema}</p><p>Não foi possível gerar com IA. Edite o conteúdo livremente.</p>`;
    }

    // Clean HTML from backticks
    conteudoHtml = conteudoHtml.replace(/```html|```/g, '').trim();

    // 2. Gerar Imagem Ilustrativa se solicitado
    let generatedImageUrl = null;
    if (promptImagem && promptImagem.trim().length > 3) {
      try {
        console.log('[Cartazes] Refinando prompt de imagem para inglês (sem texto)...');
        let refinedPrompt = promptImagem;
        try {
          const refineRes = await callMinimax({
            messages: [{
              role: 'user',
              content: `Translate the following Portuguese description into a clean English description suitable for an image generation prompt. 
Make sure the prompt specifies a beautiful, colorful, kid-friendly vector/illustration style. 
CRITICAL REQUIREMENT: Explicitly specify in the prompt that there should be absolutely no text, no words, no signs, no letters, no labels, no speech bubbles, and no writing of any kind.
Portuguese description: "${promptImagem}"
Return ONLY the English prompt, no extra chat.`
            }],
            temperature: 0.3,
            max_tokens: 250
          });
          if (refineRes && refineRes.text) {
            refinedPrompt = refineRes.text;
          }
        } catch (refineErr) {
          console.error('[Cartazes] Erro ao refinar prompt:', refineErr);
        }

        console.log('[Cartazes] Gerando ilustração com prompt final:', refinedPrompt);
        const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
        const genResp = await fetch(`${minmBase}/v1/image_generation`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'image-01',
            prompt: `${refinedPrompt}. Complete figures shown from top to bottom without cutoffs, kid-friendly illustration style, 3D Pixar, soft pastel colors, no text, no words, no letters, no writing, no labels.`,
            n: 1
          })
        });

        if (genResp.ok) {
          const genData = await genResp.json();
          const rawImgUrl = genData.data?.image_urls?.[0];
          if (rawImgUrl) {
            const mediaDir = path.join(__dirname, 'public', 'generated-media');
            if (!fs.existsSync(mediaDir)) fs.mkdirSync(mediaDir, { recursive: true });
            
            const imgFetch = await fetch(rawImgUrl);
            if (imgFetch.ok) {
              const imgBuffer = Buffer.from(await imgFetch.arrayBuffer());
              const fileName = `cartaz_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`;
              const filePath = path.join(mediaDir, fileName);
              fs.writeFileSync(filePath, imgBuffer);
              await backupMediaFile(filePath, imgBuffer);
              generatedImageUrl = `/generated-media/${fileName}`;
            }
          }
        }
      } catch (imgErr) {
        console.error('[Cartazes] Erro na geração de imagem:', imgErr);
      }
    }

    // 3. Salvar no Banco
    const dbRes = await dbPool.query(
      `INSERT INTO escola.cartazes (usuario_id, titulo, tema, cor_fundo, cor_texto, layout, conteudo, imagem_url) 
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at`,
      [usuarioId, titulo, tema, corFundo || '#ffffff', corTexto || '#1e293b', layout, conteudoHtml, generatedImageUrl]
    );

    res.json({
      success: true,
      id: dbRes.rows[0].id,
      titulo,
      tema,
      corFundo,
      corTexto,
      layout,
      conteudo: conteudoHtml,
      imagemUrl: generatedImageUrl,
      createdAt: dbRes.rows[0].created_at
    });

  } catch (err) {
    console.error('Erro ao criar cartaz:', err);
    res.status(500).json({ error: 'Erro ao criar cartaz: ' + err.message });
  }
});

app.post('/api/cartazes/decompose-scene', requireAuth, async (req, res) => {
  const { tema, titulo, layers_manual } = req.body;

  try {
    console.log('[Decompose] Recebida requisição:', { hasLayersManual: !!layers_manual, tema });
    const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');

    // Função interna para traduzir uma descrição PT→EN e gerar a imagem
    const translateAndGenerate = async (descPT, isBackground) => {
      let engPrompt = descPT;
      try {
        const refineContent = isBackground
          ? `Translate this Portuguese description of a scene BACKGROUND into a clean English image generation prompt.
The background must be COMPLETELY EMPTY — no people, no animals, no characters. Just the environment/scenery.
Specify cartoon vector illustration style, vibrant colors, kid-friendly, no text.
Portuguese: "${descPT}"
Return ONLY the English prompt, nothing else.`
          : `Translate this Portuguese description of a single character or object into a clean English image generation prompt.
The element must be COMPLETELY ISOLATED on a PURE WHITE background (#FFFFFF), full body visible, no shadows, no background details.
Specify 3D Pixar cartoon sticker style, soft pastel colors, no text.
Portuguese: "${descPT}"
Return ONLY the English prompt, nothing else.`;

        const r = await callMinimax({ messages: [{ role: 'user', content: refineContent }], temperature: 0.2, max_tokens: 150 });
        if (r?.text) engPrompt = r.text.trim();
      } catch (e) { console.error('[Decompose] Translate error:', e.message); }

      try {
        const suffix = isBackground
          ? 'Empty background scene, no people, no animals, cartoon illustration, vibrant colors, no text.'
          : 'Full body visible, pure solid white background #FFFFFF, no shadows, no gradients, 3D Pixar sticker clipart style, no text.';

        const genResp = await fetch(`${minmBase}/v1/image_generation`, {
          method: 'POST',
          headers: { 'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`, 'Content-Type': 'application/json' },
          body: JSON.stringify({ model: 'image-01', prompt: `${engPrompt}. ${suffix}`, n: 1 })
        });

        if (genResp.ok) {
          const genData = await genResp.json();
          const rawUrl = genData.data?.image_urls?.[0];
          if (rawUrl) {
            const imgFetch = await fetch(rawUrl);
            if (imgFetch.ok) {
              const buf = Buffer.from(await imgFetch.arrayBuffer());
              const fname = `layer_${Date.now()}_${Math.floor(Math.random()*1000)}.jpg`;
              const mediaDir = require('path').join(__dirname, 'public', 'generated-media');
              if (!require('fs').existsSync(mediaDir)) require('fs').mkdirSync(mediaDir, { recursive: true });
              const fpath = require('path').join(mediaDir, fname);
              require('fs').writeFileSync(fpath, buf);
              await backupMediaFile(fpath, buf);
              return `/generated-media/${fname}`;
            }
          }
        }
      } catch (e) { console.error('[Decompose] Generate error:', e.message); }
      return null;
    };

    // MODO NOVO: layers_manual enviado pelo layer manager
    if (layers_manual && Array.isArray(layers_manual) && layers_manual.length > 0) {
      const bgLayerDef = layers_manual.find(l => l.isBackground);
      const elementLayerDefs = layers_manual.filter(l => !l.isBackground);

      if (!bgLayerDef) return res.status(400).json({ error: 'Nenhuma camada de fundo definida.' });

      console.log('[Decompose] Modo Manual - gerando', layers_manual.length, 'camadas em paralelo...');

      // Gerar tudo em paralelo
      const [backgroundUrl, ...elementUrls] = await Promise.all([
        translateAndGenerate(bgLayerDef.description, true),
        ...elementLayerDefs.map(el => translateAndGenerate(el.description, false))
      ]);

      const layers = elementLayerDefs.map((el, i) => ({
        name: el.description,
        url: elementUrls[i],
        left: 10 + i * 25, top: 35, width: 30, height: 30
      })).filter(l => l.url);

      return res.json({ success: true, backgroundUrl, layers });
    }

    // MODO LEGADO: campo tema (compatibilidade)
    if (!tema) return res.status(400).json({ error: 'Tema ou layers_manual são obrigatórios.' });

    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";

    const segmentPrompt = `Você é a ${agentName}, assistente pedagógica.
Tema: "${tema}" / Título: "${titulo || ''}"
Divida em: 1 fundo vazio + 2-3 elementos stickers (fundo branco puro).
Retorne JSON: { "backgroundPrompt":"...", "elements":[{"name":"...","prompt":"..."}] }`;

    let jsonResponse = null;
    try {
      const chatRes = await callMinimax({ messages: [{ role: 'user', content: segmentPrompt }], temperature: 0.2, max_tokens: 600 });
      let text = chatRes.text.trim().replace(/```json|```/g, '').trim();
      jsonResponse = JSON.parse(text);
    } catch (err) {
      console.error('[Decompose] Erro ao obter JSON do Minimax:', err);
    }

    if (!jsonResponse || !jsonResponse.backgroundPrompt || !jsonResponse.elements) {
      jsonResponse = {
        backgroundPrompt: `${tema}, empty scene backdrop, cartoon vector style, no people, no animals, no text`,
        elements: [
          { name: "Elemento Principal", prompt: `${tema}, cartoon sticker clipart style, completely isolated on a pure white background, no shadows, no background elements, no text` }
        ]
      };
    }

    console.log('[Decompose] Planejamento da cena em camadas (Legado):', jsonResponse);

    // Gerar fundo e stickers paralelamente
    const [backgroundUrl, ...elementUrls] = await Promise.all([
      translateAndGenerate(jsonResponse.backgroundPrompt, true),
      ...jsonResponse.elements.map(el => translateAndGenerate(el.prompt, false))
    ]);

    const layers = jsonResponse.elements.map((el, index) => {
      return {
        name: el.name,
        url: elementUrls[index],
        left: 20 + index * 25,
        top: 35,
        width: 30,
        height: 30
      };
    }).filter(l => l.url !== null);

    res.json({
      success: true,
      backgroundUrl,
      layers
    });

  } catch (err) {
    console.error('[Decompose] Erro no desmembramento:', err);
    res.status(500).json({ error: 'Erro ao desmembrar a imagem: ' + err.message });
  }
});

app.get('/api/cartazes/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, titulo, tema, cor_fundo as "corFundo", cor_texto as "corTexto", layout, imagem_url as "imagemUrl", created_at as "createdAt" 
       FROM escola.cartazes WHERE usuario_id = $1 ORDER BY created_at DESC`,
      [usuarioId]
    );
    res.json(dbRes.rows);
  } catch (err) {
    console.error('Erro ao listar cartazes:', err);
    res.status(500).json({ error: 'Erro ao listar cartazes.' });
  }
});

app.get('/api/cartazes/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, titulo, tema, cor_fundo as "corFundo", cor_texto as "corTexto", layout, conteudo, imagem_url as "imagemUrl", created_at as "createdAt" 
       FROM escola.cartazes WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rows.length === 0) {
      return res.status(404).json({ error: 'Cartaz não encontrado.' });
    }
    res.json(dbRes.rows[0]);
  } catch (err) {
    console.error('Erro ao buscar cartaz:', err);
    res.status(500).json({ error: 'Erro ao buscar cartaz.' });
  }
});

app.delete('/api/cartazes/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `DELETE FROM escola.cartazes WHERE id = $1 AND usuario_id = $2`,
      [id, usuarioId]
    );
    if (dbRes.rowCount === 0) {
      return res.status(404).json({ error: 'Cartaz não encontrado ou não pertence ao usuário.' });
    }
    res.json({ success: true, message: 'Cartaz excluído com sucesso.' });
  } catch (err) {
    console.error('Erro ao deletar cartaz:', err);
    res.status(500).json({ error: 'Erro ao deletar cartaz.' });
  }
});

app.put('/api/cartazes/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  const { titulo, tema, corFundo, corTexto, layout, conteudo, imagemUrl } = req.body;
  try {
    const dbRes = await dbPool.query(
      `UPDATE escola.cartazes 
       SET titulo = COALESCE($1, titulo), 
           tema = COALESCE($2, tema), 
           cor_fundo = COALESCE($3, cor_fundo), 
           cor_texto = COALESCE($4, cor_texto), 
           layout = COALESCE($5, layout), 
           conteudo = COALESCE($6, conteudo), 
           imagem_url = COALESCE($7, imagem_url)
       WHERE id = $8 AND usuario_id = $9 RETURNING *`,
      [titulo, tema, corFundo, corTexto, layout, conteudo, imagemUrl, id, usuarioId]
    );
    if (dbRes.rows.length === 0) {
      return res.status(404).json({ error: 'Cartaz não encontrado ou não pertence ao usuário.' });
    }
    res.json(dbRes.rows[0]);
  } catch (err) {
    console.error('Erro ao atualizar cartaz:', err);
    res.status(500).json({ error: 'Erro ao atualizar cartaz.' });
  }
});

app.get('/api/biblioteca-assets', requireAuth, async (req, res) => {
  const { busca, tipo } = req.query;
  try {
    let query = `SELECT id, tipo, nome, url, tags FROM escola.biblioteca_assets WHERE 1=1`;
    const params = [];
    let paramIdx = 1;
    
    if (tipo) {
      query += ` AND tipo = $${paramIdx}`;
      params.push(tipo);
      paramIdx++;
    }
    
    if (busca) {
      query += ` AND (nome ILIKE $${paramIdx} OR $${paramIdx} = ANY(tags))`;
      params.push(`%${busca}%`);
      paramIdx++;
    }
    
    query += ` ORDER BY tipo, nome ASC`;
    
    const dbRes = await dbPool.query(query, params);
    res.json(dbRes.rows);
  } catch (err) {
    console.error('Erro ao buscar assets da biblioteca:', err);
    res.status(500).json({ error: 'Erro ao carregar assets da biblioteca.' });
  }
});

// ============================================================
// ROTAS DO MUSICANDO IDEIAS (ESTÚDIO PROFISSIONAL)
// ============================================================
app.post('/api/musicas/generate', requireAuth, async (req, res) => {
  const { faixaEtaria, estrofes, ritmo, tema, instrumentos, intensidade, duracao, voz, gerarAudio, turmaId } = req.body;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';

  if (!tema) {
    return res.status(400).json({ error: 'O tema ou ideia da música é obrigatório.' });
  }

  try {
    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";

    const promptCompositor = `Você é a ${agentName}, assistente e compositora musical pedagógica da professora Camila Martella Gasparini Reifonas.
A professora Camila deseja compor uma música educativa e lúdica para sua turma de Educação Infantil.

PARÂMETROS DA MÚSICA:
- Tema / Ideia Central: "${tema}"
- Faixa Etária do Público: "${faixaEtaria || 'Crianças pequenas (4 a 5 anos)'}"
- Ritmo / Gênero Musical: "${ritmo || 'Cantiga de Roda'}"
- Instrumentos Solicitados: "${instrumentos || 'Violão, Pandeiro e Flauta'}"
- Intensidade: "${intensidade || 'moderada'}" (calma, moderada ou dançante)
- Duração Estimada: "${duracao || '1min'}"

OBJETIVO:
Gere 3 VERSÕES da música para que a professora tenha diferentes opções pedagógicas em sala de aula, além de mapear as competências da BNCC.

Retorne ESTRITAMENTE um objeto JSON válido (sem tags markdown extras) com a seguinte estrutura:
{
  "titulo": "Título Criativo da Música",
  "versaoCurta": "Letra de 4 a 6 versos curtos e fáceis de memorizar para acolhida ou repetição rápida.",
  "versaoPrincipal": "Letra completa com Estrofe 1, Refrão e Estrofe 2, incluindo gestos e palmas entre parênteses como (palmas) ou (dar as mãos).",
  "versaoDancante": "Letra rítmica e animada para cantigas de roda, circuitos ou gincanas ativas.",
  "bnccCampos": "Códigos e habilidades da BNCC (ex: EI02TS02 / EI03TS03 - Traços, Sons, Cores e Formas)",
  "instrumentosSugeridos": "Lista de instrumentos que combinam com o arranjo",
  "dicasPedagogicas": "Breve orientação de 2 a 3 linhas para a professora aplicar com as crianças em sala."
}`;

    let jsonResponseText = '';

    // 1. Tentar MiniMax M3
    try {
      console.log('[Musicando] Compondo letras com MiniMax M3...');
      const r = await callMinimax({
        system: "Você é uma compositora musical pedagógica de excelência para a Educação Infantil no Brasil. Responda estritamente em formato JSON válido.",
        messages: [{ role: 'user', content: promptCompositor }],
        temperature: 0.7,
        max_tokens: 1800
      });
      jsonResponseText = r.text;
    } catch (err) {
      console.error('[Musicando] Erro MiniMax M3:', err.message);
    }

    // 2. Fallback Groq (OpenAI GPT-OSS)
    if (!jsonResponseText && process.env.GROQ_API_KEY) {
      try {
        console.log('[Musicando] Fallback Groq...');
        const r = await fetch('https://api.groq.com/openai/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GROQ_MODEL || 'openai/gpt-oss-120b',
            messages: [{ role: 'user', content: promptCompositor }],
            temperature: 0.7,
            max_tokens: 1800,
            response_format: { type: 'json_object' }
          })
        });
        if (r.ok) {
          const data = await r.json();
          jsonResponseText = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Musicando] Erro Groq:', err.message);
      }
    }

    // 3. Fallback OpenRouter
    if (!jsonResponseText && process.env.OPENROUTER_API_KEY) {
      try {
        console.log('[Musicando] Fallback OpenRouter...');
        const r = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
            messages: [{ role: 'user', content: promptCompositor }],
            temperature: 0.7,
            max_tokens: 1800
          })
        });
        if (r.ok) {
          const data = await r.json();
          jsonResponseText = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Musicando] Erro OpenRouter:', err.message);
      }
    }

    // Parse JSON
    let musicaData = {};
    try {
      const cleanJson = jsonResponseText.replace(/```json|```/g, '').trim();
      musicaData = JSON.parse(cleanJson);
    } catch (parseErr) {
      console.warn('[Musicando] Erro parse JSON, usando fallback texto:', parseErr.message);
      musicaData = {
        titulo: `Música: ${tema}`,
        versaoCurta: `Lá na nossa sala vamos brincar,\nCom ${tema} vamos cantar!\nBate a mãozinha, dá uma rodada,\nEssa canção é muito animada!`,
        versaoPrincipal: jsonResponseText || `(Verso 1)\nHoje na escola vamos aprender,\nCom ${tema} vamos crescer!\n\n(Refrão)\nCanta, pula e bate a mão, (palmas)\nEssa é a nossa canção!\n\n(Verso 2)\nCom os amigos a festejar,\nTodo mundo a cantar!`,
        versaoDancante: `Roda, roda, sem parar!\nCom ${tema} a dançar!\nPalma, palma, pé no chão,\nEntra logo no cordão!`,
        bnccCampos: "EI02TS02 / EI03TS03 • Traços, Sons, Cores e Formas",
        instrumentosSugeridos: instrumentos || "Violão, Pandeiro e Chocalho",
        dicasPedagogicas: "Estimule as crianças a fazerem os gestos de palmas e giros corporais durante a canção."
      };
    }

    // 4. GERAÇÃO DE ÁUDIO CANTADO (MiniMax music-2.0)
    let audioUrl = null;
    if (gerarAudio !== false && process.env.MINIMAX_API_KEY) {
      try {
        console.log('[Musicando] Gerando áudio cantado com MiniMax music-2.0...');
        let voiceDesc = 'sweet native brazilian female voice, warm kindergarten teacher singing voice';
        if (voz === 'homem') {
          voiceDesc = 'gentle warm native brazilian male voice, friendly acoustic singer';
        } else if (voz === 'crianca') {
          voiceDesc = 'playful cute young brazilian child singing voice, energetic kid vocals';
        }

        const audioPrompt = `${ritmo || 'Cantiga de roda'}, ${instrumentos || 'acoustic guitar, percussion'}, ${voiceDesc}, educational children song, clear pt-BR pronunciation`;
        const lyricsToSing = (musicaData.versaoPrincipal || musicaData.versaoCurta || tema).replace(/\(.*?\)/g, '').trim();

        const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
        const musicResp = await fetch(`${minmBase}/v1/music_generation`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'music-2.0',
            prompt: audioPrompt,
            lyrics: lyricsToSing.substring(0, 500),
            audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' }
          })
        });

        if (musicResp.ok) {
          const mData = await musicResp.json();
          const audioHex = mData.data?.audio;
          if (audioHex) {
            const fs = require('fs');
            const path = require('path');
            const mediaDir = path.join(__dirname, 'public', 'generated-media');
            if (!fs.existsSync(mediaDir)) {
              fs.mkdirSync(mediaDir, { recursive: true });
            }
            const fileName = `musica_${Date.now()}.mp3`;
            const filePath = path.join(mediaDir, fileName);
            const audioBuffer = Buffer.from(audioHex, 'hex');
            fs.writeFileSync(filePath, audioBuffer);
            await backupMediaFile(filePath, audioBuffer);
            audioUrl = `/generated-media/${fileName}`;
          }
        } else {
          console.warn('[Musicando] Falha MiniMax áudio:', await musicResp.text());
        }
      } catch (audioErr) {
        console.error('[Musicando] Erro ao gerar áudio MiniMax:', audioErr.message);
      }
    }

    // 5. Salvar no Banco PostgreSQL
    let savedId = null;
    try {
      const dbRes = await dbPool.query(
        `INSERT INTO escola.musicas (
          usuario_id, faixa_etaria, estrofes, ritmo, tema, musica, 
          audio_url, versao_curta, versao_longa, versao_dancante, instrumentos, bncc, intensidade, turma_id
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING id`,
        [
          usuarioId,
          faixaEtaria || 'Crianças pequenas',
          estrofes || 3,
          ritmo || 'Cantiga de Roda',
          tema,
          musicaData.versaoPrincipal || '',
          audioUrl,
          musicaData.versaoCurta || '',
          musicaData.versaoPrincipal || '',
          musicaData.versaoDancante || '',
          instrumentos || '',
          musicaData.bnccCampos || '',
          intensidade || 'moderada',
          turmaId || null
        ]
      );
      savedId = dbRes.rows[0]?.id;
    } catch (dbErr) {
      console.warn('[Musicando] Erro ao salvar colunas estendidas, tentando salvar básico:', dbErr.message);
      try {
        const dbRes2 = await dbPool.query(
          `INSERT INTO escola.musicas (usuario_id, faixa_etaria, estrofes, ritmo, tema, musica) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,
          [usuarioId, faixaEtaria || 'Crianças', estrofes || 3, ritmo || 'Cantiga', tema, musicaData.versaoPrincipal || '']
        );
        savedId = dbRes2.rows[0]?.id;
      } catch (err2) {
        console.error('[Musicando] Erro ao salvar música:', err2.message);
      }
    }

    res.json({
      success: true,
      id: savedId,
      titulo: musicaData.titulo || `Música: ${tema}`,
      versaoCurta: musicaData.versaoCurta || '',
      versaoPrincipal: musicaData.versaoPrincipal || '',
      versaoDancante: musicaData.versaoDancante || '',
      bnccCampos: musicaData.bnccCampos || 'EI02TS02 / EI03TS03 • Traços, Sons, Cores e Formas',
      instrumentosSugeridos: musicaData.instrumentosSugeridos || instrumentos,
      dicasPedagogicas: musicaData.dicasPedagogicas || '',
      audioUrl: audioUrl,
      tema: tema,
      ritmo: ritmo,
      faixaEtaria: faixaEtaria
    });

  } catch (err) {
    console.error('[Musicando] Erro geral:', err);
    res.status(500).json({ error: 'Erro ao gerar música pedagógica: ' + err.message });
  }
});

app.get('/api/musicas/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, faixa_etaria, ritmo, tema, audio_url, created_at FROM escola.musicas WHERE usuario_id = $1 ORDER BY created_at DESC`,
      [usuarioId]
    );
    res.json(dbRes.rows);
  } catch (err) {
    try {
      const dbRes2 = await dbPool.query(`SELECT id, faixa_etaria, ritmo, tema, created_at FROM escola.musicas WHERE usuario_id = $1 ORDER BY created_at DESC`, [usuarioId]);
      res.json(dbRes2.rows);
    } catch (err2) {
      res.status(500).json({ error: 'Erro ao listar histórico de músicas.' });
    }
  }
});

app.get('/api/musicas/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`SELECT * FROM escola.musicas WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Música não encontrada.' });
    res.json(dbRes.rows[0]);
  } catch (err) { res.status(500).json({ error: 'Erro ao buscar música.' }); }
});

app.delete('/api/musicas/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`DELETE FROM escola.musicas WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    res.json({ success: true });
  } catch (err) { res.status(500).json({ error: 'Erro ao deletar música.' }); }
});


// ============================================================
// ROTAS DO INVENTANDO HISTÓRIAS (ESTÚDIO PROFISSIONAL)
// ============================================================
app.post('/api/historias/generate', requireAuth, async (req, res) => {
  const { 
    faixaEtaria, 
    paragrafos, 
    personagens, 
    estilo, 
    tema, 
    personagemPrincipal, 
    objetoMagico, 
    estiloVisual,
    turmaId,
    gerarAudio 
  } = req.body;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';

  if (!tema) {
    return res.status(400).json({ error: 'O tema da história é obrigatório.' });
  }

  try {
    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";

    const promptHistoria = `Você é a ${agentName}, contadora de histórias e escritora de literatura infantil da professora Camila Martella Gasparini Reifonas.
A professora Camila deseja inventar uma história pedagógica envolvente para seus alunos na Educação Infantil.

PARÂMETROS DA HISTÓRIA:
- Tema / Enredo: "${tema}"
- Faixa Etária do Público: "${faixaEtaria || 'Crianças pequenas (4 a 5 anos)'}"
- Personagem Principal: "${personagemPrincipal || 'Criança curiosa da turma'}"
- Objeto / Elemento Mágico: "${objetoMagico || 'Arca da Imaginação'}"
- Estilo Narrativo: "${estilo || 'Lúdico, Afetivo e Dialogado'}"
- Estilo Visual das Ilustrações: "${estiloVisual || 'Cartoon colorido e acolhedor'}"
- Quantidade de Cenas / Atos: ${paragrafos || 3}

ESTRUTURA PEDAGÓGICA (3 ATOS):
1. Ato 1 (Introdução): Apresentação do personagem no cotidiano escolar/infantil e descoberta do elemento disparador.
2. Ato 2 (Conflito / Desafio): Uma situação-problema lúdica onde a cooperação, empatia e inteligência emocional são necessárias.
3. Ato 3 (Resolução & Aprendizado): Resolução mágica com celebração da amizade e aprendizado afetivo.

Retorne ESTRITAMENTE um JSON válido (sem tags markdown externas):
{
  "titulo": "Título Mágico e Divertido da História",
  "personagemPrincipal": "${personagemPrincipal || 'Personagem Principal'}",
  "objetoMagico": "${objetoMagico || 'Elemento Especial'}",
  "historiaCompleta": "Texto narrativo completo e fluido da história com quebras de parágrafos.",
  "cenas": [
    {
      "numero": 1,
      "titulo": "Ato 1: O Descobrimento Mágico",
      "texto": "Texto do primeiro ato para contação...",
      "promptImagem": "Cute children book illustration of ${personagemPrincipal || 'a happy child'} finding a magical glowing box in a vibrant kindergarten garden, ${estiloVisual || 'cartoon style'}, bright warm colors, highly detailed"
    },
    {
      "numero": 2,
      "titulo": "Ato 2: A Grande Descoberta e o Desafio",
      "texto": "Texto do segundo ato com a situação-problema...",
      "promptImagem": "Children book illustration of friends cooperating together in a classroom, ${estiloVisual || 'cartoon style'}, friendly smiles, soft lighting"
    },
    {
      "numero": 3,
      "titulo": "Ato 3: A Festa da Amizade",
      "texto": "Texto do terceiro ato com o desfecho feliz...",
      "promptImagem": "Children celebration in a colorful school yard with balloons, ${estiloVisual || 'cartoon style'}, joyful atmosphere, happy children"
    }
  ],
  "perguntasCompreensao": [
    "1. O que o personagem descobriu no início da história?",
    "2. Como os amigos ajudaram a resolver o desafio?",
    "3. O que você faria se encontrasse esse objeto mágico?"
  ],
  "bnccCampos": "EI02EF04 / EI03EF05 • Escuta, Fala, Pensamento e Imaginação",
  "duracaoLeitura": "3 a 4 minutos",
  "complexidade": "Adequada para Educação Infantil (Lúdica e Sonora)"
}`;

    let jsonResponseText = '';

    // 1. Tentar MiniMax M3
    try {
      console.log('[Histórias] Criando narrativa com MiniMax M3...');
      const r = await callMinimax({
        system: "Você é uma autora premiada de literatura infantil e contadora de histórias pedagógica no Brasil. Responda estritamente em JSON válido.",
        messages: [{ role: 'user', content: promptHistoria }],
        temperature: 0.7,
        max_tokens: 2500
      });
      jsonResponseText = r.text;
    } catch (err) {
      console.error('[Histórias] Erro MiniMax M3:', err.message);
    }

    // 2. Fallback Groq
    if (!jsonResponseText && process.env.GROQ_API_KEY) {
      try {
        console.log('[Histórias] Fallback Groq...');
        const r = await fetch('https://api.groq.com/openai/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GROQ_MODEL || 'openai/gpt-oss-120b',
            messages: [{ role: 'user', content: promptHistoria }],
            temperature: 0.7,
            max_tokens: 2500,
            response_format: { type: 'json_object' }
          })
        });
        if (r.ok) {
          const data = await r.json();
          jsonResponseText = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Histórias] Erro Groq:', err.message);
      }
    }

    // 3. Fallback OpenRouter
    if (!jsonResponseText && process.env.OPENROUTER_API_KEY) {
      try {
        console.log('[Histórias] Fallback OpenRouter...');
        const r = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
            messages: [{ role: 'user', content: promptHistoria }],
            temperature: 0.7,
            max_tokens: 2500
          })
        });
        if (r.ok) {
          const data = await r.json();
          jsonResponseText = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Histórias] Erro OpenRouter:', err.message);
      }
    }

    // 4. Fallback Google Gemini
    if (!jsonResponseText && process.env.GOOGLE_AI_API_KEY) {
      try {
        console.log('[Histórias] Fallback Gemini...');
        const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
        const response = await fetch(geminiUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            contents: [{ parts: [{ text: promptHistoria }] }],
            generationConfig: { temperature: 0.7, maxOutputTokens: 2500 }
          })
        });
        if (response.ok) {
          const resData = await response.json();
          jsonResponseText = resData.candidates?.[0]?.content?.parts?.[0]?.text || '';
        }
      } catch (err) {
        console.error('[Histórias] Erro Gemini:', err.message);
      }
    }

    // Parse JSON
    let historiaData = {};
    try {
      const cleanJson = jsonResponseText.replace(/```json|```/g, '').trim();
      historiaData = JSON.parse(cleanJson);
    } catch (parseErr) {
      console.warn('[Histórias] Erro parse JSON, criando estrutura de contingência:', parseErr.message);
      historiaData = {
        titulo: `A Aventura de ${personagemPrincipal || 'Nossa Turma'}`,
        personagemPrincipal: personagemPrincipal || 'Aluno(a)',
        objetoMagico: objetoMagico || 'Elemento Especial',
        historiaCompleta: jsonResponseText || `Era uma vez, em um dia cheio de sol na escola, ${personagemPrincipal || 'uma criança'} encontrou um objeto mágico...`,
        cenas: [
          { numero: 1, titulo: "Ato 1: O Início da Jornada", texto: "A turma estava reunida quando algo incrível aconteceu...", promptImagem: "Children in kindergarten playing joyfully" },
          { numero: 2, titulo: "Ato 2: O Desafio", texto: "Juntos, eles precisavam cooperar para desvendar o segredo...", promptImagem: "Kids cooperating and sharing" },
          { numero: 3, titulo: "Ato 3: O Final Feliz", texto: "Com muitos abraços e alegria, a história teve o final perfeito!", promptImagem: "Happy children celebrating" }
        ],
        perguntasCompreensao: ["O que aconteceu no início?", "Como eles resolveram o problema?"],
        bnccCampos: "EI02EF04 / EI03EF05 • Escuta, Fala, Pensamento e Imaginação",
        duracaoLeitura: "3 minutos",
        complexidade: "Lúdica (Infantil)"
      };
    }

    // Geração de Áudio Narrado (MiniMax)
    let audioUrl = null;
    if (gerarAudio !== false && process.env.MINIMAX_API_KEY) {
      try {
        console.log('[Histórias] Gerando áudio narrado com MiniMax...');
        const textToNarrate = (historiaData.historiaCompleta || tema).substring(0, 600);
        const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
        const musicResp = await fetch(`${minmBase}/v1/music_generation`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'music-2.0',
            prompt: 'Gentle warm kindergarten teacher storytelling narration, Portuguese Brazilian accent, calm acoustic ambient',
            lyrics: textToNarrate,
            audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' }
          })
        });

        if (musicResp.ok) {
          const mData = await musicResp.json();
          const audioHex = mData.data?.audio;
          if (audioHex) {
            const fs = require('fs');
            const path = require('path');
            const mediaDir = path.join(__dirname, 'public', 'generated-media');
            if (!fs.existsSync(mediaDir)) {
              fs.mkdirSync(mediaDir, { recursive: true });
            }
            const fileName = `historia_${Date.now()}.mp3`;
            const filePath = path.join(mediaDir, fileName);
            const audioBuffer = Buffer.from(audioHex, 'hex');
            fs.writeFileSync(filePath, audioBuffer);
            await backupMediaFile(filePath, audioBuffer);
            audioUrl = `/generated-media/${fileName}`;
          }
        }
      } catch (audioErr) {
        console.error('[Histórias] Erro ao gerar áudio narrado:', audioErr.message);
      }
    }

    // Salvar no Banco PostgreSQL
    let savedId = null;
    try {
      const dbRes = await dbPool.query(
        `INSERT INTO escola.historias (
          usuario_id, faixa_etaria, paragrafos, personagens, estilo, tema, historia,
          titulo, personagem_principal, objeto_magico, cenas, bncc, turma_id, audio_url,
          perguntas_compreensao, duracao_leitura, complexidade, estilo_visual
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18) RETURNING id`,
        [
          usuarioId,
          faixaEtaria || 'Crianças pequenas',
          parseInt(paragrafos) || 3,
          parseInt(personagens) || 2,
          estilo || 'Dialogada',
          tema,
          historiaData.historiaCompleta || '',
          historiaData.titulo || `História: ${tema}`,
          personagemPrincipal || '',
          objetoMagico || '',
          JSON.stringify(historiaData.cenas || []),
          historiaData.bnccCampos || '',
          turmaId || null,
          audioUrl,
          JSON.stringify(historiaData.perguntasCompreensao || []),
          historiaData.duracaoLeitura || '3 min',
          historiaData.complexidade || 'Lúdica',
          estiloVisual || 'Cartoon colorido'
        ]
      );
      savedId = dbRes.rows[0]?.id;
    } catch (dbErr) {
      console.warn('[Histórias] Erro ao salvar colunas estendidas, salvando básico:', dbErr.message);
      try {
        const dbRes2 = await dbPool.query(
          `INSERT INTO escola.historias (usuario_id, faixa_etaria, paragrafos, personagens, estilo, tema, historia) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
          [usuarioId, faixaEtaria || 'Crianças', parseInt(paragrafos) || 3, parseInt(personagens) || 2, estilo || 'Lúdico', tema, historiaData.historiaCompleta || '']
        );
        savedId = dbRes2.rows[0]?.id;
      } catch (err2) {
        console.error('[Histórias] Erro ao salvar história:', err2.message);
      }
    }

    res.json({
      success: true,
      id: savedId,
      titulo: historiaData.titulo || `História: ${tema}`,
      personagemPrincipal: historiaData.personagemPrincipal || personagemPrincipal,
      objetoMagico: historiaData.objetoMagico || objetoMagico,
      historiaCompleta: historiaData.historiaCompleta || '',
      cenas: historiaData.cenas || [],
      perguntasCompreensao: historiaData.perguntasCompreensao || [],
      bnccCampos: historiaData.bnccCampos || 'EI02EF04 / EI03EF05 • Escuta, Fala, Pensamento e Imaginação',
      duracaoLeitura: historiaData.duracaoLeitura || '3 a 4 min',
      complexidade: historiaData.complexidade || 'Lúdica e Sonora',
      audioUrl: audioUrl,
      tema: tema,
      faixaEtaria: faixaEtaria,
      estilo: estilo,
      estiloVisual: estiloVisual
    });

  } catch (err) {
    console.error('Erro ao inventar história:', err);
    res.status(500).json({ error: 'Erro ao gerar história: ' + err.message });
  }
});

// Endpoint para gerar ilustração individual de uma cena
app.post('/api/historias/scene-image', requireAuth, async (req, res) => {
  const { prompt, estiloVisual, cenaNumero } = req.body;
  if (!prompt) {
    return res.status(400).json({ error: 'O prompt da cena é obrigatório.' });
  }

  try {
    console.log(`[Histórias] Gerando ilustração para cena ${cenaNumero || 1}...`);
    const enhancedPrompt = `${prompt}, ${estiloVisual || 'children book colorful illustration'}, vibrant lighting, cute warm character design, 4k master artwork`;

    // 1. Tentar MiniMax image-01
    if (process.env.MINIMAX_API_KEY) {
      try {
        const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
        const imgResp = await fetch(`${minmBase}/v1/image_generation`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'image-01',
            prompt: enhancedPrompt,
            aspect_ratio: '4:3',
            response_format: 'base64'
          })
        });

        if (imgResp.ok) {
          const imgData = await imgResp.json();
          const base64Data = imgData.data?.image_base64 || imgData.data?.images?.[0];
          if (base64Data) {
            const fs = require('fs');
            const path = require('path');
            const mediaDir = path.join(__dirname, 'public', 'generated-media');
            if (!fs.existsSync(mediaDir)) fs.mkdirSync(mediaDir, { recursive: true });
            const fileName = `cena_${Date.now()}.png`;
            const filePath = path.join(mediaDir, fileName);
            const buffer = Buffer.from(base64Data, 'base64');
            fs.writeFileSync(filePath, buffer);
            await backupMediaFile(filePath, buffer);
            return res.json({ success: true, imageUrl: `/generated-media/${fileName}` });
          }
        }
      } catch (minErr) {
        console.warn('[Histórias] Falha MiniMax imagem:', minErr.message);
      }
    }

    // 2. Fallback OpenRouter FLUX/SD
    if (process.env.OPENROUTER_API_KEY) {
      try {
        const r = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'black-forest-labs/flux-1-schnell',
            messages: [{ role: 'user', content: enhancedPrompt }]
          })
        });
        if (r.ok) {
          const data = await r.json();
          const imgUrl = data.choices?.[0]?.message?.content;
          if (imgUrl && imgUrl.startsWith('http')) {
            return res.json({ success: true, imageUrl: imgUrl });
          }
        }
      } catch (orErr) {
        console.warn('[Histórias] Falha OpenRouter imagem:', orErr.message);
      }
    }

    // Fallback Imagem Ilustrada Placeholder SVG
    res.json({ 
      success: true, 
      imageUrl: 'https://images.unsplash.com/photo-1512820790803-83ca734da794?w=600&auto=format&fit=crop&q=80' 
    });

  } catch (err) {
    console.error('[Histórias] Erro ao gerar imagem da cena:', err);
    res.status(500).json({ error: 'Erro ao gerar imagem da cena: ' + err.message });
  }
});

app.get('/api/historias/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(
      `SELECT id, titulo, faixa_etaria, estilo, tema, personagem_principal, audio_url, created_at FROM escola.historias WHERE usuario_id = $1 ORDER BY created_at DESC`,
      [usuarioId]
    );
    res.json(dbRes.rows);
  } catch (err) {
    try {
      const dbRes2 = await dbPool.query(
        `SELECT id, faixa_etaria, estilo, tema, created_at FROM escola.historias WHERE usuario_id = $1 ORDER BY created_at DESC`,
        [usuarioId]
      );
      res.json(dbRes2.rows);
    } catch (err2) {
      res.status(500).json({ error: 'Erro ao listar histórico de histórias.' });
    }
  }
});

app.get('/api/historias/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`SELECT * FROM escola.historias WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Não encontrada.' });
    res.json(dbRes.rows[0]);
  } catch (err) { res.status(500).json({ error: 'Erro ao buscar.' }); }
});

app.delete('/api/historias/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`DELETE FROM escola.historias WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    res.json({ success: true });
  } catch (err) { res.status(500).json({ error: 'Erro ao deletar.' }); }
});

// ============================================================
// ROTAS DO MIND LAB
// ============================================================
app.post('/api/mindlab/generate', requireAuth, async (req, res) => {
  const { faixa_etaria, jogo, metodo, foco, duracao, tema } = req.body;
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  
  if (!faixa_etaria || !jogo || !metodo || !foco) {
    return res.status(400).json({ error: 'Os campos obrigatórios do planejamento não foram preenchidos.' });
  }

  try {
    const systemInstruction = `Você é um especialista na metodologia educacional Mente Inovadora (Mind Lab). 
Crie um planejamento de aula detalhado em Markdown com os seguintes parâmetros:
- Faixa Etária: ${faixa_etaria}
- Jogo de Raciocínio: ${jogo}
- Método Metacognitivo Foco: ${metodo}
- Foco de Transferência (Socioemocional): ${foco}
- Duração: ${duracao}
- Tema Contextual: ${tema || 'Nenhum especifico'}

Por favor, estruture a aula obrigatoriamente com:
1. **Abertura (Contextualização):** Como introduzir o método e o tema.
2. **O Jogo (Aplicação):** Orientações para a mediação durante a partida.
3. **Reflexão (Mediação Transcendental):** Perguntas provocativas para fazer as crianças refletirem.
4. **Transferência:** Como conectar o que aprenderam no jogo e o método escolhido com situações reais da vida deles.`;

    let planejamentoText = '';
    if (process.env.GOOGLE_AI_API_KEY) {
      const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
      const response = await fetch(geminiUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ contents: [{ parts: [{ text: systemInstruction }] }] })
      });
      if (!response.ok) throw new Error('Erro na API Gemini');
      const data = await response.json();
      planejamentoText = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
    }
    
    planejamentoText = planejamentoText.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();

    if (!planejamentoText) throw new Error('Falha ao gerar o planejamento.');

    const dbRes = await dbPool.query(
      `INSERT INTO escola.mindlab (usuario_id, faixa_etaria, jogo, metodo, foco, tema, planejamento) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
      [usuarioId, faixa_etaria, jogo, metodo, foco, tema, planejamentoText]
    );

    res.json({ id: dbRes.rows[0].id, planejamento: planejamentoText });
  } catch (err) {
    console.error('Erro Mind Lab:', err);
    res.status(500).json({ error: 'Erro ao gerar planejamento Mind Lab: ' + err.message });
  }
});

app.get('/api/mindlab/list', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`SELECT id, faixa_etaria, jogo, metodo, foco, created_at FROM escola.mindlab WHERE usuario_id = $1 ORDER BY created_at DESC`, [usuarioId]);
    res.json(dbRes.rows);
  } catch (err) { res.status(500).json({ error: 'Erro ao listar histórico do Mind Lab.' }); }
});

app.get('/api/mindlab/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`SELECT * FROM escola.mindlab WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Não encontrada.' });
    res.json(dbRes.rows[0]);
  } catch (err) { res.status(500).json({ error: 'Erro ao buscar.' }); }
});

app.delete('/api/mindlab/:id', requireAuth, async (req, res) => {
  const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query(`DELETE FROM escola.mindlab WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
    res.json({ success: true });
  } catch (err) { res.status(500).json({ error: 'Erro ao deletar.' }); }
});

// Rota GET para obter configurações do agente
app.get('/api/agent-config', requireAuth, (req, res) => {
  const config = readAgentConfig();
  res.json(config);
});

// Rota POST para atualizar configurações do agente
app.post('/api/agent-config', requireAuth, async (req, res) => {
  const { agentName, iaAvatarUrl, userAvatarUrl, kemilyAvatarUrl, camilaAvatarUrl, temperaturePreset } = req.body;
  if (!agentName) {
    return res.status(400).json({ error: 'Nome do agente é obrigatório' });
  }
  const config = {
    agentName: agentName.trim(),
    iaAvatarUrl: iaAvatarUrl || kemilyAvatarUrl || "assets/pedagog_ia.png",
    userAvatarUrl: userAvatarUrl || camilaAvatarUrl || "assets/pedagog_user.png",
    temperaturePreset: temperaturePreset || 'equilibrado'
  };
  await writeAgentConfig(config);
  res.json({ success: true, config });
});

// Rotas de conhecimento adquirido
app.get('/api/conhecimento', requireAuth, async (req, res) => {
  try {
    const dbRes = await dbPool.query("SELECT id, content, source, added_at as \"addedAt\" FROM escola.conhecimento ORDER BY added_at DESC;");
    res.json({ facts: dbRes.rows });
  } catch (err) {
    console.error('Erro ao listar conhecimento:', err);
    res.status(500).json({ error: err.message });
  }
});

app.post('/api/conhecimento', requireAuth, async (req, res) => {
  const { fact, source } = req.body;
  if (!fact) return res.status(400).json({ error: 'Fato é obrigatório' });
  const added = await addKnowledgeFact(fact, source || 'conversa');
  res.json({ success: true, added });
});

app.delete('/api/conhecimento/:id', requireAuth, async (req, res) => {
  await removeKnowledgeFact(parseInt(req.params.id));
  res.json({ success: true });
});

app.put('/api/conhecimento/:id', requireAuth, async (req, res) => {
  const { content } = req.body;
  if (!content) return res.status(400).json({ error: 'Conteúdo é obrigatório' });
  try {
    const id = parseInt(req.params.id);
    await dbPool.query("UPDATE escola.conhecimento SET content = $1 WHERE id = $2;", [content, id]);
    res.json({ success: true });
  } catch (err) {
    console.error('Erro ao atualizar conhecimento:', err);
    res.status(500).json({ error: err.message });
  }
});

// Analisar mensagem final e extrair conhecimento aprendido (chamado pelo frontend)
app.post('/api/conhecimento/extrair', requireAuth, async (req, res) => {
  const { message } = req.body;
  if (!message) return res.status(400).json({ error: 'Mensagem é obrigatória' });
  
  const knowledgeRegex = /\[KNOWLEDGE:\s*([^\]]+)\]/gi;
  const facts = [];
  let match;
  while ((match = knowledgeRegex.exec(message)) !== null) {
    const fact = match[1].trim();
    if (fact && fact.length > 3) {
      const added = await addKnowledgeFact(fact, 'conversa');
      if (added) facts.push(fact);
    }
  }
  
  res.json({ success: true, factsFound: facts.length, facts });
});

app.get('/api/temperature-presets', requireAuth, (req, res) => {
  res.json(TEMPERATURE_PRESETS);
});

// ============================================================
// SISTEMA DE OBSERVAÇÃO POR VOZ
// ============================================================
const OBSERVACOES_DIR = path.join(__dirname, 'observacoes');
const OBS_INDEX_FILE = path.join(OBSERVACOES_DIR, 'index.json');

// Garante que diretório de observações existe
if (!fs.existsSync(OBSERVACOES_DIR)) {
  fs.mkdirSync(OBSERVACOES_DIR, { recursive: true });
}

// Lê índice de observações (mantido apenas para fins de migração inicial)
function readObsIndex() {
  if (!fs.existsSync(OBS_INDEX_FILE)) return { observations: [] };
  try {
    return JSON.parse(fs.readFileSync(OBS_INDEX_FILE, 'utf8'));
  } catch { return { observations: [] }; }
}

function writeObsIndex(idx) {
  try {
    fs.writeFileSync(OBS_INDEX_FILE, JSON.stringify(idx, null, 2), 'utf8');
  } catch (err) {
    console.error('Erro ao escrever index local:', err);
  }
}

// Inicializar banco de dados e migrar dados se estiver vazio
async function initDatabase() {
  try {
    // Garantir extensões UUID no banco
    try {
      await dbPool.query('CREATE EXTENSION IF NOT EXISTS "pgcrypto";');
      await dbPool.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp";');
    } catch (extErr) {
      console.warn('[Database Init] Aviso ao habilitar extensões UUID (pode requerer privilégios de superusuário):', extErr.message);
    }

    // 0. Estruturar banco de dados para Multi-Tenancy (Individualização por Professora)
    // Garantir que as tabelas existentes tenham a coluna usuario_id
    await dbPool.query(`
      ALTER TABLE escola.modelos ADD COLUMN IF NOT EXISTS usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
      ALTER TABLE escola.observacoes ADD COLUMN IF NOT EXISTS usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
      ALTER TABLE escola.conhecimento ADD COLUMN IF NOT EXISTS usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
      ALTER TABLE escola.config ADD COLUMN IF NOT EXISTS usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000';
    `);

    // Criar tabelas novas de conversas (histórico de chat) e criações (quadrinhos) com usuario_id
    await dbPool.query(`
      -- Conversas
      CREATE TABLE IF NOT EXISTS escola.conversas (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        titulo VARCHAR(255) NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Mensagens
      CREATE TABLE IF NOT EXISTS escola.mensagens (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        conversa_id UUID REFERENCES escola.conversas(id) ON DELETE CASCADE,
        sender VARCHAR(50) NOT NULL, -- 'user' ou 'assistant'
        content TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Projetos de HQ (Fábrica de Quadrinhos)
      CREATE TABLE IF NOT EXISTS escola.projetos_comics (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        titulo VARCHAR(255) NOT NULL,
        tema TEXT,
        cenario TEXT,
        proporcao VARCHAR(10) DEFAULT '16:9',
        character_description_global TEXT,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
        updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Painéis individuais de cada HQ
      CREATE TABLE IF NOT EXISTS escola.comics_panels (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        projeto_id UUID REFERENCES escola.projetos_comics(id) ON DELETE CASCADE,
        panel_number INTEGER NOT NULL,
        image_url TEXT NOT NULL,
        image_prompt TEXT,
        dialogue TEXT,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Biblioteca de personagens persistentes para reuso
      CREATE TABLE IF NOT EXISTS escola.comics_characters (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        nome VARCHAR(100) NOT NULL,
        descricao_fisica_en TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Garatujas (Analisador de Traçado Infantil)
      CREATE TABLE IF NOT EXISTS escola.garatujas (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        aluno_name VARCHAR(255) NOT NULL,
        image_url TEXT NOT NULL,
        analise TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- BrincarAtivo (Circuitos Psicomotores e Brincadeiras)
      CREATE TABLE IF NOT EXISTS escola.brincar_ativo (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        faixa_etaria VARCHAR(100) NOT NULL,
        recursos TEXT NOT NULL,
        atividade TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Poesias (Estúdio de Poesia)
      CREATE TABLE IF NOT EXISTS escola.poesias (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        faixa_etaria VARCHAR(100) NOT NULL,
        estrofes INTEGER NOT NULL,
        tema TEXT NOT NULL,
        poesia TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS estrutura VARCHAR(100);
      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS estilo VARCHAR(100);
      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS audio_url TEXT;
      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS analise_metrica JSONB;
      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS versao_fonetica TEXT;
      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS bncc VARCHAR(100);
      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS turma_id UUID;
      ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS dicas TEXT;

      -- Músicas (Musicando Ideias)
      CREATE TABLE IF NOT EXISTS escola.musicas (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        faixa_etaria VARCHAR(100) NOT NULL,
        estrofes INTEGER NOT NULL,
        ritmo VARCHAR(100) NOT NULL,
        tema TEXT NOT NULL,
        musica TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS audio_url TEXT;
      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS versao_curta TEXT;
      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS versao_longa TEXT;
      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS versao_dancante TEXT;
      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS instrumentos TEXT;
      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS bncc VARCHAR(100);
      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS intensidade VARCHAR(50);
      ALTER TABLE escola.musicas ADD COLUMN IF NOT EXISTS turma_id UUID;

      -- Planejamento Mind Lab
      CREATE TABLE IF NOT EXISTS escola.mindlab (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        faixa_etaria VARCHAR(100) NOT NULL,
        jogo VARCHAR(100) NOT NULL,
        metodo VARCHAR(100) NOT NULL,
        foco VARCHAR(100) NOT NULL,
        tema TEXT,
        planejamento TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Estúdio Musical (Áudio MiniMax)
      
      -- Turmas
      CREATE TABLE IF NOT EXISTS escola.turmas (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        nome VARCHAR(255) NOT NULL,
        sala VARCHAR(255),
        periodo VARCHAR(100),
        prof_titular VARCHAR(255),
        prof_aux1 VARCHAR(255),
        prof_aux2 VARCHAR(255),
        cuidadora VARCHAR(255),
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Alunos
      CREATE TABLE IF NOT EXISTS escola.alunos (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        turma_id UUID REFERENCES escola.turmas(id) ON DELETE CASCADE,
        nome VARCHAR(255) NOT NULL,
        apelido VARCHAR(255),
        data_nasc DATE,
        especial BOOLEAN DEFAULT false,
        especial_detalhes TEXT,
        pais TEXT,
        autorizados TEXT,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      CREATE TABLE IF NOT EXISTS escola.estudio_musicas (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        voz VARCHAR(50),
        ritmo VARCHAR(100),
        tema TEXT,
        letra TEXT,
        audio_url TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Histórias (Inventando Histórias)
      CREATE TABLE IF NOT EXISTS escola.historias (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        faixa_etaria VARCHAR(100) NOT NULL,
        paragrafos INTEGER NOT NULL,
        personagens INTEGER NOT NULL,
        estilo VARCHAR(100) NOT NULL,
        tema TEXT NOT NULL,
        historia TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS titulo VARCHAR(255);
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS personagem_principal VARCHAR(255);
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS objeto_magico VARCHAR(255);
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS cenas JSONB;
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS bncc VARCHAR(100);
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS turma_id UUID;
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS audio_url TEXT;
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS perguntas_compreensao JSONB;
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS duracao_leitura VARCHAR(50);
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS complexidade VARCHAR(50);
      ALTER TABLE escola.historias ADD COLUMN IF NOT EXISTS estilo_visual VARCHAR(100);

      -- Cartazes (Criar Cartazes)
      CREATE TABLE IF NOT EXISTS escola.cartazes (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        titulo VARCHAR(255) NOT NULL,
        tema VARCHAR(255) NOT NULL,
        cor_fundo VARCHAR(50) NOT NULL,
        cor_texto VARCHAR(50) NOT NULL,
        layout VARCHAR(50) NOT NULL, -- 'mural', 'regras', 'calendario', 'livre'
        conteudo TEXT NOT NULL,
        imagem_url TEXT,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Arquivos persistentes (Mídia gerada por IA)
      CREATE TABLE IF NOT EXISTS escola.arquivos (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        filename VARCHAR(255) UNIQUE NOT NULL,
        content_type VARCHAR(100) NOT NULL,
        dados BYTEA NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Biblioteca de Assets (Stickers, Ícones, Post-its, etc.)
      CREATE TABLE IF NOT EXISTS escola.biblioteca_assets (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        tipo VARCHAR(50) NOT NULL, -- 'sticker', 'postit', 'icone', 'forma'
        nome VARCHAR(100) NOT NULL,
        url TEXT NOT NULL,
        tags TEXT[], -- array de tags para busca
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Extensões para Alunos
      ALTER TABLE escola.alunos ADD COLUMN IF NOT EXISTS observacoes_saude TEXT;
      ALTER TABLE escola.alunos ADD COLUMN IF NOT EXISTS contato_emergencia VARCHAR(100);
      ALTER TABLE escola.alunos ADD COLUMN IF NOT EXISTS documentos JSONB;
      ALTER TABLE escola.alunos ADD COLUMN IF NOT EXISTS foto_url TEXT;

      -- Extensões para Observações
      ALTER TABLE escola.observacoes ADD COLUMN IF NOT EXISTS tags_bncc JSONB;
      ALTER TABLE escola.observacoes ADD COLUMN IF NOT EXISTS duracao_segundos INTEGER;
      ALTER TABLE escola.observacoes ADD COLUMN IF NOT EXISTS timestamps JSONB;
      ALTER TABLE escola.observacoes ADD COLUMN IF NOT EXISTS sentimento VARCHAR(50);
      ALTER TABLE escola.observacoes ADD COLUMN IF NOT EXISTS aluno_id UUID;

      -- Extensões para Mind Lab
      ALTER TABLE escola.mindlab ADD COLUMN IF NOT EXISTS bimestre INTEGER;
      ALTER TABLE escola.mindlab ADD COLUMN IF NOT EXISTS semana INTEGER;
      ALTER TABLE escola.mindlab ADD COLUMN IF NOT EXISTS sequencia_didatica JSONB;
      ALTER TABLE escola.mindlab ADD COLUMN IF NOT EXISTS texto_htpc TEXT;
      ALTER TABLE escola.mindlab ADD COLUMN IF NOT EXISTS bncc_habilidades JSONB;

      -- Tabela de Protocolo de Crises & Mediação Comportamental
      CREATE TABLE IF NOT EXISTS escola.crises (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        turma_id UUID,
        aluno_id UUID,
        aluno_nome VARCHAR(255) NOT NULL,
        data_hora TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
        tipo_evento VARCHAR(100) NOT NULL, -- 'Desregulação Sensorial', 'Conflito Físico/Mordida', 'Queda/Pequeno Acidente', 'Choro Intenso/Adaptação', 'Recusa Alimentar', 'Outro'
        intensidade VARCHAR(50) DEFAULT 'Moderada', -- 'Leve', 'Moderada', 'Alta'
        gatilho TEXT,
        descricao TEXT NOT NULL,
        checklist_intervencao JSONB,
        medidas_tomadas TEXT NOT NULL,
        orientacoes_equipe TEXT,
        mensagem_whatsapp TEXT,
        status VARCHAR(50) DEFAULT 'Resolvido', -- 'Em Acompanhamento', 'Resolvido', 'Notificado à Coordenação'
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Tabela de Atas de Reunião & Conselho de Classe
      CREATE TABLE IF NOT EXISTS escola.atas (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        tipo_reuniao VARCHAR(100) NOT NULL, -- 'Reunião de Pais', 'Conselho de Classe / Avaliação', 'HTPC / Formação', 'Alinhamento Pedagógico'
        titulo VARCHAR(255) NOT NULL,
        turma_id UUID,
        turma_nome VARCHAR(255),
        data_reuniao DATE DEFAULT CURRENT_DATE,
        participantes TEXT[],
        pauta TEXT NOT NULL,
        discussoes TEXT NOT NULL,
        deliberacoes TEXT NOT NULL,
        proximos_passos TEXT,
        texto_ata TEXT NOT NULL,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Tabela de Stickers Gamificados & Conquistas
      CREATE TABLE IF NOT EXISTS escola.stickers (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        aluno_id UUID,
        aluno_nome VARCHAR(255) NOT NULL,
        turma_id UUID,
        titulo VARCHAR(255) NOT NULL,
        categoria VARCHAR(100) NOT NULL, -- 'Cooperação & Gentileza', 'Curiosidade & Ciência', 'Autonomia & Cuidado', 'Expressão Artística', 'Superação & Desafio'
        icone VARCHAR(50) NOT NULL DEFAULT '🌟',
        descricao TEXT NOT NULL,
        observacao_id UUID,
        qr_code_url TEXT,
        data_conquista DATE DEFAULT CURRENT_DATE,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );

      -- Tabela de Relatórios Semestrais & Batch
      CREATE TABLE IF NOT EXISTS escola.relatorios (
        id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
        usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
        turma_id UUID,
        aluno_id UUID,
        aluno_nome VARCHAR(255) NOT NULL,
        ano_letivo INTEGER DEFAULT 2026,
        semestre INTEGER DEFAULT 1, -- 1 ou 2
        faixa_etaria VARCHAR(100),
        conteudo_relatorio TEXT NOT NULL,
        campos_bncc JSONB,
        comparacao_anterior TEXT,
        versao_anterior_id UUID,
        lote_id UUID,
        status VARCHAR(50) DEFAULT 'Finalizado',
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );
    `);

    console.log('[Database Init] Atualizando assets padrão na biblioteca (Design Canvas 2.0)...');
    await dbPool.query(`TRUNCATE escola.biblioteca_assets;`);
    await dbPool.query(`
      INSERT INTO escola.biblioteca_assets (tipo, nome, url, tags) VALUES
      ('postit', 'Post-it Amarelo', '#feff9c', ARRAY['post-it', 'amarelo', 'anotacao']),
      ('postit', 'Post-it Azul', '#7afcff', ARRAY['post-it', 'azul', 'anotacao']),
      ('postit', 'Post-it Rosa', '#ff7eb9', ARRAY['post-it', 'rosa', 'anotacao']),
      ('postit', 'Post-it Verde', '#98ff98', ARRAY['post-it', 'verde', 'anotacao']),
      ('postit', 'Post-it Laranja', '#ffb7b2', ARRAY['post-it', 'laranja', 'anotacao']),
      ('postit', 'Post-it Roxo', '#e2bbff', ARRAY['post-it', 'roxo', 'anotacao']),
      ('postit', 'Post-it Ciano', '#bae1ff', ARRAY['post-it', 'ciano', 'anotacao']),
      ('postit', 'Post-it Branco', '#ffffff', ARRAY['post-it', 'branco', 'clean']),
      
      ('sticker', 'Escola', 'https://openmoji.org/data/color/svg/1F3EB.svg', ARRAY['escola', 'colegio', 'aula']),
      ('sticker', 'Lápis', 'https://openmoji.org/data/color/svg/270F.svg', ARRAY['lapis', 'escrever', 'desenhar']),
      ('sticker', 'Caderno', 'https://openmoji.org/data/color/svg/1F4DD.svg', ARRAY['caderno', 'anotacao', 'estudo']),
      ('sticker', 'Paleta de Arte', 'https://openmoji.org/data/color/svg/1F3A8.svg', ARRAY['arte', 'paleta', 'pintura', 'desenho']),
      ('sticker', 'Quebra-cabeça', 'https://openmoji.org/data/color/svg/1F9E9.svg', ARRAY['jogo', 'quebra-cabeca', 'brinquedo']),
      ('sticker', 'Ursinho', 'https://openmoji.org/data/color/svg/1F9F8.svg', ARRAY['brinquedo', 'ursinho', 'fofo']),
      ('sticker', 'Foguete', 'https://openmoji.org/data/color/svg/1F680.svg', ARRAY['foguete', 'espaco', 'voar']),
      ('sticker', 'Notas Musicais', 'https://openmoji.org/data/color/svg/1F3B6.svg', ARRAY['musica', 'som', 'notas']),
      ('sticker', 'Gatinho', 'https://openmoji.org/data/color/svg/1F431.svg', ARRAY['animal', 'gato', 'gatinho', 'fofo']),
      ('sticker', 'Cachorrinho', 'https://openmoji.org/data/color/svg/1F436.svg', ARRAY['animal', 'cao', 'cachorro', 'fofo']),
      ('sticker', 'Maçã', 'https://openmoji.org/data/color/svg/1F34E.svg', ARRAY['fruta', 'maca', 'comida']),
      ('sticker', 'Balão', 'https://openmoji.org/data/color/svg/1F388.svg', ARRAY['festa', 'balao', 'bexiga']),
      ('sticker', 'Estrela Brilhante', 'https://openmoji.org/data/color/svg/1F31F.svg', ARRAY['estrela', 'brilho', 'premio']),
      ('sticker', 'Coração', 'https://openmoji.org/data/color/svg/1F496.svg', ARRAY['amor', 'coracao', 'carinho']),
      ('sticker', 'Balão de Fala', 'https://openmoji.org/data/color/svg/1F4AC.svg', ARRAY['fala', 'dialogo', 'comunicacao']),
      ('sticker', 'Estrela de Ouro', 'https://openmoji.org/data/color/svg/2B50.svg', ARRAY['estrela', 'ouro', 'premio']),
      ('sticker', 'Medalha', 'https://openmoji.org/data/color/svg/1F3C5.svg', ARRAY['medalha', 'premio', 'vitoria']),
      ('sticker', 'Troféu', 'https://openmoji.org/data/color/svg/1F3C6.svg', ARRAY['trofeu', 'premio', 'campeao']),
      ('sticker', 'Lâmpada (Ideia)', 'https://openmoji.org/data/color/svg/1F4A1.svg', ARRAY['lampada', 'ideia', 'pensamento']),
      ('sticker', 'Sol', 'https://openmoji.org/data/color/svg/2600.svg', ARRAY['sol', 'dia', 'tempo']),
      ('sticker', 'Nuvem', 'https://openmoji.org/data/color/svg/2601.svg', ARRAY['nuvem', 'chuva', 'tempo']),
      ('sticker', 'Arco-íris', 'https://openmoji.org/data/color/svg/1F308.svg', ARRAY['arco-iris', 'cores', 'natureza']),
      ('sticker', 'Planta/Folha', 'https://openmoji.org/data/color/svg/1F331.svg', ARRAY['planta', 'folha', 'natureza', 'crescer']),
      ('sticker', 'Livros', 'https://openmoji.org/data/color/svg/1F4DA.svg', ARRAY['livros', 'leitura', 'estudo']),
      ('sticker', 'Lupa', 'https://openmoji.org/data/color/svg/1F50D.svg', ARRAY['lupa', 'pesquisa', 'detetive']),
      ('sticker', 'Globo', 'https://openmoji.org/data/color/svg/1F30D.svg', ARRAY['globo', 'terra', 'mapa', 'mundo']),
      ('sticker', 'Relógio', 'https://openmoji.org/data/color/svg/231A.svg', ARRAY['relogio', 'tempo', 'hora']),
      ('sticker', 'Tesoura', 'https://openmoji.org/data/color/svg/2702.svg', ARRAY['tesoura', 'recorte', 'escola']),
      ('sticker', 'Pincel', 'https://openmoji.org/data/color/svg/1F58C.svg', ARRAY['pincel', 'pintura', 'arte']),
      ('sticker', 'Régua', 'https://openmoji.org/data/color/svg/1F4CF.svg', ARRAY['regua', 'medida', 'escola']),
      
      ('forma', 'Retângulo', 'rect', ARRAY['forma', 'retangulo', 'fundo']),
      ('forma', 'Círculo', 'circle', ARRAY['forma', 'circulo', 'bola', 'redondo']),
      ('forma', 'Elipse', 'ellipse', ARRAY['forma', 'elipse', 'oval']),
      ('forma', 'Linha Horizontal', 'line', ARRAY['forma', 'linha', 'divisoria']),
      ('forma', 'Quadrado', 'square', ARRAY['forma', 'quadrado']),
      ('forma', 'Triângulo', 'triangle', ARRAY['forma', 'triangulo']),
      ('forma', 'Estrela', 'star', ARRAY['forma', 'estrela']),
      ('forma', 'Seta', 'arrow', ARRAY['forma', 'seta', 'direcao'])
    `);

    // Garantir que a coluna character_description_english exista na tabela projetos_comics
    await dbPool.query(`ALTER TABLE escola.projetos_comics ADD COLUMN IF NOT EXISTS character_description_english TEXT;`);

    // 0.1. Garantir que a tabela de modelos exista e tenha dados padrão
    await dbPool.query(`
      CREATE TABLE IF NOT EXISTS escola.modelos (
        id SERIAL PRIMARY KEY,
        nome VARCHAR(255) NOT NULL,
        finalidade TEXT,
        periodicidade VARCHAR(100),
        estrutura TEXT,
        created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
      );
    `);

    const checkModelos = await dbPool.query("SELECT COUNT(*) FROM escola.modelos;");
    if (parseInt(checkModelos.rows[0].count) === 0) {
      console.log('[Database Init] Inserindo modelos/templates padrão de relatório...');
      await dbPool.query(`
        INSERT INTO escola.modelos (nome, finalidade, periodicidade, estrutura) VALUES
        ($1, $2, $3, $4),
        ($5, $6, $7, $8);
      `, [
        "Relatório Semanal de Desenvolvimento",
        "Acompanhar a evolução cognitiva e social das crianças semanalmente",
        "Semanal",
        `# Relatório Semanal de Desenvolvimento Pedagógico\n\n## 1. Aspectos Cognitivos e Linguagem\n- Como a criança interagiu com os materiais e desafios propostos esta semana.\n- Expressão verbal, argumentação e participação em rodas de conversa.\n\n## 2. Aspectos Socioemocionais e Autonomia\n- Colaboração com os colegas e resolução de pequenos conflitos.\n- Nível de independência na realização das atividades diárias.\n\n## 3. Aspectos Motores e Brincar\n- Coordenação motora ampla e fina durante as brincadeiras e atividades direcionadas.`,
        "Avaliação Semestral Individual",
        "Avaliar o progresso do aluno combinando uma nova introdução global gerada a partir das atividades do semestre com a narrativa individual de desenvolvimento.",
        "Semestral",
        `O relatório final deve ser gerado sem cabeçalhos institucionais ou títulos de seções (como "I. O Eu..."), contendo apenas texto corrido e estruturado em parágrafos pedagógicos formais e acolhedores no estilo da Professora Camila. O relatório deve ser composto por duas partes principais integradas:\n\nPARTE 1: INTRODUÇÃO GERAL E CONTEXTUALIZAÇÃO COLETIVA (Texto Comum da Turma)\n(Instrução para a IA: Com base nas vivências, projetos e dinâmicas coletivas que aparecem no histórico de observações, redija um texto inédito de 4 a 5 parágrafos descrevendo o trabalho pedagógico coletivo do semestre. Não cite nomes de crianças nesta parte. Siga rigorosamente a estrutura de parágrafos abaixo):\n- Parágrafo 1 — Planejamento Pedagógico & Objetivos Gerais da Turma: Apresente os focos pedagógicos do período (desenvolvimento físico, cognitivo, afetivo/emocional) e o trabalho com a convivência harmoniosa, organização dos espaços/materiais e os combinados de rotina.\n- Parágrafo 2 — Vivências Lúdicas e Simbolismo: Descreva como as brincadeiras e interações simbólicas (faz de conta) serviram para as crianças compartilharem emoções, resolverem conflitos, desenvolverem a empatia, a linguagem e o raciocínio lógico.\n- Parágrafo 3 — Contato com a Cultura Escrita: Descreva o contato das crianças com o mundo letrado e a função social da escrita por meio do acesso a diferentes portadores e gêneros textuais (livros, cartazes, listas, receitas, canções, etc.).\n- Parágrafo 4 — Projeto Temático do Semestre: Detalhe o principal projeto de pesquisa da turma no semestre (ex: Identidade e Diversidade, plantio, etc.), descrevendo as investigações, atividades práticas realizadas e os registros/painéis construídos coletivamente.\n- Parágrafo 5 — Corporeidade, Jogos e Desportividade: Descreva o trabalho com jogos (de tabuleiro, confeccionados com recicláveis) e atividades motoras (circuitos, cantigas de roda, parlendas, corda), destacando o estímulo à atenção, cooperação, agilidade e respeito a regras de convivência.\n\n---\n\nPARTE 2: DESENVOLVIMENTO INDIVIDUAL (Texto Personalizado do Aluno)\n(Instrução para a IA: Redija na sequência um texto narrativo e contínuo, focado exclusivamente no aluno específico, baseado nas observações desse estudante. Adote como inspiração de estilo, tom e estrutura os dois exemplos reais abaixo):\n\nEXEMPLOS REAIS DE INSPIRAÇÃO (Alice e Theo):\nExemplo 1:\n"Alice apresentou muitos avanços neste período. Demonstra muita autonomia nas atividades de rotina, organizando seus pertences pessoais, bem como os brinquedos de uso coletivo após o término das vivências lúdicas. Na hora da alimentação, a aluna demonstra segurança ao servir-se e porcionar sua comida sem desperdícios. No parque, interage alegremente com os colegas, preferindo brincar com potes e pazinhas na areia, demonstrando grande companheirismo e compartilhando vivências com seus amigos, em especial com a Mirella e o Natan. Apresenta ótimo desenvolvimento motor, demonstrando equilíbrio ao pedalar os triciclos, participando de gincanas e realizando circuitos com destreza. Na roda de conversa, expressa-se com clareza e demonstra interesse pela leitura e contação de histórias, folheando livros individualmente. Demonstra facilidade em seguir regras e combinados coletivos."\n\nExemplo 2:\n"Theo apresentou muitos avanços neste período. No parque, gosta de brincar com brinquedos de rodas e na areia com seus amigos, em especial o Izack, o Gabriel e o Murilo, interagindo em brincadeiras simbólicas como o jogo do \'zumbi\'. Demonstra bom desenvolvimento motor amplo, realizando circuitos de equilíbrio e gincanas com agilidade. Nas atividades de expressão oral e corporal, participa de cantigas de roda e recitações. Theo demonstra grande interesse pela leitura e pelos livros, solicitando a contação de histórias com frequência. Constrói belas estruturas tridimensionais com blocos e lego, compartilhando brinquedos após intervenção do adulto. Apresenta boa autonomia na alimentação, porcionando sua refeição de forma consciente. Demonstra evolução na organização e no cumprimento de combinados de convivência na roda e na fila, necessitando ocasionalmente do direcionamento de um adulto."\n\nDIRETRIZES DE CONTRUÇÃO DO TEXTO DO ALUNO:\n- Início Obrigatório: Comece o primeiro parágrafo exatamente com a frase: "[Nome do Aluno] apresentou muitos avanços neste período." (Substitua pelo nome do aluno avaliado).\n- Corpo da narrativa: Integre de forma orgânica, fluida e sem subtítulos ou tópicos:\n  1. Autonomia na rotina diária (organização de materiais individuais/coletivos e porcionamento alimentar na escola sem desperdício).\n  2. Brincadeiras e interações sociais (preferências no parque/areia, identificando os melhores amigos entre aspas, ex: "a Mirella").\n  3. Desenvolvimento motor amplo e fino (pular cordas, pedalar triciclos, circuitos, gincanas) e o respeito aos combinados de organização (fila, roda).\n  4. Linguagem e oralidade (vocabulário, clareza verbal, participação em recitações/música e interesse em livros de histórias).\n  5. Criatividade nas atividades diversificadas (lego/blocos 3D, pinturas, desenhos de formas definidas).\n  6. Desafios/Pontos de atenção: Se houver, utilize sempre linguagem pedagógica atenuada (ex: "necessitando de orientação do adulto", "permanece o desafio de", "com intervenções constantes"). Nunca faça críticas diretas.\n- Fechamento Obrigatório: Finalize o relatório com o seguinte parágrafo isolado: "Espero que o/a [Nome do Aluno] continue se interessando bastante pela escola e avançando muito em suas aprendizagens no próximo ano." (Substitua pelo nome do aluno).`
      ]);
    }

    // 1. Sincronizar cache de configurações do agente ou migrar
    try {
      await dbPool.query(`
        DO $$ 
        BEGIN 
          IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='escola' AND table_name='config' AND column_name='kemily_avatar_url') THEN
            ALTER TABLE escola.config RENAME COLUMN kemily_avatar_url TO ia_avatar_url;
          END IF;
          IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_schema='escola' AND table_name='config' AND column_name='camila_avatar_url') THEN
            ALTER TABLE escola.config RENAME COLUMN camila_avatar_url TO user_avatar_url;
          END IF;
        END $$;
      `);
    } catch (migErr) {
      console.warn('[Database Init] Aviso ao verificar colunas da tabela escola.config:', migErr.message);
    }

    const configRes = await dbPool.query("SELECT agent_name, ia_avatar_url, user_avatar_url, temperature_preset FROM escola.config WHERE key = 'current';");
    if (configRes.rows.length > 0) {
      agentConfigCache = {
        agentName: configRes.rows[0].agent_name || "PedagogIA",
        iaAvatarUrl: configRes.rows[0].ia_avatar_url || "assets/pedagog_ia.png",
        userAvatarUrl: configRes.rows[0].user_avatar_url || "assets/pedagog_user.png",
        temperaturePreset: configRes.rows[0].temperature_preset || "equilibrado"
      };
      console.log('[Database Init] Configurações do agente carregadas do banco de dados:', agentConfigCache.agentName);
    } else {
      console.log('[Database Init] Migrando configurações do agente do arquivo local...');
      let localConfig = {
        agentName: "PedagogIA",
        iaAvatarUrl: "assets/pedagog_ia.png",
        userAvatarUrl: "assets/pedagog_user.png",
        temperaturePreset: "equilibrado"
      };
      try {
        if (fs.existsSync(CONFIG_FILE_PATH)) {
          localConfig = JSON.parse(fs.readFileSync(CONFIG_FILE_PATH, 'utf8'));
        }
      } catch (err) {
        console.error('[Database Init] Erro ao ler agent_config.json local:', err.message);
      }
      agentConfigCache = {
        agentName: localConfig.agentName || "PedagogIA",
        iaAvatarUrl: localConfig.iaAvatarUrl || localConfig.kemilyAvatarUrl || "assets/pedagog_ia.png",
        userAvatarUrl: localConfig.userAvatarUrl || localConfig.camilaAvatarUrl || "assets/pedagog_user.png",
        temperaturePreset: localConfig.temperaturePreset || "equilibrado"
      };
      await dbPool.query(
        "INSERT INTO escola.config (key, agent_name, ia_avatar_url, user_avatar_url, temperature_preset) VALUES ($1, $2, $3, $4, $5) ON CONFLICT (key) DO NOTHING;",
        ['current', agentConfigCache.agentName, agentConfigCache.iaAvatarUrl, agentConfigCache.userAvatarUrl, agentConfigCache.temperaturePreset]
      );
    }

    // 2. Verificar/Migrar fatos de conhecimento
    const knowledgeRes = await dbPool.query("SELECT COUNT(*) FROM escola.conhecimento;");
    if (parseInt(knowledgeRes.rows[0].count) === 0) {
      console.log('[Database Init] Migrando conhecimento do arquivo local...');
      let localKnowledge = { facts: [], lastUpdated: null };
      try {
        if (fs.existsSync(KNOWLEDGE_FILE_PATH)) {
          localKnowledge = JSON.parse(fs.readFileSync(KNOWLEDGE_FILE_PATH, 'utf8'));
        }
      } catch (err) {
        console.error('[Database Init] Erro ao ler conhecimento_adquirido.json local:', err.message);
      }
      for (const fact of localKnowledge.facts || []) {
        await dbPool.query(
          "INSERT INTO escola.conhecimento (content, source, added_at) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;",
          [fact.content, fact.source, fact.addedAt]
        );
      }
    }

    // 3. Verificar/Migrar observações pedagógicas
    const obsRes = await dbPool.query("SELECT COUNT(*) FROM escola.observacoes;");
    if (parseInt(obsRes.rows[0].count) === 0) {
      console.log('[Database Init] Migrando observações do arquivo local...');
      let localIndex = { observations: [] };
      try {
        if (fs.existsSync(OBS_INDEX_FILE)) {
          localIndex = JSON.parse(fs.readFileSync(OBS_INDEX_FILE, 'utf8'));
        }
      } catch (err) {
        console.error('[Database Init] Erro ao ler index.json local:', err.message);
      }
      for (const obs of localIndex.observations || []) {
        let reportContent = obs.preview;
        try {
          const yearMonth = obs.date.slice(0, 7);
          const filePath = path.join(OBSERVACOES_DIR, yearMonth, obs.filename);
          if (fs.existsSync(filePath)) {
            const rawFile = fs.readFileSync(filePath, 'utf8');
            const parts = rawFile.split('---\n');
            if (parts.length >= 3) {
              reportContent = parts.slice(2).join('---\n').trim();
            } else {
              reportContent = rawFile;
            }
          }
        } catch (fileErr) {
          console.error('[Database Init] Erro ao ler arquivo de relatório:', obs.filename, fileErr.message);
        }

        await dbPool.query(
          "INSERT INTO escola.observacoes (id, filename, date, time, timestamp, criancas, turma, tags, report) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (id) DO NOTHING;",
          [
            obs.id,
            obs.filename,
            obs.date,
            obs.time,
            obs.timestamp,
            JSON.stringify(obs.criancas),
            obs.turma,
            JSON.stringify(obs.tags),
            reportContent
          ]
        );
      }
    }
    console.log('[Database Init] Inicialização e migração do banco de dados concluídas com sucesso!');
  } catch (err) {
    console.error('[Database Init] Erro crítico ao inicializar banco de dados:', err);
  }
}

// Rota: POST /api/observacao/transcrever — recebe áudio e transcreve via Whisper
app.post('/api/observacao/transcrever', requireAuth, multerUpload.single('audio'), async (req, res) => {
  if (!req.file) {
    return res.status(400).json({ error: 'Nenhum áudio recebido' });
  }

  let ext = 'webm';
  if (req.file.mimetype) {
    if (req.file.mimetype.includes('mp4')) ext = 'mp4';
    else if (req.file.mimetype.includes('ogg')) ext = 'ogg';
    else if (req.file.mimetype.includes('wav')) ext = 'wav';
    else if (req.file.mimetype.includes('mpeg')) ext = 'mp3';
  }

  try {
    let transcribedText = '';
    const fileBlob = new Blob([req.file.buffer], { type: req.file.mimetype || 'audio/webm' });

    if (process.env.OPENAI_API_KEY) {
      console.log('[Smart Transcription] Usando OpenAI Whisper...');
      const whisperFormData = new FormData();
      whisperFormData.append('file', fileBlob, `audio.${ext}`);
      whisperFormData.append('model', 'whisper-1');
      whisperFormData.append('language', 'pt');
      whisperFormData.append('response_format', 'text');

      const whisperRes = await fetch('https://api.openai.com/v1/audio/transcriptions', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`
        },
        body: whisperFormData
      });

      if (!whisperRes.ok) {
        const errText = await whisperRes.text();
        console.error('OpenAI Whisper error:', errText);
        throw new Error('Erro na transcrição via OpenAI');
      }
      transcribedText = await whisperRes.text();
    } else if (process.env.GROQ_API_KEY) {
      console.log('[Smart Transcription] Usando Groq Whisper...');
      const whisperFormData = new FormData();
      whisperFormData.append('file', fileBlob, `audio.${ext}`);
      whisperFormData.append('model', 'whisper-large-v3');
      whisperFormData.append('language', 'pt');
      whisperFormData.append('response_format', 'text');

      const whisperRes = await fetch('https://api.groq.com/openai/v1/audio/transcriptions', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.GROQ_API_KEY}`
        },
        body: whisperFormData
      });

      if (!whisperRes.ok) {
        const errText = await whisperRes.text();
        console.error('Groq Whisper error:', errText);
        throw new Error('Erro na transcrição via Groq');
      }
      transcribedText = await whisperRes.text();
    } else {
      throw new Error('Nenhuma chave de API configurada para transcrição de áudio (OPENAI_API_KEY ou GROQ_API_KEY).');
    }

    if (!transcribedText || transcribedText.trim().length < 2) {
      return res.status(400).json({ error: 'O áudio não contém fala identificável.' });
    }

    res.json({ transcribedText });

  } catch (err) {
    console.error('Erro transcrever:', err);
    res.status(500).json({ error: err.message });
  }
});

// Rota: POST /api/observacao/analisar — recebe texto transcrito e gera relatório pedagógico
app.post('/api/observacao/analisar', requireAuth, async (req, res) => {
  const { transcribedText, turmaContext } = req.body;
  if (!transcribedText || transcribedText.trim().length < 5) {
    return res.status(400).json({ error: 'Texto muito curto ou inexistente para análise.' });
  }

  try {
    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";
    const temperaturePreset = agentConfig.temperaturePreset || 'equilibrado';
    const presetDesc = TEMPERATURE_PRESETS[temperaturePreset]?.description || TEMPERATURE_PRESETS['equilibrado'].description;
    const presetLabel = TEMPERATURE_PRESETS[temperaturePreset]?.label || 'Equilibrado';
    const temperatureValue = TEMPERATURE_PRESETS[temperaturePreset]?.value || 0.6;

    let contextStr = '';
    if (turmaContext && Array.isArray(turmaContext) && turmaContext.length > 0) {
      contextStr = `\nCONTEXTO DA TURMA DA PROFESSORA (IMPORTANTE):\nEsta é a lista atualizada das crianças cadastradas na "Minha Turma".\nATENÇÃO: Se algum nome no áudio corresponder a uma criança listada abaixo, você DEVE incorporar as necessidades ou condições relatadas nela para enriquecer o relatório, especialmente se a criança tiver "especial": true (Educação Especial). Se a criança for especial, a análise da observação deve ser orientada por essa condição e pelo diagnóstico associado.\nLista de Crianças:\n`;
      contextStr += JSON.stringify(turmaContext.map(c => ({
        nome: c.nome,
        apelido: c.apelido,
        especial: c.especial,
        diagnostico_observacoes: c.especialDetalhes
      })), null, 2);
      contextStr += `\n\n`;
    }

    const reportPrompt = `Você é a ${agentName}, assistente pedagógica da professora Camila Martella Gasparini Reifonas.
${contextStr}Ela acabou de registrar uma observação sobre seus alunos da educação infantil. Sua tarefa é ANALISAR o texto e retornar UM OBJETO JSON (sem markdown, sem código, apenas o JSON puro) com:

{
  "criancas": ["lista de nomes de crianças mencionadas ou 'null' se não identificar nenhuma"],
  "turma": "turma mencionada ou 'não informada'",
  "tags": ["tag1", "tag2"] (social, cognitivo, motor, linguagem, emocional, colaborativo, autónomo),
  "relatorio": "...markdown estruturado do relatório pedagógico..."
}

REGRAS:
- Identifique crianças pelo nome próprio mencionado na fala/texto
- Se não conseguir identificar nomes, use "criancas": null
- Tags: escolha entre social, cognitivo, motor, linguagem, emocional, colaborativo, autónomo
- Relatório em português, linguagem formal-pedagógica, seções claras em **markdown**
- Foque em evidências observáveis

🎯 DIRETRIZ DE ESTILO DE RESPOSTA (Obrigatório):
Você foi configurada para atuar no modo: "${presetLabel}".
Instrução comportamental: ${presetDesc}
Ajuste a extensão, a objetividade e o nível de detalhes do conteúdo da chave "relatorio" para refletir ESTRITAMENTE essa diretriz de estilo (ex: se "técnico e direto", faça um relatório mais sintético, pontual e breve; se "detalhista", elabore e estenda mais os contextos e reflexões pedagógicas; se "equilibrado", mantenha um tamanho médio).

OBSERVAÇÃO REGISTRADA:
"""
${transcribedText}
"""

Retorne APENAS o JSON válido:`;

    let rawContent = '';

    // Tentar MiniMax (M3) como primário
    try {
      console.log('[Smart Report] Tentando gerar relatório via MiniMax M3...');
      const result = await callMinimax({
        messages: [{ role: 'user', content: reportPrompt }],
        temperature: temperatureValue,
        max_tokens: 1500
      });
      rawContent = result.text;
    } catch (err) {
      console.error('[Smart Report] Erro de conexão com MiniMax:', err.message);
    }

    // Fallback para OpenRouter se MiniMax falhar
    if (!rawContent && process.env.OPENROUTER_API_KEY) {
      try {
        console.log('[Smart Report] Usando OpenRouter como fallback...');
        const reportRes = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
            messages: [{ role: 'user', content: reportPrompt }],
            temperature: temperatureValue,
            max_tokens: 1500
          })
        });

        if (reportRes.ok) {
          const reportData = await reportRes.json();
          rawContent = reportData.choices?.[0]?.message?.content || '';
        } else {
          console.warn('[Smart Report] Falha no OpenRouter:', await reportRes.text());
        }
      } catch (err) {
        console.error('[Smart Report] Erro de conexão com OpenRouter:', err.message);
      }
    }

    // Fallback para Groq se OpenRouter falhar
    if (!rawContent && process.env.GROQ_API_KEY) {
      try {
        console.log('[Smart Report] Usando Groq como fallback para gerar relatório...');
        const reportRes = await fetch('https://api.groq.com/openai/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GROQ_MODEL || 'llama-3.3-70b-versatile',
            messages: [{ role: 'user', content: reportPrompt }],
            temperature: temperatureValue,
            max_tokens: 1500
          })
        });

        if (reportRes.ok) {
          const reportData = await reportRes.json();
          rawContent = reportData.choices?.[0]?.message?.content || '';
        } else {
          console.warn('[Smart Report] Falha no Groq:', await reportRes.text());
        }
      } catch (err) {
        console.error('[Smart Report] Erro de conexão com Groq:', err.message);
      }
    }

    // Fallback para Google Gemini se OpenRouter e Groq falharem
    if (!rawContent && process.env.GOOGLE_AI_API_KEY) {
      try {
        console.log('[Smart Report] Usando Google Gemini como fallback para gerar relatório...');
        const reportRes = await fetch('https://generativelanguage.googleapis.com/v1beta/openai/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GOOGLE_AI_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash',
            messages: [{ role: 'user', content: reportPrompt }],
            temperature: temperatureValue,
            max_tokens: 1500
          })
        });

        if (reportRes.ok) {
          const reportData = await reportRes.json();
          rawContent = reportData.choices?.[0]?.message?.content || '';
        } else {
          console.warn('[Smart Report] Falha no Google Gemini:', await reportRes.text());
        }
      } catch (err) {
        console.error('[Smart Report] Erro de conexão com Google Gemini:', err.message);
      }
    }

    // Fallback para MiniMax se OpenRouter, Groq e Google Gemini falharem
    if (!rawContent && process.env.MINIMAX_API_KEY) {
      try {
        console.log('[Smart Report] Usando MiniMax-M2.7 como fallback para gerar relatório...');
        const reportRes = await fetch('https://api.minimaxi.chat/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: 'MiniMax-M2.7',
            messages: [{ role: 'user', content: reportPrompt }],
            temperature: 0.5,
            max_tokens: 1500
          })
        });

        if (reportRes.ok) {
          const reportData = await reportRes.json();
          rawContent = reportData.choices?.[0]?.message?.content || '';
        } else {
          console.warn('[Smart Report] Falha no MiniMax:', await reportRes.text());
        }
      } catch (err) {
        console.error('[Smart Report] Erro de conexão com MiniMax:', err.message);
      }
    }

    if (!rawContent) {
      throw new Error('Falha ao processar o relatório pedagógico em todos os provedores de IA (OpenRouter, Groq, Google Gemini e MiniMax-M2.7).');
    }

    // Parse JSON da resposta
    let parsedData = { criancas: null, turma: 'não informada', tags: [], relatorio: rawContent };
    try {
      const firstBrace = rawContent.indexOf('{');
      const lastBrace = rawContent.lastIndexOf('}');
      if (firstBrace !== -1 && lastBrace !== -1) {
        const jsonString = rawContent.substring(firstBrace, lastBrace + 1);
        const parsed = JSON.parse(jsonString);
        parsedData = {
          criancas: parsed.criancas || null,
          turma: parsed.turma || 'não informada',
          tags: parsed.tags || [],
          relatorio: parsed.relatorio || rawContent
        };
      } else {
        throw new Error('Nenhum JSON encontrado na resposta da IA');
      }
    } catch (parseErr) {
      console.warn('Não foi possível parsear JSON da resposta da IA:', parseErr.message);
      parsedData.relatorio = rawContent;
    }

    res.json(parsedData);

  } catch (err) {
    console.error('Erro ao analisar observação:', err);
    res.status(500).json({ error: err.message });
  }
});

// Rota: POST /api/observacao/salvar — salva relatório em .md
app.post('/api/observacao/salvar', requireAuth, async (req, res) => {
  const { report, criancas, turma, tags } = req.body;
  if (!report) return res.status(400).json({ error: 'Relatório vazio' });

  const now = new Date();
  const yearMonth = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
  const dateStr = now.toISOString().split('T')[0];
  const timeStr = now.toTimeString().slice(0, 5).replace(':', 'h') + 'min';
  const timestamp = now.toISOString();
  const id = Date.now();
  const filename = `${dateStr}_${timeStr}.md`;

  // Salvar no Banco (Supabase)
  try {
    await dbPool.query(
      "INSERT INTO escola.observacoes (id, filename, date, time, timestamp, criancas, turma, tags, report) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);",
      [id, filename, dateStr, timeStr, timestamp, JSON.stringify(criancas), turma || 'não informada', JSON.stringify(tags), report]
    );
  } catch (dbErr) {
    console.error('Erro ao salvar observação no banco:', dbErr);
  }

  // Monta markdown com frontmatter
  const mdContent = `---\ndata: "${dateStr}"\nhora: "${timeStr}"\ntimestamp: "${timestamp}"\ncriancas: ${JSON.stringify(criancas)}\nturma: "${turma || 'não informada'}"\ntags: ${JSON.stringify(tags)}\n---\n\n${report}\n`;
  const monthDir = path.join(OBSERVACOES_DIR, yearMonth);
  if (!fs.existsSync(monthDir)) fs.mkdirSync(monthDir, { recursive: true });

  const filePath = path.join(monthDir, filename);
  fs.writeFileSync(filePath, mdContent, 'utf8');

  // Também mantemos a escrita no index.json local para fins de compatibilidade
  try {
    const idx = readObsIndex();
    idx.observations.unshift({
      id,
      filename,
      date: dateStr,
      time: timeStr,
      timestamp,
      criancas: criancas || null,
      turma: turma || 'não informada',
      tags: tags || [],
      preview: report.slice(0, 120) + '...'
    });
    writeObsIndex(idx);
  } catch (idxErr) {
    console.error('Erro ao salvar índice local:', idxErr);
  }

  res.json({ success: true, filename, id });
});

// ============================================================
// CRUD DE MODELOS DE RELATÓRIO (TEMPLATES) & COMPILAÇÃO
// ============================================================

// Listar todos os modelos
app.get('/api/modelos', requireAuth, async (req, res) => {
  try {
    const dbRes = await dbPool.query("SELECT * FROM escola.modelos ORDER BY nome ASC;");
    res.json(dbRes.rows);
  } catch (err) {
    console.error('Erro ao buscar modelos:', err);
    res.status(500).json({ error: err.message });
  }
});

// Criar novo modelo
app.post('/api/modelos', requireAuth, async (req, res) => {
  const { nome, finalidade, periodicidade, estrutura } = req.body;
  if (!nome || !estrutura) return res.status(400).json({ error: 'Nome e estrutura são obrigatórios' });
  try {
    const dbRes = await dbPool.query(
      "INSERT INTO escola.modelos (nome, finalidade, periodicidade, estrutura) VALUES ($1, $2, $3, $4) RETURNING *;",
      [nome, finalidade, periodicidade, estrutura]
    );
    res.status(201).json(dbRes.rows[0]);
  } catch (err) {
    console.error('Erro ao criar modelo:', err);
    res.status(500).json({ error: err.message });
  }
});

// Atualizar modelo existente
app.put('/api/modelos/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  const { nome, finalidade, periodicidade, estrutura } = req.body;
  if (!nome || !estrutura) return res.status(400).json({ error: 'Nome e estrutura são obrigatórios' });
  try {
    const dbRes = await dbPool.query(
      "UPDATE escola.modelos SET nome = $1, finalidade = $2, periodicidade = $3, estrutura = $4 WHERE id = $5 RETURNING *;",
      [nome, finalidade, periodicidade, estrutura, id]
    );
    if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Modelo não encontrado' });
    res.json(dbRes.rows[0]);
  } catch (err) {
    console.error('Erro ao atualizar modelo:', err);
    res.status(500).json({ error: err.message });
  }
});

// Excluir modelo
app.delete('/api/modelos/:id', requireAuth, async (req, res) => {
  const { id } = req.params;
  try {
    const dbRes = await dbPool.query("DELETE FROM escola.modelos WHERE id = $1 RETURNING *;", [id]);
    if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Modelo não encontrado' });
    res.json({ success: true, message: 'Modelo excluído com sucesso' });
  } catch (err) {
    console.error('Erro ao excluir modelo:', err);
    res.status(500).json({ error: err.message });
  }
});

// Obter lista única de crianças já observadas
app.get('/api/observacoes/criancas', requireAuth, async (req, res) => {
  const { ano, turma } = req.query;
  try {
    let sql = `
      SELECT DISTINCT jsonb_array_elements_text(criancas) as nome 
      FROM escola.observacoes 
      WHERE jsonb_typeof(criancas) = 'array'
    `;
    const params = [];
    let pCount = 1;

    if (ano) {
      sql += ` AND substring(date from 1 for 4) = $${pCount}`;
      params.push(ano);
      pCount++;
    }

    if (turma) {
      sql += ` AND turma = $${pCount}`;
      params.push(turma);
      pCount++;
    }

    sql += ` ORDER BY nome;`;
    const dbRes = await dbPool.query(sql, params);
    res.json(dbRes.rows.map(r => r.nome));
  } catch (err) {
    console.error('Erro ao buscar crianças únicas:', err);
    res.status(500).json({ error: err.message });
  }
});

// Obter metadados únicos para filtros (anos letivos e turmas)
app.get('/api/observacoes/metadata', requireAuth, async (req, res) => {
  try {
    const yearsRes = await dbPool.query(`
      SELECT DISTINCT substring(date from 1 for 4) as ano 
      FROM escola.observacoes 
      WHERE date IS NOT NULL AND date != ''
      ORDER BY ano DESC;
    `);
    const turmasRes = await dbPool.query(`
      SELECT DISTINCT turma 
      FROM escola.observacoes 
      WHERE turma IS NOT NULL AND turma != ''
      ORDER BY turma;
    `);
    res.json({
      anos: yearsRes.rows.map(r => r.ano).filter(Boolean),
      turmas: turmasRes.rows.map(r => r.turma).filter(Boolean)
    });
  } catch (err) {
    console.error('Erro ao buscar metadados de observações:', err);
    res.status(500).json({ error: err.message });
  }
});

// Contar observações baseadas em filtros
app.get('/api/observacoes/contar', requireAuth, async (req, res) => {
  const { crianca, dataInicio, dataFim, tags, ano, turma } = req.query;
  try {
    let sql = `SELECT COUNT(*) FROM escola.observacoes WHERE 1=1`;
    const params = [];
    let pCount = 1;

    if (crianca && crianca !== 'todas') {
      sql += ` AND (
        (jsonb_typeof(criancas) = 'array' AND EXISTS (
          SELECT 1 FROM jsonb_array_elements_text(criancas) AS c 
          WHERE LOWER(c) LIKE $${pCount}
        ))
        OR jsonb_typeof(criancas) = 'null'
        OR criancas IS NULL
      )`;
      params.push(`%${crianca.toLowerCase()}%`);
      pCount++;
    }

    if (dataInicio) {
      sql += ` AND date >= $${pCount}`;
      params.push(dataInicio);
      pCount++;
    }

    if (dataFim) {
      sql += ` AND date <= $${pCount}`;
      params.push(dataFim);
      pCount++;
    }

    if (ano) {
      sql += ` AND substring(date from 1 for 4) = $${pCount}`;
      params.push(ano);
      pCount++;
    }

    if (turma) {
      sql += ` AND turma = $${pCount}`;
      params.push(turma);
      pCount++;
    }

    if (tags) {
      const tagList = tags.split(',').map(t => t.trim().toLowerCase());
      sql += ` AND jsonb_typeof(tags) = 'array' AND EXISTS (
        SELECT 1 FROM jsonb_array_elements_text(tags) AS t 
        WHERE LOWER(t) = ANY($${pCount})
      )`;
      params.push(tagList);
      pCount++;
    }

    const dbRes = await dbPool.query(sql, params);
    res.json({ count: parseInt(dbRes.rows[0].count) });
  } catch (err) {
    console.error('Erro ao contar observações:', err);
    res.status(500).json({ error: err.message });
  }
});

// Gerar relatório consolidado com IA baseado em observações e um modelo
app.post('/api/modelos/gerar-relatorio', requireAuth, async (req, res) => {
  const { crianca, dataInicio, dataFim, tags, modeloId, ano, turma } = req.body;
  if (!modeloId) return res.status(400).json({ error: 'O ID do modelo é obrigatório' });

  try {
    // 1. Buscar o modelo/template
    const templateRes = await dbPool.query("SELECT * FROM escola.modelos WHERE id = $1;", [modeloId]);
    if (templateRes.rows.length === 0) return res.status(404).json({ error: 'Modelo de relatório não encontrado' });
    const template = templateRes.rows[0];

    // 2. Buscar as observações filtradas
    let sql = `
      SELECT date, time, report, criancas, tags 
      FROM escola.observacoes 
      WHERE 1=1
    `;
    const params = [];
    let pCount = 1;

    if (crianca && crianca !== 'todas') {
      sql += ` AND (
        (jsonb_typeof(criancas) = 'array' AND EXISTS (
          SELECT 1 FROM jsonb_array_elements_text(criancas) AS c 
          WHERE LOWER(c) LIKE $${pCount}
        ))
        OR jsonb_typeof(criancas) = 'null'
        OR criancas IS NULL
      )`;
      params.push(`%${crianca.toLowerCase()}%`);
      pCount++;
    }

    if (dataInicio) {
      sql += ` AND date >= $${pCount}`;
      params.push(dataInicio);
      pCount++;
    }

    if (dataFim) {
      sql += ` AND date <= $${pCount}`;
      params.push(dataFim);
      pCount++;
    }

    if (ano) {
      sql += ` AND substring(date from 1 for 4) = $${pCount}`;
      params.push(ano);
      pCount++;
    }

    if (turma) {
      sql += ` AND turma = $${pCount}`;
      params.push(turma);
      pCount++;
    }

    if (tags) {
      const tagList = Array.isArray(tags) ? tags : [tags];
      sql += ` AND jsonb_typeof(tags) = 'array' AND EXISTS (
        SELECT 1 FROM jsonb_array_elements_text(tags) AS t 
        WHERE LOWER(t) = ANY($${pCount})
      )`;
      params.push(tagList.map(t => t.toLowerCase()));
      pCount++;
    }

    sql += ` ORDER BY date ASC`;
    const dbRes = await dbPool.query(sql, params);
    const observations = dbRes.rows;

    if (observations.length === 0) {
      return res.status(400).json({ error: 'Nenhuma observação encontrada para os filtros selecionados.' });
    }

    // 3. Montar prompt para IA usando a estrutura do template
    const combinedObsText = observations.map((o, idx) => {
      const dateText = o.date ? `Data: ${o.date}` : '';
      const tagsText = o.tags ? `Tags: ${Array.isArray(o.tags) ? o.tags.join(', ') : o.tags}` : '';
      return `[Observação #${idx + 1} ${dateText} ${tagsText}]\n${o.report}`;
    }).join('\n\n---\n\n');

    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";

    const isGlobal = crianca === 'todas';
    const targetStudentInfo = isGlobal 
      ? '' 
      : `\nATENÇÃO CRÍTICA: A criança que está sendo avaliada e para quem você deve gerar o relatório individual é especificamente: "${crianca}". Todas as observações, avanços, preferências e conclusões descritas na PARTE 2 (Desenvolvimento Individual) devem referir-se exclusivamente a ${crianca}. Nunca confunda ${crianca} com outros colegas eventualmente citados nas observações (como Joãozinho, Vanderlei, etc.), e não atribua as ações deles a ela.\n`;

    const systemPrompt = `Você é a ${agentName}, uma assistente pedagógica e especialista em educação infantil.
Seu objetivo é analisar o histórico de observações ${isGlobal ? 'da turma' : 'de uma criança'} e redigir um relatório pedagógico consolidado e profissional ${isGlobal ? 'globalizado da sala de aula / turma' : 'sobre o desenvolvimento individual desta criança'}.
${targetStudentInfo}

Você DEVE estruturar o relatório seguindo exatamente as seções e diretrizes fornecidas no modelo de relatório selecionado.

MODELO DE RELATÓRIO: "${template.nome}"
FINALIDADE: "${template.finalidade || 'Não informada'}"
PERIODICIDADE: "${template.periodicidade || 'Não informada'}"

ESTRUTURA/DIRETRIZES DO MODELO:
"""
${template.estrutura}
"""

HISTÓRICO DE OBSERVAÇÕES REGISTRADAS:
"""
${combinedObsText.slice(0, 8000)}
"""

Orientações adicionais:
- Redija o relatório em português brasileiro formal e adequado para pedagogia.
- Preencha cada seção da estrutura de forma rica e detalhada, baseando-se estritamente nos fatos reais descritos nas observações.
- Não invente acontecimentos que não estejam descritos nas observações, mas faça as conexões pedagógicas pertinentes de acordo com as evidências apresentadas.
- Retorne apenas o relatório em formato Markdown estruturado.`;

    let generatedReport = '';
    
    // Tenta MiniMax (M3) como primário
    try {
      console.log('[Compilar Relatório] Chamando MiniMax M3...');
      const result = await callMinimax({
        messages: [{ role: 'user', content: systemPrompt }],
        temperature: 0.5,
        max_tokens: 6000
      });
      generatedReport = result.text;
    } catch (err) {
      console.error('[Compilar Relatório] Erro no MiniMax:', err.message);
    }

    // Fallback para OpenRouter se MiniMax falhar
    if (!generatedReport && process.env.OPENROUTER_API_KEY) {
      try {
        console.log('[Compilar Relatório] Fallback para OpenRouter...');
        const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
            messages: [{ role: 'user', content: systemPrompt }],
            temperature: 0.5,
            max_tokens: 6000
          })
        });
        if (response.ok) {
          const data = await response.json();
          generatedReport = data.choices?.[0]?.message?.content || '';
        } else {
          console.warn('[Compilar Relatório] Falha no OpenRouter status:', response.status);
        }
      } catch (err) {
        console.error('[Compilar Relatório] Erro no OpenRouter:', err.message);
      }
    }

    // Fallback para Google Gemini
    if (!generatedReport && process.env.GOOGLE_AI_API_KEY) {
      try {
        console.log('[Compilar Relatório] Fallback para Google Gemini...');
        const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            contents: [{ parts: [{ text: systemPrompt }] }],
            generationConfig: { temperature: 0.5, maxOutputTokens: 8192 }
          })
        });
        if (response.ok) {
          const data = await response.json();
          generatedReport = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
        }
      } catch (err) {
        console.error('[Compilar Relatório] Erro no Google Gemini:', err.message);
      }
    }

    // Fallback para Groq
    if (!generatedReport && process.env.GROQ_API_KEY) {
      try {
        console.log('[Compilar Relatório] Fallback para Groq...');
        const response = await fetch('https://api.groq.com/openai/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GROQ_MODEL || 'llama-3.3-70b-versatile',
            messages: [{ role: 'user', content: systemPrompt }],
            temperature: 0.5,
            max_tokens: 6000
          })
        });
        if (response.ok) {
          const data = await response.json();
          generatedReport = data.choices?.[0]?.message?.content || '';
        }
      } catch (err) {
        console.error('[Compilar Relatório] Erro no Groq:', err.message);
      }
    }

    if (!generatedReport) {
      throw new Error('Falha ao compilar relatório pedagógico com todos os provedores de IA.');
    }

    res.json({
      report: generatedReport,
      templateNome: template.nome,
      crianca: crianca === 'todas' ? 'Sala de Aula (Relatório Global)' : (crianca || 'Todos os alunos'),
      periodo: (dataInicio || '') + (dataInicio && dataFim ? ' a ' : '') + (dataFim || 'Todo o período'),
      totalObservacoes: observations.length
    });
  } catch (err) {
    console.error('Erro geral ao compilar relatório:', err);
    res.status(500).json({ error: err.message });
  }
});

// Rota: GET /api/observacoes — lista todas as observações (índice)
app.get('/api/observacoes', requireAuth, async (req, res) => {
  try {
    const dbRes = await dbPool.query("SELECT id, filename, date, time, timestamp, criancas, turma, tags, SUBSTRING(report FROM 1 FOR 120) || '...' as preview FROM escola.observacoes ORDER BY timestamp DESC;");
    res.json({ observations: dbRes.rows });
  } catch (err) {
    console.error('Erro ao listar observações:', err);
    res.status(500).json({ error: err.message });
  }
});

// Rota: GET /api/observacoes/:yearMonth/:filename — busca relatório específico
app.get('/api/observacoes/:yearMonth/:filename', requireAuth, async (req, res) => {
  const { filename } = req.params;
  try {
    const dbRes = await dbPool.query("SELECT report, date, time, timestamp, criancas, turma, tags FROM escola.observacoes WHERE filename = $1;", [filename]);
    if (dbRes.rows.length === 0) {
      return res.status(404).json({ error: 'Não encontrado' });
    }
    const obs = dbRes.rows[0];
    const mdContent = `---\ndata: "${obs.date}"\nhora: "${obs.time}"\ntimestamp: "${obs.timestamp}"\ncriancas: ${JSON.stringify(obs.criancas)}\nturma: "${obs.turma}"\ntags: ${JSON.stringify(obs.tags)}\n---\n\n${obs.report}`;
    res.setHeader('Content-Type', 'text/markdown');
    res.send(mdContent);
  } catch (err) {
    console.error('Erro ao ler observação do banco:', err);
    res.status(500).json({ error: err.message });
  }
});

// Rota: GET /api/observacoes/buscar — busca observações por criança e/ou período
app.get('/api/observacoes/buscar', requireAuth, async (req, res) => {
  const { crianca, mes, tags, limit = 20 } = req.query;
  try {
    let sql = "SELECT id, filename, date, time, timestamp, criancas, turma, tags, report as \"fullContent\", SUBSTRING(report FROM 1 FOR 120) || '...' as preview FROM escola.observacoes WHERE 1=1";
    const params = [];
    let pCount = 1;

    if (crianca) {
      sql += ` AND jsonb_typeof(criancas) = 'array' AND EXISTS (
        SELECT 1 FROM jsonb_array_elements_text(criancas) AS c 
        WHERE LOWER(c) LIKE $${pCount}
      )`;
      params.push(`%${crianca.toLowerCase()}%`);
      pCount++;
    }

    if (mes) {
      sql += ` AND date LIKE $${pCount}`;
      params.push(`${mes}%`);
      pCount++;
    }

    if (tags) {
      const tagList = tags.split(',').map(t => t.trim().toLowerCase());
      sql += ` AND jsonb_typeof(tags) = 'array' AND EXISTS (
        SELECT 1 FROM jsonb_array_elements_text(tags) AS t 
        WHERE LOWER(t) = ANY($${pCount})
      )`;
      params.push(tagList);
      pCount++;
    }

    sql += ` ORDER BY timestamp DESC LIMIT $${pCount}`;
    params.push(parseInt(limit));

    const dbRes = await dbPool.query(sql, params);
    
    const totalRes = await dbPool.query("SELECT COUNT(*) FROM escola.observacoes;");
    const totalCount = parseInt(totalRes.rows[0].count);

    res.json({ observations: dbRes.rows, total: totalCount });
  } catch (err) {
    console.error('Erro ao buscar observações:', err);
    res.status(500).json({ error: err.message });
  }
});

// Rota: GET /api/observacoes/resumo — compila observações em resumo para documentos
app.get('/api/observacoes/resumo', requireAuth, async (req, res) => {
  const { crianca, mes } = req.query;
  if (!crianca) return res.status(400).json({ error: 'Nome da criança é obrigatório' });

  try {
    let sql = `
      SELECT date, time, report, tags, turma, SUBSTRING(report FROM 1 FOR 120) || '...' as preview 
      FROM escola.observacoes 
      WHERE jsonb_typeof(criancas) = 'array' AND EXISTS (
        SELECT 1 FROM jsonb_array_elements_text(criancas) AS c 
        WHERE LOWER(c) LIKE $1
      )
    `;
    const params = [`%${crianca.toLowerCase()}%`];

    if (mes) {
      sql += " AND date LIKE $2";
      params.push(`${mes}%`);
    }

    sql += " ORDER BY timestamp DESC LIMIT 15";

    const dbRes = await dbPool.query(sql, params);

    if (dbRes.rows.length === 0) {
      return res.json({ resumo: null, total: 0, message: `Nenhuma observação encontrada para "${crianca}"${mes ? ` em ${mes}` : ''}.` });
    }

    const observations = dbRes.rows;
    const texts = observations.map(o => o.report).filter(Boolean);
    const combined = texts.join('\n\n---\n\n');

    const agentConfig = readAgentConfig();
    const agentName = agentConfig.agentName || "PedagogIA";
    
    const compilePrompt = `Você é a ${agentName}, assistente pedagógica. Compilar um RESUMO PEDAGÓGICO consolidado a partir de várias observações de uma criança.

CRIANÇA: ${crianca}
NÚMERO DE OBSERVAÇÕES: ${observations.length}
${mes ? `PERÍODO: ${mes}` : ''}

COLEÇÃO DE OBSERVAÇÕES:
"""
${combined.slice(0, 4000)}
"""

GERE UM RESUMO ESTRUTURADO COM:

## Perfil da Criança
[Padrões de comportamento, habilidades, desenvolvimento observado]

## Aspectos Sociais e Emocionais
[Interações com outras crianças, gestão emocional, socialização]

## Aspectos Cognitivos e de Aprendizagem
[Curiosidade, resolução de problemas, atenção, concentration]

## Aspectos Motores e de Linguagem
[Desenvolvimento motor, linguagem oral, comunicação]

## Pontos de Atenção
[Dificuldades, necessidades de suporte, áreas a desenvolver]

## Pontos Fortes
[Conquistas, habilidades desenvolvidas, aspectos positivos]

## Sugestões para Planejamento
[Recomendações para próximos passos, atividades sugeridas, seguimento]

-use linguagem formal-pedagógica
-Máximo 300 palavras
-Seja objetivo e baseado nas evidências das observações`;

    const compileResult = await callMinimax({
      messages: [{ role: 'user', content: compilePrompt }],
      temperature: 0.4,
      max_tokens: 800
    });
    const resumoText = compileResult.text;

    res.json({
      crianca,
      mes: mes || 'todos',
      total: observations.length,
      datas: observations.map(o => o.date).filter(Boolean),
      resumo: resumoText,
      observations: observations.map(o => ({
        date: o.date, time: o.time, tags: o.tags, turma: o.turma, preview: o.preview
      }))
    });
  } catch (err) {
    console.error('Erro ao compilar resumo:', err);
    res.status(500).json({ error: 'Erro ao gerar resumo pedagógico' });
  }
});

// Rota POST para upload de imagem em base64
app.post('/api/upload-avatar', requireAuth, (req, res) => {
  const { avatarType, base64Data } = req.body;
  if (!avatarType || !base64Data) {
    return res.status(400).json({ error: 'Dados inválidos' });
  }

  const matches = base64Data.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
  if (!matches || matches.length !== 3) {
    return res.status(400).json({ error: 'Formato de imagem inválido' });
  }

  const imageBuffer = Buffer.from(matches[2], 'base64');
  const fileExt = matches[1].split('/')[1] || 'png';
  const fileName = `custom_${avatarType}_avatar_${Date.now()}.${fileExt}`;
  
  // Garantir que a pasta public/assets existe
  const assetsDir = path.join(__dirname, 'public', 'assets');
  if (!fs.existsSync(assetsDir)) {
    fs.mkdirSync(assetsDir, { recursive: true });
  }

  const filePath = path.join(assetsDir, fileName);

  fs.writeFile(filePath, imageBuffer, (err) => {
    if (err) {
      console.error('Erro ao salvar avatar:', err);
      return res.status(500).json({ error: 'Erro ao salvar arquivo' });
    }
    
    // Otimizar a imagem em segundo plano com Python
    const { exec } = require('child_process');
    exec(`python3 scratch/optimize_avatars.py "${filePath}"`, (pyErr) => {
      if (pyErr) {
        console.error('Erro ao otimizar o avatar enviado com Python:', pyErr);
      }
      return res.json({ success: true, url: `assets/${fileName}` });
    });
  });
});

// Rota de login (página)
app.get('/login', (req, res) => {
  const session = req.signedCookies.pedagog_session || req.cookies.pedagog_session || req.signedCookies.camila_session || req.cookies.camila_session;
  if (session === 'authenticated') {
    res.redirect('/');
  } else {
    res.sendFile(path.join(__dirname, 'public', 'login.html'));
  }
});

// API de status do modelo
app.get('/api/status', requireAuth, (req, res) => {
  res.json({
    model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
    provider: 'openrouter',
    minimax_available: !!process.env.MINIMAX_API_KEY,
    timestamp: new Date().toISOString()
  });
});

// API para Login
app.post('/api/login', (req, res) => {
  const { password } = req.body;
  if (password === ACCESS_PASSWORD) {
    // Define cookie de sessão assinado que dura 30 dias
    res.cookie('pedagog_session', 'authenticated', {
      signed: true,
      httpOnly: true,
      maxAge: 30 * 24 * 60 * 60 * 1000, // 30 dias
      sameSite: 'lax',
      secure: process.env.NODE_ENV === 'production'
    });
    res.json({ success: true });
  } else {
    res.status(401).json({ error: 'Senha incorreta' });
  }
});

// API para Logout
app.post('/api/logout', (req, res) => {
  res.clearCookie('pedagog_session');
  res.clearCookie('camila_session');
  res.json({ success: true });
});

app.use((req, res, next) => {
  if (
    req.path === '/login' ||
    req.path === '/login.html' ||
    req.path === '/login.js' ||
    req.path === '/style.css' ||
    req.path === '/manifest.json' ||
    req.path === '/sw.js' ||
    req.path === '/favicon.ico' ||
    req.path.startsWith('/assets/') ||
    req.path.startsWith('/generated-media/')
  ) {
    return next();
  }
  requireAuth(req, res, next);
});

// Interceptar requisições de mídia gerada para restaurar do banco se sumir do disco
app.get('/generated-media/:filename', async (req, res, next) => {
  const { filename } = req.params;
  const localPath = path.join(__dirname, 'public', 'generated-media', filename);

  if (fs.existsSync(localPath)) {
    return next();
  }

  try {
    const dbRes = await dbPool.query(
      `SELECT content_type, dados FROM escola.arquivos WHERE filename = $1`,
      [filename]
    );

    if (dbRes.rowCount === 0) {
      console.log(`[Media Server] Arquivo não encontrado no banco: ${filename}`);
      return res.status(404).send('Arquivo não encontrado');
    }

    const { content_type, dados } = dbRes.rows[0];

    const mediaDir = path.join(__dirname, 'public', 'generated-media');
    if (!fs.existsSync(mediaDir)) {
      fs.mkdirSync(mediaDir, { recursive: true });
    }
    fs.writeFileSync(localPath, dados);
    console.log(`[Media Server] Arquivo ${filename} carregado do banco de dados e gravado em cache.`);

    res.setHeader('Content-Type', content_type);
    return res.send(dados);
  } catch (err) {
    console.error(`[Media Server] Erro ao carregar ${filename} do banco:`, err);
    return res.status(500).send('Erro ao carregar arquivo');
  }
});

// Servir arquivos estáticos do diretório public
app.use(express.static(path.join(__dirname, 'public')));

// System prompt da PedagogIA (injetado em todas as conversas)
const PEDAGOG_IA_SYSTEM_PROMPT = {
  role: "system",
  content: `Você é a PedagogIA, assistente virtual e extensionista digital da Profª Camila Martella Gasparini Reifonas.

═══════════════════════════════════════════════════
IDENTIDADE — Quem é a Camila (para que você possa falar dela com precisão)
═══════════════════════════════════════════════════

INFORMAÇÕES PESSOAIS:
- Nome completo: Camila Martella Gasparini Reifonas
- Profissão: Professora da Educação Infantil na rede municipal de São Bernardo do Campo, SP

SITUAÇÃO FUNCIONAL / CONTEXTO TEMPORAL:
- **Ano de 2024 (ano passado)**: Professora efetiva de turma fixa, atuava no Infantil III (turma de 4-5 anos) como professora titular da turma.
- **Ano de 2025 (este ano)**: Professora substituta (não efetiva), não está em turma fixa. Atua no projeto Mind Lab como professora substituta, ajudando em atividades e projetos da coordenação.
- Etapa de atuação atual: projeto Mind Lab / substituição na Educação Infantil
- Local de trabalho: EMEB Mariana Neves Interliche — Sala 7, Turma B (contexto 2024)

ESCOLA — EMEB MARIANA NEVES INTERLICHE:
- Tipo: Escola Municipal de Educação Infantil (e Ensino Fundamental)
- Diretora: Maria (conforme documentos da Camila)
- Coordenadoras: Sirlene e Fernanda (coordenação pedagógica)
- Localização: São Bernardo do Campo, SP — região do Grande ABC

REDE MUNICIPAL DE SÃO BERNARDO DO CAMPO:
- Secretaria Municipal de Educação (SME)
- Subsidiado por: BNCC, Proposta Curricular de SBC, documento orientador da rede
- Formadora de referência (2024): Formadora Márcia (formações sobre leitura e escrita)
- Coordenadora de área/volante: Professora Sirlene

**ATENÇÃO - COORDENAÇÃO 2025:**
- Márcia era a formadora de referência em 2024. Para 2025, a coordenação pode ter mudado — não temos informação confirmada sobre quem ocupa esse papel neste ano. Se não souber, diga "não tenho essa informação".

SUPERIORAS/DOCENTES DE REFERÊNCIA (2024):
- Gestão da escola: Maria (diretora)
- Coordenação pedagógica: Sirlene e Fernanda (coordenadoras)
- Formadora da rede: Márcia (2024)
- Professora referenciada para substituição: Professora com quem Camila substitui

QUANDO A CAMILA PERGUNTAR "QUEM É A CAMILA" OU "FALE SOBRE MIM":
Responda com orgulho e detalhes, usando informações dos documentos reais. Exemplo:
"Você é a Profª Camila Martella Gasparini Reifonas, professora substituta da rede municipal de São Bernardo do Campo. Atua no Infantil III, turma B, Sala 7 da EMEB Mariana Neves Interliche. Você é reconhecida pelo seu compromisso com o protagonismo infantil, a escuta ativa e o desenvolvimento integral das crianças. Escreve em estilo formal-pedagógico com calor humano, utilizando expressões como 'colocando as crianças como protagonistas' e 'nos mais diversos contextos'. Trabalha com a metodologia Mind Lab, desenvolvimento fonológico e seguimento da BNCC, sempre priorizando a intencionalidade educativa em suas práticas."

SE NÃO SOUBER ALGUMA INFORMAÇÃO: Diga "Não tenho essa informação nos meus documentos, mas posso te ajudar a encontrar isso" — nunca invente.

═══════════════════════════════════════════════════
PERFIL DA PROFESSORA
═══════════════════════════════════════════════════

- Rede municipal de São Bernardo do Campo, SP
- EMEB Mariana Neves Interliche
- Turma: Infantil III – Turma B, Sala 7
- Faixa etária: crianças de 4-5 anos
- Atuação em substituição (professora substituta)
- Metodologias: Mind Lab (jogos certificados INMETRO), desenvolvimento fonológico, BNCC
- Estilo de escrita: formal-pedagógico com calor humano — frases longas, encadeadas, com posicionamentos pessoais ("ao meu ver", "creio que")

SUA MISSÃO:
Ajudar a Camila na criação de relatórios HTPC, planejamentos quinzenais/mensais, resumos de livros infantis, registros de formação, estudos de caso e documentos pedagógicos diversos.

═══════════════════════════════════════════════════
DIRETRIZ DE ATENDIMENTO DIRETO E OBJETIVO
═══════════════════════════════════════════════════

Você é inteligente o suficiente para discernir a NATUREZA da solicitação da Camila:
1. PEDIDOS DIRETOS DE CONTEÚDO: Se ela pedir algo DIRETAMENTE (ex: "me mostre a poesia A Porta de Vinicius de Moraes", "me dê a receita Y", "escreva o texto Z"), você DEVE fornecer EXATAMENTE E IMEDIATAMENTE o conteúdo pedido, sem longas análises biográficas, sem introduções teóricas enormes e sem rodeios. Apenas entregue o material solicitado.
2. PEDIDOS DE ANÁLISE/REFLEXÃO: Se ela pedir uma ANÁLISE ou CONTEXTUALIZAÇÃO (ex: "analise essa observação", "o que você acha dessa poesia para a educação infantil?", "faça um relatório sobre isso"), aí sim você deve usar todo o seu lado analítico, detalhista e aplicar intensamente o estilo pedagógico.
RESUMO: Respeite a intenção primária do pedido. Entregue primeiro o que foi efetivamente solicitado.

═══════════════════════════════════════════════════
APRENDIZADO CONTÍNUO — Aprendendo com a Camila
═══════════════════════════════════════════════════

Você pode e DEVE aprender novas informações com a Camila durante nossas conversas.

QUANDO APRENDER algo novo (fato, nome, projeto, detalhe):
- Quando a Camila te ensinar algo, diga: "Anotado! Vou lembrar disso." ou similar
- Em seguida, chame a função para salvar esse conhecimento: o sistema gravará automaticamente

QUANDO NÃO SOUBER algo ou tiver DÚVIDA:
- Pergunte diretamente à Camila: "Camila, pode me ajudar a entender [assunto]? Quem é [pessoa]? O que é [projeto]?"
- Nunca finja que sabe — seja transparente e peça ajuda
- Após a resposta da Camila, o sistema salvará essa informação para consultas futuras

FONTES DE CONHECIMENTO VÁLIDAS:
- Conversas com a Camila (ela te ensina diretamente)
- Documentos que ela compartilhar (enviados por ela ou mencionados)
- Informações da pasta /Agente (dossiers, estilos, projetos)
- NÃO invente informações — se não aprendeu, pergunte

MANEIRA DE PEDIR ESCLARECIMENTO à Camila:
"Percebi que não tenho essa informação nos meus documentos. Você poderia me ensinar sobre [assunto]? Assim consigo te ajudar melhor da próxima vez."

QUANDO APRENDER algo novo da Camila:
Quando ela te ensinar um fato, nome, projeto ou detalhe novo, inclua NO FINAL da sua resposta a tag:
[KNOWLEDGE: o que você aprendeu]

Exemplo: se a Camila disser "a coordenação deste ano está sob responsabilidade da profª Ana", você responde normal e inclui:
[KNOWLEDGE: Coordenação 2025 está sob responsabilidade da profª Ana]

═══════════════════════════════════════════════════
ESTILO DE ESCRITA DA CAMILA — REGRAS RIGOROSAS
═══════════════════════════════════════════════════

A Camila escreve em estilo FORMAL-PEDAGÓGICO COM CALOR HUMANO. Você DEVE seguir todas estas regras ao produzir textos para ela:

1. FRASES LONGAS E ENCADEADAS:
   ✅ "O professor media esse conhecimento prévio, estando atento e propondo experiências significativas."
   ❌ Não use frases curtas e secas. Nunca fragmente o pensamento.

2. VOCABULÁRIO TÉCNICO-PEDAGÓGICO (uso natural, não forçado):
   - protagonismo, intencionalidade educativa, desenvolvimento integral
   - escuta ativa, vivências, saberes prévios, mediação, autorregulação
   - potencialidades, avanços, coerência, propostas
   - campos de experiência, BNCC, etapa do ensino

3. EXPRESSÕES CARACTERÍSTICAS (são MARCA, use com naturalidade):
   - "colocando as crianças como protagonistas"
   - "faz parte do mundo letrado"
   - "nos mais diversos contextos/formatos/portadores"
   - "propiciando assim"
   - "a meu entender" / "ao meu ver" / "creio que"
   - "dessa forma" / "nesse sentido" / "para além de"
   - "a partir de" / "por meio de" / "acerca de"
   - "partilhou" / "socializou" (em vez de "compartilhou")
   - "aguçando interesses e curiosidade"
   - "compartilhada" / "coletiva"

4. ESTRUTURA DE PARÁGRAFO:
   Afirmação inicial → Desenvolvimento com exemplos/conexões → Transição para próximo ponto

5. CENTRALIDADE NA CRIANÇA:
   Sempre volte ao estudante como protagonista do processo ensino-aprendizagem.

6. POSICIONAMENTO PESSOAL:
   Use "Considero que...", "Entendo que...", "Creio que...", "Ao meu ver..." para se posicionar.

7. O QUE EVITAR:
   - ❌ "de acordo com" → use "conforme", "segundo", "a partir de"
   - ❌ "no dia a dia" → use "cotidianamente", "no cotidiano"
   - ❌ "em relação a" → use "sobre", "acerca de", "a respeito de"
   - ❌ fragmentação em frases curtas
   - ❌ gírias ou linguagem informal
   - ❌ linguagem burocrática vazia

═══════════════════════════════════════════════════
EXEMPLOS REAIS DA ESCRITA DA CAMILA
═══════════════════════════════════════════════════

EXEMPLO 1 — Relatório HTPC (avaliação anual):
"O professor como observador, faz escuta ativa da criança, para compreender o que mais chama atenção da turma, leva para a roda de conversa, aponta as curiosidades e assim consegue planejar e executar as práticas pedagógicas, colocando as crianças como protagonistas do processo ensino-aprendizagem. O planejamento se baseia principalmente nos interesses das crianças, trazendo situações que desafiam e aguçam seus interesses e curiosidade."

EXEMPLO 2 — Carta de Intenções:
"Inicio esta carta de intenções para o ano de 2026 com esta citação atribuída à escritora Lya Luft, pois reconheço claramente que a organização e o atendimento estabelecidos na Educação Infantil atuam como fundamentos desta etapa basilar da vida humana, merecendo especial atenção, dos aspectos simples aos mais complexos, e da qualidade deste trabalho dependem as fases posteriores de crescimento, aprendizagem, amadurecimento e desenvolvimento integral de cada criança."

EXEMPLO 3 — Planejamento quinzenal:
"Durante a visita ao mercadinho, pretendo propor que as crianças criem uma salad nutritiva, a partir dos vegetais que encontrarem nos setores de hortifruti, latarias e congelados, verificando o preço desses alimentos, e posteriormente faremos uma listagem verificando quais tipos de vitaminas e sais minerais encontraremos nessa salada e sua funcionalidade para o organismo."

EXEMPLO 4 — Registro de formação:
"Fomos divididas nos seguintes grupos: Grupo 1 — Viviane, Camila, Patrícia e Vanessa: jogos estruturados, conversamos sobre apresentar jogos de chão para ir trabalhando regras gerais com as crianças, para depois utilizar material ATTO e Mind Lab com regras mais estabelecidas, cuidando das peças, utilizar Lego, repertoriar as crianças, deixar explorar o material utilizando peças maiores, deixar as crianças criando por um momento livres e depois contextualizando as montagens com alguma história."

═══════════════════════════════════════════════════
CONHECIMENTO ESPECÍFICO DA CAMILA
═══════════════════════════════════════════════════

PROJETOS E VIVÊNCIAS DA ESCOLA:
- Entradas coletivas (com presentations de poesias)
- Atividades intersalas (jogos de tabuleiro, brincadeiras coletivas, tema)
- Piquenique literário (contração de histórias pelas crianças do Infantil IV e V)
- Saraus de poesia
- Painéis coletivos e murais
- Dia da família na escola
- Sábado letivo (apresentações de turmas para famílias)
- Estudo do meio (ex: Borboletário de Diadema)
- Mercadinho (sala 4 — trabalho com números, preços, lista)
- Projeto de leitura (empréstimo de livros da BEI)
- Show de acrobacias, baile à fantasia, cabelo maluco
- Teatro de bonecos, cineminha com pipoca
- Gincana, cataventos

MOMENTOS DO HTPC:
- "Nutrição" (momento inicial com livro/vídeo compartilhado)
- Frase do dia
- Momento da brincadeira
- Pauta da reunião
- Grupos de discussão
- Encerramento com registro

LIVROS INFANTIS CONHECIDOS:
- "Shhh! Nós temos um plano" (Chris Haughton)
- "A Galinha Ruiva" (Byron Barton)
- "Marcelo, Marmelo, Martelo" (Ruth Rocha)
- "O Ponto" (Peter H. Reynolds)
- "O Fabricante de Lápis"
- "A Primeira Infância" (vídeo)
- "Como os doces brasileiros ficaram tão doces" (vídeo)

FORMADORAS/MENTORAS:
- Formadora Márcia (formações sobre leitura e escrita)
- Sirlene (coordenadora)
- Fernanda (coordenadora)
- Maria (gestora)

METODOLOGIA:
- Mind Lab (jogos certificados INMETRO: Tik Tak Toy, Hora do Rush, Caixa Mágica, Resta-Um, Quatro-em-linha)
- Material ATTO (durável, para jogos de chão)
- Brincadeiras musicais e cantigas de roda
- Contação de histórias
- Desenvolvimento fonológico (rimas, ritmo)
- Desenvolvimento sintático (estruturação de frases)
- Desenvolvimento semântico (significados e vocabulário)
- Desenvolvimento pragmático (uso social da linguagem)
- Professor como escriba

BNCC/CAMPOS DE EXPERIÊNCIA:
- O eu, o outro e o nós
- Corpo, gestos e movimentos
- Traços, sons, cores e formas
- Escuta, fala, pensamento e imaginação
- Espaços, tempos, quantidades, relações e transformações

ESCRITA DA CRIANÇA:
- Hipótese de escrita silábico-alfabética
- Hipótese de escrita alfabética

═══════════════════════════════════════════════════
SISTEMA DE OBSERVAÇÕES POR VOZ — Como acessar registros
═══════════════════════════════════════════════════

A Camila possui um sistema de OBSERVAÇÕES POR VOZ onde grava registros sobre crianças durante o dia. Esses registros são salvos e ficam disponíveis para consulta.

QUANDO A CAMILA PEDIR "observações da [nome da criança]", "resumo da [criança]", "ler observações da [nome]" ou similar:
→ Use a API: GET /api/observacoes/resumo?crianca=[nome]&mes=YYYY-MM
→ O sistema retorna um RESUMO PEDAGÓGICO compilado de todas as observações daquela criança no período
→ Use esse resumo para ajudar a elaborar HTPCs, relatórios, planejamentos

EXEMPLOS DE COMANDO:
- "mostre as observações da Maria deste mês"
- "resumo pedagógico da Ana"
- "ler observações do João"
- "compilar observações da Sofia em maio"

AO RECEBER O RESUMO:
- Analise o conteúdo returned
- Use para fundamentar documentos que a Camila pedir
- Se não houver observações, diga: "Não encontrei observações registradas para [criança] neste período. Posso ajudar de outra forma?"
- Integre o resumo de forma natural no documento que estiver criando

LIMITES:
- Máximo 15 observações por consulta
- Resumos são compactados para caber no contexto
- Se a Camila pedir mais detalhes, pode mostrar a lista de observações individuais

═══════════════════════════════════════════════════
REGRAS DE ATUAÇÃO
═══════════════════════════════════════════════════

1. Quando a Camila pedir um documento (relatório, planejamento, etc):
   - Seguir ESTRITAMENTE o estilo de escrita dela
   - Usar frases longas e encadeadas
   - Incluir vocabulário técnico-pedagógico natural
   - Posicionar-se pessoalmente quando apropriado
   - Manter centralidade na criança

2. Quando pedir resumo de livro infantil:
   - Identificar temas, personagens, lições
   - Sugerir atividades práticas de sala
   - Conectar com BNCC e campos de experiência

3. Quando pedir pesquisa pedagógica:
   - Fundamentar com referências reais
   - Dar exemplos práticos conectando com a realidade da turma

4. Para relatórios HTPC, usar estrutura:
   - Planejamento e execução das práticas pedagógicas
   - Coerência entre propostas e desenvolvimento
   - Diversidade de experiências e intencionalidade educativa
   - Avaliação e acompanhamento das aprendizagens

5. Nunca inventar nomes de crianças, atividades ou datas. Use apenas o que conhece dos documentos.

6. Referências: usar "conforme a BNCC", "segundo a Proposta Curricular de SBC", "a partir do documento orientador".

7. Se perguntarem sobre a Camila (quem ela é, onde trabalha, quem são suas superiores, etc): use as informações da seção IDENTIDADE. Se não souber algo, diga claramente que não tem a informação — nunca invente.

═══════════════════════════════════════════════════════════════════
ESTILO DOS RELATÓRIOS SEMESTRAIS — Como analisar e compor
═══════════════════════════════════════════════════════════════════

A Camila produz RELATÓRIOS SEMESTRAIS em um estilo pessoal, narrativo, 
positivo e descritivo — diferente dos modelos institucionais. Você DEVE 
seguir esse estilo quando a Camila pedir ajuda para analisar observações 
ou compor relatórios.

Estrutura de um relatório semestral completo:

1. INTRODUÇÃO COMUM (um texto só, para TODOS os alunos)
   → Refere-se à turma ("as crianças", "a turma", "foram realizadas")
   → Não menciona nomes de alunos
   → Cobre: planejamento pedagógico, projetos, atividades coletivas
   → Exemplo de abertura: "Em continuidade ao trabalho iniciado no 
     semestre anterior, o planejamento das ações em relação às crianças 
     visa favorecer o desenvolvimento físico, cognitivo, afetivo e 
     emocional..."

2. RELATÓRIO INDIVIDUAL DO ALUNO (um texto por aluno)
   → Sempre inicia: "[Nome] apresentou muitos avanços neste período."
   → Estrutura interna em blocos temáticos (sem numeração, sem títulos)
   → Finaliza com esperança/prognóstico para o próximo período

3. FECHAMENTO (obrigatório, padrão para todos os alunos)
   → "Espero que o/a [nome] continue se interessando bastante pela 
      escola e avançando muito em suas aprendizagens 
      [no próximo ano / e potencialidades no próximo ano]"

ESTRUTURA INTERNA DO RELATÓRIO INDIVIDUAL (blocos implícitos):

  Bloco 1 — Abertura
    "[Nome] apresentou muitos avanços neste período."

  Bloco 2 — Brincadeiras e Preferências
    Locomoção e playground, materiais preferidos, simbolismo nas 
    brincadeiras (faz-de-conta)
    Exemplo: "Gosta de utilizar objetos como potes e pazinhas para criar 
    formas com areia e simbolizar brincadeiras com esses elementos."

  Bloco 3 — Interações Sociais
    Melhores amigos (nomes entre aspas: "o Izack", "a Mirella"), 
    dinâmicas de grupo, resolução de conflitos
    Exemplo: "Seus melhores amigos no momento são a Mirella, o Natan 
    e a Cecília, mas sem prejuízo nas interações com os outros colegas."

  Bloco 4 — Desenvolvimento Motor e Corporal
    Habilidades físicas, regras e combinados, atividades coletivas
    Exemplo: "Demonstrou bastante agilidade nas brincadeiras envolvendo 
    cordas de pular, triciclos e circuitos com materiais diversificados."

  Bloco 5 — Linguagem, Oralidade e Comunicação
    Vocabulário, clareza, participação em recitação/histórias/música
    Exemplo: "Demonstra um bom vocabulário e expressa suas ideias 
    verbalmente com bastante clareza, sendo capaz de relacionar os 
    fatos em sequência e com coerência."

  Bloco 6 — Criatividade e Atividades Diversificadas
    Brinquedos, jogos, construções, arte
    Exemplo: "Nas atividades diversificadas tem preferido utilizar o 
    lego e peças plásticas, e produz formas relacionadas a casas, 
    prédios e torres."

  Bloco 7 — Autonomia e Cuidados (quando aplicável)
    Organização do material, alimentação, porcionamento
    Exemplo: "Ampliou sua preferência pelas carnes, frutas, verduras 
    e legumes, necessitando de orientação diária para porcionar 
    corretamente, evitando o desperdício."

  Bloco 8 — Desafios para o próximo período (se houver)
    FORMULAR SEMPRE com linguagem atenuada
    Use: "necessitando de", "permanece o desafio de", "algumas vezes"
    Nunca critique diretamente — sempre contextualizar
    Exemplo: "necessitando de intervenção dos adultos para que respeite 
    seu momento de participar, organizando-se em fila"
    Exemplo: "permanece para o próximo ano o desafio de esclarecer e 
    retomar comandas das propostas a serem realizadas"

  Bloco 9 — Fechamento (obrigatório)
    "Espero que o/a [nome] continue se interessando bastante pela 
    escola e avançando muito em suas aprendizagens no próximo ano."

VOCABULÁRIO E EXPRESSÕES CARACTERÍSTICAS:

  TERMOS QUE VOCÊ DEVE USAR:
  • "apresentou muitos avanços" → abertura (SEMPRE)
  • "demonstrou" → para qualidades observadas (nunca "mostrou")
  • "gosta de" / "gostava de" → preferências
  • "participou de" → atividades coletivas
  • "avançou" / "evoluiu" → progresso
  • "interações" → relação entre crianças
  • "brincadeiras simbólicas" → faz-de-conta
  • "vivências lúdicas" → experiências de aprendizagem
  • "espirito desportivo" → fair play
  • "trabalho coletivo" → cooperação
  • "cultura de paz" → resolução de conflitos
  • "sensibilidade e afetividade" → empatia
  • "concentração e foco" → atenção
  • "combidados" → regras da turma (nunca diga "regras" sozinho)
  • "porcionar" → para servings de comida
  • "ampliou sua preferência por" → alimentação

  EXPRESSÕES CARACTERÍSTICAS:
  • "a todo momento"
  • "juntos com" (nunca "junto com")
  • "de acordo com o que lhe é proposto"
  • "especialmente" → para destacar pontos marcantes
  • "demonstrando" → ações que evidenciam qualidades
  • "algumas vezes" → atenuar críticas
  • "assim como", "bem como" → conectivos

  O QUE NUNCA USAR:
  • "mostrou" (use "demonstrou")
  • "acordos" (use "combidados")
  • "regras" isoladamente (use "regras e combinados")
  • Críticas diretas ou palavras como "problema", "dificuldade"
  • Menção a condições de saúde ou situações familiares

  ESTILO DE FRASE:
  • Frases longas e descritivas, com vírgulas separando subordinadas
  • Predominância de verbos no pretérito imperfeito e perfeito
  • Nunca frases curtas e secas

EXEMPLO DE ABERTURA DO RELATÓRIO INDIVIDUAL (Alice):
"Alice apresentou muitos avanços neste período. Demonstra muita 
autonomia em todas as tarefas referentes à rotina escolar, na 
realização das atividades propostas, nos cuidados com o material de 
uso individual e coletivo, na organização e classificação dos 
brinquedos durante e após a sua utilização. Gosta de brincar nos 
balanços, e se entretém bastante com os potes e pazinhas na areia, 
criando muitas brincadeiras simbólicas utilizando esses materiais."

EXEMPLO DE FECHAMENTO DO RELATÓRIO INDIVIDUAL:
"Espero que a Alice continue se interessando bastante pela escola e 
avançando muito em suas aprendizagens no próximo ano."
"Espero que o Theo continue se interessando bastante pela escola e 
avançando muito em suas aprendizagens e potencialidades no próximo ano."

QUANDO A CAMILA PEDIR PARA COMPOR UM RELATÓRIO SEMESTRAL:
1. Analise as observações coletadas no período
2. Extraia FACTS puros: comportamentos observados (não interpretações)
3. Classifique cada fato em uma das 8 dimensões acima
4. Selecione os 6-8 fatos mais significativos
5. Compõe o relatório seguindo a estrutura narrativa da Camila
6. Use frases longas e encadeadas, vocabulário pedagógico natural
7. Desafios/formulações negativas → use linguagem atenuada

Respond SEMPRE em português do Brasil.`

};

// Rota de Chat com OpenRouter com Smart Router Multimodal integrado (MiniMax)
app.post('/api/chat', requireAuth, async (req, res) => {
  const { messages, forcedIntent } = req.body;

  if (!messages || !Array.isArray(messages)) {
    return res.status(400).json({ error: 'Mensagens inválidas' });
  }

  const lastMessage = messages[messages.length - 1];
  const promptText = lastMessage ? lastMessage.content : '';

  // 1. Detectar se há imagens anexadas para desviar para OpenRouter multimodal (Vision)
  const hasAttachedImages = promptText.includes('*[Imagem anexada:');

  let intent = forcedIntent || 'TEXT';

  // 1.1 Validações cruzadas de coerência com a intenção forçada
  if (forcedIntent && promptText) {
    if (forcedIntent === 'IMAGE' && /música|musica|cantiga|canção|cancao|trilha|áudio|audio|ouvir|som|melodia/i.test(promptText)) {
      res.setHeader('Content-Type', 'text/event-stream');
      res.setHeader('Cache-Control', 'no-cache');
      res.setHeader('Connection', 'keep-alive');
      res.write(`data: ${JSON.stringify({ content: "Ops! Você digitou uma solicitação de música/áudio, mas clicou no botão **Criar Imagem** (🎨). Para compor uma música, por favor, digite sua solicitação e clique no botão **Criar Música** (🎵)!" })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();
    }
    if (forcedIntent === 'AUDIO' && /imagem|desenhe|foto|ilustração|ilustracao|ilustre|desenho|figura|vídeo|video|animação|animacao/i.test(promptText)) {
      const isVideo = /vídeo|video|animação|animacao/i.test(promptText);
      res.setHeader('Content-Type', 'text/event-stream');
      res.setHeader('Cache-Control', 'no-cache');
      res.setHeader('Connection', 'keep-alive');
      res.write(`data: ${JSON.stringify({ content: `Ops! Você digitou uma solicitação de ${isVideo ? 'vídeo' : 'imagem'}, mas clicou no botão **Criar Música** (🎵). Para gerar um ${isVideo ? 'vídeo' : 'desenho/imagem'}, por favor, clique no botão correspondente (**${isVideo ? 'Criar Vídeo 🎬' : 'Criar Imagem 🎨'}**)!` })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();
    }
    if (forcedIntent === 'VIDEO' && /música|musica|cantiga|canção|cancao|trilha|áudio|audio|ouvir|som|melodia|imagem|desenhe|foto|ilustração/i.test(promptText)) {
      const isMusic = /música|musica|cantiga|canção|cancao/i.test(promptText);
      res.setHeader('Content-Type', 'text/event-stream');
      res.setHeader('Cache-Control', 'no-cache');
      res.setHeader('Connection', 'keep-alive');
      res.write(`data: ${JSON.stringify({ content: `Ops! Você digitou uma solicitação de ${isMusic ? 'música' : 'imagem'}, mas clicou no botão **Criar Vídeo** (🎬). Para realizar essa tarefa, por favor, clique no botão **${isMusic ? 'Criar Música 🎵' : 'Criar Imagem 🎨'}**!` })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();
    }
  }

  // Executar classificação automática apenas se não houver intenção forçada, não houver imagens anexadas e houver prompt
  if (!forcedIntent && !hasAttachedImages && promptText && (process.env.MINIMAX_API_KEY || process.env.OPENROUTER_API_KEY || process.env.GROQ_API_KEY)) {
    try {
      const systemDetector = "Você é um classificador de intenções de prompt. Classifique a última mensagem do usuário em uma destas quatro categorias exatas: 'TEXT' (para conversas normais, perguntas, redação de relatórios, planejamentos, textos pedagógicos, respostas gerais), 'IMAGE' (se o usuário estiver pedindo explicitamente para gerar, desenhar, criar ou ilustrar uma imagem, figura, ilustração ou foto), 'AUDIO' (se o usuário estiver pedindo explicitamente para gerar uma música, melodia, trilha sonora, cantiga ou som), ou 'VIDEO' (se o usuário estiver pedindo explicitamente para gerar ou criar um vídeo ou animação). Responda APENAS com a palavra da categoria em letras maiúsculas: TEXT, IMAGE, AUDIO, VIDEO.";

      let detectedText = null;
      let usingGroqForDetection = false;

      // 1) Tentar Groq como primário (ultrarrápido para classificação)
      if (process.env.GROQ_API_KEY) {
        console.log('[Smart Router] Detectando intenção via Groq (llama-3.1-8b-instant)...');
        try {
          const detectResponse = await fetch('https://api.groq.com/openai/v1/chat/completions', {
            method: 'POST',
            headers: {
              'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              model: 'llama-3.1-8b-instant',
              messages: [
                { role: 'system', content: systemDetector },
                { role: 'user', content: promptText }
              ],
              max_tokens: 20,
              temperature: 0.0
            })
          });
          if (detectResponse.ok) {
            const detectData = await detectResponse.json();
            detectedText = detectData.choices?.[0]?.message?.content?.toUpperCase() || null;
            usingGroqForDetection = !!detectedText;
          }
        } catch (groqErr) {
          console.error('[Smart Router] Falha de detecção na API da Groq, tentando MiniMax...', groqErr.message);
        }
      }

      // 2) Fallback: MiniMax M3
      if (!detectedText && process.env.MINIMAX_API_KEY) {
        try {
          console.log('[Smart Router] Detectando intenção via MiniMax M3...');
          const r = await callMinimax({
            messages: [{ role: 'user', content: promptText }],
            system: systemDetector,
            temperature: 0.0,
            max_tokens: 20
          });
          detectedText = r.text;
        } catch (err) {
          console.error('[Smart Router] Falha na detecção via MiniMax, tentando OpenRouter...', err.message);
        }
      }

      // 3) Fallback final: OpenRouter
      if (!detectedText && process.env.OPENROUTER_API_KEY) {
        try {
          console.log('[Smart Router] Detectando intenção via OpenRouter...');
          const detectResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', {
            method: 'POST',
            headers: {
              'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({
              model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
              messages: [
                { role: 'system', content: systemDetector },
                { role: 'user', content: promptText }
              ],
              max_tokens: 20,
              temperature: 0.0
            })
          });
          if (detectResponse.ok) {
            const detectData = await detectResponse.json();
            detectedText = detectData.choices?.[0]?.message?.content?.toUpperCase() || null;
          }
        } catch (err) {
          console.error('[Smart Router] Falha de detecção final na OpenRouter:', err.message);
        }
      }

      if (detectedText) {
        const txt = detectedText.toUpperCase();
        console.log(`[Smart Router] Resposta crua do detector: "${txt}"`);

        if (txt.includes('IMAGE')) {
          intent = 'IMAGE';
        } else if (txt.includes('AUDIO') || txt.includes('MUSIC')) {
          intent = 'AUDIO';
        } else if (txt.includes('VIDEO')) {
          intent = 'VIDEO';
        } else {
          intent = 'TEXT';
        }
        console.log(`[Smart Router] Intenção detectada para o prompt "${promptText.substring(0, 40)}...": ${intent}`);
      } else {
        console.error('[Smart Router] Nenhum detector respondeu; usando TEXT como fallback.');
      }
    } catch (err) {
      console.error('Erro no classificador de intenções (usando fallback TEXT):', err);
    }
  }

  // ==========================================================================
  // FLUXO DE IMAGEM (MiniMax M3 + image-01)
  // ==========================================================================
  if (intent === 'IMAGE') {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'Iniciando geração de imagem com o MiniMax...' })}\n\n`);

    try {
      // Prompt rewriting: pedir ao M3 para enriquecer o prompt em inglês
      let enrichedPrompt = promptText;
      try {
        const r = await callMinimax({
          system: "Reescreva o pedido de imagem do usuário em inglês como um prompt altamente detalhado para geradores de imagem (como Midjourney/Stable Diffusion). Adicione estilo de ilustração infantil fofa, vibrante, adequada para educação infantil, traço limpo, cores harmoniosas. Se a imagem contiver qualquer cartaz, letreiro, texto ou escrita, estes textos devem obrigatoriamente ser escritos em português do Brasil (Brazilian Portuguese), com ortografia correta, sem misturar outros idiomas ou gerar letras ilegíveis. Retorne apenas a tradução/prompt em inglês.",
          messages: [{ role: 'user', content: promptText }],
          temperature: 0.7,
          max_tokens: 200
        });
        enrichedPrompt = r.text || promptText;
        console.log('[Smart Router] Prompt reescrito para imagem (M3):', enrichedPrompt);
      } catch (err) {
        console.error('Erro ao reescrever prompt de imagem via M3:', err.message);
      }

      res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'Gerando ilustração pedagógica com MiniMax image-01...' })}\n\n`);

      // 1) Gerar imagem via endpoint nativo da MiniMax
      const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
      const genResp = await fetch(`${minmBase}/v1/image_generation`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model: 'image-01',
          prompt: enrichedPrompt,
          n: 1
        })
      });

      if (!genResp.ok) {
        const errText = await genResp.text();
        throw new Error(`MiniMax image-01 respondeu erro: ${errText}`);
      }

      const genData = await genResp.json();
      const imageUrl = genData.data?.image_urls?.[0];
      if (!imageUrl) {
        const msg = genData.base_resp?.status_msg || 'desconhecido';
        throw new Error(`Nenhuma URL de imagem. Status MiniMax: ${msg}`);
      }

      // 2) Baixar e salvar localmente no servidor (a URL expira em pouco tempo)
      const fs = require('fs');
      const mediaDir = path.join(__dirname, 'public', 'generated-media');
      if (!fs.existsSync(mediaDir)) {
        fs.mkdirSync(mediaDir, { recursive: true });
      }
      const imgFetch = await fetch(imageUrl);
      if (!imgFetch.ok) {
        throw new Error(`Falha ao baixar imagem: ${imgFetch.statusText}`);
      }
      const imgBuffer = Buffer.from(await imgFetch.arrayBuffer());
      const fileName = `imagem_${Date.now()}.jpg`;
      const filePath = path.join(mediaDir, fileName);
      fs.writeFileSync(filePath, imgBuffer);
      await backupMediaFile(filePath, imgBuffer);
      const localUrl = `/generated-media/${fileName}`;
      console.log(`[Smart Router] Imagem salva em e backup no banco: ${localUrl}`);

      res.write(`data: ${JSON.stringify({ type: 'image', url: localUrl, originalPrompt: promptText })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();

    } catch (error) {
      console.error('Erro na geração de imagem:', error);
      res.write(`data: ${JSON.stringify({ content: `Ocorreu um erro ao gerar a imagem: ${error.message}` })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();
    }
  }

  // ==========================================================================
  // FLUXO DE ÁUDIO/MÚSICA (MiniMax music-2.0)
  // ==========================================================================
  if (intent === 'AUDIO') {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'Compondo e gerando música pedagógica com MiniMax music-2.0...' })}\n\n`);

    try {
      if (!process.env.MINIMAX_API_KEY) {
        throw new Error('MINIMAX_API_KEY não configurada no servidor.');
      }

      // Estratégia: usar o M3 para gerar a letra (em PT-BR) e um prompt descritivo do estilo,
      // depois enviar pro music-2.0 gerar a música.
      let lyrics = '';
      let stylePrompt = '';
      try {
        const r = await callMinimax({
          system: "Você é um compositor pedagógico infantil. Para o pedido do usuário, gere APENAS um JSON válido com duas chaves: 'lyrics' (letra curta em português brasileiro, de 8 a 20 versos, rimada, alegre, repetitiva, com vocabulário adequado para educação infantil) e 'style' (descrição curta em inglês do estilo musical, ex: 'happy acoustic children song, ukulele, claps, 100 BPM'). Responda somente o JSON.",
          messages: [{ role: 'user', content: promptText }],
          temperature: 0.7,
          max_tokens: 400
        });
        const txt = r.text || '';
        // Tentar extrair JSON mesmo se vier com lixo
        const m = txt.match(/\{[\s\S]*\}/);
        if (m) {
          const parsed = JSON.parse(m[0]);
          lyrics = parsed.lyrics || '';
          stylePrompt = parsed.style || '';
        }
      } catch (err) {
        console.error('[Smart Router] Falha ao gerar letra/estilo via M3:', err.message);
      }

      // Fallback simples se M3 não produziu
      if (!lyrics) {
        lyrics = `Vamos brincar e aprender,\nCom alegria vamos cantar,\n${promptText}\nÉ divertido estudar!`;
      }
      if (!stylePrompt) {
        stylePrompt = 'happy acoustic children song, ukulele, claps, 100 BPM';
      }

      res.write(`data: ${JSON.stringify({ content: `🎵 *Compondo sua cantiga...* \n\n${lyrics}\n\n` })}\n\n`);

      // 1) Gerar música via endpoint nativo MiniMax music-2.0
      const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
      const musicResp = await fetch(`${minmBase}/v1/music_generation`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model: 'music-2.0',
          prompt: `${stylePrompt}, singing in Brazilian Portuguese, Brazilian Portuguese vocals, clear Brazilian pronunciation`,
          lyrics: lyrics,
          audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' }
        })
      });

      if (!musicResp.ok) {
        const errText = await musicResp.text();
        throw new Error(`MiniMax music-2.0 respondeu erro: ${errText}`);
      }

      const musicData = await musicResp.json();
      // O endpoint pode retornar audio em base64 dentro de data.audio
      const audioHex = musicData.data?.audio;
      if (!audioHex) {
        throw new Error('Nenhum dado de áudio retornado pela MiniMax music-2.0');
      }

      // 2) Salvar arquivo local
      const fs = require('fs');
      const mediaDir = path.join(__dirname, 'public', 'generated-media');
      if (!fs.existsSync(mediaDir)) {
        fs.mkdirSync(mediaDir, { recursive: true });
      }
      const fileName = `musica_${Date.now()}.mp3`;
      const filePath = path.join(mediaDir, fileName);
      const audioBuffer = Buffer.from(audioHex, 'hex');
      fs.writeFileSync(filePath, audioBuffer);
      await backupMediaFile(filePath, audioBuffer);
      const audioUrl = `/generated-media/${fileName}`;
      console.log(`[Smart Router] Música salva em e backup no banco: ${audioUrl}`);

      res.write(`data: ${JSON.stringify({ type: 'audio', url: audioUrl, originalPrompt: promptText })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();

    } catch (error) {
      console.error('Erro na geração de música:', error);
      res.write(`data: ${JSON.stringify({ content: `Desculpe, ocorreu um erro ao gerar a música: ${error.message}. No entanto, se você quiser, eu posso compor uma letra pedagógica sobre o assunto para você!` })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();
    }
  }

  // ==========================================================================
  // FLUXO DE VÍDEO (google/veo-3.1-lite via OpenRouter)
  // ==========================================================================
  if (intent === 'VIDEO') {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'Iniciando geração de vídeo com o modelo google/veo-3.1-lite...' })}\n\n`);

    try {
      // 1. Criar o job de geração de vídeo no OpenRouter
      const createResponse = await fetch('https://openrouter.ai/api/v1/videos', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model: 'google/veo-3.1-lite',
          prompt: promptText
        })
      });

      if (!createResponse.ok) {
        const errorText = await createResponse.ok ? '' : await createResponse.text();
        throw new Error(`Falha ao iniciar geração de vídeo no OpenRouter: ${errorText || createResponse.statusText}`);
      }

      const jobData = await createResponse.json();
      const jobId = jobData.id;
      const pollingUrl = jobData.polling_url || `https://openrouter.ai/api/v1/videos/${jobId}`;

      console.log(`[Smart Router] Job de vídeo iniciado no OpenRouter com ID: ${jobId}`);
      res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'Vídeo enfileirado no OpenRouter, aguardando processamento...' })}\n\n`);

      // 2. Polling loop para monitorar a conclusão do job
      let status = 'queued';
      let videoUrl = null;
      let attempts = 0;
      const maxAttempts = 30; // 3 minutos no total (6s por tentativa)

      while (attempts < maxAttempts) {
        attempts++;
        // Aguarda 6 segundos antes de verificar novamente
        await new Promise(resolve => setTimeout(resolve, 6000));

        console.log(`[Smart Router] Verificando status do job de vídeo ${jobId} (Tentativa ${attempts})...`);

        const statusResponse = await fetch(pollingUrl, {
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`
          }
        });

        if (!statusResponse.ok) {
          console.error(`[Smart Router] Falha ao consultar status do job de vídeo: ${statusResponse.statusText}`);
          continue;
        }

        const statusData = await statusResponse.json();
        status = statusData.status;

        if (status === 'completed') {
          videoUrl = statusData.unsigned_urls?.[0];
          break;
        } else if (status === 'failed') {
          throw new Error('A geração do vídeo foi marcada como falha no OpenRouter.');
        } else {
          // in_progress / queued / processing
          const progressPercent = Math.round((attempts / maxAttempts) * 100);
          res.write(`data: ${JSON.stringify({ type: 'media_status', message: `Processando vídeo no OpenRouter (${progressPercent}%)...` })}\n\n`);
        }
      }

      if (!videoUrl) {
        throw new Error('Tempo limite esgotado para a geração do vídeo no OpenRouter.');
      }

      res.write(`data: ${JSON.stringify({ type: 'media_status', message: 'Vídeo concluído! Baixando arquivo para o servidor...' })}\n\n`);

      // 3. Baixar o arquivo MP4 e salvar localmente
      const videoFetch = await fetch(videoUrl);
      if (!videoFetch.ok) {
        throw new Error('Falha ao efetuar download do arquivo de vídeo temporário');
      }

      const videoBuffer = await videoFetch.arrayBuffer();
      const fs = require('fs');
      const mediaDir = path.join(__dirname, 'public', 'generated-media');
      if (!fs.existsSync(mediaDir)) {
        fs.mkdirSync(mediaDir, { recursive: true });
      }

      const fileName = `video_${Date.now()}.mp4`;
      const filePath = path.join(mediaDir, fileName);
      const vBuf = Buffer.from(videoBuffer);
      fs.writeFileSync(filePath, vBuf);
      await backupMediaFile(filePath, vBuf);

      const localVideoUrl = `/generated-media/${fileName}`;
      console.log(`[Smart Router] Vídeo salvo em e backup no banco: ${localVideoUrl}`);

      res.write(`data: ${JSON.stringify({ type: 'video', url: localVideoUrl, originalPrompt: promptText })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();

    } catch (error) {
      console.error('Erro na geração de vídeo:', error);
      res.write(`data: ${JSON.stringify({ content: `Desculpe, ocorreu um erro ao gerar o vídeo: ${error.message}.` })}\n\n`);
      res.write('data: [DONE]\n\n');
      return res.end();
    }
  }

  // ==========================================================================
  // FLUXO PADRÃO DE TEXTO (OpenRouter com SSE streaming original, com fallback Groq)
  // ==========================================================================
  const agentConfig = readAgentConfig();
  const agentName = agentConfig.agentName || "PedagogIA";
  const temperaturePreset = agentConfig.temperaturePreset || 'equilibrado';
  const temperature = TEMPERATURE_PRESETS[temperaturePreset]?.value || 0.6;

  const knowledgeText = getKnowledgeAsText();

  // 1.2 Integração Dinâmica de Observações no Chat da IA
  let observationsContext = '';
  try {
    const idx = readObsIndex();
    const allChildren = new Set();
    (idx.observations || []).forEach(o => {
      if (o.criancas) {
        o.criancas.forEach(c => allChildren.add(c.trim()));
      }
    });

    const mentionedChildren = [];
    const promptLower = promptText.toLowerCase();
    
    for (const child of allChildren) {
      const regex = new RegExp(`\\b${child.toLowerCase()}\\b`, 'i');
      if (regex.test(promptLower)) {
        mentionedChildren.push(child);
      }
    }

    if (mentionedChildren.length > 0) {
      // Verificar palavras-chave relacionadas à busca de observações
      const askKeywords = ['observa', 'registro', 'historico', 'histórico', 'resumo', 'relatorio', 'relatório', 'desenvolvimento', 'evolução', 'evolucao', 'comportamento', 'como foi', 'como está', 'como esta', 'pedagó'];
      const isAskingAboutObs = askKeywords.some(kw => promptLower.includes(kw));

      if (isAskingAboutObs) {
        let compiledObservations = [];
        for (const child of mentionedChildren) {
          const childObs = (idx.observations || []).filter(o => 
            o.criancas && o.criancas.some(c => c.toLowerCase() === child.toLowerCase())
          );
          
          if (childObs.length > 0) {
            // Carrega as últimas 5 observações
            const lastObs = childObs.slice(0, 5);
            const obsTexts = [];
            for (const o of lastObs) {
              const filePath = path.join(OBSERVACOES_DIR, o.date?.slice(0, 7), o.filename);
              if (fs.existsSync(filePath)) {
                let content = fs.readFileSync(filePath, 'utf8');
                content = content.replace(/^---[\s\S]*?---\n/, '').trim();
                obsTexts.push(`- Data: ${o.date} (${o.time}):\n${content}`);
              }
            }
            if (obsTexts.length > 0) {
              compiledObservations.push(`### Observações de ${child}:\n${obsTexts.join('\n\n')}`);
            }
          }
        }

        if (compiledObservations.length > 0) {
          observationsContext = `\n\n════════════════════════════════════════\nOBSERVAÇÕES REGISTRADAS SOBRE AS CRIANÇAS CITADAS:\n${compiledObservations.join('\n\n')}\nUse as observações acima para responder à pergunta da Camila sobre o desenvolvimento/comportamento da criança de forma precisa e baseada em evidências.\n════════════════════════════════════════\n`;
          console.log(`[Smart Router] Injetando contexto de observações para:`, mentionedChildren);
        }
      }
    }
  } catch (obsErr) {
    console.error('Erro ao integrar observações no prompt da IA:', obsErr);
  }

  const styleDirective = `\n\n🎯 DIRETRIZ DE ESTILO DE RESPOSTA (Obrigatório):\nVocê foi configurada para responder no modo: "${TEMPERATURE_PRESETS[temperaturePreset]?.label || 'Equilibrado'}".\nInstrução comportamental: ${TEMPERATURE_PRESETS[temperaturePreset]?.description || 'Bom para uso geral — nem muito curto, nem muito longo.'}\nSiga rigorosamente este nível de detalhamento, extensão e objetividade em TODAS as suas respostas.\n\n`;

  const languageDirective = `\n\n⚠️ DIRETRIZ CRÍTICA DE IDIOMA E SAÍDA:
- Você deve responder EXCLUSIVAMENTE em português do Brasil (Brazilian Portuguese).
- Nunca inclua justificativas, introduções, pensamentos, comentários ou notas em inglês.
- Toda a resposta (incluindo qualquer texto lúdico, histórias, explicações ou poemas) deve ser apresentada 100% em português brasileiro, sem misturar outros idiomas.
- Retorne apenas o resultado final que o usuário solicitou.\n\n`;

  const dynamicSystemPrompt = {
    role: "system",
    content: styleDirective + languageDirective + PEDAGOG_IA_SYSTEM_PROMPT.content
      .replace(/Você é a PedagogIA/g, `Você é a ${agentName}`)
      .replace(/Você é a Kemily/g, `Você é a ${agentName}`)
      .replace('INFORMAÇÕES PESSOAIS:', `${knowledgeText}${observationsContext}INFORMAÇÕES PESSOAIS:`) + styleDirective + languageDirective
  };

  const enrichedMessages = [dynamicSystemPrompt, ...messages];

  try {
    let chatResponse;
    let providerUsed = 'MiniMax-M3';

    // 1) Tentar MiniMax M3 (Streaming OpenAI-compatível) para velocidade instantânea
    let useMinimaxStream = false;
    try {
      if (process.env.MINIMAX_API_KEY) {
        console.log('[Smart Router] Chamando MiniMax M3 (streaming OpenAI-compat)...');
        const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
        chatResponse = await fetch(`${minmBase}/v1/chat/completions`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.MINIMAX_MODEL || 'MiniMax-M3',
            messages: enrichedMessages,
            stream: true,
            temperature
          })
        });
        if (chatResponse.ok) {
          useMinimaxStream = true;
        } else {
          console.warn('[Smart Router] M3 streaming status', chatResponse.status, '- caindo no OpenRouter.');
        }
      }
    } catch (e) {
      console.error('Erro na conexão streaming com MiniMax M3, tentando OpenRouter...', e.message);
    }

    // 2) Fallback: OpenRouter (streaming SSE, igual ao original)
    if ((!chatResponse || !chatResponse.ok) && !useMinimaxStream && process.env.OPENROUTER_API_KEY) {
      console.log('[Smart Router] Usando OpenRouter como fallback de streaming de texto...');
      providerUsed = 'OpenRouter';
      try {
        chatResponse = await fetch('https://openrouter.ai/api/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
            'HTTP-Referer': 'https://pedagog.app',
            'X-Title': 'Pedagog AI',
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
            messages: enrichedMessages,
            stream: true,
            temperature
          })
        });
      } catch (e) {
        console.error('Erro na conexão com OpenRouter, tentando Groq...', e.message);
      }
    }

    // Se o OpenRouter falhar ou retornar erro (ex: status != 200), caímos no fallback do Groq
    if ((!chatResponse || !chatResponse.ok) && process.env.GROQ_API_KEY) {
      console.log('[Smart Router] Usando Groq (llama-3.3-70b-versatile) como fallback de streaming de texto...');
      providerUsed = 'Groq';
      try {
        chatResponse = await fetch('https://api.groq.com/openai/v1/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GROQ_MODEL || 'llama-3.3-70b-versatile',
            messages: enrichedMessages,
            stream: true,
            temperature
          })
        });
      } catch (groqErr) {
        console.error('[Smart Router] Erro crítico: Falha no fallback do Groq também:', groqErr.message);
      }
    }

    // Se o OpenRouter e Groq falharem ou retornarem erro, caímos no fallback do Google Gemini
    if ((!chatResponse || !chatResponse.ok) && process.env.GOOGLE_AI_API_KEY) {
      console.log('[Smart Router] Usando Google Gemini como fallback de streaming de texto...');
      providerUsed = 'Google Gemini';
      try {
        chatResponse = await fetch('https://generativelanguage.googleapis.com/v1beta/openai/chat/completions', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.GOOGLE_AI_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash',
            messages: enrichedMessages,
            stream: true,
            temperature
          })
        });
      } catch (geminiErr) {
        console.error('[Smart Router] Erro crítico: Falha no fallback do Google Gemini também:', geminiErr.message);
      }
    }

    // Se o OpenRouter, Groq e Google Gemini falharem ou retornarem erro, caímos no fallback do MiniMax M3 (streaming)
    if ((!chatResponse || !chatResponse.ok) && process.env.MINIMAX_API_KEY) {
      console.log('[Smart Router] Usando MiniMax-M3 (streaming OpenAI-compat) como último fallback...');
      providerUsed = 'MiniMax-M3';
      try {
        const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
        chatResponse = await fetch(`${minmBase}/v1/chat/completions`, {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            model: process.env.MINIMAX_MODEL || 'MiniMax-M3',
            messages: enrichedMessages,
            stream: true,
            temperature
          })
        });
      } catch (minimaxErr) {
        console.error('[Smart Router] Erro crítico: Falha no fallback do MiniMax M3 também:', minimaxErr.message);
      }
    }

    if (!chatResponse || !chatResponse.ok) {
      const errorText = chatResponse ? await chatResponse.text() : 'Sem conexão com os servidores de IA';
      console.error(`Erro na API de IA (${providerUsed}):`, errorText);
      return res.status(500).json({ error: `Erro ao processar requisição na API de IA (${providerUsed})` });
    }

    // Configurar headers para Server-Sent Events (SSE)
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache');
    res.setHeader('Connection', 'keep-alive');

    const reader = chatResponse.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let buffer = '';

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      buffer += decoder.decode(value, { stream: true });
      const lines = buffer.split('\n');
      
      // Manter a última linha inacabada no buffer
      buffer = lines.pop();

      for (const line of lines) {
        const cleanLine = line.trim();
        if (cleanLine.startsWith('data: ')) {
          const dataContent = cleanLine.slice(6);
          if (dataContent === '[DONE]') {
            res.write('data: [DONE]\n\n');
            continue;
          }
          try {
            const parsed = JSON.parse(dataContent);
            const content = parsed.choices?.[0]?.delta?.content || '';
            if (content) {
              res.write(`data: ${JSON.stringify({ content })}\n\n`);
            }
          } catch (e) {
            // Ignorar linhas incompletas ou JSON inválido
          }
        }
      }
    }

    // Enviar qualquer conteúdo restante no buffer
    if (buffer) {
      const cleanLine = buffer.trim();
      if (cleanLine.startsWith('data: ')) {
        const dataContent = cleanLine.slice(6);
        if (dataContent !== '[DONE]') {
          try {
            const parsed = JSON.parse(dataContent);
            const content = parsed.choices?.[0]?.delta?.content || '';
            if (content) {
              res.write(`data: ${JSON.stringify({ content })}\n\n`);
            }
          } catch (e) {}
        }
      }
    }

    res.write('data: [DONE]\n\n');
    res.end();

  } catch (error) {
    console.error('Erro na rota de chat:', error);
    res.status(500).json({ error: 'Erro interno no servidor ao chamar a API de IA' });
  }
});



// ============================================================
// CRUD TURMAS E ALUNOS (MINHA TURMA)
// ============================================================
app.get('/api/turmas', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query("SELECT * FROM escola.turmas WHERE usuario_id = $1 ORDER BY created_at ASC;", [usuarioId]);
    res.json(dbRes.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.post('/api/turmas', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
  if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
  try {
    const dbRes = await dbPool.query(
      "INSERT INTO escola.turmas (usuario_id, nome, sala, periodo, prof_titular, prof_aux1, prof_aux2, cuidadora) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *;",
      [usuarioId, nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora]
    );
    res.status(201).json(dbRes.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.put('/api/turmas/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;
  const { nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora } = req.body;
  if (!nome) return res.status(400).json({ error: 'Nome é obrigatório' });
  try {
    const dbRes = await dbPool.query(
      "UPDATE escola.turmas SET nome = $1, sala = $2, periodo = $3, prof_titular = $4, prof_aux1 = $5, prof_aux2 = $6, cuidadora = $7 WHERE id = $8 AND usuario_id = $9 RETURNING *;",
      [nome, sala, periodo, profTitular, profAux1, profAux2, cuidadora, id, usuarioId]
    );
    res.json(dbRes.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.delete('/api/turmas/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;
  try {
    await dbPool.query("DELETE FROM escola.turmas WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
    res.json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.get('/api/alunos', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { turma_id } = req.query;
  try {
    let query = "SELECT * FROM escola.alunos WHERE usuario_id = $1";
    let params = [usuarioId];
    if (turma_id) {
      query += " AND turma_id = $2";
      params.push(turma_id);
    }
    query += " ORDER BY nome ASC;";
    const dbRes = await dbPool.query(query, params);
    res.json(dbRes.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.post('/api/alunos', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
  if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
  let nascDate = data_nasc ? data_nasc : null;
  try {
    const dbRes = await dbPool.query(
      "INSERT INTO escola.alunos (usuario_id, turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *;",
      [usuarioId, turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados]
    );
    res.status(201).json(dbRes.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.put('/api/alunos/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;
  const { turma_id, nome, apelido, data_nasc, especial, especial_detalhes, pais, autorizados } = req.body;
  if (!nome || !turma_id) return res.status(400).json({ error: 'Nome e Turma são obrigatórios' });
  let nascDate = data_nasc ? data_nasc : null;
  try {
    const dbRes = await dbPool.query(
      "UPDATE escola.alunos SET turma_id = $1, nome = $2, apelido = $3, data_nasc = $4, especial = $5, especial_detalhes = $6, pais = $7, autorizados = $8 WHERE id = $9 AND usuario_id = $10 RETURNING *;",
      [turma_id, nome, apelido, nascDate, especial, especial_detalhes, pais, autorizados, id, usuarioId]
    );
    res.json(dbRes.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.delete('/api/alunos/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;
  try {
    await dbPool.query("DELETE FROM escola.alunos WHERE id = $1 AND usuario_id = $2;", [id, usuarioId]);
    res.json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ============================================================
// ROTAS DE IMPORTAÇÃO CSV, TIMELINE E CONSELHO DE ALUNOS
// ============================================================
app.post('/api/alunos/import-csv', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { turma_id, csv_content } = req.body;

  if (!turma_id || !csv_content) {
    return res.status(400).json({ error: 'Turma e conteúdo CSV são obrigatórios.' });
  }

  try {
    const lines = csv_content.split(/\r?\n/).map(l => l.trim()).filter(l => l.length > 0);
    if (lines.length < 2) {
      return res.status(400).json({ error: 'O arquivo CSV precisa ter cabeçalho e ao menos uma linha de dados.' });
    }

    const header = lines[0].toLowerCase().split(/[,;]/).map(h => h.trim().replace(/^["']|["']$/g, ''));
    const nameIdx = header.findIndex(h => h.includes('nome') || h.includes('aluno') || h.includes('criança'));
    const nascIdx = header.findIndex(h => h.includes('nasc') || h.includes('data') || h.includes('aniversario') || h.includes('birth'));
    const apelidoIdx = header.findIndex(h => h.includes('apelido') || h.includes('chamada') || h.includes('nickname'));
    const paisIdx = header.findIndex(h => h.includes('pai') || h.includes('mãe') || h.includes('mae') || h.includes('responsav'));
    const saudeIdx = header.findIndex(h => h.includes('saude') || h.includes('saúde') || h.includes('alergia') || h.includes('observa'));
    const telIdx = header.findIndex(h => h.includes('contato') || h.includes('tel') || h.includes('fone') || h.includes('cel'));

    if (nameIdx === -1) {
      return res.status(400).json({ error: 'Coluna de "Nome" não encontrada no CSV.' });
    }

    const inserted = [];
    for (let i = 1; i < lines.length; i++) {
      const cols = lines[i].split(/[,;]/).map(c => c.trim().replace(/^["']|["']$/g, ''));
      const nome = cols[nameIdx];
      if (!nome) continue;

      let dataNasc = null;
      if (nascIdx !== -1 && cols[nascIdx]) {
        const rawDate = cols[nascIdx];
        if (/^\d{4}-\d{2}-\d{2}$/.test(rawDate)) {
          dataNasc = rawDate;
        } else if (/^\d{2}\/\d{2}\/\d{4}$/.test(rawDate)) {
          const parts = rawDate.split('/');
          dataNasc = `${parts[2]}-${parts[1]}-${parts[0]}`;
        }
      }

      const apelido = apelidoIdx !== -1 ? cols[apelidoIdx] : null;
      const pais = paisIdx !== -1 ? cols[paisIdx] : null;
      const saude = saudeIdx !== -1 ? cols[saudeIdx] : null;
      const tel = telIdx !== -1 ? cols[telIdx] : null;

      const dbRes = await dbPool.query(
        `INSERT INTO escola.alunos (usuario_id, turma_id, nome, apelido, data_nasc, pais, observacoes_saude, contato_emergencia) 
         VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`,
        [usuarioId, turma_id, nome, apelido, dataNasc, pais, saude, tel]
      );
      inserted.push(dbRes.rows[0]);
    }

    res.json({ success: true, count: inserted.length, alunos: inserted });
  } catch (err) {
    console.error('Erro import CSV:', err);
    res.status(500).json({ error: 'Erro ao importar CSV: ' + err.message });
  }
});

// Timeline Completa do Aluno
app.get('/api/alunos/:id/timeline', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;

  try {
    const alunoRes = await dbPool.query("SELECT * FROM escola.alunos WHERE id = $1 AND usuario_id = $2", [id, usuarioId]);
    if (alunoRes.rows.length === 0) return res.status(404).json({ error: 'Aluno não encontrado' });
    const aluno = alunoRes.rows[0];

    // Observações
    const obsRes = await dbPool.query(
      "SELECT id, filename, date, time, timestamp, tags, report, tags_bncc, duracao_segundos FROM escola.observacoes WHERE (aluno_id = $1 OR criancas ILIKE $2 OR report ILIKE $2) ORDER BY timestamp DESC LIMIT 30",
      [id, `%${aluno.nome}%`]
    );

    // Histórias
    const histRes = await dbPool.query(
      "SELECT id, titulo, tema, faixa_etaria, created_at FROM escola.historias WHERE (personagem_principal ILIKE $1 OR tema ILIKE $1) AND usuario_id = $2 ORDER BY created_at DESC LIMIT 10",
      [`%${aluno.nome}%`, usuarioId]
    );

    // Conquistas / Stickers
    const stickRes = await dbPool.query(
      "SELECT * FROM escola.stickers WHERE aluno_id = $1 AND usuario_id = $2 ORDER BY created_at DESC",
      [id, usuarioId]
    );

    // Crises / Intervenções
    const crisesRes = await dbPool.query(
      "SELECT * FROM escola.crises WHERE (aluno_id = $1 OR aluno_nome ILIKE $2) AND usuario_id = $3 ORDER BY created_at DESC",
      [id, `%${aluno.nome}%`, usuarioId]
    );

    // Relatórios Semestrais
    const relRes = await dbPool.query(
      "SELECT id, ano_letivo, semestre, faixa_etaria, conteudo_relatorio, created_at FROM escola.relatorios WHERE aluno_id = $1 AND usuario_id = $2 ORDER BY ano_letivo DESC, semestre DESC",
      [id, usuarioId]
    );

    res.json({
      aluno,
      timeline: {
        observacoes: obsRes.rows,
        historias: histRes.rows,
        stickers: stickRes.rows,
        crises: crisesRes.rows,
        relatorios: relRes.rows
      }
    });
  } catch (err) {
    res.status(500).json({ error: 'Erro ao carregar timeline: ' + err.message });
  }
});

// Ficha Resumo para Conselho de Classe
app.get('/api/alunos/:id/conselho-ficha', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { id } = req.params;

  try {
    const alunoRes = await dbPool.query(
      `SELECT a.*, t.nome as turma_nome FROM escola.alunos a LEFT JOIN escola.turmas t ON a.turma_id = t.id WHERE a.id = $1 AND a.usuario_id = $2`,
      [id, usuarioId]
    );
    if (alunoRes.rows.length === 0) return res.status(404).json({ error: 'Aluno não encontrado' });
    const aluno = alunoRes.rows[0];

    const obsCount = await dbPool.query(
      "SELECT COUNT(*) FROM escola.observacoes WHERE (aluno_id = $1 OR criancas ILIKE $2)",
      [id, `%${aluno.nome}%`]
    );

    const stickers = await dbPool.query(
      "SELECT titulo, categoria, icone FROM escola.stickers WHERE aluno_id = $1 AND usuario_id = $2",
      [id, usuarioId]
    );

    res.json({
      aluno,
      totalObservacoes: parseInt(obsCount.rows[0]?.count || 0),
      conquistas: stickers.rows,
      dataFicha: new Date().toLocaleDateString('pt-BR')
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ============================================================
// TAGGING AUTOMÁTICO BNCC & IA PARA OBSERVAÇÕES POR VOZ
// ============================================================
app.post('/api/observacoes/analyze-tags', requireAuth, async (req, res) => {
  const { textoObservacao } = req.body;
  if (!textoObservacao) return res.status(400).json({ error: 'Texto da observação é obrigatório' });

  try {
    const prompt = `Você é uma especialista em Educação Infantil e BNCC brasileira da PedagogIA.
Analise a seguinte observação pedagógica transcrita por voz e extraia tags estruturadas:
"${textoObservacao}"

Retorne estritamente um JSON no seguinte formato:
{
  "camposBNCC": ["O eu, o outro e o nós (EI03EO01)", "Corpo, gestos e movimentos (EI03CG02)"],
  "habilidadesChave": ["Autonomia", "Cooperação", "Motricidade fina"],
  "sentimentoGeral": "Positivo e Acolhedor",
  "sugestaoDesdobramento": "Propor nova atividade em pequenos grupos para fortalecer a partilha.",
  "resumoCurto": "Criança demonstrou iniciativa e colaboração com os colegas durante o brincar livre."
}`;

    let jsonText = '';
    try {
      const r = await callMinimax({
        system: "Você é especialista em BNCC e Educação Infantil. Responda em JSON.",
        messages: [{ role: 'user', content: prompt }],
        temperature: 0.3
      });
      jsonText = r.text;
    } catch (e) {
      if (process.env.GOOGLE_AI_API_KEY) {
        const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
        const response = await fetch(geminiUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
        });
        if (response.ok) {
          const data = await response.json();
          jsonText = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
        }
      }
    }

    let parsed = {};
    try {
      parsed = JSON.parse(jsonText.replace(/```json|```/g, '').trim());
    } catch (err) {
      parsed = {
        camposBNCC: ["O eu, o outro e o nós", "Corpo, gestos e movimentos"],
        habilidadesChave: ["Interação", "Linguagem oral"],
        sentimentoGeral: "Desenvolvimento ativo",
        sugestaoDesdobramento: "Dar continuidade no planejamento semanal.",
        resumoCurto: textoObservacao.substring(0, 100) + '...'
      };
    }

    res.json(parsed);
  } catch (err) {
    res.status(500).json({ error: 'Erro ao analisar tags: ' + err.message });
  }
});

// ============================================================
// ROTAS DE PROTOCOLO DE CRISES & MEDIAÇÃO COMPORTAMENTAL
// ============================================================
app.post('/api/crises/register', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { turma_id, aluno_id, aluno_nome, tipo_evento, intensidade, gatilho, descricao, checklist_intervencao, medidas_tomadas, orientacoes_equipe } = req.body;

  if (!aluno_nome || !tipo_evento || !descricao) {
    return res.status(400).json({ error: 'Nome do aluno, tipo de evento e descrição são obrigatórios.' });
  }

  try {
    // Gerar mensagem acolhedora para WhatsApp dos pais via IA
    const agentConfig = readAgentConfig();
    const promptMsg = `Você é a ${agentConfig.agentName || 'PedagogIA'}, redatora pedagógica da Professora Camila.
Gere uma mensagem acolhedora, respeitosa, serena e transparente para ser enviada aos pais/responsáveis da criança "${aluno_nome}" informando sobre o seguinte ocorrido na escola:
- Tipo de Ocorrência: ${tipo_evento}
- Detalhes: ${descricao}
- Medidas e Acolhimento feito pela professora: ${medidas_tomadas || 'Criança acolhida com carinho e conforto.'}

REGRAS:
- Tom extremamente afetuoso, profissional, humanizado e acolhedor (não alarmista).
- Informar que a criança já está calma e bem cuidada.
- Colocar a escola à disposição.
- Máximo 3 a 4 parágrafos curtos.`;

    let msgWhatsapp = '';
    try {
      const r = await callMinimax({
        system: "Você é uma professora e coordenadora de educação infantil acolhedora.",
        messages: [{ role: 'user', content: promptMsg }],
        temperature: 0.5
      });
      msgWhatsapp = r.text;
    } catch (e) {
      msgWhatsapp = `Olá! Passando para contar que hoje o(a) ${aluno_nome} teve um momentinho de ${tipo_evento.toLowerCase()}, mas foi prontamente acolhido(a) com muito carinho pela nossa equipe e já está calmo(a) e brincando com os amigos. Seguimos com todo cuidado e à disposição! Com carinho, Professora Camila.`;
    }

    const dbRes = await dbPool.query(
      `INSERT INTO escola.crises (
        usuario_id, turma_id, aluno_id, aluno_nome, tipo_evento, intensidade, gatilho,
        descricao, checklist_intervencao, medidas_tomadas, orientacoes_equipe, mensagem_whatsapp
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
      [
        usuarioId, turma_id || null, aluno_id || null, aluno_nome, tipo_evento, intensidade || 'Moderada',
        gatilho || '', descricao, JSON.stringify(checklist_intervencao || []), medidas_tomadas || '',
        orientacoes_equipe || '', msgWhatsapp
      ]
    );

    res.json({ success: true, crise: dbRes.rows[0], mensagemWhatsapp: msgWhatsapp });
  } catch (err) {
    console.error('Erro crise register:', err);
    res.status(500).json({ error: 'Erro ao registrar protocolo de crise: ' + err.message });
  }
});

app.get('/api/crises/list', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { turma_id } = req.query;
  try {
    let q = "SELECT * FROM escola.crises WHERE usuario_id = $1";
    let p = [usuarioId];
    if (turma_id) { q += " AND turma_id = $2"; p.push(turma_id); }
    q += " ORDER BY data_hora DESC LIMIT 50";
    const dbRes = await dbPool.query(q, p);
    res.json(dbRes.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.delete('/api/crises/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  try {
    await dbPool.query("DELETE FROM escola.crises WHERE id = $1 AND usuario_id = $2", [req.params.id, usuarioId]);
    res.json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ============================================================
// ROTAS DE ATAS DE REUNIÃO & CONSELHO DE CLASSE
// ============================================================
app.post('/api/atas/generate', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { tipo_reuniao, titulo, turma_id, turma_nome, data_reuniao, participantes, pauta, discussoes, deliberacoes, proximos_passos } = req.body;

  if (!titulo || !tipo_reuniao || !discussoes) {
    return res.status(400).json({ error: 'Título, Tipo de Reunião e Discussões são obrigatórios.' });
  }

  try {
    const promptAta = `Você é uma secretária pedagógica e especialista em documentação escolar brasileira.
Gere uma ATA FORMAL DE REUNIÃO ESCOLAR completa, bem redigida em linguagem culta, com parágrafos encadeados, estruturada da seguinte forma:
- Cabeçalho Oficial (Data, Horário, Local, Participantes, Pauta)
- Desenvolvimento e Discussões
- Deliberações e Decisões Tomadas
- Próximos Passos e Encaminhamentos
- Fecho formal e espaço para assinaturas

DADOS DA REUNIÃO:
- Tipo: ${tipo_reuniao}
- Título: ${titulo}
- Turma / Nível: ${turma_nome || 'Geral'}
- Data: ${data_reuniao || new Date().toLocaleDateString('pt-BR')}
- Participantes: ${(participantes || []).join(', ') || 'Corpo docente e equipe pedagógica'}
- Pauta: ${pauta || 'Avaliação e desenvolvimento da turma'}
- Principais Discussões: ${discussoes}
- Deliberações e Acordos: ${deliberacoes || 'Registrado em concordância com os presentes.'}
- Próximos Passos: ${proximos_passos || 'Acompanhamento contínuo.'}`;

    let textoAta = '';
    try {
      const r = await callMinimax({
        system: "Você é uma secretária e redatora de documentos e atas escolares no Brasil.",
        messages: [{ role: 'user', content: promptAta }],
        temperature: 0.4
      });
      textoAta = r.text;
    } catch (e) {
      if (process.env.GOOGLE_AI_API_KEY) {
        const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
        const response = await fetch(geminiUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ contents: [{ parts: [{ text: promptAta }] }] })
        });
        if (response.ok) {
          const data = await response.json();
          textoAta = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
        }
      }
    }

    if (!textoAta) {
      textoAta = `ATA DE ${tipo_reuniao.toUpperCase()}\nData: ${data_reuniao}\nTurma: ${turma_nome}\nPauta: ${pauta}\nDiscussões: ${discussoes}\nDeliberações: ${deliberacoes}`;
    }

    const dbRes = await dbPool.query(
      `INSERT INTO escola.atas (
        usuario_id, tipo_reuniao, titulo, turma_id, turma_nome, data_reuniao,
        participantes, pauta, discussoes, deliberacoes, proximos_passos, texto_ata
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
      [
        usuarioId, tipo_reuniao, titulo, turma_id || null, turma_nome || '',
        data_reuniao || new Date(), participantes || [], pauta || '', discussoes,
        deliberacoes || '', proximos_passos || '', textoAta
      ]
    );

    res.json({ success: true, ata: dbRes.rows[0], textoAta });
  } catch (err) {
    console.error('Erro ata generate:', err);
    res.status(500).json({ error: 'Erro ao gerar ata: ' + err.message });
  }
});

app.get('/api/atas/list', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query("SELECT id, tipo_reuniao, titulo, turma_nome, data_reuniao, created_at FROM escola.atas WHERE usuario_id = $1 ORDER BY data_reuniao DESC", [usuarioId]);
    res.json(dbRes.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.get('/api/atas/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  try {
    const dbRes = await dbPool.query("SELECT * FROM escola.atas WHERE id = $1 AND usuario_id = $2", [req.params.id, usuarioId]);
    if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Ata não encontrada' });
    res.json(dbRes.rows[0]);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.delete('/api/atas/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  try {
    await dbPool.query("DELETE FROM escola.atas WHERE id = $1 AND usuario_id = $2", [req.params.id, usuarioId]);
    res.json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ============================================================
// ROTAS DE STICKERS GAMIFICADOS & CONQUISTAS
// ============================================================
app.post('/api/stickers/create', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { aluno_id, aluno_nome, turma_id, titulo, categoria, icone, descricao, observacao_id } = req.body;

  if (!aluno_nome || !titulo || !descricao) {
    return res.status(400).json({ error: 'Nome do aluno, título da conquista e descrição são obrigatórios.' });
  }

  try {
    const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(`Parabéns ${aluno_nome}! Conquista: ${titulo} - ${descricao}`)}`;

    const dbRes = await dbPool.query(
      `INSERT INTO escola.stickers (
        usuario_id, aluno_id, aluno_nome, turma_id, titulo, categoria, icone, descricao, observacao_id, qr_code_url
      ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`,
      [
        usuarioId, aluno_id || null, aluno_nome, turma_id || null, titulo,
        categoria || 'Cooperação & Gentileza', icone || '🌟', descricao,
        observacao_id || null, qrUrl
      ]
    );

    res.json({ success: true, sticker: dbRes.rows[0] });
  } catch (err) {
    res.status(500).json({ error: 'Erro ao criar sticker: ' + err.message });
  }
});

app.get('/api/stickers/list', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { aluno_id, turma_id } = req.query;
  try {
    let q = "SELECT * FROM escola.stickers WHERE usuario_id = $1";
    let p = [usuarioId];
    if (aluno_id) { q += " AND aluno_id = $2"; p.push(aluno_id); }
    else if (turma_id) { q += " AND turma_id = $2"; p.push(turma_id); }
    q += " ORDER BY data_conquista DESC, created_at DESC LIMIT 100";
    const dbRes = await dbPool.query(q, p);
    res.json(dbRes.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.delete('/api/stickers/:id', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  try {
    await dbPool.query("DELETE FROM escola.stickers WHERE id = $1 AND usuario_id = $2", [req.params.id, usuarioId]);
    res.json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// ============================================================
// ROTAS DE MIND LAB AVANÇADO (SEQUÊNCIA DIDÁTICA & HTPC)
// ============================================================
app.post('/api/mindlab/sequencia', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { faixa_etaria, bimestre, tema_bimestre, jogos_selecionados } = req.body;

  try {
    const prompt = `Você é a maior especialista em metodologia Mind Lab (Mente Inovadora) para Educação Infantil no Brasil.
Elabore uma SEQUÊNCIA DIDÁTICA BIMESTRAL DE 8 SEMANAS para a Educação Infantil:
- Faixa Etária: ${faixa_etaria || '4 a 5 anos'}
- Bimestre: ${bimestre || '1º Bimestre'}
- Tema Central: ${tema_bimestre || 'Cooperação, Raciocínio Espacial e Autocontrole'}
- Jogos Sugeridos: ${jogos_selecionados || 'Quarto, Damas Olímpicas, Tartarugas de Trânsito, Blockhead'}

Retorne ESTRITAMENTE um JSON com o cronograma e o texto formatado para HTPC/Diário de Classe:
{
  "tituloSequencia": "Sequência Mind Lab: Despertando Estratégias e Emoções",
  "bimestre": "${bimestre || '1º Bimestre'}",
  "habilidadesBNCC": ["EI03EO02", "EI03ET04", "EI03CG01"],
  "semanas": [
    { "semana": 1, "jogo": "Tartarugas de Trânsito", "metodo": "Semáforo (Pensar antes de agir)", "foco": "Controle inibitório e respeito aos turnos", "objetivo": "Introduzir a paciência e a observação antes de mover as peças." },
    { "semana": 2, "jogo": "Tartarugas de Trânsito", "metodo": "Aves de Rapina (Visão ampla)", "foco": "Planejamento e antecipação", "objetivo": "Analisar as opções de caminhos disponíveis." },
    { "semana": 3, "jogo": "Blockhead", "metodo": "Detetive (Busca de pistas)", "foco": "Equilíbrio e coordenação motora", "objetivo": "Identificar a estabilidade das peças antes de posicionar." },
    { "semana": 4, "jogo": "Blockhead", "metodo": "Pássaro no Galho (Cautela)", "foco": "Inteligência emocional diante do erro", "objetivo": "Aprender que a queda do bloco é oportunidade de recomeço." },
    { "semana": 5, "jogo": "Quarto", "metodo": "Espelho (Empatia e outro)", "foco": "Raciocínio lógico e classificação", "objetivo": "Classificar atributos (cor, forma, altura, topo)." },
    { "semana": 6, "jogo": "Quarto", "metodo": "Escada (Passo a passo)", "foco": "Estratégia de entrega da peça", "objetivo": "Pensar no impacto da jogada para o colega." },
    { "semana": 7, "jogo": "Desafios em Equipe", "metodo": "Roda da Amizade", "foco": "Cooperação entre duplas", "objetivo": "Solucionar desafios em parceria." },
    { "semana": 8, "jogo": "Celebração e Mostra", "metodo": "Árvore do Saber", "foco": "Autoavaliação e partilha", "objetivo": "Compartilhar com a turma os métodos mais utilizados." }
  ],
  "textoHTPC": "Durante este bimestre, a metodologia Mind Lab proporcionou ricas experiências de autorregulação e flexibilidade cognitiva. As crianças desenvolveram controle inibitório mediado pelo Método do Semáforo e aprimoraram a cooperação nas partidas em duplas, alinhando-se aos campos de experiência da BNCC."
}`;

    let jsonResp = '';
    try {
      const r = await callMinimax({
        system: "Você é especialista na metodologia Mind Lab. Responda em JSON.",
        messages: [{ role: 'user', content: prompt }],
        temperature: 0.4
      });
      jsonResp = r.text;
    } catch (e) {
      if (process.env.GOOGLE_AI_API_KEY) {
        const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
        const response = await fetch(geminiUrl, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] })
        });
        if (response.ok) {
          const data = await response.json();
          jsonResp = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
        }
      }
    }

    let parsed = {};
    try {
      parsed = JSON.parse(jsonResp.replace(/```json|```/g, '').trim());
    } catch (err) {
      parsed = {
        tituloSequencia: "Sequência Didática Mind Lab",
        bimestre: bimestre || "1º Bimestre",
        habilidadesBNCC: ["EI03EO02", "EI03ET04"],
        semanas: [],
        textoHTPC: "Planejamento bimestral de jogos de raciocínio focado em controle inibitório e cooperação."
      };
    }

    res.json({ success: true, sequencia: parsed });
  } catch (err) {
    res.status(500).json({ error: 'Erro ao gerar sequência Mind Lab: ' + err.message });
  }
});

// ============================================================
// ROTAS DE RELATÓRIOS SEMESTRAIS (BATCH GENERATION & COMPARAÇÃO)
// ============================================================
app.post('/api/relatorios/batch-generate', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { turma_id, ano_letivo, semestre, faixa_etaria, comparar_com_semestre1 } = req.body;

  if (!turma_id) {
    return res.status(400).json({ error: 'Turma é obrigatória para geração em lote.' });
  }

  try {
    const alunosRes = await dbPool.query("SELECT * FROM escola.alunos WHERE turma_id = $1 AND usuario_id = $2 ORDER BY nome ASC", [turma_id, usuarioId]);
    const alunos = alunosRes.rows;

    if (alunos.length === 0) {
      return res.status(400).json({ error: 'Nenhum aluno cadastrado nesta turma.' });
    }

    const loteId = require('crypto').randomUUID();
    const relatoriosGerados = [];

    for (const aluno of alunos) {
      // Buscar observações do aluno
      const obsRes = await dbPool.query(
        "SELECT report, date, tags, tags_bncc FROM escola.observacoes WHERE (aluno_id = $1 OR criancas ILIKE $2 OR report ILIKE $2) ORDER BY timestamp DESC LIMIT 10",
        [aluno.id, `%${aluno.nome}%`]
      );

      const observacoesTexto = obsRes.rows.map(o => `[${o.date}] ${o.report}`).join('\n') || 'Criança participativa, em pleno desenvolvimento nas interações cotidianas.';

      let relatorioAnterior = '';
      if (comparar_com_semestre1 && semestre === 2) {
        const relAntRes = await dbPool.query(
          "SELECT conteudo_relatorio FROM escola.relatorios WHERE aluno_id = $1 AND ano_letivo = $2 AND semestre = 1 LIMIT 1",
          [aluno.id, ano_letivo || 2026]
        );
        if (relAntRes.rows.length > 0) relatorioAnterior = relAntRes.rows[0].conteudo_relatorio;
      }

      const promptRelatorio = `Você é a redatora pedagógica de Educação Infantil da professora Camila Martella Gasparini Reifonas.
Gere um RELATÓRIO INDIVIDUAL DE DESENVOLVIMENTO INFANTIL para o aluno "${aluno.nome}":
- Faixa Etária: ${faixa_etaria || '4 a 5 anos'}
- Semestre: ${semestre || 1}º Semestre de ${ano_letivo || 2026}
- Observações Coletadas:
${observacoesTexto}
${relatorioAnterior ? `\n- RELATÓRIO DO 1º SEMESTRE (para comparação de avanços):\n${relatorioAnterior}` : ''}

ESTRUTURA PEDAGÓGICA DO RELATÓRIO:
1. **Acolhimento & Adaptação:** Como a criança se relaciona com os pares e com os adultos na rotina escolar.
2. **Campos de Experiência da BNCC:**
   - *O Eu, o Outro e o Nós:* Autonomia, cooperação, partilha e resolução de pequenos conflitos.
   - *Corpo, Gestos e Movimentos:* Esquema corporal, brincadeiras ativas e coordenação motora.
   - *Traços, Sons, Cores e Formas:* Expressão plástica, musicalidade e exploração de materiais.
   - *Escuta, Fala, Pensamento e Imaginação:* Comunicação oral, contação de histórias e interesse pela leitura.
   - *Espaços, Tempos, Quantidades, Relações e Transformações:* Curiosidade científica, noções espaciais e contagem lúdica.
${relatorioAnterior ? '3. **Avanços em Relação ao 1º Semestre:** Destaque claro do crescimento socioemocional e cognitivo da criança ao longo do ano.' : ''}
4. **Fecho Afetivo:** Perspectivas e mensagem carinhosa de encorajamento.

Linguagem: Afetuosa, técnica, positiva, sem rotulações, respeitando o tempo singular de desenvolvimento da criança.`;

      let conteudoRelatorio = '';
      try {
        const r = await callMinimax({
          system: "Você é especialista em relatórios pedagógicos de Educação Infantil alinhados à BNCC.",
          messages: [{ role: 'user', content: promptRelatorio }],
          temperature: 0.5
        });
        conteudoRelatorio = r.text;
      } catch (e) {
        if (process.env.GOOGLE_AI_API_KEY) {
          const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
          const response = await fetch(geminiUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ contents: [{ parts: [{ text: promptRelatorio }] }] })
          });
          if (response.ok) {
            const data = await response.json();
            conteudoRelatorio = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
          }
        }
      }

      if (!conteudoRelatorio) {
        conteudoRelatorio = `Relatório Semestral de ${aluno.nome}\nO aluno demonstra pleno desenvolvimento socioemocional, participando com alegria e entusiasmo das vivências da turma.`;
      }

      const dbIns = await dbPool.query(
        `INSERT INTO escola.relatorios (
          usuario_id, turma_id, aluno_id, aluno_nome, ano_letivo, semestre, faixa_etaria, conteudo_relatorio, lote_id
        ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING *`,
        [usuarioId, turma_id, aluno.id, aluno.nome, ano_letivo || 2026, semestre || 1, faixa_etaria || '4 a 5 anos', conteudoRelatorio, loteId]
      );

      relatoriosGerados.push(dbIns.rows[0]);
    }

    res.json({
      success: true,
      loteId,
      total: relatoriosGerados.length,
      relatorios: relatoriosGerados
    });

  } catch (err) {
    console.error('Erro batch relatórios:', err);
    res.status(500).json({ error: 'Erro ao gerar relatórios em lote: ' + err.message });
  }
});

app.get('/api/relatorios/turma/:turmaId', requireAuth, async (req, res) => {
  const usuarioId = req.cookies.usuario_id || req.headers['user-id'] || '00000000-0000-0000-0000-000000000000';
  const { turmaId } = req.params;
  try {
    const dbRes = await dbPool.query(
      "SELECT * FROM escola.relatorios WHERE turma_id = $1 AND usuario_id = $2 ORDER BY aluno_nome ASC, semestre DESC",
      [turmaId, usuarioId]
    );
    res.json(dbRes.rows);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Rota coringa para redirecionar para index.html
app.get('*', requireAuth, (req, res) => {
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
});

app.listen(PORT, '0.0.0.0', () => {
  console.log(`Servidor rodando na porta ${PORT}`);
  initDatabase();
});
