"use client";
import { useState, useMemo, useRef } from "react";
import { usePathname } from "next/navigation";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import PageHero from "@/components/PageHero";
import * as diagnosticoPt from "@/lib/diagnosticoData";
import * as diagnosticoEn from "@/lib/data/en/diagnostico";
import {
Activity,
AlertTriangle,
Check,
CheckCircle2,
ChevronDown,
Copy,
Cpu,
ExternalLink,
HelpCircle,
Layers,
LayoutGrid,
RotateCcw,
Search,
ShieldAlert,
Terminal,
Wifi,
Wrench,
X,
XCircle,
Zap,
} from "lucide-react";
/** Os rótulos dos sintomas vêm com emoji nos dados; a interface usa só texto e ícones. */
const semEmoji = (texto) => texto.replace(/^[\p{Extended_Pictographic}\uFE0F\s]+/u, "");
const ICONES = { Zap, Cpu, Layers, Activity, Terminal, Wifi };
const EXEMPLOS_LOG_PT = [
["Brownout", "Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13"],
["LoadProhibited", "Guru Meditation Error: Core 0 panic'ed (LoadProhibited).\nEXCVADDR: 0x00000000"],
["Task watchdog", "Task watchdog got triggered. The following tasks/users did not reset the watchdog: - IDLE0"],
["Handshake TLS", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
];
const EXEMPLOS_LOG_EN = [
["Brownout", "Brownout detector was triggered\n\nrst:0xc (SW_CPU_RESET),boot:0x13"],
["LoadProhibited", "Guru Meditation Error: Core 0 panic'ed (LoadProhibited).\nEXCVADDR: 0x00000000"],
["Task watchdog", "Task watchdog got triggered. The following tasks/users did not reset the watchdog: - IDLE0"],
["TLS Handshake", "mbedtls_ssl_handshake returned -0x2700\nThe certificate is not yet valid"],
];
const GRAVIDADE = {
"Crítica": "text-danger",
"Critical": "text-danger",
Alta: "text-gold",
High: "text-gold",
"Média": "text-accent",
Medium: "text-accent",
};
const CHECKLISTS_PT = [
{
icon: Zap,
titulo: "Hardware e alimentação",
itens: [
"Capacitor de 10 µF a 100 µF de baixo ESR no VCC, mais um cerâmico de 100 nF junto ao chip.",
"LDO dimensionado para picos de 600 mA a 1 A, com folga térmica.",
"Nenhum periférico forçando nível lógico nos pinos de strapping durante o boot.",
"Capacitor de 10 µF ou 1 µF no pino EN (RST) para auto-reset estável na gravação.",
],
},
{
icon: Layers,
titulo: "Firmware e FreeRTOS",
itens: [
"Toda tarefa FreeRTOS cede a CPU com vTaskDelay, filas ou semáforos.",
"Toda ISR marcada com IRAM_ATTR e sem chamadas bloqueantes.",
"Sem uso contínuo de String do Arduino em laços, para evitar fragmentação do heap.",
"Horário sincronizado por SNTP antes de abrir conexões TLS/HTTPS.",
],
},
];
const CHECKLISTS_EN = [
{
icon: Zap,
titulo: "Hardware and Power Supply",
itens: [
"10 µF to 100 µF low-ESR capacitor on VCC, plus a 100 nF ceramic capacitor close to the chip.",
"LDO regulator sized for 600 mA to 1 A current spikes, with adequate thermal margin.",
"No external peripherals forcing logic levels on strapping pins during reset and boot.",
"10 µF or 1 µF capacitor on the EN (RST) pin for stable auto-reset flashing.",
],
},
{
icon: Layers,
titulo: "Firmware and FreeRTOS",
itens: [
"Every FreeRTOS task yields CPU time with vTaskDelay, queues, or semaphores.",
"Every ISR marked with IRAM_ATTR with zero blocking calls.",
"Avoid continuous heap reallocations using Arduino Strings in tight loops.",
"Clock synchronized via SNTP before attempting TLS/HTTPS handshakes.",
],
},
];
export default function DiagnosticoPage() {
const pathname = usePathname();
const isEn = pathname?.startsWith("/en");
const { categoriasDiagnostico, sintomasRapidos, errosCatalogo, regrasDecodificadorLog } = isEn ? diagnosticoEn : diagnosticoPt;
const exemplosLog = isEn ? EXEMPLOS_LOG_EN : EXEMPLOS_LOG_PT;
const checklists = isEn ? CHECKLISTS_EN : CHECKLISTS_PT;
const [termoBusca, setTermoBusca] = useState("");
const [categoriaAtiva, setCategoriaAtiva] = useState("todos");
const [sintomaAtivo, setSintomaAtivo] = useState(null);
const [cardAberto, setCardAberto] = useState(null);
const [linguagemCodigo, setLinguagemCodigo] = useState({}); // { [id]: 'arduino' | 'espidf' }
const [copiadoId, setCopiadoId] = useState(null);
const searchInputRef = useRef(null);
// Estado do Analisador de Logs
const [logInput, setLogInput] = useState("");
const [resultadoLog, setResultadoLog] = useState(null);
// Função de Análise do Log
const analisarLog = (texto) => {
setLogInput(texto);
if (!texto || texto.trim() === "") {
setResultadoLog(null);
return;
}
for (const regra of regrasDecodificadorLog) {
if (regra.padrao.test(texto)) {
setResultadoLog(regra);
return;
}
}
setResultadoLog(
isEn
? {
titulo: "Error pattern not automatically classified",
tipo: "Unknown",
explicacao:
"We could not identify a classic Guru Meditation or Brownout signature in this snippet. Verify that the baud rate (e.g. 115200) is correct and whether the 'rst:' or 'panic' header is present.",
acaoRecomendada:
"Look for keywords such as 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog', or 'panic' in the serial output.",
}
: {
titulo: "Padrão de erro não categorizado automaticamente",
tipo: "Desconhecido",
explicacao:
"Não identificamos uma assinatura clássica de Guru Meditation ou Brownout neste trecho. Verifique se a taxa de baud rate (ex: 115200) está correta e se o cabeçalho 'rst:' ou 'panic' está presente.",
acaoRecomendada:
"Procure por palavras-chave como 'Guru Meditation', 'rst:', 'LoadProhibited', 'watchdog' ou 'panic' no console serial.",
}
);
};
const copiarParaClipboard = (texto, id) => {
navigator.clipboard.writeText(texto);
setCopiadoId(id);
setTimeout(() => setCopiadoId(null), 2000);
};
// Filtragem dos erros
const errosFiltrados = useMemo(() => {
const termo = termoBusca.toLowerCase().trim();
return errosCatalogo.filter((erro) => {
const matchCategoria =
categoriaAtiva === "todos" || erro.categoria === categoriaAtiva;
const matchSintoma =
!sintomaAtivo || erro.sintomas.includes(sintomaAtivo);
const matchBusca =
!termo ||
erro.titulo.toLowerCase().includes(termo) ||
erro.resumo.toLowerCase().includes(termo) ||
erro.causaRaiz.toLowerCase().includes(termo) ||
erro.logSerial.toLowerCase().includes(termo);
return matchCategoria && matchSintoma && matchBusca;
});
}, [categoriaAtiva, sintomaAtivo, termoBusca, errosCatalogo]);
const temFiltroAtivo =
termoBusca.trim() !== "" ||
categoriaAtiva !== "todos" ||
sintomaAtivo !== null;
const limparTodosFiltros = () => {
setTermoBusca("");
setCategoriaAtiva("todos");
setSintomaAtivo(null);
searchInputRef.current?.focus();
};
const toggleCard = (id) => {
setCardAberto((prev) => (prev === id ? null : id));
};
const setLang = (id, lang) => {
setLinguagemCodigo((prev) => ({ ...prev, [id]: lang }));
};
const totalFiltros = (termoBusca.trim() ? 1 : 0) + (categoriaAtiva !== "todos" ? 1 : 0) + (sintomaAtivo ? 1 : 0);
return (
{isEn ? "Based on article by Ciência Embarcada" : "Baseado no artigo do Ciência Embarcada"}
{isEn ? "(opens in a new tab)" : "(abre em nova aba)"}
{isEn ? "Paste the log to see the cause" : "Cole o log e veja a causa"}
{isEn ? "Copy the serial monitor output snippet. Diagnostics appear instantly." : "Copie o trecho do monitor serial. O diagnóstico aparece na hora."}
{isEn ? "Test with" : "Testar com"}
{exemplosLog.map(([rotulo, texto]) => (
analisarLog(texto)} className="chip-btn">
{rotulo}
))}
{isEn ? "Serial monitor log" : "Log do monitor serial"}
{resultadoLog && (
{resultadoLog.titulo}
{resultadoLog.tipo}
{isEn ? "Likely cause:" : "Causa provável:"} {resultadoLog.explicacao}
{isEn ? "What to do:" : "O que fazer:"} {resultadoLog.acaoRecomendada}
)}
{isEn ? "Fault catalog" : "Catálogo de falhas"}
{isEn ? "Search by error, symptom, or serial message" : "Buscar por erro, sintoma ou mensagem serial"}
setTermoBusca(e.target.value)}
placeholder={isEn ? "Search error, symptom, or message" : "Buscar erro, sintoma ou mensagem"}
autoComplete="off"
className="field pl-12 pr-4 text-base"
/>
{isEn ? "Symptom" : "Sintoma"}
{sintomasRapidos.map((sintoma) => (
setSintomaAtivo(sintomaAtivo === sintoma.id ? null : sintoma.id)} aria-pressed={sintomaAtivo === sintoma.id} className="chip-btn">
{semEmoji(sintoma.rotulo)}
))}
{isEn ? "Area" : "Área"}
setCategoriaAtiva("todos")} aria-pressed={categoriaAtiva === "todos"} className="chip-btn">
{isEn ? "All" : "Todas"}
{errosCatalogo.length}
{categoriasDiagnostico.map((cat) => {
const Icone = ICONES[cat.icone] ?? AlertTriangle;
const total = errosCatalogo.filter((e) => e.categoria === cat.id).length;
return (
setCategoriaAtiva(cat.id)} aria-pressed={categoriaAtiva === cat.id} className="chip-btn">
{cat.nome.split(/ &|,/)[0]}
{total}
);
})}
{errosFiltrados.length} {errosFiltrados.length === 1 ? (isEn ? "fault" : "falha") : (isEn ? "faults" : "falhas")}
{totalFiltros > 0 && {isEn ? " with current filters" : " com os filtros atuais"} }
{temFiltroAtivo && (
{isEn ? "Reset filters" : "Redefinir filtros"}
)}
{errosFiltrados.length === 0 ? (
{isEn ? "No faults matching these filters" : "Nenhuma falha com esses filtros"}
{isEn ? "Search for a more general term or remove selected symptom and area." : "Busque por um termo mais geral ou remova o sintoma e a área selecionados."}
{isEn ? "Clear filters" : "Limpar filtros"}
) : (
errosFiltrados.map((erro) => {
const estaAberto = cardAberto === erro.id;
const lang = linguagemCodigo[erro.id] || "arduino";
const codigoAtual =
lang === "arduino" ? erro.codigoArduino : lang === "espidf" ? erro.codigoEspIdf : erro.codigoMicroPython || (isEn ? "# Example not applicable" : "# Exemplo não aplicável");
const catInfo = categoriasDiagnostico.find((c) => c.id === erro.categoria);
const IconeCat = ICONES[catInfo?.icone] ?? AlertTriangle;
return (
toggleCard(erro.id)}
aria-expanded={estaAberto}
aria-controls={`detalhe-${erro.id}`}
className="flex w-full cursor-pointer items-start justify-between gap-4 p-5 text-left sm:p-6"
>
{catInfo?.nome}
{isEn ? (
<>Severity: {erro.gravidade.toLowerCase()}>
) : (
<>Gravidade {erro.gravidade.toLowerCase()}>
)}
{erro.titulo}
{erro.resumo}
{estaAberto && (
{isEn ? "How it appears in serial monitor" : "Como aparece no monitor serial"}
copiarParaClipboard(erro.logSerial, `log-${erro.id}`)} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
{copiadoId === `log-${erro.id}` ? : }
{copiadoId === `log-${erro.id}` ? (isEn ? "Copied" : "Copiado") : (isEn ? "Copy log" : "Copiar log")}
{isEn ? "Root cause" : "Causa raiz"}
{erro.causaRaiz}
{isEn ? "The common workaround" : "O remendo comum"}
{erro.gambiarra.titulo}
{erro.gambiarra.descricao}
{isEn ? "Consequence:" : "Consequência:"} {erro.gambiarra.consequencia}
{isEn ? "The engineering solution" : "A solução de engenharia"}
{erro.solucaoEngenharia.titulo}
{erro.solucaoEngenharia.passos.map((passo) => (
{passo}
))}
{[
["arduino", "Arduino C++"],
["espidf", "ESP-IDF (C)"],
["micropython", "MicroPython"],
].map(([id, rotulo]) => (
setLang(erro.id, id)} aria-pressed={lang === id} className="chip-btn">
{rotulo}
))}
copiarParaClipboard(codigoAtual, `code-${erro.id}`)} className="inline-flex min-h-11 items-center gap-1.5 text-sm font-medium text-accent hover:text-accent-strong">
{copiadoId === `code-${erro.id}` ? : }
{copiadoId === `code-${erro.id}` ? (isEn ? "Copied" : "Copiado") : (isEn ? "Copy code" : "Copiar código")}
{isEn ? "Engineering tip:" : "Dica de engenharia:"} {erro.dicaPro}
)}
);
})
)}
{isEn ? "Pre-deployment checklist" : "Checklist antes de ir para campo"}
{isEn ? "Essential items to validate on hardware and firmware before shipping a product." : "Itens essenciais para validar no hardware e no firmware antes de entregar o produto."}
{checklists.map(({ icon: Icon, titulo, itens }) => (
{titulo}
{itens.map((item) => (
{item}
))}
))}
);
}