# motor_pesquisa_visual.py
# Gera motor_pesquisa_visual.html a partir de workorders.json + comentarios.jsonl
# A pesquisa é feita em JavaScript puro no browser (mesma lógica do motor_pesquisa.py)
#
# Uso:
#   python motor_pesquisa_visual.py            (só gera)
#   python motor_pesquisa_visual.py --abrir    (gera e abre no browser)

import os
import json
import re
import unicodedata
import webbrowser
import base64
from datetime import datetime
from collections import Counter

DESKTOP = os.path.expanduser("~/Desktop")
WORKORDERS_FILE = os.path.join(DESKTOP, "workorders.json")
COMENTARIOS_FILE = os.path.join(DESKTOP, "comentarios.jsonl")
HTML_FILE = os.path.join(DESKTOP, "motor_pesquisa_visual.html")
GIF_FILE = os.path.join(DESKTOP, "puxar_banana.gif")   # GIF personalizado

# Mapa de estados (IDs → nomes)
ESTADOS = {
    '8b959fa9-92da-48af-83a8-8960b86e5d3e': 'Por fazer',
    '08059026-de6e-8b90-bde1-4ae92f676643': 'Suspenso',
    '32fbd621-0458-4e3e-968d-77ffc56c892a': 'Em curso',
    'd17964fa-1174-ed36-eb9c-f50d58980859': 'A aguardar intervenção externa',
    '658ee047-7054-cae7-8e55-b9ab543b67c7': 'A aguardar material',
    'df9e4a1c-4670-8242-3843-d0a3e8d175da': 'A aguardar reparação da peça',
    '7b419f98-ebc2-dcd4-8db2-312cdfbe8072': 'A aguardar validação qualidade',
    '8240c123-1dd5-860b-6c08-041805b85dbf': 'A aguardar validação segurança',
    'ecdfc42d-e39a-d2e4-1455-78fc9d35dc05': 'Em analise com o fabricante',
    '85c3cbee-2503-4bf5-8271-b13b41b02a08': 'Concluída',
    '529a1f42-ebb9-0688-3523-54e7b4dd5783': 'A aguardar documentação',
    '1ee4fca1-325d-948d-af5d-bc5fefa53635': 'Em teste',
    '0ef45d0d-1901-959e-8727-869a178e0f6b': 'Reparação corretiva temporaria',
    'd67894ef-2d53-4e63-a840-71089d2cc851': 'Anulada',
    'ada41e02-5e6a-4dc7-9f5c-e6b14340f1a6': 'Encerrada',
}

ESTADO_ENCERRADA = 'ada41e02-5e6a-4dc7-9f5c-e6b14340f1a6'
ESTADO_CONCLUIDA = '85c3cbee-2503-4bf5-8271-b13b41b02a08'

SINONIMOS = {
    "nao arranca": ["nao liga", "nao parte", "nao funciona", "sem arranque",
                    "nao inicia", "parada", "parou", "desligada"],
    "erro": ["alarme", "falha", "avaria", "defeito", "problema"],
    "carregador": ["alimentador", "iemca", "bar feeder", "charger"],
    "emergencia": ["emerg", "emergency", "botao emergencia", "seta"],
    "carregamento": ["carrega", "alimentacao", "introducao de barra"],
    "vibracao": ["vibra", "trepida", "treme"],
    "fuga": ["vazamento", "escape", "perda"],
    "oleo": ["lubrificante", "fluido", "oleo hidraulico"],
    "ar comprimido": ["pneumatico", "pressao ar"],
    "barulho": ["ruido", "som estranho"],
    "sobreaquecimento": ["aquecimento", "quente", "temperatura alta"],
}


# ==================================================================
# Normalização
# ==================================================================
def _normalizar(texto):
    if not texto:
        return ""
    texto = unicodedata.normalize("NFKD", str(texto))
    texto = "".join(c for c in texto if not unicodedata.combining(c))
    texto = texto.lower()
    texto = re.sub(r"[^\w\s]", " ", texto)
    texto = re.sub(r"\s+", " ", texto).strip()
    return texto


def _familia_asset(asset_code):
    if not asset_code:
        return ""
    return re.sub(r"[A-Z]+$", "", asset_code.upper())


def _extrair_codigos_maquina(texto):
    if not texto:
        return []
    padroes = [
        r"\b\d{3,4}[A-Z]?\b",
        r"\b[A-Z]{2,3}\d{1,3}[A-Z]?\b",
        r"\b[A-Z]\d{2,3}[A-Z]?\b",
        r"\bP\d{3}\b",
    ]
    encontrados = set()
    texto_upper = texto.upper()
    for p in padroes:
        for m in re.findall(p, texto_upper):
            encontrados.add(m)
    return sorted(encontrados)


def _expandir_sinonimos(texto_norm):
    extras = []
    for chave, alts in SINONIMOS.items():
        if chave in texto_norm:
            extras.extend(alts)
    if extras:
        return texto_norm + " " + " ".join(extras)
    return texto_norm


# ==================================================================
# Carregar dados
# ==================================================================
def _carregar_workorders():
    if not os.path.exists(WORKORDERS_FILE):
        print(f"❌ {WORKORDERS_FILE} não encontrado.")
        return []
    with open(WORKORDERS_FILE, encoding="utf-8") as f:
        return json.load(f)


def _carregar_comentarios():
    if not os.path.exists(COMENTARIOS_FILE):
        print(f"⚠️ {COMENTARIOS_FILE} não encontrado. Sem comentários.")
        return {}
    resultado = {}
    with open(COMENTARIOS_FILE, encoding="utf-8") as f:
        for linha in f:
            linha = linha.strip()
            if not linha:
                continue
            try:
                obj = json.loads(linha)
                resultado[obj["id"]] = obj
            except Exception:
                continue
    return resultado


def _carregar_gif_base64():
    """Lê o GIF de loading e devolve como data URI base64.
    Devolve "" se não existir."""
    if not os.path.exists(GIF_FILE):
        print(f"⚠️ {GIF_FILE} não encontrado — será usado spinner CSS.")
        return ""
    try:
        with open(GIF_FILE, "rb") as f:
            dados = f.read()
        b64 = base64.b64encode(dados).decode("ascii")
        tamanho_kb = round(len(dados) / 1024, 1)
        print(f"🎨 GIF de loading carregado ({tamanho_kb} KB)")
        return f"data:image/gif;base64,{b64}"
    except Exception as e:
        print(f"⚠️ Erro ao ler GIF: {e}")
        return ""


# ==================================================================
# Construir índice
# ==================================================================
def _construir_indice(workorders, comentarios):
    print(f"📚 A indexar {len(workorders)} OTs...")
    docs = []
    for wo in workorders:
        ot_id = wo.get("id")
        com = comentarios.get(ot_id, {})

        partes = [
            wo.get("code", ""),
            wo.get("name", ""),
            wo.get("assetCode", ""),
            wo.get("assetName", ""),
            wo.get("brand", ""),
            wo.get("brandModel", ""),
            wo.get("serialNumber", ""),
            wo.get("remedy") or "",
        ]
        for c in com.get("comments", []):
            partes.append(c.get("text", ""))

        texto_bruto = " ".join(str(p) for p in partes if p)
        texto_norm = _normalizar(texto_bruto)
        texto_norm = _expandir_sinonimos(texto_norm)

        comentarios_simples = []
        for c in com.get("comments", []):
            comentarios_simples.append({
                "author": c.get("author") or "Desconhecido",
                "date": c.get("date") or "",
                "text": (c.get("text") or "")[:2000],
            })

        docs.append({
            "id": ot_id,
            "code": wo.get("code"),
            "name": wo.get("name"),
            "assetCode": wo.get("assetCode"),
            "assetFamily": _familia_asset(wo.get("assetCode", "")),
            "assetName": wo.get("assetName"),
            "brand": wo.get("brand"),
            "brandModel": wo.get("brandModel"),
            "serialNumber": wo.get("serialNumber"),
            "technician": wo.get("technician"),
            "urgency": wo.get("urgency"),
            "criticality": wo.get("criticality"),
            "beginDateTime": wo.get("beginDateTime"),
            "stateId": wo.get("stateId"),
            "stateName": ESTADOS.get(wo.get("stateId"), ""),
            "remedy": wo.get("remedy"),
            "texto_norm": texto_norm,
            "comentarios": comentarios_simples,
        })

    print(f"✅ Índice pronto: {len(docs)} documentos")
    return docs


# ==================================================================
# Gerar HTML
# ==================================================================
def gerar_motor_visual(filename=HTML_FILE, refresh_interval=0, max_docs=None, abrir=False):
    print(f"📝 A gerar motor de pesquisa visual: {filename}")

    workorders = _carregar_workorders()
    if not workorders:
        print("❌ Sem workorders. Nada a gerar.")
        return False

    if max_docs:
        workorders = workorders[:max_docs]

    comentarios = _carregar_comentarios()
    print(f"💬 {len(comentarios)} OTs com comentários em cache")

    docs = _construir_indice(workorders, comentarios)

    # GIF de loading
    gif_data_uri = _carregar_gif_base64()
    if gif_data_uri:
        gif_html = f'<img src="{gif_data_uri}" alt="loading" class="loading-gif">'
        gif_pesquisa_html = f'<img src="{gif_data_uri}" alt="loading" class="loading-gif">'
    else:
        gif_html = '<div class="loading-spinner-fallback"></div>'
        gif_pesquisa_html = '<div class="loading-spinner-fallback"></div>'

    # Estatísticas
    total = len(docs)
    com_remedy = sum(1 for d in docs if d.get("remedy"))
    com_comentarios = sum(1 for d in docs if d.get("comentarios"))
    agora = datetime.now().strftime("%d/%m/%Y %H:%M:%S")

    maquinas_counter = Counter()
    for d in docs:
        cod = (d.get("assetCode") or "").strip().upper()
        if cod:
            maquinas_counter[cod] += 1
    top_maquinas = [m for m, _ in maquinas_counter.most_common(15)]

    # Serializar para JS
    docs_js = []
    for d in docs:
        docs_js.append({
            "id": d["id"],
            "code": d["code"],
            "name": d["name"],
            "assetCode": d["assetCode"],
            "assetFamily": d["assetFamily"],
            "assetName": d["assetName"],
            "brand": d["brand"],
            "brandModel": d["brandModel"],
            "serialNumber": d["serialNumber"],
            "technician": d["technician"],
            "urgency": d["urgency"],
            "criticality": d["criticality"],
            "beginDateTime": d["beginDateTime"],
            "stateId": d["stateId"],
            "stateName": d["stateName"],
            "remedy": d["remedy"],
            "comentarios": d["comentarios"],
        })

    dados_json = json.dumps(
        {
            "docs": docs_js,
            "sinonimos": SINONIMOS,
            "estados": ESTADOS,
            "estadoEncerrada": ESTADO_ENCERRADA,
            "estadoConcluida": ESTADO_CONCLUIDA,
            "maquinas": top_maquinas,
        },
        ensure_ascii=False,
    )

    refresh_meta = ""
    if refresh_interval and refresh_interval > 0:
        refresh_meta = f'<meta http-equiv="refresh" content="{refresh_interval}">'

    # ==================================================================
    # HTML (f-string — TODAS as chavetas literais são duplicadas)
    # ==================================================================
    html = f'''<!DOCTYPE html>
<html lang="pt">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{refresh_meta}
<title>🔍 Motor de Pesquisa de Avarias</title>
<style>
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
body {{
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
    min-height: 100vh;
    padding: 20px;
}}
.container {{ max-width: 1200px; margin: 0 auto; }}

/* ============ LOADING OVERLAY (inicial) ============ */
#loadingOverlay {{
    position: fixed;
    inset: 0;
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    transition: opacity 0.5s ease;
}}
#loadingOverlay.esconder {{
    opacity: 0;
    pointer-events: none;
}}
#loadingOverlay .loading-gif {{
    width: 140px;
    height: 140px;
    object-fit: contain;
    margin-bottom: 22px;
    filter: drop-shadow(0 0 25px rgba(245, 87, 108, 0.4));
}}
#loadingOverlay .loading-spinner-fallback {{
    width: 80px;
    height: 80px;
    border: 6px solid rgba(255,255,255,0.15);
    border-top: 6px solid #f5576c;
    border-radius: 50%;
    animation: spinnerSpin 1s linear infinite;
    margin-bottom: 22px;
}}

/* ============ OVERLAY DE PESQUISA ============ */
#pesquisaOverlay {{
    display: none;
    position: fixed;
    inset: 0;
    background: rgba(26, 26, 46, 0.85);
    backdrop-filter: blur(4px);
    z-index: 9998;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    transition: opacity 0.25s ease;
}}
#pesquisaOverlay.activo {{
    display: flex;
}}
#pesquisaOverlay.esconder {{
    opacity: 0;
    pointer-events: none;
}}
#pesquisaOverlay .loading-gif {{
    width: 110px;
    height: 110px;
    object-fit: contain;
    margin-bottom: 18px;
    filter: drop-shadow(0 0 20px rgba(245, 87, 108, 0.5));
}}
#pesquisaOverlay .loading-spinner-fallback {{
    width: 70px;
    height: 70px;
    border: 5px solid rgba(255,255,255,0.15);
    border-top: 5px solid #f5576c;
    border-radius: 50%;
    animation: spinnerSpin 1s linear infinite;
    margin-bottom: 18px;
}}

/* Spinner / dots (partilhados) */
@keyframes spinnerSpin {{
    to {{ transform: rotate(360deg); }}
}}
.loading-titulo {{
    font-size: 18px;
    font-weight: 600;
    margin-bottom: 8px;
    letter-spacing: 0.3px;
}}
.loading-sub {{
    font-size: 13px;
    opacity: 0.75;
}}
.loading-dots::after {{
    content: "";
    animation: dots 1.5s steps(4, end) infinite;
}}
@keyframes dots {{
    0%   {{ content: ""; }}
    25%  {{ content: "."; }}
    50%  {{ content: ".."; }}
    75%  {{ content: "..."; }}
    100% {{ content: ""; }}
}}

/* ============ HEADER ============ */
.header {{
    background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
    color: white;
    padding: 28px 32px;
    border-radius: 20px;
    margin-bottom: 22px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.2);
    position: relative;
    overflow: hidden;
}}
.header::before {{
    content: '';
    position: absolute;
    top: -50%; right: -20%;
    width: 500px; height: 500px;
    background: rgba(255,255,255,0.05);
    border-radius: 50%;
}}
.header h1 {{ font-size: 26px; margin-bottom: 6px; position: relative; z-index: 1; }}
.header .subtitle {{ opacity: 0.85; font-size: 14px; position: relative; z-index: 1; }}
.header .timestamp {{ opacity: 0.7; font-size: 12px; margin-top: 6px; position: relative; z-index: 1; }}
.header .stats {{
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
    gap: 12px;
    margin-top: 18px;
    position: relative; z-index: 1;
}}
.stat-item {{
    background: rgba(255,255,255,0.12);
    backdrop-filter: blur(10px);
    padding: 12px 14px;
    border-radius: 12px;
    text-align: center;
}}
.stat-item .number {{ font-size: 22px; font-weight: bold; }}
.stat-item .label {{ font-size: 11px; opacity: 0.85; margin-top: 4px; }}

/* ============ SEARCH BOX ============ */
.search-box {{
    background: white;
    padding: 22px 26px;
    border-radius: 16px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.08);
    margin-bottom: 20px;
    border-left: 6px solid #f5576c;
}}
.search-box textarea {{
    width: 100%;
    min-height: 70px;
    padding: 12px 14px;
    border: 2px solid #e0e0e0;
    border-radius: 10px;
    font-size: 15px;
    font-family: inherit;
    resize: vertical;
    transition: border-color 0.2s;
}}
.search-box textarea:focus {{
    outline: none;
    border-color: #f5576c;
}}
.search-actions {{
    display: flex;
    gap: 10px;
    margin-top: 12px;
    align-items: center;
    flex-wrap: wrap;
}}
.btn {{
    padding: 10px 22px;
    border: none;
    border-radius: 10px;
    cursor: pointer;
    font-weight: 600;
    font-size: 14px;
    transition: all 0.25s;
}}
.btn-primary {{ background: #f5576c; color: white; }}
.btn-primary:hover {{ background: #d6304a; }}
.btn-primary:disabled {{ background: #bbb; cursor: not-allowed; }}
.btn-clear {{
    background: #f8f9fa; border: 2px solid #e0e0e0; color: #333;
}}
.btn-clear:hover {{ background: #e9ecef; border-color: #f5576c; }}
.btn-clear:disabled {{ opacity: 0.5; cursor: not-allowed; }}

.opcoes {{
    display: flex; gap: 16px; align-items: center;
    margin-left: auto; font-size: 13px; color: #555;
}}
.opcoes label {{ display: flex; align-items: center; gap: 6px; font-weight: 600; }}
.opcoes input[type="number"] {{
    padding: 6px 10px; border: 2px solid #e0e0e0;
    border-radius: 8px; width: 80px; font-size: 13px;
}}
.opcoes input[type="number"]:focus {{ outline: none; border-color: #f5576c; }}

.sugestoes {{
    margin-top: 10px;
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
    align-items: center;
    font-size: 12px;
    color: #888;
}}
.sugestao {{
    background: #f8f9fa;
    border: 1px solid #e0e0e0;
    border-radius: 14px;
    padding: 4px 12px;
    cursor: pointer;
    font-size: 12px;
    transition: all 0.2s;
}}
.sugestao:hover {{
    background: #f5576c; color: white; border-color: #f5576c;
}}

/* ============ RESULTADOS ============ */
.resultado-info {{
    background: white;
    padding: 12px 20px;
    border-radius: 12px;
    margin-bottom: 16px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
    display: flex;
    justify-content: space-between;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    font-size: 14px;
    color: #555;
}}
.resultado-info .maq-tag {{
    background: #e3f2fd; color: #1976d2;
    padding: 3px 10px; border-radius: 12px;
    font-size: 12px; font-weight: 700;
    margin-left: 6px;
}}

.solucoes {{
    background: white;
    padding: 20px 24px;
    border-radius: 14px;
    margin-bottom: 20px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.08);
}}
.solucoes h3 {{
    font-size: 16px; color: #1a1a2e;
    margin-bottom: 14px;
    display: flex; align-items: center; gap: 8px;
}}
.solucao-item {{
    display: grid;
    grid-template-columns: 70px 1fr auto;
    gap: 14px;
    align-items: center;
    padding: 10px 0;
    border-bottom: 1px solid #f0f0f0;
}}
.solucao-item:last-child {{ border-bottom: none; }}
.solucao-pct {{
    font-weight: 700; font-size: 17px;
    color: #f5576c;
}}
.solucao-bar-wrap {{
    background: #f0f0f0; height: 10px; border-radius: 6px;
    overflow: hidden; margin-top: 6px;
}}
.solucao-bar {{
    height: 100%;
    background: linear-gradient(90deg, #f5576c, #f093fb);
    border-radius: 6px;
    transition: width 0.4s;
}}
.solucao-texto {{
    font-size: 13px; color: #333; line-height: 1.4;
    word-break: break-word;
}}
.solucao-casos {{
    font-size: 11px; color: #888; margin-top: 3px;
    white-space: nowrap;
}}

.casos-header {{
    font-size: 15px; color: #333;
    margin: 20px 0 12px;
    font-weight: 700;
}}
.caso {{
    background: white;
    border-radius: 14px;
    padding: 16px 20px;
    margin-bottom: 12px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
    border-left: 6px solid #667eea;
    transition: all 0.25s;
    opacity: 0;
    animation: fadeInUp 0.4s ease forwards;
}}
.caso:hover {{
    transform: translateY(-2px);
    box-shadow: 0 8px 25px rgba(0,0,0,0.12);
}}
.caso.score-alto {{ border-left-color: #28a745; }}
.caso.score-medio {{ border-left-color: #ffc107; }}
.caso.score-baixo {{ border-left-color: #dc3545; }}

.caso-header {{
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 14px;
    flex-wrap: wrap;
}}
.caso-code {{
    font-family: monospace; font-size: 14px;
    font-weight: 700; color: #667eea;
}}
.caso-score {{
    font-size: 20px; font-weight: 800;
    padding: 4px 14px; border-radius: 12px;
    white-space: nowrap;
}}
.score-alto .caso-score {{ background: #d4edda; color: #155724; }}
.score-medio .caso-score {{ background: #fff3cd; color: #856404; }}
.score-baixo .caso-score {{ background: #f8d7da; color: #721c24; }}

.caso-title {{
    font-size: 14px; color: #333;
    margin: 8px 0;
    font-weight: 600;
}}
.caso-meta {{
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    font-size: 12px;
    color: #666;
    margin: 10px 0;
}}
.caso-meta span {{ display: flex; align-items: center; gap: 4px; }}
.caso-remedy {{
    background: #f8f9fa;
    border-left: 4px solid #667eea;
    border-radius: 8px;
    padding: 10px 14px;
    margin-top: 10px;
    font-size: 13px;
    color: #333;
    line-height: 1.5;
}}
.caso-remedy strong {{ color: #667eea; }}

.caso-comentarios-btn {{
    background: none; border: none;
    color: #667eea; cursor: pointer;
    font-size: 12px; font-weight: 600;
    padding: 6px 0; margin-top: 8px;
}}
.caso-comentarios-btn:hover {{ text-decoration: underline; }}

.caso-comentarios {{
    display: none;
    margin-top: 10px;
    border-top: 1px dashed #e0e0e0;
    padding-top: 10px;
}}
.caso-comentarios.aberto {{ display: block; }}
.caso-comentario {{
    background: #f8f9fa;
    border-radius: 6px;
    padding: 8px 12px;
    margin-bottom: 6px;
    font-size: 12px;
    border-left: 3px solid #e0e0e0;
}}
.caso-comentario .author {{ font-weight: 700; color: #667eea; }}
.caso-comentario .date {{ color: #888; margin-left: 8px; font-size: 11px; }}
.caso-comentario .text {{
    margin-top: 4px; color: #333;
    white-space: pre-wrap; word-break: break-word;
}}

.no-result {{
    text-align: center;
    padding: 50px 20px;
    color: #666;
    background: white;
    border-radius: 14px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.06);
}}
.no-result .icon {{ font-size: 56px; margin-bottom: 14px; }}
.no-result h2 {{ font-size: 18px; margin-bottom: 8px; color: #333; }}

.estado-badge {{
    display: inline-block;
    padding: 2px 10px;
    border-radius: 12px;
    font-size: 11px;
    font-weight: 600;
    background: #e9ecef;
    color: #495057;
}}
.estado-Encerrada {{ background: #e9ecef; color: #495057; }}
.estado-Concluída {{ background: #d4edda; color: #155724; }}
.estado-Em_curso {{ background: #fff3cd; color: #856404; }}
.estado-Por_fazer {{ background: #f8d7da; color: #721c24; }}
.estado-Suspenso {{ background: #ffe5d0; color: #7b4a1e; }}

@keyframes fadeInUp {{
    from {{ opacity: 0; transform: translateY(12px); }}
    to {{ opacity: 1; transform: translateY(0); }}
}}

@media (max-width: 768px) {{
    .solucao-item {{ grid-template-columns: 1fr; }}
    .opcoes {{ width: 100%; margin-left: 0; justify-content: flex-start; }}
    .header h1 {{ font-size: 20px; }}
    .caso-header {{ flex-direction: column; gap: 6px; }}
}}
</style>
</head>
<body>

<!-- LOADING OVERLAY (inicial) -->
<div id="loadingOverlay">
    {gif_html}
    <div class="loading-titulo">🔍 A preparar o motor de pesquisa</div>
    <div class="loading-sub">A carregar índice de OTs e comentários<span class="loading-dots"></span></div>
</div>

<!-- OVERLAY DE PESQUISA -->
<div id="pesquisaOverlay">
    {gif_pesquisa_html}
    <div class="loading-titulo">🔎 A analisar casos...</div>
    <div class="loading-sub">A comparar com <span id="pesquisaTotal">0</span> OTs<span class="loading-dots"></span></div>
</div>

<div class="container">

    <div class="header">
        <h1>🔍 Motor de Pesquisa de Avarias</h1>
        <div class="subtitle">Encontra casos históricos semelhantes e soluções aplicadas</div>
        <div class="timestamp">📅 Índice gerado em {agora}</div>

        <div class="stats">
            <div class="stat-item">
                <div class="number">{total}</div>
                <div class="label">📋 OTs indexadas</div>
            </div>
            <div class="stat-item">
                <div class="number">{com_remedy}</div>
                <div class="label">💡 Com solução</div>
            </div>
            <div class="stat-item">
                <div class="number">{com_comentarios}</div>
                <div class="label">💬 Com comentários</div>
            </div>
            <div class="stat-item">
                <div class="number">{len(maquinas_counter)}</div>
                <div class="label">🖥️ Máquinas</div>
            </div>
        </div>
    </div>

    <div class="search-box">
        <textarea id="pergunta" placeholder="Descreve a avaria... Ex: 754 erro emergencia carregador nao arranca" onkeydown="if(event.ctrlKey && event.key==='Enter') pesquisar()"></textarea>
        <div class="search-actions">
            <button class="btn btn-primary" onclick="pesquisar()">🔍 Pesquisar</button>
            <button class="btn btn-clear" onclick="limpar()">✖️ Limpar</button>
            <div class="opcoes">
                <label>Top <input type="number" id="topN" value="10" min="1" max="50"></label>
                <label>Score mín. <input type="number" id="minScore" value="30" min="0" max="100"></label>
            </div>
        </div>
        <div class="sugestoes" id="sugestoes">
            <span>💡 Exemplos:</span>
        </div>
    </div>

    <div id="resultados">
        <div class="no-result">
            <div class="icon">🔎</div>
            <h2>Escreve uma descrição e clica em Pesquisar</h2>
            <p>Ou prime Ctrl+Enter</p>
        </div>
    </div>
</div>

<script>
// ==================================================================
// Dados embutidos
// ==================================================================
const BUNDLE = {dados_json};
const DOCS = BUNDLE.docs;
const SINONIMOS = BUNDLE.sinonimos;
const MAQUINAS_SUGERIDAS = BUNDLE.maquinas;
const ESTADO_ENCERRADA = BUNDLE.estadoEncerrada;
const ESTADO_CONCLUIDA = BUNDLE.estadoConcluida;

// ==================================================================
// Normalização
// ==================================================================
function normalizar(texto) {{
    if (!texto) return "";
    return String(texto)
        .normalize("NFKD")
        .replace(/[\\u0300-\\u036f]/g, "")
        .toLowerCase()
        .replace(/[^\\w\\s]/g, " ")
        .replace(/\\s+/g, " ")
        .trim();
}}

function expandirSinonimos(textoNorm) {{
    const extras = [];
    for (const chave in SINONIMOS) {{
        if (textoNorm.indexOf(chave) !== -1) {{
            extras.push.apply(extras, SINONIMOS[chave]);
        }}
    }}
    return extras.length ? textoNorm + " " + extras.join(" ") : textoNorm;
}}

function extrairCodigosMaquina(texto) {{
    if (!texto) return [];
    const upper = texto.toUpperCase();
    const padroes = [
        /\\b\\d{{3,4}}[A-Z]?\\b/g,
        /\\b[A-Z]{{2,3}}\\d{{1,3}}[A-Z]?\\b/g,
        /\\b[A-Z]\\d{{2,3}}[A-Z]?\\b/g,
        /\\bP\\d{{3}}\\b/g,
    ];
    const encontrados = {{}};
    for (const p of padroes) {{
        const m = upper.match(p);
        if (m) for (const x of m) encontrados[x] = true;
    }}
    return Object.keys(encontrados);
}}

// ==================================================================
// Similaridade
// ==================================================================
function levenshtein(a, b) {{
    if (a === b) return 0;
    if (!a.length) return b.length;
    if (!b.length) return a.length;
    const m = a.length, n = b.length;
    let prev = new Array(n + 1);
    let curr = new Array(n + 1);
    for (let j = 0; j <= n; j++) prev[j] = j;
    for (let i = 1; i <= m; i++) {{
        curr[0] = i;
        for (let j = 1; j <= n; j++) {{
            const cost = a[i - 1] === b[j - 1] ? 0 : 1;
            curr[j] = Math.min(
                curr[j - 1] + 1,
                prev[j] + 1,
                prev[j - 1] + cost
            );
        }}
        const tmp = prev; prev = curr; curr = tmp;
    }}
    return prev[n];
}}

function ratio(a, b) {{
    if (!a && !b) return 100;
    if (!a || !b) return 0;
    const d = levenshtein(a, b);
    const maxLen = Math.max(a.length, b.length);
    return (1 - d / maxLen) * 100;
}}

function partialRatio(needle, haystack) {{
    if (!needle || !haystack) return 0;
    if (needle.length > haystack.length) {{
        const t = needle; needle = haystack; haystack = t;
    }}
    if (haystack.indexOf(needle) !== -1) return 100;

    const winLen = needle.length;
    let best = 0;
    const step = Math.max(1, Math.floor(winLen / 4));
    for (let i = 0; i <= haystack.length - winLen; i += step) {{
        const window = haystack.substring(i, i + winLen);
        const r = ratio(needle, window);
        if (r > best) {{
            best = r;
            if (best >= 99) return 100;
        }}
    }}
    return best;
}}

function tokenSetRatio(a, b) {{
    const ta = a.split(" ").filter(Boolean);
    const tb = b.split(" ").filter(Boolean);
    const setA = new Set(ta);
    const setB = new Set(tb);
    const inter = [];
    const diffA = [];
    const diffB = [];
    for (const t of setA) {{
        if (setB.has(t)) inter.push(t);
        else diffA.push(t);
    }}
    for (const t of setB) {{
        if (!setA.has(t)) diffB.push(t);
    }}
    inter.sort();
    diffA.sort();
    diffB.sort();

    const sInter = inter.join(" ");
    const c1 = (sInter + " " + diffA.join(" ")).trim();
    const c2 = (sInter + " " + diffB.join(" ")).trim();

    return Math.max(
        ratio(sInter, c1),
        ratio(sInter, c2),
        ratio(c1, c2)
    );
}}

// ==================================================================
// Scoring
// ==================================================================
function calcularScore(perguntaNorm, doc, maquinas) {{
    let scoreMaq = 0;
    const assetCode = (doc.assetCode || "").toUpperCase();
    const familia = (doc.assetFamily || "").toUpperCase();

    if (maquinas.length > 0) {{
        if (maquinas.indexOf(assetCode) !== -1) scoreMaq = 40;
        else if (familia && maquinas.indexOf(familia) !== -1) scoreMaq = 25;
        else if (maquinas.some(m => assetCode.indexOf(m) !== -1 || m.indexOf(assetCode) !== -1)) scoreMaq = 30;
    }}

    const partes = [
        doc.code || "",
        doc.name || "",
        doc.assetCode || "",
        doc.assetName || "",
        doc.brand || "",
        doc.brandModel || "",
        doc.serialNumber || "",
        doc.remedy || "",
    ];
    if (doc.comentarios) {{
        for (const c of doc.comentarios) partes.push(c.text || "");
    }}
    let textoDocNorm = normalizar(partes.join(" "));
    textoDocNorm = expandirSinonimos(textoDocNorm);

    let scoreTxt = 0;
    if (perguntaNorm && textoDocNorm) {{
        const s1 = partialRatio(perguntaNorm, textoDocNorm);
        const s2 = tokenSetRatio(perguntaNorm, textoDocNorm);
        scoreTxt = (s1 * 0.4 + s2 * 0.6) * 0.40;
    }}

    const bonusRemedy = doc.remedy ? 10 : 0;
    const bonusEncerrada = doc.stateId === ESTADO_ENCERRADA ? 10 : 0;

    return scoreMaq + scoreTxt + bonusRemedy + bonusEncerrada;
}}

// ==================================================================
// Pesquisa (com overlay de loading)
// ==================================================================
function pesquisar() {{
    const pergunta = document.getElementById("pergunta").value.trim();
    if (!pergunta) {{
        document.getElementById("pergunta").focus();
        return;
    }}

    const topN = parseInt(document.getElementById("topN").value) || 10;
    const minScore = parseFloat(document.getElementById("minScore").value) || 30;

    // Mostrar overlay de pesquisa
    const overlay = document.getElementById("pesquisaOverlay");
    const totalEl = document.getElementById("pesquisaTotal");
    if (totalEl) totalEl.textContent = DOCS.length;
    overlay.classList.add("activo");
    overlay.classList.remove("esconder");

    // Desativar botões durante a pesquisa
    const btnPesquisar = document.querySelector(".btn-primary");
    const btnLimpar = document.querySelector(".btn-clear");
    if (btnPesquisar) btnPesquisar.disabled = true;
    if (btnLimpar) btnLimpar.disabled = true;

    // Dar tempo ao browser para pintar o overlay ANTES de bloquear com o cálculo
    setTimeout(function() {{
        try {{
            _executarPesquisa(pergunta, topN, minScore);
        }} finally {{
            // Esconder overlay com fade-out
            overlay.classList.add("esconder");
            setTimeout(function() {{
                overlay.classList.remove("activo");
                overlay.classList.remove("esconder");
            }}, 220);

            // Reactivar botões
            if (btnPesquisar) btnPesquisar.disabled = false;
            if (btnLimpar) btnLimpar.disabled = false;
        }}
    }}, 40);
}}

function _executarPesquisa(pergunta, topN, minScore) {{
    const maquinas = extrairCodigosMaquina(pergunta);
    let perguntaNorm = normalizar(pergunta);
    perguntaNorm = expandirSinonimos(perguntaNorm);

    const t0 = performance.now();

    const resultados = [];
    for (const doc of DOCS) {{
        const s = calcularScore(perguntaNorm, doc, maquinas);
        if (s >= minScore) {{
            resultados.push({{ score: s, doc: doc }});
        }}
    }}

    resultados.sort((a, b) => b.score - a.score);
    const top = resultados.slice(0, topN);

    const dt = (performance.now() - t0).toFixed(0);

    renderizar(pergunta, maquinas, top, resultados.length, dt);
}}

function renderizar(pergunta, maquinas, top, totalResultados, dt) {{
    const el = document.getElementById("resultados");

    let html = "";

    html += '<div class="resultado-info">';
    html += '<div>';
    html += '<strong>' + totalResultados + '</strong> caso(s) encontrado(s) · top ' + top.length + ' mostrado(s) · <span style="color:#888;">' + dt + ' ms</span>';
    if (maquinas.length > 0) {{
        html += ' · Máquinas:';
        for (const m of maquinas) {{
            html += ' <span class="maq-tag">' + escapeHtml(m) + '</span>';
        }}
    }}
    html += '</div>';
    html += '</div>';

    if (top.length === 0) {{
        html += '<div class="no-result">';
        html += '<div class="icon">🤷</div>';
        html += '<h2>Sem resultados</h2>';
        html += '<p>Tenta baixar o score mínimo ou reformular a pergunta.</p>';
        html += '</div>';
        el.innerHTML = html;
        return;
    }}

    // Agregação de soluções
    const contagem = {{}};
    for (const r of top) {{
        const rem = r.doc.remedy;
        if (rem) contagem[rem] = (contagem[rem] || 0) + 1;
    }}
    const totalComRemedy = Object.values(contagem).reduce((a, b) => a + b, 0);

    if (totalComRemedy > 0) {{
        const solucoes = Object.entries(contagem)
            .map(([solucao, casos]) => ({{
                solucao: solucao,
                casos: casos,
                prob: (casos / totalComRemedy) * 100,
            }}))
            .sort((a, b) => b.prob - a.prob);

        html += '<div class="solucoes">';
        html += '<h3>📊 Soluções por probabilidade</h3>';
        for (const s of solucoes) {{
            html += '<div class="solucao-item">';
            html += '  <div class="solucao-pct">' + s.prob.toFixed(1) + '%</div>';
            html += '  <div>';
            html += '    <div class="solucao-texto">' + escapeHtml(s.solucao) + '</div>';
            html += '    <div class="solucao-bar-wrap"><div class="solucao-bar" style="width:' + s.prob.toFixed(1) + '%"></div></div>';
            html += '  </div>';
            html += '  <div class="solucao-casos">' + s.casos + ' caso(s)</div>';
            html += '</div>';
        }}
        html += '</div>';
    }} else {{
        html += '<div class="solucoes"><h3>📊 Soluções por probabilidade</h3>';
        html += '<p style="color:#888;font-size:13px;">⚠️ Nenhuma solução registada nos casos encontrados.</p>';
        html += '</div>';
    }}

    // Casos
    html += '<div class="casos-header">🏆 Casos mais similares</div>';

    let idx = 0;
    for (const r of top) {{
        idx++;
        const doc = r.doc;
        const score = r.score;

        let classe = "score-baixo";
        if (score >= 75) classe = "score-alto";
        else if (score >= 50) classe = "score-medio";

        const estadoNome = doc.stateName || "";
        const estadoClasse = estadoNome.replace(/\\s+/g, "_");

        html += '<div class="caso ' + classe + '" style="animation-delay:' + (idx * 0.03) + 's">';
        html += '  <div class="caso-header">';
        html += '    <div>';
        html += '      <div class="caso-code">' + escapeHtml(doc.code || doc.id || '') + '</div>';
        html += '      <div style="font-size:11px;color:#888;margin-top:3px;">';
        html += '        🖥️ ' + escapeHtml(doc.assetCode || '?') + ' — ' + escapeHtml(doc.assetName || '');
        html += '      </div>';
        html += '    </div>';
        html += '    <div class="caso-score">' + score.toFixed(1) + '%</div>';
        html += '  </div>';

        html += '  <div class="caso-title">' + escapeHtml(doc.name || 'Sem título') + '</div>';

        html += '  <div class="caso-meta">';
        if (doc.technician) html += '<span>👤 ' + escapeHtml(doc.technician) + '</span>';
        if (doc.beginDateTime) html += '<span>📅 ' + escapeHtml(String(doc.beginDateTime).slice(0, 10)) + '</span>';
        if (doc.brand || doc.brandModel) html += '<span>🏷️ ' + escapeHtml((doc.brand || '') + ' ' + (doc.brandModel || '')) + '</span>';
        if (estadoNome) html += '<span class="estado-badge estado-' + estadoClasse + '">' + escapeHtml(estadoNome) + '</span>';
        html += '  </div>';

        if (doc.remedy) {{
            html += '  <div class="caso-remedy"><strong>💡 Solução:</strong> ' + escapeHtml(doc.remedy) + '</div>';
        }} else {{
            html += '  <div class="caso-remedy" style="opacity:0.6;"><strong>💡 Solução:</strong> (sem solução registada)</div>';
        }}

        if (doc.comentarios && doc.comentarios.length > 0) {{
            const cid = 'com_' + idx;
            html += '  <button class="caso-comentarios-btn" onclick="toggleComentarios(\\'' + cid + '\\', this)">';
            html += '    💬 Ver ' + doc.comentarios.length + ' comentário(s)';
            html += '  </button>';
            html += '  <div class="caso-comentarios" id="' + cid + '">';
            const comentariosOrd = doc.comentarios.slice().sort((a, b) => {{
                const da = new Date(a.date || 0).getTime() || 0;
                const db = new Date(b.date || 0).getTime() || 0;
                return db - da;
            }});
            for (const c of comentariosOrd) {{
                html += '<div class="caso-comentario">';
                html += '  <span class="author">👤 ' + escapeHtml(c.author || 'Desconhecido') + '</span>';
                if (c.date) html += '  <span class="date">📅 ' + escapeHtml(formatarData(c.date)) + '</span>';
                html += '  <div class="text">' + escapeHtml(c.text || '') + '</div>';
                html += '</div>';
            }}
            html += '  </div>';
        }}

        html += '</div>';
    }}

    el.innerHTML = html;
}}

function toggleComentarios(id, btn) {{
    const el = document.getElementById(id);
    if (!el) return;
    el.classList.toggle("aberto");
    if (el.classList.contains("aberto")) {{
        btn.innerHTML = btn.innerHTML.replace("Ver", "Ocultar");
    }} else {{
        btn.innerHTML = btn.innerHTML.replace("Ocultar", "Ver");
    }}
}}

function formatarData(iso) {{
    if (!iso) return "";
    try {{
        const d = new Date(iso);
        if (isNaN(d.getTime())) return String(iso).slice(0, 16);
        return d.toLocaleString("pt-PT", {{
            day: "2-digit", month: "2-digit", year: "numeric",
            hour: "2-digit", minute: "2-digit"
        }});
    }} catch (e) {{
        return String(iso).slice(0, 16);
    }}
}}

function escapeHtml(s) {{
    if (s === null || s === undefined) return "";
    return String(s)
        .replace(/&/g, "&amp;")
        .replace(/</g, "&lt;")
        .replace(/>/g, "&gt;")
        .replace(/"/g, "&quot;");
}}

function limpar() {{
    document.getElementById("pergunta").value = "";
    document.getElementById("pergunta").focus();
    document.getElementById("resultados").innerHTML =
        '<div class="no-result"><div class="icon">🔎</div><h2>Escreve uma descrição e clica em Pesquisar</h2><p>Ou prime Ctrl+Enter</p></div>';
}}

// ==================================================================
// Sugestões
// ==================================================================
function initSugestoes() {{
    const el = document.getElementById("sugestoes");
    const exemplos = [];

    const top3 = MAQUINAS_SUGERIDAS.slice(0, 3);
    for (const m of top3) {{
        exemplos.push(m + " erro emergencia");
        exemplos.push(m + " nao arranca");
    }}
    exemplos.push("carregador nao arranca");
    exemplos.push("erro emergencia");

    const unicos = Array.from(new Set(exemplos)).slice(0, 6);

    for (const ex of unicos) {{
        const span = document.createElement("span");
        span.className = "sugestao";
        span.textContent = ex;
        span.onclick = function() {{
            document.getElementById("pergunta").value = ex;
            pesquisar();
        }};
        el.appendChild(span);
    }}
}}

// Esconder loading overlay inicial quando tudo estiver pronto
window.addEventListener("load", function() {{
    const ov = document.getElementById("loadingOverlay");
    if (ov) {{
        setTimeout(function() {{
            ov.classList.add("esconder");
            setTimeout(function() {{
                if (ov.parentNode) ov.remove();
            }}, 600);
        }}, 250);
    }}
}});

document.addEventListener("DOMContentLoaded", function() {{
    initSugestoes();
    document.getElementById("pergunta").focus();
}});
</script>
</body>
</html>'''

    try:
        os.makedirs(os.path.dirname(filename), exist_ok=True)
        with open(filename, "w", encoding="utf-8") as f:
            f.write(html)
        tamanho_mb = round(os.path.getsize(filename) / (1024 * 1024), 2)
        print(f"✅ HTML gerado com sucesso!")
        print(f"   📄 {filename}")
        print(f"   📊 Tamanho: {tamanho_mb} MB")
        print(f"   📈 {total} OTs | {com_remedy} com solução | {com_comentarios} com comentários")
    except Exception as e:
        print(f"❌ Erro ao gerar HTML: {e}")
        import traceback
        traceback.print_exc()
        return False

    if abrir:
        print(f"\n🌐 A abrir no browser...")
        webbrowser.open(f"file://{filename}")

    return True


# ==================================================================
# CLI
# ==================================================================
if __name__ == "__main__":
    import sys

    abrir = "--abrir" in sys.argv or "-a" in sys.argv

    ok = gerar_motor_visual(abrir=abrir)

    if ok and not abrir:
        print()
        print("💡 Para abrir automaticamente, corre:")
        print("   python motor_pesquisa_visual.py --abrir")
        print()
        print(f"   Ou abre manualmente: file://{HTML_FILE}")