"""Catalogo CAA — codice client, scritto in Python ed eseguito da Brython.

Il sito è completamente utilizzabile anche senza questo file: le pagine arrivano
già complete dal server e i moduli funzionano con l'invio tradizionale. Qui si
aggiunge ciò che rende l'uso più comodo — filtri immediati, suggerimenti di
ricerca, opzioni di lettura, invio del modulo senza ricaricare la pagina.

La pagina carica `brython.min.js` e `brython_stdlib.js`, quindi qui valgono gli
import consueti (`browser.ajax`, `browser.timer`, e all'occorrenza `json`, `re`,
`urllib`). Costa circa un megabyte compresso al primo caricamento, poi resta in
cache: è il prezzo per scrivere codice normale invece di aggirare le assenze del
solo core.
"""

from browser import ajax, document, html, timer, window

ANNO = 31536000  # secondi in un anno, per la durata dei cookie


# ==========================================================================
# Richieste HTTP e ritardi
# ==========================================================================
# Involucri sottili su `browser.ajax` e `browser.timer`: mettono in un punto
# solo l'intestazione che identifica le richieste del client e uniformano la
# firma dei callback a `(stato, testo)`.


def richiedi(metodo, indirizzo, quando_pronto, dati=None, tipo=None):
    """Richiesta asincrona. `quando_pronto(stato, testo)` a risposta completa."""
    def completata(risposta):
        quando_pronto(risposta.status, risposta.text or "")

    intestazioni = {"X-Richiesta": "brython"}
    if metodo == "GET":
        ajax.get(indirizzo, headers=intestazioni, oncomplete=completata)
    else:
        if tipo:
            intestazioni["Content-Type"] = tipo
        ajax.post(indirizzo, data=dati or "", headers=intestazioni,
                  oncomplete=completata)


def fra(millisecondi, funzione):
    """Esegue `funzione` dopo un ritardo. Ritorna l'identificativo per annullare."""
    return timer.set_timeout(funzione, millisecondi)


def annulla(identificativo):
    if identificativo is not None:
        timer.clear_timeout(identificativo)


# ==========================================================================
# Testi e indirizzi, forniti dal server
# ==========================================================================
# Il client non conosce né la lingua né il sottopercorso su cui il sito è
# pubblicato: entrambi arrivano dal documento. Le stringhe stanno negli
# attributi `data-` di #testi-client, i prefissi su <html>. Nessun secondo
# scaricamento e nessuna traduzione duplicata nel codice.

_TESTI_PREDEFINITI = {
    "invio": "…",
    "invia": "OK",
    "controlla": "",
    "non-inviata": "",
    "inviata": "",
    "impostazione": "",
    "err-nome": "",
    "err-email": "",
    "err-messaggio": "",
    "err-consenso": "",
}


def testo_di(chiave):
    """Stringa tradotta preparata dal server, o un ripiego innocuo."""
    contenitore = document.select_one("#testi-client")
    if contenitore is not None:
        valore = attributo(contenitore, "data-" + chiave)
        if valore:
            return valore
    return _TESTI_PREDEFINITI.get(chiave, "")


def base():
    """Sottopercorso pubblico del sito ("" se è alla radice del dominio)."""
    radice = document.select_one("html")
    if radice is not None:
        valore = attributo(radice, "data-base")
        if valore:
            return valore
    return ""


def connessione_sicura():
    radice = document.select_one("html")
    return attributo(radice, "data-sicuro") == "1"


# ==========================================================================
# Utilità
# ==========================================================================

def uno(selettore, radice=None):
    """Primo elemento corrispondente, oppure None."""
    return (radice or document).select_one(selettore)


def tutti(selettore, radice=None):
    return (radice or document).select(selettore)


def mostra(elemento):
    if elemento is not None:
        elemento.removeAttribute("hidden")


def nascondi(elemento):
    if elemento is not None:
        elemento.setAttribute("hidden", "")


def e_nascosto(elemento):
    return elemento is None or elemento.hasAttribute("hidden")


def antenato(elemento, verifica, limite=40):
    """Risale l'albero fino al primo elemento che soddisfa `verifica`.

    La risalita si ferma su <html>, non su un genitore assente: il `null` di
    JavaScript non è `None` per Python, quindi `while elemento is not None` lo
    lascerebbe passare e il giro successivo fallirebbe leggendo un attributo su
    nulla. Fermarsi un gradino prima evita del tutto la questione.

    `limite` è una cintura di sicurezza contro un albero ciclico: nessuna
    pagina ha quaranta livelli di annidamento sopra un elemento cliccabile.
    """
    for _ in range(limite):
        if elemento is None:
            return None
        try:
            if verifica(elemento):
                return elemento
        except Exception:
            pass
        try:
            nome = elemento.tagName
        except Exception:
            return None
        # Senza tagName non è un elemento (nodo di testo, document); con HTML
        # siamo alla radice e sopra non c'è altro da guardare.
        if not nome or str(nome).upper() == "HTML":
            return None
        try:
            elemento = elemento.parentElement
        except Exception:
            return None
    return None


def ha_classe(elemento, nome):
    try:
        return elemento.classList.contains(nome)
    except Exception:
        return False


def attributo(elemento, nome):
    """Valore di un attributo, oppure None se assente.

    Si decide con `hasAttribute` invece di fidarsi del valore restituito:
    `getAttribute` dà il `null` di JavaScript per gli attributi mancanti, che in
    Brython non è `None` e in un test booleano può perfino risultare vero — con
    l'effetto che una ricerca "il primo antenato che ha questo attributo"
    troverebbe il primo elemento qualunque.
    """
    try:
        if not elemento.hasAttribute(nome):
            return None
        return elemento.getAttribute(nome)
    except Exception:
        return None


def cod(valore):
    """Codifica un valore per la query string (senza dipendere da urllib)."""
    return window.encodeURIComponent(str(valore))


def query_string(coppie):
    parti = ["%s=%s" % (cod(k), cod(v)) for k, v in coppie if v not in (None, "")]
    return "&".join(parti)


def imposta_cookie(nome, valore):
    # Si passa da `window.document` per scrivere direttamente sull'oggetto JS.
    # Il percorso è quello pubblico del sito e `Secure` compare solo in HTTPS,
    # altrimenti il browser scarterebbe il cookie in sviluppo.
    pezzi = [
        "%s=%s" % (nome, valore),
        "path=%s/" % base(),
        "max-age=%d" % ANNO,
        "SameSite=Lax",
    ]
    if connessione_sicura():
        pezzi.append("Secure")
    window.document.cookie = "; ".join(pezzi)


def frammento(testo_html):
    """Trasforma una risposta HTML in un nodo su cui interrogare il DOM."""
    contenitore = html.DIV()
    contenitore.html = testo_html
    return contenitore


def annuncia(messaggio):
    """Comunica un cambiamento alle tecnologie assistive."""
    regione = uno("#annunci")
    if regione is not None:
        regione.text = messaggio


# ==========================================================================
# 1. Opzioni di lettura (tema, dimensione del testo, animazioni)
# ==========================================================================

PREFERENZE = (
    ("tema", "auto", "data-tema"),
    ("testo", "normale", "data-testo"),
    ("moto", "si", "data-moto"),
)


def radice():
    """L'elemento <html>, dove il server e il client scrivono le preferenze.

    `documentElement` è la via diretta e non dipende da un selettore: se per
    qualsiasi ragione la ricerca fallisse, le opzioni di lettura resterebbero
    senza effetto senza che nulla lo segnali.
    """
    try:
        elemento = document.documentElement
    except Exception:
        elemento = None
    return elemento if elemento is not None else uno("html")


def valore_preferenza(nome, predefinito):
    valore = attributo(radice(), "data-" + nome)
    return valore or predefinito


def applica_preferenza(nome, valore, predefinito):
    elemento = radice()
    if valore == predefinito:
        elemento.removeAttribute("data-" + nome)
    else:
        elemento.setAttribute("data-" + nome, valore)
    imposta_cookie("pref_" + nome, valore)
    aggiorna_stato_chip()


def aggiorna_stato_chip():
    for nome, predefinito, _ in PREFERENZE:
        corrente = valore_preferenza(nome, predefinito)
        for bottone in tutti("[data-%s]" % nome, uno("#pannello-accessibilita")):
            attivo = attributo(bottone, "data-" + nome) == corrente
            bottone.setAttribute("aria-pressed", "true" if attivo else "false")
            if attivo:
                bottone.classList.add("chip-attiva")
            else:
                bottone.classList.remove("chip-attiva")


def avvia_opzioni_lettura():
    pannello = uno("#pannello-accessibilita")
    apri = uno("#apri-accessibilita")
    if pannello is None or apri is None:
        return

    def commuta(evento):
        aperto = attributo(apri, "aria-expanded") == "true"
        apri.setAttribute("aria-expanded", "false" if aperto else "true")
        if aperto:
            nascondi(pannello)
        else:
            mostra(pannello)
            primo = uno("button", pannello)
            if primo is not None:
                primo.focus()

    apri.bind("click", commuta)

    def su_clic(evento):
        bersaglio = antenato(evento.target, lambda e: e.tagName == "BUTTON")
        if bersaglio is None:
            return
        for nome, predefinito, _ in PREFERENZE:
            valore = attributo(bersaglio, "data-" + nome)
            if valore:
                applica_preferenza(nome, valore, predefinito)
                annuncia(testo_di("impostazione"))
                return

    pannello.bind("click", su_clic)

    def su_esc(evento):
        if evento.key == "Escape" and not e_nascosto(pannello):
            apri.setAttribute("aria-expanded", "false")
            nascondi(pannello)
            apri.focus()

    document.bind("keydown", su_esc)
    aggiorna_stato_chip()


# ==========================================================================
# 2. Menu su schermi stretti
# ==========================================================================

def avvia_menu():
    bottone = uno("#apri-menu")
    if bottone is None:
        return

    def commuta(evento):
        aperto = attributo(bottone, "aria-expanded") == "true"
        bottone.setAttribute("aria-expanded", "false" if aperto else "true")
        if aperto:
            document.body.classList.remove("nav-aperto")
        else:
            document.body.classList.add("nav-aperto")

    bottone.bind("click", commuta)


# ==========================================================================
# 3. Ricerca con suggerimenti
# ==========================================================================

class Ricerca:
    def __init__(self):
        self.campo = uno('input[data-suggest="1"]')
        self.pannello = uno("#suggerimenti")
        self.attesa = None
        self.indice = -1
        # L'indirizzo dipende dalla lingua e dal sottopercorso: lo dichiara il
        # server sul campo stesso, il client non lo ricostruisce.
        self.endpoint = attributo(self.campo, "data-suggerimenti") if self.campo else None

    def avvia(self):
        if self.campo is None or self.pannello is None or not self.endpoint:
            return
        self.campo.bind("input", self.su_digitazione)
        self.campo.bind("keydown", self.su_tasto)
        self.campo.setAttribute("role", "combobox")
        self.campo.setAttribute("aria-expanded", "false")
        self.campo.setAttribute("aria-autocomplete", "list")
        document.bind("click", self.su_clic_fuori)

    def su_digitazione(self, evento):
        annulla(self.attesa)
        # Piccolo ritardo: si evita una richiesta per ogni tasto premuto.
        self.attesa = fra(220, self.interroga)

    def interroga(self):
        termine = self.campo.value.strip()
        if len(termine) < 2:
            self.chiudi()
            return
        richiedi("GET", self.endpoint + "?q=" + cod(termine), self.mostra_voci)

    def mostra_voci(self, stato, testo):
        if stato != 200 or not testo.strip():
            self.chiudi()
            return
        self.pannello.html = testo
        mostra(self.pannello)
        self.campo.setAttribute("aria-expanded", "true")
        self.indice = -1

    def chiudi(self):
        nascondi(self.pannello)
        self.pannello.html = ""
        self.campo.setAttribute("aria-expanded", "false")
        self.indice = -1

    def voci(self):
        return tutti("a", self.pannello)

    def su_tasto(self, evento):
        voci = self.voci()
        if evento.key == "Escape":
            self.chiudi()
            return
        if not voci or e_nascosto(self.pannello):
            return
        if evento.key == "ArrowDown":
            evento.preventDefault()
            self.indice = (self.indice + 1) % len(voci)
            voci[self.indice].focus()
        elif evento.key == "ArrowUp":
            evento.preventDefault()
            self.indice = (self.indice - 1) % len(voci)
            voci[self.indice].focus()

    def su_clic_fuori(self, evento):
        dentro = antenato(
            evento.target,
            lambda e: ha_classe(e, "ricerca-testata"),
        )
        if dentro is None:
            self.chiudi()


# ==========================================================================
# 4. Filtri immediati
# ==========================================================================

class Filtri:
    CAMPI = ("categoria", "tipo", "accesso", "ordine", "q")

    def __init__(self):
        self.modulo = uno("#filtri")
        self.area = uno("#area-risultati")
        self.conteggio = uno("#conteggio-risultati")
        self.conteggio_filtri = uno("#conteggio-filtri")
        self.chip = uno("#chip-attivi")
        self.base = ""
        self.endpoint = ""
        self.in_corso = False

    def avvia(self):
        if self.modulo is None or self.area is None:
            return
        self.base = attributo(self.modulo, "action") or ""
        self.endpoint = attributo(self.modulo, "data-api") or ""
        if not self.base or not self.endpoint:
            return

        self.modulo.bind("change", self.su_cambio)
        self.modulo.bind("submit", self.su_invio)
        if self.chip is not None:
            self.chip.bind("click", self.su_chip)

        self.avvia_pannello_mobile()
        window.addEventListener("popstate", self.su_navigazione)

    # ---- lettura dello stato -------------------------------------------

    def selezione(self):
        dati = {}
        for nome in self.CAMPI:
            elemento = uno('[name="%s"]:checked' % nome, self.modulo)
            if elemento is None:
                elemento = uno('select[name="%s"]' % nome, self.modulo)
            if elemento is None:
                elemento = uno('input[name="%s"]' % nome, self.modulo)
            dati[nome] = elemento.value if elemento is not None else ""
        if dati.get("ordine") == "predefinito":
            dati["ordine"] = ""
        return dati

    def imposta(self, nome, valore):
        for elemento in tutti('[name="%s"]' % nome, self.modulo):
            tipo = (attributo(elemento, "type") or "").lower()
            if tipo == "radio":
                elemento.checked = elemento.value == valore
            else:
                elemento.value = valore

    # ---- eventi ---------------------------------------------------------

    def su_cambio(self, evento):
        self.aggiorna()

    def su_invio(self, evento):
        evento.preventDefault()
        self.aggiorna()

    def su_chip(self, evento):
        bersaglio = antenato(evento.target, lambda e: attributo(e, "data-rimuovi"))
        if bersaglio is None:
            return
        evento.preventDefault()
        campo = attributo(bersaglio, "data-rimuovi")
        self.imposta(campo, "predefinito" if campo == "ordine" else "")
        self.aggiorna()

    def su_navigazione(self, evento):
        # Ritorno indietro nella cronologia: si ricarica lo stato dall'indirizzo.
        window.location.reload()

    # ---- aggiornamento --------------------------------------------------

    def aggiorna(self):
        if self.in_corso:
            return
        self.in_corso = True
        dati = self.selezione()
        coppie = [(k, dati[k]) for k in self.CAMPI]
        interrogazione = query_string(coppie)

        self.area.classList.add("in-caricamento")
        indirizzo = self.base + ("?" + interrogazione if interrogazione else "")
        window.history.pushState("", "", indirizzo)

        completo = self.endpoint + "?" + query_string(
            coppie + [("azione", self.base)]
        )
        richiedi("GET", completo, self.ricevuto)

    def ricevuto(self, stato, testo_html):
        self.in_corso = False
        self.area.classList.remove("in-caricamento")
        if stato != 200:
            return
        nodo = frammento(testo_html)
        dati = uno("#frammento", nodo)
        if dati is None:
            return

        griglia = uno("#frammento-griglia", nodo)
        self.area.html = griglia.html if griglia is not None else ""

        conteggio = attributo(dati, "data-conteggio") or ""
        if self.conteggio is not None:
            self.conteggio.text = conteggio
        if self.conteggio_filtri is not None:
            self.conteggio_filtri.text = conteggio

        nuovi_chip = uno("#frammento-chip", nodo)
        if self.chip is not None:
            self.chip.html = nuovi_chip.html if nuovi_chip is not None else ""

        annuncia(conteggio)

    # ---- pannello dei filtri su schermi stretti -------------------------

    def avvia_pannello_mobile(self):
        apri = uno("#apri-filtri")
        chiudi = uno("#chiudi-filtri")

        # Nomi distinti da `mostra`/`nascondi` di modulo, che qui verrebbero
        # oscurati: agiscono su una classe del body, non sull'attributo hidden.
        def apri_pannello(evento):
            document.body.classList.add("filtri-aperti")
            if apri is not None:
                apri.setAttribute("aria-expanded", "true")
            primo = uno("input, select, button", self.modulo)
            if primo is not None:
                primo.focus()

        def chiudi_pannello(evento=None):
            document.body.classList.remove("filtri-aperti")
            if apri is not None:
                apri.setAttribute("aria-expanded", "false")
                apri.focus()

        if apri is not None:
            apri.bind("click", apri_pannello)
        if chiudi is not None:
            chiudi.bind("click", chiudi_pannello)

        def su_esc(evento):
            if evento.key == "Escape" and ha_classe(document.body, "filtri-aperti"):
                chiudi_pannello()

        document.bind("keydown", su_esc)


# ==========================================================================
# 5. Indice della scheda prodotto
# ==========================================================================

class IndiceProdotto:
    def __init__(self):
        self.collegamenti = tutti(".prodotto-indice a")
        self.sezioni = []
        self.attesa = None

    def avvia(self):
        if not self.collegamenti:
            return
        for collegamento in self.collegamenti:
            riferimento = attributo(collegamento, "href") or ""
            if riferimento.startswith("#"):
                sezione = uno(riferimento)
                if sezione is not None:
                    self.sezioni.append((collegamento, sezione))
        if self.sezioni:
            window.addEventListener("scroll", self.su_scorrimento)
            self.evidenzia()

    def su_scorrimento(self, evento):
        if self.attesa is not None:
            return
        self.attesa = fra(120, self.evidenzia_e_riparti)

    def evidenzia_e_riparti(self):
        self.attesa = None
        self.evidenzia()

    def evidenzia(self):
        soglia = 160
        corrente = None
        for collegamento, sezione in self.sezioni:
            if sezione.getBoundingClientRect().top <= soglia:
                corrente = collegamento
        for collegamento, _ in self.sezioni:
            if collegamento is corrente:
                collegamento.classList.add("attivo")
            else:
                collegamento.classList.remove("attivo")


# ==========================================================================
# 6. Modulo di richiesta informazioni
# ==========================================================================

class Modulo:
    CAMPI = ("nome", "email", "telefono", "messaggio")

    def __init__(self):
        self.modulo = uno("#modulo-richiesta")
        self.bottone_originale = ""
        # `data-invio-url` e non `data-invio`: quest'ultimo, su #testi-client,
        # è la scritta del bottone durante l'invio. Due cose diverse.
        self.endpoint = attributo(self.modulo, "data-invio-url") if self.modulo else None

    def avvia(self):
        if self.modulo is None or not self.endpoint:
            return
        bottone = uno('button[type="submit"]', self.modulo)
        if bottone is not None:
            self.bottone_originale = bottone.html
        self.modulo.bind("submit", self.su_invio)
        for nome in self.CAMPI:
            campo = uno("#campo-" + nome, self.modulo)
            if campo is not None:
                campo.bind("blur", self.su_uscita)

    # ---- validazione ----------------------------------------------------

    def valore(self, nome):
        campo = uno("#campo-" + nome, self.modulo)
        return campo.value.strip() if campo is not None else ""

    def errore_di(self, nome):
        """Stesse regole del server, applicate subito per non far attendere."""
        valore = self.valore(nome)
        if nome == "nome":
            if len(valore) < 2:
                return testo_di("err-nome")
        elif nome == "email":
            if not self.email_valida(valore):
                return testo_di("err-email")
        elif nome == "messaggio":
            if len(valore) < 10:
                return testo_di("err-messaggio")
        elif nome == "consenso":
            campo = uno("#campo-consenso", self.modulo)
            if campo is not None and not campo.checked:
                return testo_di("err-consenso")
        return ""

    @staticmethod
    def email_valida(valore):
        if " " in valore or valore.count("@") != 1:
            return False
        locale, _, dominio = valore.partition("@")
        if not locale or "." not in dominio:
            return False
        return len(dominio.rsplit(".", 1)[-1]) >= 2

    # ---- interfaccia ----------------------------------------------------

    def mostra_errore(self, nome, messaggio):
        contenitore = uno("#campo-" + nome, self.modulo)
        if contenitore is None:
            return
        blocco = antenato(contenitore, lambda e: ha_classe(e, "campo"))
        avviso = uno("#campo-%s-errore" % nome, self.modulo)
        if messaggio:
            if blocco is not None:
                blocco.classList.add("campo-errato")
            contenitore.setAttribute("aria-invalid", "true")
            contenitore.setAttribute("aria-describedby", "campo-%s-errore" % nome)
            if avviso is not None:
                avviso.text = messaggio
                mostra(avviso)
        else:
            if blocco is not None:
                blocco.classList.remove("campo-errato")
            contenitore.removeAttribute("aria-invalid")
            if avviso is not None:
                avviso.text = ""
                nascondi(avviso)

    def avviso_generale(self, messaggio):
        """Messaggio in testa al modulo, al posto di una finestra di dialogo."""
        banda = uno("#errore-generale", self.modulo)
        if banda is None:
            return
        banda.text = messaggio
        if messaggio:
            mostra(banda)
        else:
            nascondi(banda)

    def su_uscita(self, evento):
        nome = (attributo(evento.target, "name") or "").strip()
        if nome in self.CAMPI:
            self.mostra_errore(nome, self.errore_di(nome))

    # ---- invio ----------------------------------------------------------

    def su_invio(self, evento):
        evento.preventDefault()

        errori = {}
        for nome in ("nome", "email", "messaggio", "consenso"):
            messaggio = self.errore_di(nome)
            if messaggio:
                errori[nome] = messaggio
            self.mostra_errore(nome, messaggio)

        if errori:
            self.avviso_generale(testo_di("controlla"))
            primo = list(errori.keys())[0]
            campo = uno("#campo-" + primo, self.modulo)
            if campo is not None:
                campo.focus()
            return
        self.avviso_generale("")

        consenso = uno("#campo-consenso", self.modulo)
        trappola = uno("#campo-sito", self.modulo)
        coppie = [(nome, self.valore(nome)) for nome in self.CAMPI]
        coppie.append(("consenso", "1" if consenso is not None and consenso.checked else ""))
        coppie.append(("prodotto", attributo(self.modulo, "data-prodotto") or ""))
        coppie.append(("sito", trappola.value if trappola is not None else ""))

        bottone = uno('button[type="submit"]', self.modulo)
        if bottone is not None:
            bottone.disabled = True
            bottone.text = testo_di("invio")

        richiedi(
            "POST", self.endpoint, self.ricevuto,
            dati=query_string(coppie),
            tipo="application/x-www-form-urlencoded; charset=UTF-8",
        )

    def ricevuto(self, stato, testo_html):
        bottone = uno('button[type="submit"]', self.modulo)
        nodo = frammento(testo_html or "")
        esito = uno("[data-esito]", nodo)
        tipo = attributo(esito, "data-esito") if esito is not None else None

        if stato == 200 and tipo == "ok":
            sezione = antenato(self.modulo, lambda e: e.id == "richiesta")
            if sezione is not None:
                sezione.html = esito.html
                nuovo = uno("#richiesta h2")
                if nuovo is not None:
                    nuovo.setAttribute("tabindex", "-1")
                    nuovo.focus()
                annuncia(testo_di("inviata"))
            return

        if bottone is not None:
            bottone.disabled = False
            bottone.html = self.bottone_originale or testo_di("invia")

        if tipo == "errore":
            generale = ""
            for avviso in tutti("[data-campo]", nodo):
                campo = attributo(avviso, "data-campo")
                if campo == "_generale":
                    generale = avviso.text
                else:
                    self.mostra_errore(campo, avviso.text)
            testo = generale or testo_di("controlla")
            self.avviso_generale(testo)
            annuncia(testo)
        else:
            testo = testo_di("non-inviata")
            self.avviso_generale(testo)
            annuncia(testo)


# ==========================================================================
# Avvio
# ==========================================================================

def prepara_regione_annunci():
    if uno("#annunci") is not None:
        return
    regione = html.DIV(id="annunci", Class="solo-lettori")
    regione.setAttribute("role", "status")
    regione.setAttribute("aria-live", "polite")
    document.body <= regione


ESITI = []


def in_diagnostica():
    """Vero con `?debug=1` nell'indirizzo: allora il riepilogo si vede in pagina."""
    try:
        return "debug=1" in (window.location.search or "")
    except Exception:
        return False


def avvia(nome, funzione):
    """Avvia un modulo isolandone i guasti.

    Ogni funzione qui sotto è un miglioramento facoltativo: se una si rompe, le
    altre devono continuare a funzionare e la pagina resta usabile senza. Il
    guasto però non deve sparire: finisce nella console con la traccia completa,
    e con `?debug=1` anche in un riquadro in fondo alla pagina — perché un
    messaggio che nessuno guarda è come non averlo scritto.
    """
    try:
        funzione()
    except Exception:
        import traceback
        dettaglio = traceback.format_exc()
        ESITI.append((nome, "GUASTO", dettaglio))
        window.console.log("catalogo: %s non avviato\n%s" % (nome, dettaglio))
        return
    ESITI.append((nome, "avviato", ""))


def spia_clic():
    """Con ?debug=1 registra ogni clic: su cosa cade e se arriva fino in fondo.

    Serve a distinguere due situazioni che da fuori sembrano identiche — un
    gestore che non viene chiamato e un gestore che viene chiamato e non fa
    nulla — senza doverlo dedurre.
    """
    if not in_diagnostica():
        return

    def registra(evento):
        bersaglio = evento.target
        try:
            descrizione = bersaglio.tagName
        except Exception:
            descrizione = "?"
        for attributo_nome in ("id", "class"):
            valore = attributo(bersaglio, attributo_nome)
            if valore:
                descrizione += " %s=%s" % (attributo_nome, valore)
        antenato_bottone = antenato(bersaglio, lambda e: e.tagName == "BUTTON")
        annota("clic su %s   (bottone risalendo: %s)"
               % (descrizione,
                  attributo(antenato_bottone, "id") or "-"
                  if antenato_bottone is not None else "nessuno"))

    document.bind("click", registra)


def annota(riga):
    """Aggiunge una riga al riquadro di diagnostica, se c'è."""
    riquadro = uno("#riepilogo-avvio")
    if riquadro is not None:
        riquadro.text = riquadro.text + "\n" + riga


def riepilogo():
    """Riquadro con l'esito di ogni modulo. Solo su richiesta esplicita."""
    if not in_diagnostica():
        return
    # Se un riquadro esiste già, questa è una seconda esecuzione del client:
    # va detto subito e a chiare lettere, perché falsa qualunque altra prova.
    precedente = uno("#riepilogo-avvio")
    intestazione = "catalogo — avvio del client (versione %s)" % testo_di("versione")
    if precedente is not None:
        intestazione += "   ⚠ ESECUZIONE MULTIPLA: il client sta girando più di una volta"
    righe = [intestazione, ""]
    for nome, stato, _ in ESITI:
        righe.append("  %-22s %s" % (nome, stato))
    for nome, stato, dettaglio in ESITI:
        if dettaglio:
            righe.append("")
            righe.append("--- %s ---" % nome)
            righe.append(dettaglio.rstrip())
    righe.append("")
    righe.append("Elementi in pagina:")
    for selettore in ("#apri-accessibilita", "#pannello-accessibilita",
                      "#filtri", "#modulo-richiesta", "#testi-client"):
        righe.append("  %-26s %s" % (selettore,
                                     "trovato" if uno(selettore) is not None else "assente"))

    riquadro = html.PRE("\n".join(righe))
    riquadro.setAttribute("id", "riepilogo-avvio")
    riquadro.setAttribute(
        "style",
        "position:fixed;bottom:0;left:0;right:0;z-index:9999;margin:0;"
        "max-height:50vh;overflow:auto;padding:.9rem;background:#05090d;"
        "color:#e6edf3;font:12px/1.5 ui-monospace,monospace;white-space:pre-wrap;"
        "border-top:3px solid #ff7b72",
    )
    document.body <= riquadro


def principale():
    document.body.classList.add("js-attivo")
    avvia("annunci", prepara_regione_annunci)
    avvia("opzioni di lettura", avvia_opzioni_lettura)
    avvia("menu", avvia_menu)
    avvia("ricerca", lambda: Ricerca().avvia())
    avvia("filtri", lambda: Filtri().avvia())
    avvia("indice", lambda: IndiceProdotto().avvia())
    avvia("modulo", lambda: Modulo().avvia())
    avvia("riepilogo", riepilogo)
    avvia("spia clic", spia_clic)


principale()
