
"""
FROST Air Quality Widget - Versione 2.0
Applicazione per monitoraggio qualità dell'aria negli istituti scolastici
"""


###############################################################
#########################################
##################################
#############################
#########################
######################
##################

import streamlit as st
import yaml
from pathlib import Path
import pandas as pd
from datetime import datetime
import base64
import plotly.graph_objects as go
import streamlit.components.v1 as components
import pytz

# Import moduli comuni
from frost_client import FROSTClient, ObservationsCache, parse_time
from frost_utils import load_standard, detect_measure, get_datastream_config, classify_value, format_timestamp, build_alias_index

from frost_widgets import process_datastreams_data

def get_colors_for_category(cat_idx):
    """
    Restituisce (bg_color, text_color) in base alla categoria AQI.
    Se cat_idx è None o fuori range, applica valori di default.
    """
    if cat_idx is None:
        return "#e0f4f1", "#1a1a1a" 

    i = max(0, min(cat_idx, len(BOX_COLOR) - 1))
    return BOX_COLOR[i], TEXT_BOX_COLOR[i]

def parse_phenomenon_time_start(phenomenon_time_str):
    """Estrae lo START time da phenomenonTime formato 'start/end'"""
    if not phenomenon_time_str:
        return pd.NaT
    
    if isinstance(phenomenon_time_str, str) and "/" in phenomenon_time_str:
        start_str = phenomenon_time_str.split("/")[0]
        return pd.to_datetime(start_str, errors="coerce", utc=True)
    
    return parse_time(phenomenon_time_str)


def get_actual_time_range(obs_list_agg):
    """
    Calcola lo start e end time REALI dai dati effettivi.
    Per il PRIMO punto usa phenomenonTime START (se intervallo).
    Per tutti gli altri (incluso ultimo) usa phenomenonTime END.
    """
    if not obs_list_agg:
        return None, None
    
    # primo punto: usa start se è un intervallo
    first_obs = obs_list_agg[0]
    phenom_first = first_obs.get('phenomenonTime')
    
    if isinstance(phenom_first, str) and "/" in phenom_first:
        actual_start = parse_phenomenon_time_start(phenom_first)
    else:
        actual_start = parse_time(phenom_first)
    
    # ultimo punto: usa sempre end (comportamento normale di parse_time)
    last_obs = obs_list_agg[-1]
    phenom_last = last_obs.get('phenomenonTime')
    actual_end = parse_time(phenom_last)
    
    if pd.isna(actual_start) or pd.isna(actual_end):
        return None, None
    
    return actual_start.to_pydatetime().replace(tzinfo=None), actual_end.to_pydatetime().replace(tzinfo=None)


# CONFIGURAZIONE PATHS IMMAGINI

IMAGES = {
    'logo_istituti': 'assets/logo_istituti.png',
    'air_quality_1': 'assets/qualita_aria_ottima.png',   
    'air_quality_2': 'assets/qualita_aria_buona.png',    
    'air_quality_3': 'assets/qualita_aria_moderata.png', 
    'air_quality_4': 'assets/qualita_aria_scarsa.png',   
    'air_quality_5': 'assets/qualita_aria_pessima.png',  
    'air_quality_6': 'assets/qualita_aria_critica.png', 
    'icon_tvoc': 'assets/tvoc_icon2.png',
    'icon_no2': 'assets/no2_icon.png',
    'icon_o3': 'assets/o3_icon.png',
    'icon_co2': 'assets/co2_icon111.png',
    'icon_co': 'assets/co_icon.png',
    'icon_pm25': 'assets/pm25_icon.png',
    'icon_bottone1': 'assets/icon_bottone1.png',    
    'icon_bottone2': 'assets/icon_bottone2.png',
    'icon_bottone3': 'assets/icon_bottone3.png',
    'icon_bottone4': 'assets/icon_bottone4.png',
    'icon_bottone5': 'assets/icon_bottone5.png',
    'icon_bottone6': 'assets/icon_bottone6.png',
    'icon_bottone7': 'assets/icon_bottone7.png',
    'esclamativo': 'assets/esclamativo.png',
    'error_s': 'assets/error_s.png',
    'air_quality_nullo': 'assets/nullo.png' 
}



# DIZIONARI CONFIGURAZIONE ISTITUTI E CLASSI

ISTITUTI = {
    "Istituto Copernico": [
        "THI.FE.019","THI.FE.020","THI.FE.021","THI.FE.023", "THI.FE.022","THI.FE.024"
    ],
    "Istituto Carducci": [
        "THI.FE.014","THI.FE.015","THI.FE.016","THI.FE.017","THI.FE.018"
    ],
    "Istituto Aleotti": [
        "THI.FE.010", "THI.FE.011", "THI.FE.012", "THI.FE.013"
    ],

    "Casa Verri": [
        "THI.FE.001"
    ],

    "Agenzia Mobilità Impianti Ferrara": [
        "THI.FE.002","THI.FE.003","THI.FE.004"
    ],

    "Comune Ferrara via Maverna": [
        "THI.FE.005","THI.FE.006","THI.FE.007","THI.FE.008",
    ],

    "Ristorante Iaia": [
        "THI.FE.009"
    ],

    "Centro Universitario Sportivo di Ferrara": [
        "THI.FE.025","THI.FE.026","THI.FE.027","THI.FE.028","THI.FE.029"
    ],

    "Comune Ferrara via Marconi": [
        "THI.FE.030","THI.FE.031","THI.FE.032","THI.FE.033","THI.FE.034","THI.FE.035"
    ],
    
    "Deda Next": ["THI.FE.036"]
}

CLASSI_NAMES = {
    "THI.FE.019":"aula 13",
    "THI.FE.020": "aula 11",
    "THI.FE.021": "aula 10",
    "THI.FE.022": "aula 5",
    "THI.FE.023": "aula 2",
    "THI.FE.024":"lab",
    "THI.FE.014":"aula 5",
    "THI.FE.015":"aula T2",
    "THI.FE.016":"aula 2",
    "THI.FE.017":"aula 18",
    "THI.FE.018":"aula 21",
    "THI.FE.010":"aula 1 CAT",
    "THI.FE.011":"ricreazione",
    "THI.FE.012":"aula 23",
    "THI.FE.013":"lab. inf. 3",
    "THI.FE.001":"casa Verri",
    "THI.FE.002":"Ufficio M.",
    "THI.FE.003":"Ufficio I.",
    "THI.FE.004":"Ufficio R.",
    "THI.FE.005":"Ufficio C.",
    "THI.FE.006":"Ufficio M.",
    "THI.FE.007":"primo piano",
    "THI.FE.008":"Ufficio G.",
    "THI.FE.030":"Public Relations",
    "THI.FE.031":"waiting zone",
    "THI.FE.032":"Ufficio T.",
    "THI.FE.033":"Ufficio B.",
    "THI.FE.034":"Ufficio C.",
    "THI.FE.035":"Ufficio P.",
    "THI.FE.025":"Fitness Gym 1",
    "THI.FE.026":"Cardio Gym",
    "THI.FE.027":"Fitness Gym 2",
    "THI.FE.028":"Reception",
    "THI.FE.029":"Spinning Gym",
    "THI.FE.036":"Reception",
    "THI.FE.009":"Ristorante Iaia",
    }


# PROPORZIONI COLONNE PER OGNI ISTITUTO
COLUMN_PROPORTIONS = {
    "Istituto Copernico": [6, 4],
    "Istituto Carducci": [6, 4],
    "Istituto Aleotti": [6, 4],
    "Casa Verri": [0.8, 4],
    "Agenzia Mobilità Impianti Ferrara": [5, 4],
    "Comune Ferrara via Maverna": [6, 4],
    "Ristorante Iaia": [0.8, 4],
    "Centro Universitario Sportivo di Ferrara": [10, 3],
    "Comune Ferrara via Marconi": [10,0.5],
    "Deda Next": [0.8, 4]
}
##################################
##########
######
##########
##################################
###############################################################################
###############################################################################
###############################################################################

CLASS_BUTTON_ICONS = {
    0: 'air_quality_1',  # Buona
    1: 'air_quality_2',  # Accettabile
    2: 'air_quality_3',  # Mediocre
    3: 'air_quality_4',  # Scadente
    4: 'air_quality_5',  # Inaccettabile
    5: 'air_quality_6'   # Pessima
}

##################################################################################
# TESTI QUALITÀ DELL'ARIA

BOX_COLOR = [
    "#dafffa", #Buona 
    "#cefbed", #accettabile
    "#fbf8d5", #mediocre
    "#ffdfe4", #scadente
    "#f7d0df", #inaccettabile
    "#f4d5ed" #pessima
]

TEXT_BOX_COLOR = [
    "#00f9e5",
    "#00d5a7",
    "#ffb700",
    "#ff004f",
    "#9e0035",
    "#890082"
]

AIR_QUALITY_LABELS = [
    "Buona",
    "Accettabile",
    "Mediocre",
    "Scadente",
    "Inaccettabile",
    "Pessima"
]

AIR_QUALITY_COLORS = [
    "#00f9e5",  # Buona
    "#00d5a7",  # Accettabile
    "#ffb700",  # Mediocre
    "#ff004f",  # Scadente (nuovo colore)
    "#9e0035",  # Inaccettabile
    "#890082"  # Pessima
]

AIR_QUALITY_TEXTS = {
    0: {'title': "Qualità dell'aria BUONA", 'description': "L'aria è eccellente, respirare profondamente fa bene!"},
    1: {'title': "Qualità dell'aria ACCETTABILE", 'description': "Qualità pulita e salubre per la maggior parte delle persone."},
    2: {'title': "Qualità dell'aria MEDIOCRE", 'description': "Possibili effetti per soggetti sensibili; monitorare la situazione."},
    3: {'title': "Qualità dell'aria SCADENTE", 'description': "potrebbe andare peggio... ma potrebbe anche andare meglio! imitare attività prolungate o intense; valutare ricambio aria."},
    4: {'title': "Qualità dell'aria INACCETTABILE", 'description': "Attento! l'aria che stai respirando non ti fa per niente bene! apri le finestre subito!"},
    5: {'title': "Qualità dell'aria PESSIMA", 'description': "aria pessima, stai attento... rischio danni gravi alla tua salute, con l'aria non si scherza!"}
}

POLLUTANT_DESCRIPTIONS = {
    'PM2.5': {
        'name': 'PM2.5 (Particolato fine)',
        'description': 'Polveri sottili con un diametro inferiore a 2,5 micrometri. Sono particolarmente dannose per la salute poiché possono penetrare in profondità nei polmoni e persino entrare nel flusso sanguigno.'
    },
    'PM10': {
        'name': 'PM10 (Particolato)',
        'description': 'Polveri sottili con un diametro inferiore a 10 micrometri. Possono causare irritazione delle vie respiratorie e problemi cardiovascolari.'
    },
    'CO': {
        'name': 'CO (Monossido di Carbonio)',
        'description': 'Gas incolore e inodore altamente tossico. Si forma principalmente dalla combustione incompleta di carburanti come legna, carbone, o gas naturale.'
    },
    'CO2': {
        'name': 'CO2 (Anidride Carbonica)',
        'description': 'Gas prodotto dalla respirazione umana e da fonti come combustione e processi industriali. Livelli elevati di CO2 in ambienti chiusi indicano una ventilazione insufficiente.'
    },
    'NO2': {
        'name': 'NO2 (Biossido di Azoto)',
        'description': 'Gas tossico prodotto principalmente dalla combustione di carburanti (es. traffico veicolare, riscaldamento). Può irritare le vie respiratorie e contribuire alla formazione di smog.'
    },
    'O3': {
        'name': 'O3 (Ozono)',
        'description': 'Gas formato naturalmente nell\'atmosfera, ma può essere prodotto anche da reazioni chimiche tra inquinanti in presenza di luce solare. A livello del suolo, è un inquinante che irrita le vie respiratorie.'
    },
    'TVOCs': {
        'name': 'TVOCs (Total Volatile Organic Compounds)',
        'description': 'Totale dei composti organici volatili presenti nell\'aria. Questi composti includono sostanze come benzene, formaldeide e altri solventi chimici, spesso rilasciati da materiali da costruzione, mobili o prodotti per la pulizia. Sono associati a problemi di salute e malessere (discomfort).'
    }
}


# FUNZIONI UTILITÀ
def load_config():
    """Carica configurazione da config.yaml"""
    config_path = Path("config2.yaml")
    
    # DETERMINA IL TIMEZONE REALE DA SUBITO
    import tzlocal
    try:
        real_tz = str(tzlocal.get_localzone())
    except:
        real_tz = 'Europe/Rome'
    
    default_config = {
        'endpoint': 'https://frost.labservice.it/FROST-Server/v1.1',
        'standard_url': 'https://drive.google.com/uc?export=download&id=1qDfuIjL3NC25sZ3HDgpM3SewmrZTB-7Q',
        'timezone': real_tz
    }
    
    if config_path.exists():
        try:
            with open(config_path, 'r') as f:
                user_config = yaml.safe_load(f) or {}
                
                # SE L'UTENTE HA MESSO 'local', CONVERTILO
                if user_config.get('timezone') == 'local':
                    user_config['timezone'] = real_tz
                
                default_config.update(user_config)
        except Exception as e:
            st.warning(f"Errore caricamento config: {e}")
    
    return default_config

def get_image_base64(image_path):
    """Converte immagine in base64 per embedding HTML"""
    try:
        with open(image_path, "rb") as f:
            data = f.read()
            return base64.b64encode(data).decode()
    except:
        return None

def render_selected_class_label(selected_thing_id, thing_ids, CLASS_EMOJIS, CLASSI_NAMES):
    selected_idx = thing_ids.index(selected_thing_id) if selected_thing_id in thing_ids else 0
    selected_label = CLASSI_NAMES.get(selected_thing_id, selected_thing_id)
    st.markdown(f"""
        <div class="selected-class-label2">
            <span><strong>{selected_label}</strong></span>
        </div>
    """, unsafe_allow_html=True)

def get_all_classes_aqi(client, thing_ids, standard):
    """Calcola l'AQI per tutte le classi usando l'ULTIMO valore disponibile
    (stesso timestamp usato per i grafici e le barre)"""
    aqi_map = {}
    
    for thing_id in thing_ids:
        try:
            thing = client.get_thing(thing_id)
            if not thing:
                aqi_map[thing_id] = None
                continue
            
            # Ricarica datastreams con l'ultima osservazione
            datastreams = client.get_datastreams_with_latest(thing['@iot.id'])
            if not datastreams:
                aqi_map[thing_id] = None
                continue
            
            reference_time = st.session_state.reference_times.get(thing_id)
            
            if not reference_time:
                aqi_map[thing_id] = None
                continue
            
            # Verifica se ci sono dati recenti (ultime 6 ore)
            has_recent_data = False
            cutoff_time = reference_time - pd.Timedelta(hours=6)
            
            for ds in datastreams:
                obs = ds.get('Observations', [])
                if obs:
                    obs_time = parse_time(obs[0].get('phenomenonTime'))
                    if not pd.isna(obs_time):
                        obs_time_dt = obs_time.to_pydatetime().replace(tzinfo=None)
                        if obs_time_dt >= cutoff_time:
                            has_recent_data = True
                            break
            
            if not has_recent_data:
                aqi_map[thing_id] = None
                continue
            
            #  COSTRUISCI latest_data USANDO STESSO METODO DI process_datastreams_data
            from frost_utils import build_alias_index, detect_measure
            alias_idx = build_alias_index(standard)
            datastream_labels = standard.get("datastreamLabels", [])

            latest_data = {}
            for ds in datastreams:
                ds_id = ds.get('@iot.id')
                name = ds.get('name') or ""
                
                detected_name = detect_measure(name, alias_idx, datastream_labels)
                if not detected_name:
                    op_name = (ds.get("ObservedProperty") or {}).get("name") or ""
                    detected_name = detect_measure(op_name, alias_idx, datastream_labels)
                
                if not detected_name:
                    continue
                
                # cerca osservazione più vicina a reference_time
                if ref_time:
                    ref_obs = client.get_observations_timerange(
                        ds_id, 
                        hours_back=2,
                        limit=50,
                        reference_time=ref_time,
                        select_clause='phenomenonTime,result'
                    )
                    
                    if ref_obs:
                        # Filtra solo <= reference_time
                        ref_time_naive = ref_time.replace(tzinfo=None) if hasattr(ref_time, 'tzinfo') else ref_time
                        
                        valid_obs = []
                        for o in ref_obs:
                            obs_time = parse_time(o.get('phenomenonTime'))
                            if not pd.isna(obs_time):
                                obs_time_naive = obs_time.to_pydatetime().replace(tzinfo=None)
                                if obs_time_naive <= ref_time_naive:
                                    valid_obs.append(o)
                        
                        if valid_obs:
                            # Trova la più vicina
                            ref_obs_sorted = sorted(
                                valid_obs,
                                key=lambda o: abs((parse_time(o.get('phenomenonTime')).to_pydatetime().replace(tzinfo=None) - ref_time_naive).total_seconds())
                            )
                            obs = ref_obs_sorted[0]
                            latest_data[detected_name] = obs.get('result')
            
            # ORA USA latest_data per garantire coerenza
            _, air_category, has_null, _ = calculate_air_quality_index( 
                datastreams, 
                client, 
                standard, 
                reference_time,
                latest_data=latest_data  # ← PASSA I DATI ESATTI
            )
            aqi_map[thing_id] = air_category
        except Exception as e:
            # Debug: stampa l'errore
            aqi_map[thing_id] = None
    
    return aqi_map


def calculate_air_quality_index(datastreams, client, standard, reference_time, latest_data=None):
    """
    Versione DEBUG della funzione calculate_air_quality_index
    Stampa informazioni dettagliate per identificare dove fallisce il calcolo
    """
    if not standard:
        print("DEBUG: standard è None")
        return None, None, False, None
    
    cat_labels = standard.get("categoryLabels", [])
    datastream_labels = standard.get("datastreamLabels", [])
    
    pollutants_to_check = ['PM2.5', 'PM10', 'NO2', 'CO2', 'CO', 'O3', 'TVOCs']
    
    from frost_utils import build_alias_index
    alias_idx = build_alias_index(standard)
    
    normalized_values = []
    pollutant_scores = {} 
    has_null_values = False  
    
    # print(f"\nDEBUG calculate_air_quality_index")
    # print(f"   reference_time: {reference_time}")
    # print(f"   latest_data: {latest_data}")
    # print(f"   num datastreams: {len(datastreams)}")
    
    for ds_idx, ds in enumerate(datastreams):
        ds_id = ds.get('@iot.id')
        name = ds.get('name') or ""
        
        # print(f"\n   [{ds_idx}] Datastream: {name}")
        
        detected_name = detect_measure(name, alias_idx, datastream_labels)
        if not detected_name:
            op_name = (ds.get("ObservedProperty") or {}).get("name") or ""
            detected_name = detect_measure(op_name, alias_idx, datastream_labels)
        
        # print(f"       → detected_name: {detected_name}")
        
        if not detected_name or detected_name not in pollutants_to_check:
            # print(f"       → SKIP: non in pollutants_to_check")
            continue
        
        ds_config = get_datastream_config(detected_name, datastream_labels)
        if not ds_config:
            # print(f"       → SKIP: ds_config not found")
            continue
        
        thresholds = ds_config.get("categoryThresholds", [])
        if len(thresholds) != 5:
            # print(f"       → SKIP: thresholds length is {len(thresholds)}, need 5")
            continue
        
        greater_worse = ds_config.get("greaterIsWorse", True)
        
        # Ottieni ultimo valore
        if latest_data and detected_name in latest_data:
            val = latest_data[detected_name]
            # print(f"       → value from latest_data: {val}")
        else:
            # print(f"       → value NOT in latest_data, fetching from observations...")
            observations = ds.get('Observations', [])
            if reference_time:
                ref_obs = client.get_observations_timerange(
                    ds_id, 
                    hours_back=1,
                    limit=10,
                    reference_time=reference_time,
                    select_clause='phenomenonTime,result'
                )
                if ref_obs:
                    ref_obs_sorted = sorted(
                        ref_obs,
                        key=lambda o: abs((parse_time(o.get('phenomenonTime')).to_pydatetime().replace(tzinfo=None) - reference_time).total_seconds())
                    )
                    obs = ref_obs_sorted[0] if ref_obs_sorted else None
                else:
                    obs = None

                if not obs:
                    obs = client.get_latest_observation(ds_id)
            else:
                if not observations:
                    obs = client.get_latest_observation(ds_id)
                else:
                    obs = observations[0]

            if not obs:
                # print(f"       → SKIP: no observation found")
                has_null_values = True
                continue

            val = obs.get('result')
            # print(f"       → value from observation: {val}")

        # VALIDAZIONE VALORE
        if val is None:
            # print(f"       → SKIP: val is None")
            has_null_values = True
            continue

        try:
            val = float(val)
            # print(f"       → converted to float: {val}")
        except Exception as e:
            # print(f"       → SKIP: conversion error: {e}")
            has_null_values = True
            continue

        # SOLO valori strettamente negativi sono nulli
        if val < 0:
            # print(f"       → SKIP: val < 0 ({val})")
            has_null_values = True
            continue

        # print(f"       → VALUE VALID: {val}")
        
        # NORMALIZZAZIONE
        normalized = None

        if greater_worse:
            if val <= thresholds[0]:
                normalized = (val / thresholds[0]) * 16
            elif val <= thresholds[1]:
                normalized = 16 + ((val - thresholds[0]) / (thresholds[1] - thresholds[0])) * 16
            elif val <= thresholds[2]:
                normalized = 32 + ((val - thresholds[1]) / (thresholds[2] - thresholds[1])) * 16
            elif val <= thresholds[3]:
                normalized = 48 + ((val - thresholds[2]) / (thresholds[3] - thresholds[2])) * 16
            elif val <= thresholds[4]:
                normalized = 64 + ((val - thresholds[3]) / (thresholds[4] - thresholds[3])) * 16
            else:
                # SCALA LOGARITMICA: 80 + 8 * log2(val / thresholds[4])
                # Ogni raddoppio aggiunge 8 punti, massimo 100
                import math
                log_component = 8 * math.log2(val / thresholds[4])
                normalized = min(80 + log_component, 100)
        else:
            if val <= 0:
                normalized = 100
            elif val >= thresholds[0]:
                normalized = (thresholds[0] / val) * 16
                normalized = min(normalized, 16)
            elif val >= thresholds[1]:
                normalized = 16 + ((thresholds[0] - val) / (thresholds[0] - thresholds[1])) * 16
            elif val >= thresholds[2]:
                normalized = 32 + ((thresholds[1] - val) / (thresholds[1] - thresholds[2])) * 16
            elif val >= thresholds[3]:
                normalized = 48 + ((thresholds[2] - val) / (thresholds[2] - thresholds[3])) * 16
            elif val >= thresholds[4]:
                normalized = 64 + ((thresholds[3] - val) / (thresholds[3] - thresholds[4])) * 16
            else:
                # Oltre l'ultima soglia: scala inversa logaritmica
                import math
                if val > 0:
                    ratio = thresholds[4] / val
                    log_component = 8 * math.log2(1 / ratio)
                    normalized = min(80 + log_component, 100)
                else:
                    normalized = 100
            
        if normalized is not None:
            # print(f"       → normalized: {normalized}")
            normalized_values.append(normalized)
            pollutant_scores[detected_name] = normalized
    
    # print(f"\n   has_null_values: {has_null_values}")
    # print(f"   normalized_values: {normalized_values}")
    # print(f"   pollutant_scores: {pollutant_scores}")
    
    # SE CI SONO VALORI NULLI → INDICE NULLO
    if has_null_values:
        return None, None, True, None
    
    if not normalized_values:
        return None, None, False, None
    
    global_index = max(normalized_values)
    worst_pollutant = max(pollutant_scores, key=pollutant_scores.get) if pollutant_scores else None
    
    if global_index < 16:
        category = 0
    elif global_index < 32:
        category = 1
    elif global_index < 48:
        category = 2
    elif global_index < 64:
        category = 3
    elif global_index < 80:
        category = 4
    else:
        category = 5
    
    # print(f"   RESULT: index={global_index:.1f}, category={category}, worst={worst_pollutant}")
    
    return global_index, category, False, worst_pollutant

def calculate_normalized_indices(datastreams, client, standard, reference_time):
    """
    Calcola gli indici normalizzati (0-100+) per tutti gli inquinanti.
    Ritorna: dict con {pollutant_name: [(timestamp, normalized_index), ...]}
    """
    if not standard:
        return {}
    
    from frost_utils import build_alias_index, detect_measure, get_datastream_config
    
    datastream_labels = standard.get("datastreamLabels", [])
    pollutants_to_check = ['PM2.5', 'PM10', 'NO2', 'CO2', 'CO', 'O3', 'TVOCs']
    alias_idx = build_alias_index(standard)
    
    pollutant_indices = {}
    
    for ds in datastreams:
        ds_id = ds.get('@iot.id')
        name = ds.get('name') or ""
        
        detected_name = detect_measure(name, alias_idx, datastream_labels)
        if not detected_name:
            op_name = (ds.get("ObservedProperty") or {}).get("name") or ""
            detected_name = detect_measure(op_name, alias_idx, datastream_labels)
        
        if not detected_name or detected_name not in pollutants_to_check:
            continue
        
        ds_config = get_datastream_config(detected_name, datastream_labels)
        if not ds_config:
            continue
        
        thresholds = ds_config.get("categoryThresholds", [])
        if len(thresholds) != 5:
            continue
        
        greater_worse = ds_config.get("greaterIsWorse", True)
        
        # Scarica osservazioni
        hours_needed = st.session_state.time_window
        obs = st.session_state.obs_cache.get_or_fetch(
            client, ds_id, hours_needed=hours_needed,
            reference_time=reference_time,
            select_clause='phenomenonTime,result'
        )
        
        if not obs:
            continue
        
        time_value_pairs = []
        
        for o in obs:
            t = parse_time(o.get('phenomenonTime'))
            val = o.get('result')
            
            if pd.isna(t) or val is None:
                continue
            
            try:
                val = float(val)
            except:
                continue
            
            if val < 0:
                continue
            
            # Filtra <= reference_time
            t_dt = t.to_pydatetime().replace(tzinfo=None)
            ref_dt = reference_time.replace(tzinfo=None) if hasattr(reference_time, 'tzinfo') else reference_time
            if reference_time and t_dt > ref_dt:
                continue
            
            # Calcola indice normalizzato
            if greater_worse:
                if val <= thresholds[0]:
                    normalized = (val / thresholds[0]) * 16
                elif val <= thresholds[1]:
                    normalized = 16 + ((val - thresholds[0]) / (thresholds[1] - thresholds[0])) * 16
                elif val <= thresholds[2]:
                    normalized = 32 + ((val - thresholds[1]) / (thresholds[2] - thresholds[1])) * 16
                elif val <= thresholds[3]:
                    normalized = 48 + ((val - thresholds[2]) / (thresholds[3] - thresholds[2])) * 16
                elif val <= thresholds[4]:
                    normalized = 64 + ((val - thresholds[3]) / (thresholds[4] - thresholds[3])) * 16
                else:
                    import math
                    log_component = 8 * math.log2(val / thresholds[4])
                    normalized = min(80 + log_component, 100)
            else:
                if val <= 0:
                    normalized = 100
                elif val >= thresholds[0]:
                    normalized = (thresholds[0] / val) * 16
                    normalized = min(normalized, 16)
                elif val >= thresholds[1]:
                    normalized = 16 + ((thresholds[0] - val) / (thresholds[0] - thresholds[1])) * 16
                elif val >= thresholds[2]:
                    normalized = 32 + ((thresholds[1] - val) / (thresholds[1] - thresholds[2])) * 16
                elif val >= thresholds[3]:
                    normalized = 48 + ((thresholds[2] - val) / (thresholds[2] - thresholds[3])) * 16
                elif val >= thresholds[4]:
                    normalized = 64 + ((thresholds[3] - val) / (thresholds[3] - thresholds[4])) * 16
                else:
                    import math
                    if val > 0:
                        ratio = thresholds[4] / val
                        log_component = 8 * math.log2(1 / ratio)
                        normalized = min(80 + log_component, 100)
                    else:
                        normalized = 100
            
            if normalized is not None:
                time_value_pairs.append((t, normalized))
        
        if time_value_pairs:
            pollutant_indices[detected_name] = time_value_pairs
    
    return pollutant_indices

def get_available_time_range_from_datastreams(client, thing_id):
    """
    Calcola il range temporale disponibile leggendo phenomenonTime dai datastreams.
    Ritorna: (start_datetime, end_datetime) o (None, None) se non disponibile
    """
    try:
        # Ottieni tutti i datastreams del Thing
        datastreams = client.get_datastreams(thing_id)
        if not datastreams:
            return None, None
        
        all_starts = []
        all_ends = []
        
        for ds in datastreams:
            phenom_time = ds.get('phenomenonTime')
            if not phenom_time:
                continue
            
            # phenomenonTime è nel formato "start/end"
            if isinstance(phenom_time, str) and "/" in phenom_time:
                start_str, end_str = phenom_time.split("/")
                
                start_time = pd.to_datetime(start_str, errors="coerce", utc=True)
                end_time = pd.to_datetime(end_str, errors="coerce", utc=True)
                
                if not pd.isna(start_time):
                    all_starts.append(start_time.to_pydatetime().replace(tzinfo=None))
                if not pd.isna(end_time):
                    all_ends.append(end_time.to_pydatetime().replace(tzinfo=None))
        
        if not all_starts or not all_ends:
            return None, None
        
        # Ritorna il range più ampio disponibile
        return min(all_starts), max(all_ends)
        
    except Exception as e:
        st.warning(f"Errore calcolo range temporale: {e}")
        return None, None
    




def create_categorical_heatmap(pollutant_indices, air_index_history, timezone='Europe/Rome'):
    """Crea heatmap categorica per tutti gli inquinanti + EDIAQI Index"""
    import plotly.graph_objects as go
    import pandas as pd
    import pytz
    
    if not pollutant_indices:
        return None
    
    # Gestisci timezone
    if timezone == 'local':
        import tzlocal
        try:
            timezone = str(tzlocal.get_localzone())
        except:
            timezone = 'Europe/Rome'
    
    # Prepara dati per ogni inquinante
    pollutant_data = {}
    all_times = set()
    
    for pollutant, data in pollutant_indices.items():
        times = [d[0] for d in data]
        values = [d[1] for d in data]
        
        if not times:
            continue
        
        # Converti a categorie
        categories = []
        for val in values:
            if val < 16:
                categories.append(0)  # Buona
            elif val < 32:
                categories.append(1)  # Accettabile
            elif val < 48:
                categories.append(2)  # Mediocre
            elif val < 64:
                categories.append(3)  # Scadente
            elif val < 80:
                categories.append(4)  # Inaccettabile
            else:
                categories.append(5)  # Pessima
        
        pollutant_data[pollutant] = {'times': times, 'categories': categories}
        all_times.update(times)
    
    if not all_times:
        return None
    
    # Ordina i tempi
    sorted_times = sorted(list(all_times))
    
    # Calcola Ediaqi Index (massimo tra tutti gli inquinanti per ogni timestamp)
    ediaqi_categories = []
    for t in sorted_times:
        max_cat = 0
        for pollutant, data in pollutant_data.items():
            try:
                idx = data['times'].index(t)
                max_cat = max(max_cat, data['categories'][idx])
            except ValueError:
                continue
        ediaqi_categories.append(max_cat)
    
    # Converti timezone
    local_tz = pytz.timezone(timezone)
    times_converted = pd.to_datetime(sorted_times, utc=True).tz_convert(local_tz)
    
    # Crea matrice dati
    pollutants_order = ['<b>IAQ</b>'] + list(pollutant_data.keys())
    z_data = []
    
    # Prima riga: Ediaqi Index
    z_data.append(ediaqi_categories)
    
    # Altre righe: inquinanti
    for pollutant in pollutant_data.keys():
        row = []
        for t in sorted_times:
            try:
                idx = pollutant_data[pollutant]['times'].index(t)
                row.append(pollutant_data[pollutant]['categories'][idx])
            except ValueError:
                row.append(None)
        z_data.append(row)
    
    # Crea heatmap
    fig = go.Figure(data=go.Heatmap(
        z=z_data,
        x=times_converted,
        y=pollutants_order,
        colorscale=[
            [0, AIR_QUALITY_COLORS[0]],      # Buona
            [0.2, AIR_QUALITY_COLORS[1]],    # Accettabile
            [0.4, AIR_QUALITY_COLORS[2]],    # Mediocre
            [0.6, AIR_QUALITY_COLORS[3]],    # Scadente
            [0.8, AIR_QUALITY_COLORS[4]],    # Inaccettabile
            [1.0, AIR_QUALITY_COLORS[5]]     # Pessima
        ],
        showscale=False,
        hovertemplate='%{y}<br>%{x}<br>Categoria: %{customdata}<extra></extra>',
        customdata=[[AIR_QUALITY_LABELS[val] if val is not None else 'N/A' for val in row] for row in z_data],
        zmin=0,
        zmax=5,
        ygap=16
    ))
    
    fig.update_layout(
        height=350,
        annotations=[
            dict(
                x=0.05 + (i * 0.15),
                y=-0.28,
                xref='paper',
                yref='paper',
                text=f'<span style="color:{AIR_QUALITY_COLORS[i]}; font-size:20px">■</span> {AIR_QUALITY_LABELS[i]}',
                showarrow=False,
                font=dict(size=14),
                xanchor='left'
            ) for i in range(6)
        ],
        margin=dict(l=50, r=40, t=20, b=80),
        xaxis=dict(
            showgrid=False,
            title=None
        ),
        yaxis=dict(
            showgrid=False,
            title=None,
            tickmode='linear',
            dtick=1,
            autorange='reversed'
        ),
        plot_bgcolor='white',
        paper_bgcolor='white',
        font=dict(family='Inter', size=12)
    )
    
    return fig



def create_aqi_overview_chart(pollutant_indices, timezone='Europe/Rome'):
    """Crea grafico con tutti gli indici normalizzati sovrapposti"""
    import plotly.graph_objects as go
    import pytz
    
    if not pollutant_indices:
        return None
    
    # Gestisci timezone
    if timezone == 'local':
        import tzlocal
        try:
            timezone = str(tzlocal.get_localzone())
        except:
            timezone = 'Europe/Rome'
    
    fig = go.Figure()
    
    # Colori per ogni inquinante
    pollutant_colors = {
        'PM2.5': '#8B4513',
        'PM10': '#A0522D',
        'NO2': '#FF6347',
        'CO2': '#4169E1',
        'CO': '#FF8C00',
        'O3': '#9370DB',
        'TVOCs': '#20B2AA'
    }
    
    # Aggiungi fasce colorate di sfondo
    box_colors = [
        "rgba(0, 208, 182, 0.1)",   # Buona
        "rgba(0, 179, 132, 0.1)",   # Accettabile
        "rgba(255, 186, 0, 0.1)",   # Mediocre
        "rgba(255, 0, 78, 0.1)",    # Scadente
        "rgba(170, 0, 53, 0.1)",    # Inaccettabile
        "rgba(152, 0, 132, 0.1)"    # Pessima
    ]
    
    y_ranges = [(0, 16), (16, 32), (32, 48), (48, 64), (64, 80), (80, 100)]
    
    for i, (y0, y1) in enumerate(y_ranges):
        if i < len(box_colors):
            fig.add_shape(
                type="rect",
                xref="paper", yref="y",
                x0=0, x1=1,
                y0=y0, y1=y1,
                fillcolor=box_colors[i],
                layer="below",
                line_width=0
            )
    
    #  ottieni reference_time e normalizza a naive
    reference_time = st.session_state.reference_times.get(st.session_state.selected_classe)
    if reference_time:
        if hasattr(reference_time, 'tzinfo') and reference_time.tzinfo is not None:
            reference_time_naive = reference_time.replace(tzinfo=None)
        else:
            reference_time_naive = reference_time
    else:
        reference_time_naive = None
    
    #  calcola expected_start_time e normalizza a naive
    hours_needed = st.session_state.time_window
    
    if st.session_state.use_custom_window and st.session_state.custom_start_date:
        timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
        if timezone_cfg == 'local':
            import tzlocal
            try:
                timezone_cfg = str(tzlocal.get_localzone())
            except:
                timezone_cfg = 'Europe/Rome'
        
        import pytz
        local_tz = pytz.timezone(timezone_cfg)
        start_datetime = datetime.combine(st.session_state.custom_start_date, datetime.min.time())
        start_datetime_local = local_tz.localize(start_datetime)
        expected_start_time = pd.Timestamp(start_datetime_local.astimezone(pytz.UTC))
    else:
        if reference_time_naive:
            expected_start_time = pd.Timestamp(reference_time_naive) - pd.Timedelta(hours=hours_needed)
        else:
            expected_start_time = None
    
    #  normalizza expected_start_time a naive
    if expected_start_time:
        expected_start_naive = expected_start_time.to_pydatetime().replace(tzinfo=None)
    else:
        expected_start_naive = None
    
    # Aggiungi traccia per ogni inquinante
    for pollutant, data in pollutant_indices.items():
        times = [d[0] for d in data]
        values = [d[1] for d in data]
        
        # Salta se non ci sono dati validi
        if not times or all(t is None for t in times):
            continue
        
        #  verifica se è il caso di aggiungerew un punto fittizio in assenza di dati
        if expected_start_naive and times:
            first_time_naive = times[0].to_pydatetime().replace(tzinfo=None)
            
            if first_time_naive > expected_start_naive:
                # Aggiungi punto fittizio
                times.insert(0, expected_start_time)
                values.insert(0, None)
        
        # Inserisci None per gap > 5 minuti
        if len(times) > 1:
            times_with_gaps = [times[0]]
            values_with_gaps = [values[0]]
            
            for i in range(1, len(times)):
                # Salta il confronto se uno dei due è None
                if times[i] is not None and times[i-1] is not None:
                    #  normalizza entrambi i timestamp naive
                    t_current = times[i].to_pydatetime().replace(tzinfo=None) if hasattr(times[i], 'tzinfo') else times[i]
                    t_previous = times[i-1].to_pydatetime().replace(tzinfo=None) if hasattr(times[i-1], 'tzinfo') else times[i-1]
                    
                    time_diff = (t_current - t_previous).total_seconds() / 60
                    
                    if time_diff > 5:
                        times_with_gaps.append(None)
                        values_with_gaps.append(None)
                
                times_with_gaps.append(times[i])
                values_with_gaps.append(values[i])
            
            times = times_with_gaps
            values = values_with_gaps
        
        # Converti timezone
        try:
            local_tz = pytz.timezone(timezone)
            times_converted = []
            for t in times:
                if t is not None:
                    if isinstance(t, pd.Timestamp):
                        if t.tzinfo is not None:
                            t_local = t.tz_convert(local_tz)
                        else:
                            t_utc = t.tz_localize('UTC')
                            t_local = t_utc.tz_convert(local_tz)
                    else:
                        # Se è datetime, converti a Timestamp
                        t_pd = pd.Timestamp(t)
                        if t_pd.tzinfo is not None:
                            t_local = t_pd.tz_convert(local_tz)
                        else:
                            t_utc = t_pd.tz_localize('UTC')
                            t_local = t_utc.tz_convert(local_tz)
                    times_converted.append(t_local)
                else:
                    times_converted.append(None)
            times = times_converted
        except Exception as e:
            st.warning(f"Errore conversione timezone per {pollutant}: {e}")
            times = pd.to_datetime(times, utc=True).tz_convert('Europe/Rome')
        
        color = pollutant_colors.get(pollutant, '#666666')
        
        fig.add_trace(go.Scatter(
            x=times,
            y=values,
            mode='lines',
            name=pollutant,
            line=dict(color=color, width=2),
            connectgaps=False
        ))
    
    fig.update_layout(
        height=350,
        margin=dict(l=40, r=40, t=30, b=50),
        xaxis=dict(
            showgrid=True,
            gridcolor='#e2e8f0',
            title=None
        ),
        yaxis=dict(
            showgrid=True,
            gridcolor='#e2e8f0',
            title="IAQ",
            range=[0, 100]
        ),
        hovermode='x unified',
        plot_bgcolor='white',
        paper_bgcolor='white',
        font=dict(family='Inter', size=11),
        legend=dict(
            orientation="h",
            yanchor="bottom",
            y=1.02,
            xanchor="right",
            x=1
        )
    )
    
    return fig


# stili css
@st.dialog("Informazioni", width="large")
def show_thresholds_info():
    """Mostra le descrizioni degli Osservabili e le soglie per la qualità dell'aria indoor"""
    
    st.markdown("""
            <style>
            .pollutant-table {
                width: 100%;
                border-collapse: collapse;
                margin: 20px 0;
                font-size: 13px;
            }
            .pollutant-table th, .pollutant-table td {
                padding: 12px;
                text-align: left;
                border: 1px solid #e0e0e0;
            }
            .pollutant-table th {
                background: #f8f9fa;
                font-weight: 600;
                color: #333;
                text-align: center;
            }
            .pollutant-table td:first-child {
                font-weight: 700;
                color: #006278;
                text-align: center;
                width: 20%;
            }
            .intro-pollutants {
                color: #333;
                line-height: 1.6;
                margin-bottom: 20px;
            }
            </style>
        """, unsafe_allow_html=True)
        
    st.markdown("""
        <div class="intro-pollutants">
        I principali parametri su cui valutiamo l'andamento della qualità dell'aria sono:
        </div>
    """, unsafe_allow_html=True)
    
    st.markdown("""
        <table class="pollutant-table">
            <thead>
                <tr>
                    <th>Inquinante</th>
                    <th>Descrizione</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>PM2.5<br><span style="font-size:11px; font-weight:400; color:#666;">(Particolato fine)</span></td>
                    <td>Polveri sottili con un diametro inferiore a 2,5 micrometri. Sono particolarmente dannose per la salute poiché possono penetrare in profondità nei polmoni e persino entrare nel flusso sanguigno.</td>
                </tr>
                <tr>
                    <td>CO<br><span style="font-size:11px; font-weight:400; color:#666;">(Monossido di Carbonio)</span></td>
                    <td>Gas incolore e inodore altamente tossico. Si forma principalmente dalla combustione incompleta di carburanti come legna, carbone, o gas naturale.</td>
                </tr>
                <tr>
                    <td>CO₂<br><span style="font-size:11px; font-weight:400; color:#666;">(Anidride Carbonica)</span></td>
                    <td>Gas prodotto dalla respirazione umana e da fonti come combustione e processi industriali. Livelli elevati di CO₂ in ambienti chiusi indicano una ventilazione insufficiente.</td>
                </tr>
                <tr>
                    <td>NO₂<br><span style="font-size:11px; font-weight:400; color:#666;">(Biossido di Azoto)</span></td>
                    <td>Gas tossico prodotto principalmente dalla combustione di carburanti (es. traffico veicolare, riscaldamento). Può irritare le vie respiratorie e contribuire alla formazione di smog.</td>
                </tr>
                <tr>
                    <td>O₃<br><span style="font-size:11px; font-weight:400; color:#666;">(Ozono)</span></td>
                    <td>Gas formato naturalmente nell'atmosfera, ma può essere prodotto anche da reazioni chimiche tra inquinanti in presenza di luce solare. A livello del suolo, è un inquinante che irrita le vie respiratorie.</td>
                </tr>
                <tr>
                    <td>TVOCs<br><span style="font-size:11px; font-weight:400; color:#666;">(Composti Organici Volatili)</span></td>
                    <td>Totale dei composti organici volatili presenti nell'aria. Questi composti includono sostanze come benzene, formaldeide e altri solventi chimici, spesso rilasciati da materiali da costruzione, mobili o prodotti per la pulizia. Sono associati a problemi di salute e malessere.</td>
                </tr>
            </tbody>
        </table>
    """, unsafe_allow_html=True)
    
    # separatore
    st.markdown('<div class="section-separator"></div>', unsafe_allow_html=True)
    
    # seconda sezione: Soglie (il contenuto che già hai)

    
    st.markdown("""
        <style>
        /* Wrapper scrollabile per mobile */
        .table-wrapper {
            overflow-x: auto;
            -webkit-overflow-scrolling: touch;
            margin: 20px 0;
        }
        
        .threshold-table {
            width: 100%;
            border-collapse: collapse;
            margin: 0;
            font-size: 13px;
            min-width: 600px;  /* Larghezza minima per mantenere leggibilità */
        }
        .threshold-table th, .threshold-table td {
            padding: 12px;
            text-align: center;
            border: 1px solid #e0e0e0;
        }
        .threshold-table th {
            background: #f8f9fa;
            font-weight: 600;
            color: #333;
        }
        
        .cat-0 { background: #dafffa; color: #00f9e5; font-weight: 600; }
        .cat-1 { background: #cefbed; color: #00d5a7; font-weight: 600; }
        .cat-2 { background: #fbf8d5; color: #ffb700; font-weight: 600; }
        .cat-3 { background: #ffdfe4; color: #ff004f; font-weight: 600; }
        .cat-4 { background: #f7d0df; color: #9e0035; font-weight: 600; }
        .cat-5 { background: #f4d5ed; color: #890082; font-weight: 600; }
        .class-title {
            font-size: 18px;
            font-weight: 700;
            color: #006278;
            margin-top: 30px;
            margin-bottom: 15px;
        }
        .intro-text {
            color: #333;
            line-height: 1.6;
            margin-bottom: 20px;
        }
        </style>
    """, unsafe_allow_html=True)
    
    st.markdown("""
        <div class="intro-text">
        Secondo l'Istituto Superiore di Sanità, l'Organizzazione Mondiale della Sanità (OMS) e altri 
        organismi internazionali, per la qualità dell'aria indoor è possibile distinguere in classi di 
        ambienti indoor a seconda dell'uso e delle attività svolte.
        </div>
    """, unsafe_allow_html=True)
    
    # livello I
    st.markdown('<div class="class-title">Classe I - Ambienti con individui fragili (bambini, anziani, disabili)</div>', unsafe_allow_html=True)
    
    st.markdown("""
        <div class="table-wrapper">
        <table class="threshold-table">
            <thead>
                <tr>
                    <th>Inquinante</th>
                    <th>Buona</th>
                    <th>Accettabile</th>
                    <th>Mediocre</th>
                    <th>Scadente</th>
                    <th>Inaccettabile</th>
                    <th>Pessima</th>
                    <th>Unità</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><strong>PM2.5</strong></td>
                    <td class="cat-0">&lt; 5</td>
                    <td class="cat-1">5-10</td>
                    <td class="cat-2">10-15</td>
                    <td class="cat-3">15-20</td>
                    <td class="cat-4">20-40</td>
                    <td class="cat-5">&gt; 40</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>NO₂</strong></td>
                    <td class="cat-0">&lt; 10</td>
                    <td class="cat-1">10-20</td>
                    <td class="cat-2">20-30</td>
                    <td class="cat-3">30-40</td>
                    <td class="cat-4">40-50</td>
                    <td class="cat-5">&gt; 50</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>O₃</strong></td>
                    <td class="cat-0">&lt; 20</td>
                    <td class="cat-1">20-40</td>
                    <td class="cat-2">40-60</td>
                    <td class="cat-3">60-100</td>
                    <td class="cat-4">100-150</td>
                    <td class="cat-5">&gt; 150</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>CO</strong></td>
                    <td class="cat-0">&lt; 2000</td>
                    <td class="cat-1">2000-3000</td>
                    <td class="cat-2">3000-4000</td>
                    <td class="cat-3">4000-6000</td>
                    <td class="cat-4">6000-10000</td>
                    <td class="cat-5">&gt; 10000</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>CO₂</strong></td>
                    <td class="cat-0">&lt; 600</td>
                    <td class="cat-1">600-800</td>
                    <td class="cat-2">800-1000</td>
                    <td class="cat-3">1000-1250</td>
                    <td class="cat-4">1250-1800</td>
                    <td class="cat-5">&gt; 1800</td>
                    <td>ppm</td>
                </tr>
                <tr>
                    <td><strong>TVOCs</strong></td>
                    <td class="cat-0">&lt; 100</td>
                    <td class="cat-1">100-200</td>
                    <td class="cat-2">200-300</td>
                    <td class="cat-3">300-400</td>
                    <td class="cat-4">400-500</td>
                    <td class="cat-5">&gt; 500</td>
                    <td>µg/m³</td>
                </tr>
            </tbody>
        </table>
    </div>
""", unsafe_allow_html=True)
    
    # livello II
    st.markdown('<div class="class-title">Classe II - Ambienti senza individui fragili (residenza, uffici, commercio). Attualmente in uso su tutta la dashboard</div>', unsafe_allow_html=True)
    
    st.markdown("""
        <div class="table-wrapper">
        <table class="threshold-table">
            <thead>
                <tr>
                    <th>Inquinante</th>
                    <th>Buona</th>
                    <th>Accettabile</th>
                    <th>Mediocre</th>
                    <th>Scadente</th>
                    <th>Inaccettabile</th>
                    <th>Pessima</th>
                    <th>Unità</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><strong>PM2.5</strong></td>
                    <td class="cat-0">&lt; 10</td>
                    <td class="cat-1">10-15</td>
                    <td class="cat-2">15-20</td>
                    <td class="cat-3">20-30</td>
                    <td class="cat-4">30-50</td>
                    <td class="cat-5">&gt; 50</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>NO₂</strong></td>
                    <td class="cat-0">&lt; 20</td>
                    <td class="cat-1">20-40</td>
                    <td class="cat-2">40-60</td>
                    <td class="cat-3">60-100</td>
                    <td class="cat-4">100-150</td>
                    <td class="cat-5">&gt; 150</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>O₃</strong></td>
                    <td class="cat-0">&lt; 40</td>
                    <td class="cat-1">40-60</td>
                    <td class="cat-2">60-90</td>
                    <td class="cat-3">90-150</td>
                    <td class="cat-4">150-200</td>
                    <td class="cat-5">&gt; 200</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>CO</strong></td>
                    <td class="cat-0">&lt; 2500</td>
                    <td class="cat-1">2500-5000</td>
                    <td class="cat-2">5000-7000</td>
                    <td class="cat-3">7000-10000</td>
                    <td class="cat-4">10000-12000</td>
                    <td class="cat-5">&gt; 12000</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>CO₂</strong></td>
                    <td class="cat-0">&lt; 750</td>
                    <td class="cat-1">750-1000</td>
                    <td class="cat-2">1000-1200</td>
                    <td class="cat-3">1200-1800</td>
                    <td class="cat-4">1800-2400</td>
                    <td class="cat-5">&gt; 2400</td>
                    <td>ppm</td>
                </tr>
                <tr>
                    <td><strong>TVOCs</strong></td>
                    <td class="cat-0">&lt; 300</td>
                    <td class="cat-1">300-400</td>
                    <td class="cat-2">400-600</td>
                    <td class="cat-3">600-1000</td>
                    <td class="cat-4">1000-1500</td>
                    <td class="cat-5">&gt; 1500</td>
                    <td>µg/m³</td>
                </tr>
            </tbody>
        </table>
    </div>
""", unsafe_allow_html=True)
    
    # livello III-IV
    st.markdown('<div class="class-title">Classe III-IV - Ambienti di lavoro con emissioni (ristoranti, laboratori)</div>', unsafe_allow_html=True)
    
    st.markdown("""
        <div class="table-wrapper">
        <table class="threshold-table">
            <thead>
                <tr>
                    <th>Inquinante</th>
                    <th>Buona</th>
                    <th>Accettabile</th>
                    <th>Mediocre</th>
                    <th>Scadente</th>
                    <th>Inaccettabile</th>
                    <th>Pessima</th>
                    <th>Unità</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td><strong>PM2.5</strong></td>
                    <td class="cat-0">&lt; 15</td>
                    <td class="cat-1">15-20</td>
                    <td class="cat-2">20-25</td>
                    <td class="cat-3">25-35</td>
                    <td class="cat-4">35-50</td>
                    <td class="cat-5">&gt; 50</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>NO₂</strong></td>
                    <td class="cat-0">&lt; 40</td>
                    <td class="cat-1">40-80</td>
                    <td class="cat-2">80-120</td>
                    <td class="cat-3">120-160</td>
                    <td class="cat-4">160-200</td>
                    <td class="cat-5">&gt; 200</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>O₃</strong></td>
                    <td class="cat-0">&lt; 60</td>
                    <td class="cat-1">60-100</td>
                    <td class="cat-2">100-140</td>
                    <td class="cat-3">140-200</td>
                    <td class="cat-4">200-240</td>
                    <td class="cat-5">&gt; 240</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>CO</strong></td>
                    <td class="cat-0">&lt; 3000</td>
                    <td class="cat-1">3000-6000</td>
                    <td class="cat-2">6000-10000</td>
                    <td class="cat-3">10000-12000</td>
                    <td class="cat-4">12000-14000</td>
                    <td class="cat-5">&gt; 14000</td>
                    <td>µg/m³</td>
                </tr>
                <tr>
                    <td><strong>CO₂</strong></td>
                    <td class="cat-0">&lt; 1000</td>
                    <td class="cat-1">1000-1200</td>
                    <td class="cat-2">1200-1800</td>
                    <td class="cat-3">1800-2400</td>
                    <td class="cat-4">2400-3000</td>
                    <td class="cat-5">&gt; 3000</td>
                    <td>ppm</td>
                </tr>
                <tr>
                    <td><strong>TVOCs</strong></td>
                    <td class="cat-0">&lt; 400</td>
                    <td class="cat-1">400-600</td>
                    <td class="cat-2">600-1000</td>
                    <td class="cat-3">1000-1500</td>
                    <td class="cat-4">1500-2000</td>
                    <td class="cat-5">&gt; 2000</td>
                    <td>µg/m³</td>
                </tr>
            </tbody>
        </table>
    </div>
""", unsafe_allow_html=True)
    
    # separatore
    st.markdown('<div class="section-separator" style="margin: 40px 0; border-bottom: 2px solid #e0e0e0;"></div>', unsafe_allow_html=True)

    # Riassunto contenuti educativi
    st.markdown("""
        <div class="class-title" style="font-size: 22px; margin-top: 40px;">
            Approfondimenti sulla Qualità dell'Aria Indoor
        </div>
    """, unsafe_allow_html=True)

    # ventilazione e c02
    st.markdown('<div class="class-title">1. Ventilazione e CO₂</div>', unsafe_allow_html=True)

    st.markdown("**Perché la CO₂ è importante?**")
    st.write("La CO₂ non è solo un inquinante, ma anche un **indicatore indiretto** della qualità dell'aria. Quando respiriamo, produciamo CO₂ insieme ad acqua, aerosol, particelle, virus e batteri. Monitorare la CO₂ ci permette quindi di capire quando l'aria è 'viziata' e serve ventilare.")

    st.markdown("**Livelli raccomandati:**")
    st.markdown("""
    - All'esterno: 400-550 ppm
    - Ambiente interno salubre: 300-600 ppm sopra i livelli esterni
    -  Sopra 1000 ppm: riduzione delle prestazioni cognitive e della concentrazione
    """)

    st.markdown("**Effetti sulla salute:**")
    st.markdown("""
    - **Sistema respiratorio:** irritazione agli occhi, mal di gola, tosse, starnuti
    - **Sistema cardiovascolare:** variazioni del pH del sangue, ipertensione, alterazioni della frequenza cardiaca
    - **Sistema cognitivo:** riduzione della concentrazione, prestazioni decisionali compromesse
    """)

    # ventilazione naturale vss meccanica
    st.markdown('<div class="class-title">2. Tipi di Ventilazione</div>', unsafe_allow_html=True)

    st.markdown("**Ventilazione Naturale (CCDM):**")
    st.markdown("""
    -  **Vantaggi:** Gratuita e facile da implementare
    -  **Limiti:** Variabile (dipende da temperatura e vento), non filtra l'aria, introduce rumori e pollini
    - **Regola d'oro:** Deve essere Crociata, Continua, Distribuita e Misurabile
    """)

    st.markdown("**Ventilazione Meccanica Controllata:**")
    st.markdown("""
    -  Garantisce ricambio d'aria costante
    -  Può incorporare recupero energetico, trattamento termico e filtrazione
    -  Controllabile tramite sensori di CO₂
    -  Risparmio energetico attraverso la "ventilazione esatta"
    """)

    # trasmissione malattie
    st.markdown('<div class="class-title">3. Trasmissione delle Malattie per Via Aerea</div>', unsafe_allow_html=True)

    st.markdown("**Il ruolo degli aerosol:**")
    st.write("Quando parliamo, tossiamo o respiriamo, emettiamo circa **1200 aerosol per ogni goccia**. Questi aerosol possono trasportare virus e batteri, rimanendo sospesi nell'aria per ore.")

    st.markdown("**Il Modello Wells-Riley:**")
    st.write("Questo modello scientifico permette di calcolare il rischio di trasmissione di malattie aeree in base a:")
    st.markdown("""
    - Numero di occupanti e loro attività (parlare, cantare, fare sport)
    - Tempo di esposizione
    - Volume dell'ambiente
    - Tasso di ventilazione (ACH - ricambi d'aria per ora)
    - Uso di mascherine e DPI
    """)

    st.markdown("**Cos'è un 'quantum'?**")
    st.write("È la dose minima di aerosol con carico virale sufficiente a infettare il 63% delle persone suscettibili. Più 'quantum' si accumulano nell'aria, maggiore è il rischio di contagio.")

    # particolato e ozono
    st.markdown('<div class="class-title">4. Particolato (PM) e Ozono (O₃)</div>', unsafe_allow_html=True)

    st.markdown("**Particolato (PM2.5 e PM10):**")
    st.markdown("""
    - **PM10:** Particelle grossolane che restano nei polmoni
    - **PM2.5:** Particelle fini che possono entrare nel flusso sanguigno
    - Fonti principali: combustione, traffico veicolare, attività di cottura
    - Effetti: malattie respiratorie, cardiovascolari, potenzialmente cancerogeno
    """)

    st.markdown("**Ozono (O₃):**")
    st.markdown("""
    - Nell'alta atmosfera ci protegge dai raggi UV 
    - A livello del suolo è un inquinante pericoloso 
    - Si forma da reazioni tra inquinanti e luce solare
    - Negli spazi interni: emesso da stampanti, fotocopiatrici, purificatori d'aria con ionizzatori
    - Effetti: tosse, irritazione, malattie respiratorie e cardiovascolari
    """)

    # composti organici volatili
    st.markdown('<div class="class-title">5. Composti Organici Volatili (TVOCs)</div>', unsafe_allow_html=True)

    st.markdown("**Cosa sono?**")
    st.write("Sostanze chimiche volatili che evaporano facilmente a temperatura ambiente. Negli ambienti indoor possono essere **2-10 volte più concentrati** che all'esterno!")

    st.markdown("**Fonti principali:**")
    st.markdown("""
    - Prodotti per la pulizia (soprattutto quelli con candeggina)
    - Deodoranti per ambienti e profumi
    - Materiali da costruzione e mobili nuovi
    - Vernici e solventi
    - Prodotti cosmetici
    """)

    st.markdown("**Effetti sulla salute:**")
    st.markdown("""
    - **A breve termine:** irritazione occhi e vie respiratorie, mal di testa, vertigini
    - **A lungo termine:** danni al fegato, reni, sistema nervoso; alcuni sono cancerogeni
    """)

    st.markdown("**Soluzioni:**")
    st.markdown("""
    -  Scegliere prodotti "verdi" o senza profumo (emettono **75% in meno** di TVOCs)
    -  Ventilare durante e dopo l'uso di prodotti chimici
    -  Conservare prodotti poco usati in ambienti ventilati, lontano da bambini
    """)

    # filtrazione
    st.markdown('<div class="class-title">6. Filtrazione dell\'Aria</div>', unsafe_allow_html=True)

    st.markdown("**Tipi di filtri:**")
    st.markdown("""
    - **EPA (e10-e12):** Efficienza 85-99.5%
    - **HEPA (e13-e14):** Efficienza 99.95-99.995% - **Raccomandato per ambienti indoor**
    - **ULPA (e15-e17):** Efficienza 99.9995-99.999995% - Per ambienti critici (ospedali, laboratori)
    """)

    st.markdown("**Purificatori d'aria portatili:**")
    st.markdown("""
    - **Prefiltro:** cattura particelle grandi (polvere, polline, peli)
    - **Filtro a carbone attivo:** assorbe gas, odori, TVOCs
    - **Filtro HEPA:** cattura particelle microscopiche (virus, batteri, PM2.5)
    """)

    st.markdown("**Come dimensionare un purificatore:**")
    st.markdown("""
    - Formula: `CADR = Volume stanza (m³) × Ricambi d'aria desiderati (ACH)`
    - Esempio: Stanza 50 m³, 5 ricambi/ora → CADR minimo = 250 m³/h
    -  **Importante:** Sostituire i filtri secondo le indicazioni del produttore!
    """)

    st.markdown("**Miti da sfatare:**")
    st.markdown("""
    -  "I purificatori sono pericolosi" → Falso, se usati correttamente
    -  "Non catturano virus" → I filtri HEPA catturano particelle > 0.3 µm con efficienza 99.97%
    -  "Consumano troppo" → Consumo tipico 30-60W, come una lampadina
    """)

    # scuole
    st.markdown('<div class="class-title">7. Qualità dell\'Aria nelle Scuole</div>', unsafe_allow_html=True)

    st.markdown("**Perché è importante?**")
    st.write("I bambini passano **6-8 ore al giorno** in aula. Alti livelli di CO₂ riducono concentrazione e prestazioni scolastiche.")

    st.markdown("**Raccomandazioni per ventilare le aule:**")
    st.markdown("""
    -  Ventilazione **crociata**: finestre su pareti opposte
    -  Ventilazione **continua** durante l'occupazione (non solo nelle pause!)
    -  Mantenere CO₂ < 800 ppm (target < 600 ppm per qualità ottimale)
    -  Usare sensori NDIR per monitoraggio continuo
    -  Tenere porte aperte per favorire il flusso d'aria tra aule e corridoi
    """)

    st.markdown("**Protocollo consigliato:**")
    st.markdown("""
    1. Misurare CO₂ all'inizio della lezione
    2. Se > 700 ppm → aprire finestre
    3. Se temperatura esterna è sopportabile → mantenere ventilazione continua
    4. Altrimenti → ventilare ogni 15-20 minuti per 5 minuti
    """)

    # manutenzione impianti
    st.markdown('<div class="class-title">8. Manutenzione degli Impianti</div>', unsafe_allow_html=True)

    st.markdown("**HVAC (Condizionatori):**")
    st.markdown("""
    -  Manutenzione inadeguata → proliferazione di batteri e muffe (es. Legionella)
    -  Pulizia regolare dei filtri
    -  Controllo periodico dei condotti di ventilazione
    """)

    st.markdown("**Camini e Stufe a Legna:**")
    st.markdown("""
    -  Rilasciano PM2.5, CO e creosoto (tossico e infiammabile)
    -  Pulizia annuale della canna fumaria
    -  Usare legna secca e di qualità
    -  Assicurare ventilazione adeguata
    """)

    st.markdown("**Caldaie a Gas:**")
    st.markdown("""
    -  Possono rilasciare CO (monossido di carbonio) e NOx
    -  Revisione annuale obbligatoria
    -  Installare rilevatori di CO
    -  Ventilare locali caldaia
    """)

    # particelle biologiche
    st.markdown('<div class="class-title">9. Particelle Biologiche nell\'Aria</div>', unsafe_allow_html=True)

    st.markdown("**Cosa trasporta l'aria?**")
    st.markdown("""
    - Virus e batteri
    - Spore fungine e muffe
    - Polline
    - Acari della polvere e loro residui
    - Frammenti di origine vegetale e animale
    """)

    st.markdown("**Allergie e salute:**")
    st.write("Le particelle biologiche sono responsabili di:")
    st.markdown("""
    - Allergie respiratorie (rinite, asma)
    - Infezioni (influenza, COVID-19, tubercolosi)
    - Irritazioni cutanee e oculari
    """)

    st.markdown("**Come ridurre l'esposizione:**")
    st.markdown("""
    -  Filtrazione HEPA per catturare spore, polline e acari
    -  Controllo umidità (< 60%) per prevenire muffe
    -  Pulizia regolare con aspirapolvere con filtro HEPA
    -  Ventilazione per diluire concentrazioni di bioaerosol
    """)

    # conclusioni
    st.markdown("""
        <div class="class-title" style="font-size: 20px; color: #006278; margin-top: 40px;">
            💡 Punti Chiave da Ricordare
        </div>
    """, unsafe_allow_html=True)

    st.markdown("""
        <div class="intro-text" style="background: #f0f9ff; padding: 20px; border-radius: 12px; border-left: 4px solid #006278;">
    """, unsafe_allow_html=True)

    st.markdown("""
    1. **La CO₂ è il tuo "termometro" della qualità dell'aria** - mantienila sotto 800 ppm
    2. **Ventilare è fondamentale** - 5-10 minuti ogni ora o ventilazione continua
    3. **I filtri HEPA funzionano** - cambiali regolarmente per mantenere l'efficacia
    4. **Attenzione ai prodotti chimici** - scegli alternative "verdi" e ventila durante l'uso
    5. **La qualità dell'aria indoor è spesso peggiore di quella esterna** - monitora e agisci!
    6. **La manutenzione degli impianti salva vite** - non trascurarla mai
    """)

    st.markdown('</div>', unsafe_allow_html=True)



def get_styles():
    return """
    <style>
        @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
        
        * {
            font-family: 'Inter', sans-serif;
        }
        
        .stApp {
            background: #f1f1f1;
        }

        
        .pilot-title {
            font-size: 42px;
            font-weight: 700;
            color: #006278;
            margin-bottom: 40px;
        }
        
        .pilot-section {
            margin-bottom: 35px;
        }
        
        .pilot-section-title {
            font-size: 18px;
            font-weight: 700;
            color: #006278;
            margin-bottom: 12px;
        }
        
        .pilot-section-content {
            font-size: 15px;
            color: #333;
            line-height: 1.8;
        }
        
        .pilot-section-content ul {
            margin-top: 15px;
            padding-left: 20px;
        }
        
        .pilot-section-content li {
            margin-bottom: 10px;
        }
        
        /* Footer */
        .login-footer {
            background: white;
            padding: 40px 80px;
            border-top: 1px solid #e0e0e0;
            display: flex;
            justify-content: space-between;
            gap: 60px;
        }
        
        .footer-column {
            flex: 1;
        }
        
        .footer-logo {
            max-width: 180px;
            margin-bottom: 20px;
        }
        
        .footer-title {
            font-size: 16px;
            font-weight: 700;
            color: #006278;
            margin-bottom: 15px;
        }
        
        .footer-link {
            display: block;
            color: #333;
            text-decoration: none;
            margin-bottom: 10px;
            font-size: 14px;
        }
        
        .footer-link:hover {
            color: #006278;
            text-decoration: underline;
        }
        
        /* Login Box */
        .login-container {
            max-width: 400px;
            margin: 100px auto;
            padding: 40px;
            background: white;
            border-radius: 16px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.08);
        }
        /* Targetizza SOLO le colonne che contengono .air-quality-content */
        div[data-testid="stHorizontalBlock"]:has(.air-quality-content) {
            background: white !important;
            border-radius: 20px !important;
            padding: 30px !important;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06) !important;
            gap: 30px !important;
            margin-bottom: 0px !important;
            margin-top: 0px !important;
            max-height: 530px !important;
            overflow: hidden !important;
        }
                        
        .login-title {
            font-size: 24px;
            font-weight: 700;
            color: #1a1a1a;
            text-align: center;
            margin-bottom: 30px;
        }

        .custom-toggle-label {
            margin-top: 20px;
            font-size: 0.85em;
            color: #333;
        }
        
        /* Header con logo e select */
        .header-container {
            display: flex;
            align-items: center;
            gap: 20px;
            margin-bottom: 0px;
            margin-top: 0px;
            padding: 25px;
            background: #f8f9fa;
            border-radius: 12px;
            min-height: 120px;
        }

        /* Riduci margine per istituti single-class */
        .single-class-layout .header-container {
            margin-bottom: -20px !important;
        }

        .single-class-layout .main-container {
            margin-top: -10px !important;
            padding-top: 20px !important;
        }

        .header-logo {
            width: 80px;
            height: 80px;
            object-fit: contain;
            margin-top: -3px
        }

        /* Stile per il select box dell'istituto */
        .stSelectbox-istituto {
            margin-top: 30px;
        }
                    
        /* Container principale per le colonne */
        .main-columns-container {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 30px;
            align-items: start;
        
        }
        .custom-select-label {
            font-size: 1px !important;
            margin-top: 0px !important;
        }

        /* COLONNA SINISTRA - Qualità aria */
        .left-column {
            background: white;
            border-radius: 20px;
            padding: 0;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
            height: fit-content;
            overflow: hidden;
        }

        /* FORZA COLONNE STESSA ALTEZZA in visualizzazione avanzata */
        div[data-testid="stHorizontalBlock"]:has(.scroll-container-wrapper) {
            display: flex !important;
            align-items: stretch !important;
            gap: 30px !important;
            margin-bottom: 5px !important;
            margin-top: 0px !important;
            padding-bottom: 5px !important;
        }

        div[data-testid="stHorizontalBlock"]:has(.scroll-container-wrapper) > div[data-testid="column"] {
            display: flex !important;
            flex-direction: column !important;
            align-items: stretch !important;
            margin-bottom: 0px !important;
            margin-top: 0px !important;
            padding-bottom: 0px !important;
        } 

        /*  wrapper per la colonna sinistra */
        div[data-testid="stHorizontalBlock"]:has(.scroll-container-wrapper) > div[data-testid="column"]:first-child > div {
            height: 100% !important;
            display: flex !important;
            flex-direction: column !important;
            margin-bottom: 0px !important;
            margin-top: 0px !important;
            padding-bottom: 0px !important;
        }
        
        /* RIMUOVI PADDING EXTRA DAL CONTENITORE STREAMLIT */
        div[data-testid="stHorizontalBlock"]:has(.scroll-container-wrapper) > div[data-testid="column"] > div {
            padding-bottom: 0px !important;
            margin-bottom: 0px !important;
        }
        
        /* Data fuori dal box - in alto */
        .timestamp-outside {
            text-align: left;
            margin-bottom: 10px;
            font-size: 11px;
            color: #999;
        }

        /* Container qualità aria con background colorato */
        .air-quality-content {
            display: flex;
            align-items: center;
            gap: 30px;
            padding: 30px;
            border-radius: 20px;
            position: relative;
            margin-top:10;
        }

        .air-quality-left {
            display: flex;
            flex-direction: column;
            align-items: flex-start;
            gap: 10px;
        }

        /* Titolo qualità sopra immagine */
        .air-quality-status {
            font-size: 18px;
            font-weight: 700;
            color: #1a1a1a;
            text-transform: uppercase;
            letter-spacing: 0.5px;
            margin-bottom: 8px;
        }

        .air-quality-image-container {
            width: 100px;
            height: 100px;
            display: flex;
            align-items: center;
            justify-content: center;
        }

        .air-quality-image {
            max-width: 100%;
            max-height: 100%;
            object-fit: contain;
        }

        .air-quality-index {
            font-size: 48px;
            font-weight: 700;
            line-height: 1;
            margin-top: 5px;
        }

        .air-quality-aqi {
            font-size: 16px;
            font-weight: 600;
            color: #666;
            text-transform: uppercase;
        }

        .air-quality-right {
            flex: 1;
            display: flex;
            flex-direction: column;
            gap: 8px;
        }

        .air-quality-desc {
            font-size: 14px;
            color: #666;
            line-height: 1.4;
        }

        /* Timestamp */
        .timestamp-container {
            text-align: center;
            margin-bottom: 20px;
            padding: 12px;
            background: rgba(255, 255, 255, 0.7);
            border-radius: 8px;
            width: 100%;
        }

        .timestamp-label {
            font-size: 12px;
            color: #666;
            margin-bottom: 4px;
        }

        .timestamp-value {
            font-size: 16px;
            font-weight: 600;
            color: #1a1a1a;
        }
        
        .stSelectbox > div > div {
            max-width: 400px !important;
            background-color: #ffffff !important
        }

        /* Sfondo bianco per date picker */
        .stDateInput > div > div {
            background-color: #ffffff !important;
        }

        .stDateInput input {
            background-color: #ffffff !important;
        }
        
        /* BOTTONI CLASSI - CON SPAZIO 2PX */
        .stButton > button {
            padding: 4px 6px !important;        
            font-size: 11px !important;
            font-weight: 500 !important;
            border-radius: 8px !important;
            background: #ffffff  !important;
            color: #333 !important;
            transition: all 0.2s !important;
            white-space: nowrap !important;
            height: auto !important;
            min-height: 28px !important;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06) !important;
            transition: box-shadow .2s, transform .1s, background-color .2s !important;
            width: calc(100% - 2px) !important;
        }

        /*  SPAZIO 2PX TRA BOTTONI */
        .btn-wrap-4px {
            margin: 0 !important;
            padding: 0 1px !important;
        }
            
        .stButton > button:hover {
            background: #e8e8e8 !important;
            border-color: #b0b0b0 !important;
            transform: translateY(-1px) !important;
        }
        
        .stButton > button[data-baseweb="button"][kind="primary"] {
            background: #2563eb !important;
            color: white !important;
            border-color: #1d4ed8 !important;
        }
        
        .stButton > button[data-baseweb="button"][kind="primary"] {
            border-width: 2px !important;
        }

        
        /* Colonna destra */
        .right-column {
            background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
            border-radius: 20px;
            padding: 30px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.08);
            border: 1px solid #e2e8f0;
            height: fit-content;
        }
                
        .pollutants-card {
            background: transparent;
            max-height: 550px;
            overflow-y: auto;
            margin-top: 0px;
            font-family: 'Inter', sans-serif;
            padding-right: 10px;
        }

        .pollutants-card::-webkit-scrollbar {
            width: 8px;
        }

        .pollutants-card::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 10px;
        }

        .pollutants-card::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 10px;
        }

        .pollutants-card::-webkit-scrollbar-thumb:hover {
            background: #555;
        }
                
        .pollutant-row {
            display: flex;
            align-items: flex-start;
            gap: 12px;
            margin-bottom: 24px;
            padding-bottom: 1'px;
        }
        
        .pollutant-row:last-child {
            border-bottom: none;
            margin-bottom: 0;
            padding-bottom: 0;
        }
        
        .pollutant-icon {
            width: 32px;
            height: 32px;
            object-fit: contain;
            flex-shrink: 0;
            margin-top: 2px;
        }
        
        .pollutant-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            gap: 8px;
        }
        
        .pollutant-header {
            display: flex;
            align-items: center;
            gap: 20px;
            width: 100%;
        }

        .pollutant-info {
            display: flex;
            flex-direction: column;
            min-width: 120px;
            flex-shrink: 0;
        }

        .pollutant-bar-container {
            display: flex;
            align-items: center;
            gap: 32px;
            flex: 1;
            max-width: 400px; 
            
        }

        .pollutant-bar-bg {
            flex: 1;
            min-width: 200px;  
            max-width: 350px;  
            height: 15px;
            background: #e2e8f0; 
            border-radius: 8px;
            overflow: hidden;
        }

        .pollutant-bar-fill {
            height: 100%;
            border-radius: 8px;
            transition: width 0.5s ease;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }

        .pollutant-category {
            font-size: 15px;
            font-weight: 700;
            text-transform: capitalize;  /* MODIFICATO: era uppercase */
            white-space: nowrap;
            min-width: 100px;  /* AUMENTATO: era 80px */
            text-align: left;
        }
        .pollutant-name {
            font-size: 15px;
            font-weight: 600;
            color: #1a1a1a;
            line-height: 1.2;
        }

        .pollutant-value {
            font-size: 11px;
            font-weight: 500;
            color: #666;
        }
        
        .pollutant-bar-bg {
            flex: 1;
            height: 15px;
            background: #e2e8f0; 
            border-radius: 8px;
            overflow: hidden;
        }

        .pollutant-bar-fill {
            height: 100%;
            border-radius: 8px;
            transition: width 0.5s ease;
            box-shadow: 0 1px 3px rgba(0,0,0,0.1);
        }
        
        /* Nascondi le colonne native di Streamlit */
        .st-emotion-cache-1j9e0u5 {
            display: none !important;
        }
        
        #MainMenu, footer, .stDeployButton {
            visibility: hidden;
        }
        
        .block-container {
            padding-top: 2rem;
            max-width: 1400px;
        }
        
        .stTextInput input {
            background: #f8f9fa;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            padding: 12px;
            font-size: 14px;
        }
        
        .stTextInput input:focus {
            border-color: #2563eb;
            box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
        }
        /* Stili per input nella pagina login */
        [data-testid="stTextInput"] input {
            background: #f8f9fa !important;
            border: 1px solid #e0e0e0 !important;
        }
        
        .stButton button {
            background: #2563eb;
            color: white;
            border: none;
            border-radius: 8px;
            padding: 12px 24px;
            font-weight: 600;
            font-size: 14px;
            width: 100%;
            transition: all 0.2s;
        }
        
        .stButton button:hover {
            background: #1d4ed8;
            box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
        }

        /* Contenitore unico per visualizzazione avanzata - wrapper esterno */
        div[data-testid="stHorizontalBlock"]:has(.advanced-left-column) {
            background: white !important;
            border-radius: 20px !important;
            padding: 30px !important;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06) !important;
            gap: 30px !important;
            margin-bottom: 0px !important;
            margin-top: 0px; !important;
        }

        /* Rimuovi padding extra dalle singole colonne per evitare doppio padding */
        div[data-testid="stHorizontalBlock"]:has(.advanced-left-column) > div[data-testid="column"] {
            padding: 0 !important;
            margin-bottom: 0px !important;
            margin-top: 0px; !important;
        }

        /* VISUALIZZAZIONE AVANZATA */
        .advanced-left-column {
            background: white;
            border-radius: 20px;
            padding: 20px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.06);
            display: flex;
            flex-direction: column;
            align-items: left;
            justify-content: flex-start;
            text-align: left;
            gap: 15px;
            
            min-height: 300px;  
            height: 515px;      

            padding-top: 55px;  
            
        }

        /* Contenitore per select box e data - DENTRO il box principale */
        .time-controls-wrapper {
            background: #f8f9fa;
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 25px;
            border: 1px solid #e5e7eb;
        }

        .time-date-display {
            font-size: 14px;
            color: #1a1a1a;
            font-weight: 600;
            margin-top: 8px;
            text-align: left;
        }

        /* Contenitore pulsanti temporali */
        .time-buttons-container {
            background: white;
            border-radius: 12px;
            padding: 20px;
            margin-bottom: 20px;
            box-shadow: 0 2px 8px rgba(0,0,0,0.06);
        }

        .time-buttons-row {
            display: flex;
            gap: 0;
            margin-bottom: 15px;
            border: 1px solid #d0d0d0;
            border-radius: 8px;
            overflow: hidden;
            width: fit-content;
        }

        .time-btn {
            padding: 8px 24px;
            font-size: 14px;
            font-weight: 600;
            background: #f5f5f5;
            color: #333;
            border: none;
            border-right: 1px solid #d0d0d0;
            cursor: pointer;
            transition: all 0.2s;
            font-family: 'Inter', sans-serif;
        }

        .time-btn:last-child {
            border-right: none;
        }

        .time-btn:hover {
            background: #e8e8e8;
        }

        .time-btn.active {
            background: #2563eb;
            color: white;
        }

        .time-date-display {
            font-size: 13px;
            color: #666;
            font-weight: 500;
        }
        .advanced-status-text {
            font-size: 20px;
            font-weight: 700;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        
        .advanced-aqi-container {
            display: flex;
            align-items: center;
            gap: 15px;
        }
        
        .advanced-aqi-value {
            font-size: 64px;
            font-weight: 700;
            line-height: 1;
        }
        
        .advanced-aqi-label {
            font-size: 14px;
            font-weight: 600;
            color: #666;
            text-transform: uppercase;
        }
        
        .advanced-desc {
            font-size: 13px;
            color: #666;
            line-height: 1.5;
        }
        
        .advanced-right-column::-webkit-scrollbar {
            width: 10px;
        }

        .advanced-right-column::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 10px;
        }

        .advanced-right-column::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 10px;
        }

        .advanced-right-column::-webkit-scrollbar-thumb:hover {
            background: #555;
        }
        
        /* Riga inquinante in modalità avanzata */
        .advanced-pollutant-row {
            display: grid;
            grid-template-columns: 25% 75%;
            gap: 20px;
            margin-bottom: 60px;  
            padding: 20px;
            background: #f8f9fa;
            border-radius: 12px;
            min-height: 400px; 
            scroll-snap-align: start; 
        }
        
        /* Parte sinistra con info inquinante */
        .advanced-pollutant-left {
            display: flex;
            flex-direction: column;
            justify-content: center;
            gap: 10px;
        }
        
        .advanced-pollutant-name {
            font-size: 16px;
            font-weight: 700;
            color: #1a1a1a;
            margin-bottom: 5px;
        }
        
        .advanced-pollutant-value {
            font-size: 24px;
            font-weight: 600;
            color: #333;
            margin-bottom: 10px;
        }
        
        .advanced-bar-vertical {
            width: 80px;        
            height: 130px;
            background: #e2e8f0;
            border-radius: 6px;  
            overflow: hidden;
            position: relative;
            margin: 10px auto;
        }

        .advanced-bar-fill-vertical {
            position: absolute;
            bottom: 0;
            width: 100%;
            border-radius: 6px;  
            transition: height 0.5s ease;
        }
                        
        .advanced-pollutant-category {
            font-size: 12px;
            font-weight: 700;
            text-transform: uppercase;
            text-align: center;
        }
        
        /* Parte destra con grafico */
        .advanced-pollutant-right {
            display: flex;
            align-items: center;
            justify-content: center;
            min-height: 220px;
        }
        
        /* Scrollbar personalizzata */
        .advanced-right-column::-webkit-scrollbar {
            width: 8px;
        }
        
        .advanced-right-column::-webkit-scrollbar-track {
            background: #f1f1f1;
            border-radius: 10px;
        }
        
        .advanced-right-column::-webkit-scrollbar-thumb {
            background: #888;
            border-radius: 10px;
        }
        
        .advanced-right-column::-webkit-scrollbar-thumb:hover {
            background: #555;
        }
        
        /* Valore grande */
        .advanced-pollutant-value {
            font-size: 28px;       
            font-weight: 700;
            color: #333;
            line-height: 1.0;
        }

        /* Unità sotto, più piccola e attenuata */
        .advanced-pollutant-unit {
            display: block;        
            font-size: 12px;       
            font-weight: 600;      
            color: #6b7280;        
            margin-top: 4px;       
            letter-spacing: 0.2px;
            text-transform: none;  
        }
        /* Stile per pulsante mappa */
        button[data-testid="baseButton-secondary"]:has([aria-label="Apri mappa sensori"]) {
            padding: 8px !important;
            font-size: 20px !important;
        }

    </style>
    """

# configurazione pagina (page config)
st.set_page_config(
    page_title="Qualità dell'Aria - Monitor Istituti",
    page_icon='assets/ediaqi_logo3.png',
    layout="wide",
    initial_sidebar_state="collapsed"
)

st.markdown("""
    <style>
        .block-container {
            max-width: 1300px !important;
            min-width: 1300px !important;
            width: 1300px !important;
            margin-left: auto !important;
            margin-right: auto !important;
        }
    </style>
""", unsafe_allow_html=True)

st.markdown(get_styles(), unsafe_allow_html=True)

# session state

if 'authenticated' not in st.session_state:
    st.session_state.authenticated = False
if 'obs_cache' not in st.session_state:
    st.session_state.obs_cache = ObservationsCache()
if 'reference_times' not in st.session_state:
    st.session_state.reference_times = {}  
if 'standard' not in st.session_state:
    st.session_state.standard = None
if 'selected_istituto' not in st.session_state:
    st.session_state.selected_istituto = None
if 'selected_classe' not in st.session_state:
    st.session_state.selected_classe = None
if 'heatmap_toggle' not in st.session_state:
    st.session_state.heatmap_toggle = False
if 'advanced_view' not in st.session_state:
    st.session_state.advanced_view = False
if 'return_to_istituto' not in st.session_state:
    st.session_state.return_to_istituto = None
if 'return_to_classe' not in st.session_state:
    st.session_state.return_to_classe = None
if 'return_to_advanced' not in st.session_state:
    st.session_state.return_to_advanced = False
if 'time_window' not in st.session_state:
    st.session_state.time_window = 24 
if 'custom_start_date' not in st.session_state:
    st.session_state.custom_start_date = None
if 'custom_end_date' not in st.session_state:
    st.session_state.custom_end_date = None
if 'use_custom_window' not in st.session_state:
    st.session_state.use_custom_window = False
if 'custom_range_ready' not in st.session_state:
    st.session_state.custom_range_ready = False
if 'custom_reference_time' not in st.session_state:
    st.session_state.custom_reference_time = None
if 'original_reference_times' not in st.session_state:
    st.session_state.original_reference_times = {} 
if 'available_time_range' not in st.session_state:
    st.session_state.available_time_range = {} 
# carica config
if 'client' not in st.session_state:
    st.session_state.client = None

if st.session_state.get('force_back_to_dashboard', False):
    st.session_state.force_back_to_dashboard = False
    st.session_state.show_map_page = False

CONFIG = load_config()

# chermata login
if not st.session_state.authenticated:
    # Layout CENTRATO con colonna unica
    col_center = st.columns([0.00000001, 2, 0.00000001])[1]

    st.markdown("""
        <style>
        /* Box login centrato */
        div[data-testid="stHorizontalBlock"]:has(.login-content-centered) {
            background: white !important;
            border-radius: 20px !important;
            padding: 30px 50px !important;
            box-shadow: 0 2px 12px rgba(0,0,0,0.1) !important;
            margin: 80px auto !important;
            max-width: 800px !important;
            display: block !important;
        }
        .login-content-centered {
            text-align: center;
        }
        /* Titolo dashboard DENTRO IL BOX */
        .dashboard-title {
            font-size: 20px;
            font-weight: 600;
            color: #006278;
            text-align: center;
            margin-bottom: 20px;
        }
        /* Box informativo */
        .info-box {
            background: #fefdef;
            border-radius: 12px;
            padding: 30px;
            margin-bottom: 40px;
            text-align: left;
        }
        .info-box .section-content {
            font-size: 14px;
            color: #333;
            line-height: 1.6;
        }
        /* Campi login UNO SOTTO L'ALTRO */
        .login-fields {
            max-width: 400px;
            margin: 0 auto 20px auto;
        }
        .login-field-item {
            margin-bottom: 15px;
        }
        .login-content-centered .login-label {
            color: #006278 !important;
            font-size: 14px !important;
            font-weight: 600 !important;
            margin-bottom: 6px !important;
            text-align: left !important;
            display: block;
        }
        .login-content-centered input {
            background-color: #f7f9f9 !important;
            border: 1px solid #cde5ea !important;
            border-radius: 8px !important;
            width: 100% !important;
        }
        .login-content-centered button[kind="secondary"] {
            background-color: #006278 !important;
            color: white !important;
            font-weight: 600 !important;
            border-radius: 8px !important;
            margin-top: 10px !important;
            max-width: 400px !important;
            margin-left: auto !important;
            margin-right: auto !important;
        }
        /* Footer con logo Europa E contact */
        .login-footer-custom {
            margin-top: 40px;
            padding-top: 25px;
            border-top: 1px solid #e0e0e0;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .europa-section {
            display: flex;
            align-items: center;
            gap: 15px;
            max-width: 50%;
        }
        .europa-logo {
            width: 80px;
            height: auto;
            flex-shrink: 0;
        }
        .europa-text {
            font-size: 11px;
            color: #333;
            line-height: 1.5;
            text-align: left;
        }
        .contact-section {
            font-size: 13px;
            color: #333;
            text-align: right;
        }
        .contact-section a {
            color: #007c93;
            font-weight: 600;
            text-decoration: none;
        }
        .contact-section a:hover {
            text-decoration: underline;
        }
        </style>
    """, unsafe_allow_html=True)

    with col_center:
        st.markdown('<div class="login-content-centered">', unsafe_allow_html=True)

        logo_path = 'assets/ediaqi_logo3.png'
        if Path(logo_path).exists():
            logo_b64 = get_image_base64(logo_path)


        # Box informativo con tutto il contenuto
        st.markdown(f'''<div class="info-box"><div class="section-content" style="text-align:left;"><img src="data:image/png;base64,{logo_b64}" class="main-logo" style="display:block;width:230px;height:auto;margin:0 auto 20px;"><p>Il progetto <a href="https://ediaqi.eu/" target="_blank" style="color:#006278;text-decoration:underline;">EDIAQI</a>, finanziato dall'Unione Europea nell'ambito del programma quadro Horizon Europe, studia l'inquinamento dell'aria indoor nelle città europee, utilizzando misurazioni di diversa natura e campagne di rilevamento e monitoraggio a scala e durata diversa, che coinvolgano anche cittadini e studenti.</p><div class="dashboard-title" style="text-align:center;">Dashboard Monitoraggio Ferrara</div><p>A Ferrara sono state realizzate campagne di rilevamento dati che hanno coinvolto diverse tipologie di edifici e utenti, con l'obiettivo di raccogliere informazioni significative sulla qualità dell'aria indoor e sensibilizzare categorie di persone diverse.</p><p>Le attività hanno interessato scuole, uffici, ristoranti, palestre e residenze, dove sono stati installati dispositivi multisensore NetPID per ottenere dati sulle concentrazioni di inquinanti negli ambienti interni.</p><p>Questi sistemi monitorano la presenza di PM2.5, CO, CO₂, NO₂, O₃ e TVOCs (Composti Organici Volatili), favorendo interventi rapidi per migliorare la qualità dell'aria. La dashboard consente di esplorare i dati raccolti e analizzare le condizioni degli edifici monitorati.</p></div></div>''', unsafe_allow_html=True)

        # Campi login uno sotto l'altro

        col_user, col_pass= st.columns(2)
        
        with col_user:
            st.markdown('<div class="login-label">Nome utente</div>', unsafe_allow_html=True)
            nome_utente = st.text_input("nome_utente", placeholder="Inserisci nome utente",
                                    label_visibility="collapsed", key="ferrara_pilot_login_user")
        
        with col_pass:
            st.markdown('<div class="login-label">Password</div>', unsafe_allow_html=True)
            password = st.text_input("Password", type="password",
                                    placeholder="Inserisci password",
                                    label_visibility="collapsed", key="ferrara_pilot_login_pass")
        
        st.markdown('</div>', unsafe_allow_html=True)

        # Pulsante accedi

        col_a, col_b, col_c = st.columns([1.5, 2, 1.5])

        with col_b:
        
            if st.button("Accedi", use_container_width=True, key="ferrara_pilot_login_submit"):
                with st.spinner("Connessione in corso..."):
                    try:
                        import requests
                        auth = (nome_utente, password) if nome_utente and password else None
                        endpoint = CONFIG['endpoint']
                        response = requests.get(endpoint, auth=auth, timeout=30)
                        response.raise_for_status()
                        data = response.json()

                        if isinstance(data, dict) and ('value' in data or 'serverSettings' in data):
                            st.session_state.authenticated = True
                            st.session_state.client = FROSTClient(endpoint, auth)
                            st.success("Connesso")
                            st.rerun()
                        else:
                            st.error("Endpoint FROST Server non valido")
                    except Exception as e:
                        st.error(f"Errore di connessione: {str(e)}")


        # Footer con logo Europa sinistra e Contact A dedstra
        st.markdown('<div class="login-footer-custom">', unsafe_allow_html=True)
        


        col_a, col_b, = st.columns([1,1])
        with col_a:
            # Sezione Europa (sinistra)
            st.markdown('<div class="europa-section">', unsafe_allow_html=True)
            europa_path = 'assets/europa.png'
            
            if Path(europa_path).exists():
                europa_b64 = get_image_base64(europa_path)
                st.markdown(
                    f'<img src="data:image/png;base64,{europa_b64}" class="europa-logo" style="width:300px; height:auto;">',
                    unsafe_allow_html=True
                )

            
            st.markdown("""
                <div class="europa-text">
                    Questo progetto ha ricevuto finanziamenti dal programma di ricerca e innovazione 
                    Horizon Europe dell'Unione Europea nell'ambito della convenzione di sovvenzione n. 101057497.
                </div>
            """, unsafe_allow_html=True)
            st.markdown('</div>', unsafe_allow_html=True)
        with col_b:
            pass
        
        # Sezione Contact (destra)
        st.markdown("""
            <div class="contact-section">
                Contact <a href="mailto:info@ediaqi.eu">info@ediaqi.eu</a>
            </div>
        """, unsafe_allow_html=True)
        
        st.markdown('</div>', unsafe_allow_html=True)
        st.markdown('</div>', unsafe_allow_html=True)

# gestione immersione all'interno della mappa con visualizzazione avanzata
if (st.session_state.get('selected_istituto') is not None and 
    st.session_state.get('selected_classe') is not None and
    st.session_state.get('advanced_view') == True and
    st.session_state.get('show_map_page') == False):
    
    # Salta la visualizzazione normale e vai direttamente all'avanzata
    pass  # Continua l'esecuzione del rest del codice
# gestione della pagina della mappa
if st.session_state.get('show_map_page', False):
    # NON resettare il flag qui - lo resetteremo solo quando si torna indietro
    import importlib.util
    spec = importlib.util.spec_from_file_location("app_mappa", "app_mappa.py")
    app_mappa = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(app_mappa)
    st.stop()

# applicazione principale
client = st.session_state.client

if not st.session_state.authenticated or client is None:
    st.error("Devi effettuare il login per accedere ai dati.")
    st.stop()


# Carica standard
if CONFIG.get('standard_url') and st.session_state.standard is None:
    with st.spinner("Caricamento standard..."):
        st.session_state.standard = load_standard(CONFIG['standard_url'])

standard = st.session_state.standard

# gestione cambio istituto forzato dalla mappa
if st.session_state.get('force_istituto_change', False):
    # Imposta l'istituto e classe pending
    if st.session_state.get('pending_istituto'):
        st.session_state.selected_istituto = st.session_state.pending_istituto
    if st.session_state.get('pending_classe'):
        st.session_state.selected_classe = st.session_state.pending_classe
    
    # Reset dei flag
    st.session_state.force_istituto_change = False
    st.session_state.pending_istituto = None
    st.session_state.pending_classe = None
    
    # forza rerun immegiato per applicare i cambiamenti (non funziona per qualcje motivo bisogna guardrci in futuoro luca!)
    st.rerun()

# header logo + select istituto + logo
col_select, col_toggle = st.columns([ 8, 5])

# Determina se è un istituto single-class
is_single_class = len(ISTITUTI.get(st.session_state.selected_istituto or list(ISTITUTI.keys())[0], [])) == 1

# Applica classe CSS se necessario
if is_single_class:
    st.markdown('<div class="single-class-layout">', unsafe_allow_html=True)


with col_select:
    st.markdown('<div class="stSelectbox-istituto">', unsafe_allow_html=True)
    
    # calcola lìindex corretto per il selectbox
    istituti_list = list(ISTITUTI.keys())
    if st.session_state.selected_istituto and st.session_state.selected_istituto in istituti_list:
        default_index = istituti_list.index(st.session_state.selected_istituto)
    else:
        default_index = 0
        st.session_state.selected_istituto = istituti_list[0]
    
    selected_istituto = st.selectbox(
        "Seleziona Istituto",
        options=istituti_list,
        index=default_index,  # aggiunta dell'indice
        label_visibility="collapsed"
    )
    
    if selected_istituto != st.session_state.selected_istituto:
        st.session_state.selected_istituto = selected_istituto
        st.session_state.selected_classe = None
        st.rerun()

with col_toggle:
    # Aggiungi questi due elementi affiancati
    col_toggle_left, col_toggle_right = st.columns([3, 2])

    with col_toggle_left:

        st.markdown(
            """
            <style>
            /* Target del toggle */
            div[data-testid="stToggle"] {
                margin-top: 5px !important;
            }
            </style>
            """,
            unsafe_allow_html=True
        )
        st.markdown('<div class="custom-toggle-label">', unsafe_allow_html=True)
        advanced_view = st.toggle(
            "Visualizzazione Avanzata",
            value=st.session_state.advanced_view,
            key="advanced_view_toggle"
        )

        if advanced_view != st.session_state.advanced_view:
            st.session_state.advanced_view = advanced_view
            
            if not advanced_view:
                st.session_state.use_custom_window = False
                st.session_state.custom_range_ready = False
                st.session_state.custom_reference_time = None
                
                for tid in st.session_state.original_reference_times:
                    st.session_state.reference_times[tid] = st.session_state.original_reference_times[tid]
            
            st.rerun()
    
            
        with col_toggle_right:
            # Mostra pulsante mappa solo in visualizzazione avanzata
            st.markdown('<div class="custom-toggle-label">', unsafe_allow_html=True)

            if st.button("Mappa", key="open_map", use_container_width=True, type="primary"):
                st.session_state.return_to_istituto = st.session_state.selected_istituto
                st.session_state.return_to_classe = st.session_state.selected_classe
                st.session_state.return_to_advanced = st.session_state.advanced_view 
                st.session_state.show_map_page = True
                st.rerun()

# bottoni classi con label classe selezionata

if st.session_state.selected_istituto:
    thing_ids = ISTITUTI[st.session_state.selected_istituto]

    # Imposta default
    if (
        st.session_state.selected_classe is None
        or st.session_state.selected_classe not in thing_ids
    ):
        st.session_state.selected_classe = thing_ids[0]

# calcola reference time per tutti i things
with st.spinner("Calcolo tempi di riferimento..."):
    for tid in thing_ids:
        try:
            first_ds = client.get_datastreams(tid)
            if first_ds:
                first_obs = client.get_latest_observation(first_ds[0].get('@iot.id'))
                if first_obs:
                    ref_time = parse_time(first_obs.get('phenomenonTime'))
                    if not pd.isna(ref_time):
                        ref_time_dt = ref_time.to_pydatetime().replace(tzinfo=None)
                        
                        # Aggiorna SOLO se non siamo in custom window
                        if not st.session_state.use_custom_window:
                            st.session_state.reference_times[tid] = ref_time_dt
                        
                        # Aggiorna sempre l'originale
                        st.session_state.original_reference_times[tid] = ref_time_dt
                    else:
                        st.session_state.reference_times[tid] = None
                else:
                    st.session_state.reference_times[tid] = None
            else:
                st.session_state.reference_times[tid] = None
        except Exception as e:
            st.session_state.reference_times[tid] = None
            pass

# calcolo dell'iaq per tutte le classo (con controllo 6 ore integrato)
with st.spinner("Caricamento stato qualità aria..."):
    classes_aqi = {}
    classes_has_null_with_data = {}
    classes_images = {}
    now = datetime.now()
    
    for thing_id in thing_ids:
        ref_time = st.session_state.reference_times.get(thing_id)
        
        if ref_time is None:
            classes_aqi[thing_id] = None
            classes_has_null_with_data[thing_id] = False
            classes_images[thing_id] = 'air_quality_nullo'
            continue
        
        time_diff = (now - ref_time).total_seconds() / 3600
        
        if time_diff > 6:
            classes_aqi[thing_id] = None
            classes_has_null_with_data[thing_id] = False
            classes_images[thing_id] = 'icon_bottone7'
            continue
        
        try:
            thing = client.get_thing(thing_id)
            if not thing:
                classes_aqi[thing_id] = None
                classes_has_null_with_data[thing_id] = False
                classes_images[thing_id] = 'air_quality_nullo'
                continue
            
            datastreams = client.get_datastreams_with_latest(thing['@iot.id'])
            if not datastreams:
                classes_aqi[thing_id] = None
                classes_has_null_with_data[thing_id] = False
                classes_images[thing_id] = 'air_quality_nullo'
                continue
            
            # Costruisci latest_data come nella visualizzazione base 
            from frost_utils import build_alias_index, detect_measure
            alias_idx = build_alias_index(standard)
            datastream_labels = standard.get("datastreamLabels", [])
            
            latest_data = {}
            for ds in datastreams:
                obs = ds.get('Observations', [])
                if not obs:
                    continue
                
                #  prima il nome del datastream
                name = ds.get('name') or ""
                detected_name = detect_measure(name, alias_idx, datastream_labels)
                
                # se non trova, prova con ObservedProperty
                if not detected_name:
                    op_name = (ds.get("ObservedProperty") or {}).get("name") or ""
                    detected_name = detect_measure(op_name, alias_idx, datastream_labels)
                
                if detected_name:
                    latest_data[detected_name] = obs[0].get('result')
            
            # passa latest_data per garantire coerenza
            _, air_category, has_null_values, _ = calculate_air_quality_index( 
                datastreams, 
                client, 
                standard, 
                ref_time,
                latest_data=latest_data 
            )
            classes_aqi[thing_id] = air_category
            classes_has_null_with_data[thing_id] = has_null_values

            # determina l'immagine da usare
            # Se ha valori nulli → nullo.png
            if has_null_values:
                classes_images[thing_id] = 'air_quality_nullo'
            # Se ha categoria valida (0-5) → usa immagine corrispondente
            elif air_category is not None:
                classes_images[thing_id] = f'air_quality_{air_category + 1}'
            # Se categoria è None ma NON ha nulli → dati vecchi (già gestito sopra con icon_bottone7)
            else:
                # Questo caso non dovrebbe mai verificarsi perché se time_diff > 6 
                # abbiamo già fatto continue con icon_bottone7
                # Ma per sicurezza, mettiamo nullo
                classes_images[thing_id] = 'air_quality_nullo'
                
        except:
            classes_aqi[thing_id] = None
            classes_has_null_with_data[thing_id] = False
            classes_images[thing_id] = 'air_quality_nullo'


    st.markdown("""
        <style>
        /* FORZA RIMOZIONE SPAZI */
        .main .block-container {
            padding-top: 1rem !important;
        }
        
        /* Collassa TUTTO lo spazio verticale */
        [data-testid="stVerticalBlock"] {
            margin-top: 15px;
            gap: 3px !important;
        }
        
        [data-testid="stVerticalBlock"] > div {
            margin: 0px !important;
            padding: 0px !important;
        }
        
        /* SUPER margine negativo sui bottoni */
        div[data-testid="stHorizontalBlock"]:has(button) {
            margin-top: 0px !important;
            margin-bottom: 0px !important;
        }
        
        /* Elimina padding dal container principale */
        .element-container {
            margin: 0px !important;
            padding: 0px !important;
        }
        
        /* Stile per contenitore bottone + icona */
        .button-with-icon {
            display: flex;
            align-items: center;
            gap: 8px;
            width: 100%;
        }
        
        .button-icon-img {
            width: 28px;
            height: 28px;
            object-fit: contain;
            flex-shrink: 0;
        }
        </style>
    """, unsafe_allow_html=True)

    # 60% bottoni + 40% label classe selezionata
    # ma solo se le stanze sono più di una, senno non fare apparire i bottoni 

    if len(thing_ids) > 1:
        # Ottieni proporzioni colonne in base all'istituto selezionato
        button_col_width, label_col_width = COLUMN_PROPORTIONS.get(
            st.session_state.selected_istituto, 
            [6, 4]
        )
        col_buttons, col_label = st.columns([button_col_width, label_col_width])

        with col_buttons:
            # Crea bottoni in colonne dinamiche
            num_buttons = len(thing_ids)
            cols = st.columns(num_buttons)

        st.markdown("""
            <style>
            /* ADATTA LAYOUT BOTTONI CON ICONE */
            .button-with-icon {
                justify-content: flex-start;
                padding-left: 4px;
            }
            
            /* Riduci leggermente il padding del bottone per fare spazio all'icona */
            .button-with-icon + div button {
                padding-left: 2px !important;
            }
            </style>
        """, unsafe_allow_html=True)

        for idx, thing_id in enumerate(thing_ids):
            with cols[idx]:
                label = CLASSI_NAMES.get(thing_id, thing_id)
                is_selected = (thing_id == st.session_state.selected_classe)
                btn_type = "primary" if is_selected else "secondary"

                # usa l'immagine determinata in precedenza
                img_key = classes_images.get(thing_id, 'air_quality_nullo')
                    
                # Mappa immagine qualità aria → icona bottone
                if img_key == 'air_quality_nullo':
                    icon_key = 'error_s'
                elif img_key == 'icon_bottone7':
                    icon_key = 'icon_bottone7'
                elif img_key.startswith('air_quality_'):
                    # Estrae il numero da 'air_quality_1' → 'icon_bottone1'
                    quality_num = img_key.split('_')[-1]
                    icon_key = f'icon_bottone{quality_num}'
                else:
                    icon_key = 'error_s'  # Fallback
                
                icon_html = ""
                icon_path = IMAGES.get(icon_key)
                if icon_path and Path(icon_path).exists():
                    icon_b64 = get_image_base64(icon_path)
                    if icon_b64:
                        # Configurazione per error_s
                        if icon_key == 'error_s':
                            margin_top = "-4px"
                            margin_bottom = "-22px"
                            margin_left = "6px"
                            icon_html = f'<img src="data:image/png;base64,{icon_b64}" style="width:17.5px; height:17.5px; object-fit:contain; flex-shrink:0; position:relative; z-index:10; margin-top:{margin_top}; margin-bottom:{margin_bottom};margin-left:{margin_left}">'
                        else:
                            icon_html = f'<img src="data:image/png;base64,{icon_b64}" class="button-icon-img">'

                st.markdown('<div class="btn-wrap-4px">', unsafe_allow_html=True)
                
                if icon_html:
                    st.markdown(f'<div class="button-with-icon">{icon_html}', unsafe_allow_html=True)

                if st.button(
                    f"{label}",
                    key=f"btn_{thing_id}",
                    type=btn_type,
                    use_container_width=True
                ):
                    if thing_id != st.session_state.selected_classe:
                        st.session_state.selected_classe = thing_id
                        st.session_state.custom_range_ready = False
                        st.session_state.heatmap_toggle = False
                        if thing_id in st.session_state.available_time_range:
                            del st.session_state.available_time_range[thing_id]
                        st.rerun()
                        


    else:
        # configurazione margini advanced mode
        if st.session_state.advanced_view:
            # SOLO in visualizzazione avanzata
            st.markdown("""
                <style>
                /* IDENTIFICA PAGINA SENZA BOTTONI CLASSE */
                body:not(:has(.btn-wrap-4px)) .main [data-testid="stVerticalBlock"] {
                    gap: 0px !important;
                }
                
                /* HEATMAP TOGGLE - riduci spazio sopra/sotto */
                body:not(:has(.btn-wrap-4px)) [data-testid="stHorizontalBlock"]:has([data-testid="stCheckbox"]) {
                    margin-top: 0px !important;
                    margin-bottom: 0px !important;
                }
                
                /* SELECT BOX TEMPO - sposta su */
                body:not(:has(.btn-wrap-4px)) [data-testid="stHorizontalBlock"]:has(.custom-select-label) {
                    margin-top: -50.4815162342px !important;
                    margin-bottom: -40px !important;
                }
                
                /* LABEL CLASSE + DATA - compatta */
                body:not(:has(.btn-wrap-4px)) [data-testid="stHorizontalBlock"]:has(.selected-class-label2) {
                    margin-top: -40px !important;
                }
                
                /* BOX GRAFICI - riduci spazio sopra */
                body:not(:has(.btn-wrap-4px)) .scroll-container-wrapper {
                    margin-top: -40px !important;
                }
                
                /* HEADER - riduci margine bottom */
                body:not(:has(.btn-wrap-4px)) .header-container {
                    margin-bottom: -30px !important;
                }
                
                /* COLONNA SINISTRA ADVANCED - compatta */
                body:not(:has(.btn-wrap-4px)) .advanced-left-column {
                    padding-top: 0px !important;
                    min-height: 50px !important;
                }
                
                /* DATE PICKERS - sposta su */
                body:not(:has(.btn-wrap-4px)) [data-testid="stDateInput"] {
                    margin-top: 5px !important;
                }
                
                /* TIME CONTROLS WRAPPER - riduci padding */
                body:not(:has(.btn-wrap-4px)) .time-controls-wrapper {
                    padding: 0px 0px !important;
                    margin-bottom: 0px !important;
                }
                </style>
            """, unsafe_allow_html=True)


    st.markdown("""
        <style>
        /* RIMUOVI SPAZIO TRA HEADER E BOTTONI */
        
        /* Targetizza il CONTAINER VERTICALE principale */
        section.main > div.block-container > div[data-testid="stVerticalBlock"] {
            gap: 0px !important;
        }
        
        /* Rimuovi TUTTI i margini verticali dagli elementi dopo l'header */
        div[data-testid="stHorizontalBlock"]:has(.header-logo) + div {
            margin-top: 0px !important;
            padding-top: 0px !important;
        }
        
        /* Forza lo 0 anche sui wrapper dei bottoni */
        div[data-testid="stVerticalBlock"] > div:has(.btn-wrap-4px) {
            margin-top: 0px !important;
            margin-bottom: 0px !important;
            padding-top: 0px !important;
            padding-bottom: 0px !important;
        }
        
        /* TIRA SU i bottoni e RIMUOVI margine bottom */
        div[data-testid="stHorizontalBlock"]:has(.btn-wrap-4px) {
            margin-top: -15px !important;
            margin-bottom: -40px !important;  
            padding-top: 0px !important;
            padding-bottom: 0px !important;
            gap: 0px !important;
        }
        
        /* Assicurati che l'header non abbia margine inferiore */
        .header-container {
            margin-bottom: 0px !important;
        }
        
        /* RIDUCI margine top dalla colonna sotto i bottoni */
        div[data-testid="stHorizontalBlock"]:has(.air-quality-content) {
            margin-top: 5px !important;  
            padding-top: 0px !important;
        }
        
        /* ADATTA LAYOUT BOTTONI CON ICONE */
        .button-with-icon {
            justify-content: flex-start;
            padding-left: 4px;
            position: relative;
            align-items: flex-end;  
            padding-bottom: 2px;  
        }
        
        .button-icon-img {
            width: 28px;
            height: 28px;
            object-fit: contain;
            flex-shrink: 0;
            position: relative;
            z-index: 10;
            margin-top: 8px;  
            margin-bottom: -2px; 
        }
        
        /* Riduci leggermente il padding del bottone per fare spazio all'icona */
        .button-with-icon + div button {
            padding-left: 2px !important;
            position: relative;
            z-index: 1;
        }
        
        /* RIDUCI margine interno bottoni */
        .btn-wrap-4px {
            margin: 0px 1px !important; 
            padding: 0 1px !important;
        }
        </style>
    """, unsafe_allow_html=True)

    st.markdown("""
        <style>
        /* MARGINE SUPERIORE SOLO PER LE ICONE DEI BOTTONI */
        .button-icon-img {
            width: 28px;
            height: 28px;
            object-fit: contain;
            flex-shrink: 0;
            position: relative;
            z-index: 10;
            margin-top: 2px !important;  
            margin-bottom: -25px;
        }
        </style>
    """, unsafe_allow_html=True)

def create_pollutant_chart(client, ds_info, reference_time, timezone='Europe/Rome'):
    """Crea grafico Plotly per un inquinante"""
    import plotly.graph_objects as go
    from frost_utils import format_timestamp
    
    try:
        effective_ref_time = reference_time
        
        hours_needed = st.session_state.time_window
        
        # Scarica dati con reference time corretyto
        obs = st.session_state.obs_cache.get_or_fetch(
            client,
            ds_info['id'],
            hours_needed=hours_needed,
            reference_time=effective_ref_time, 
            select_clause='phenomenonTime,result'
        )
        if not obs:
            return None

        #  normalizza effective_ref_time naive
        if hasattr(effective_ref_time, 'tzinfo') and effective_ref_time.tzinfo is not None:
            effective_ref_time_naive = effective_ref_time.replace(tzinfo=None)
        else:
            effective_ref_time_naive = effective_ref_time

        #  calcola start time atteso
        if st.session_state.use_custom_window and st.session_state.custom_start_date:
            timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
            if timezone_cfg == 'local':
                import tzlocal
                try:
                    timezone_cfg = str(tzlocal.get_localzone())
                except:
                    timezone_cfg = 'Europe/Rome'
            
            import pytz
            local_tz = pytz.timezone(timezone_cfg)
            start_datetime = datetime.combine(st.session_state.custom_start_date, datetime.min.time())
            start_datetime_local = local_tz.localize(start_datetime)
            expected_start_time = pd.Timestamp(start_datetime_local.astimezone(pytz.UTC))
        else:
            expected_start_time = pd.Timestamp(effective_ref_time_naive) - pd.Timedelta(hours=hours_needed)
        
        #  normalizza expected_start_time a naive
        expected_start_naive = expected_start_time.to_pydatetime().replace(tzinfo=None)
        
        # Prepara dati e filtra tutto al reference time
        times = []
        values = []
        for idx, o in enumerate(obs):
            phenom = o.get('phenomenonTime')
            
            if idx == 0:
                if isinstance(phenom, str) and "/" in phenom:
                    t = parse_phenomenon_time_start(phenom)
                else:
                    t = parse_time(phenom)
            else:
                t = parse_time(phenom)
            
            v = o.get('result')
            if not pd.isna(t) and v is not None:
                t_dt = t.to_pydatetime().replace(tzinfo=None)
                
                if effective_ref_time_naive and t_dt > effective_ref_time_naive:
                    continue
                    
                times.append(t)
                values.append(float(v))

        #  verifica se sono presenti dati all'interno del range
        if not times:
            return None
        
        #  normalizza i timestamp per il confronto
        times_naive = [t.to_pydatetime().replace(tzinfo=None) if hasattr(t, 'tzinfo') else t for t in times]
        expected_start_naive = expected_start_naive
        effective_ref_time_naive = effective_ref_time_naive
        
        #  se non cis ono dati all'inizio della serie storica utilizza un punto fittizio
        first_time_naive = times_naive[0]
        if first_time_naive > expected_start_naive:
            # C'è un gap iniziale - aggiungi punto fittizio all'inizio
            times.insert(0, expected_start_time)
            values.insert(0, None)
            times_naive.insert(0, expected_start_naive)
        
        #  sempre stessa storia, se non cis ono dati aggiugi un punto fittizio alla fine della serie storica
        last_time_naive = times_naive[-1]
        if last_time_naive < effective_ref_time_naive:
            # C'è un gap finale - aggiungi punto fittizio alla fine
            times.append(pd.Timestamp(effective_ref_time_naive))
            values.append(None)
            times_naive.append(effective_ref_time_naive)

        # Inserisci None dove ci sono gap > 5 minuti 
        if len(times) > 1:
            times_with_gaps = [times[0]]
            values_with_gaps = [values[0]]
            
            for i in range(1, len(times)):
                if times[i] is not None and times[i-1] is not None:
                    t_current = times[i].to_pydatetime().replace(tzinfo=None) if hasattr(times[i], 'tzinfo') else times[i]
                    t_previous = times[i-1].to_pydatetime().replace(tzinfo=None) if hasattr(times[i-1], 'tzinfo') else times[i-1]
                    
                    time_diff = (t_current - t_previous).total_seconds() / 60
                    
                    if time_diff > 5:
                        times_with_gaps.append(None)
                        values_with_gaps.append(None)
                
                times_with_gaps.append(times[i])
                values_with_gaps.append(values[i])
            
            times = times_with_gaps
            values = values_with_gaps
        
        # verifica se ci sono dati validi (non solo None)
        if not values or all(v is None for v in values):
            return None
                
        # gestione timezon DA config2.yaml
        import pytz

        if timezone == 'local':
            import tzlocal
            try:
                timezone = str(tzlocal.get_localzone())
            except:
                timezone = 'Europe/Rome'

        # Converti timezone
        try:
            local_tz = pytz.timezone(timezone)
            times_converted = []
            for t in times:
                if t is not None:
                    if isinstance(t, pd.Timestamp):
                        if t.tzinfo is not None:
                            t_local = t.tz_convert(local_tz)
                        else:
                            t_utc = t.tz_localize('UTC')
                            t_local = t_utc.tz_convert(local_tz)
                    else:
                        # Se è datetime, converti a Timestamp
                        t_pd = pd.Timestamp(t)
                        if t_pd.tzinfo is not None:
                            t_local = t_pd.tz_convert(local_tz)
                        else:
                            t_utc = t_pd.tz_localize('UTC')
                            t_local = t_utc.tz_convert(local_tz)
                    times_converted.append(t_local)
                else:
                    times_converted.append(None)
            times = times_converted
        except Exception as e:
            st.warning(f"Errore conversione timezone {timezone}: {e}")
            times = pd.to_datetime(times, utc=True).tz_convert('Europe/Rome')
        
        # Resto della funzione rimane invariato...
        # Crea grafico
        fig = go.Figure()

        # Aggiungi aloni colorati per le soglie
        detected_name = ds_info.get('name', '')
        from frost_utils import build_alias_index, detect_measure
        alias_idx = build_alias_index(standard)
        datastream_labels = standard.get("datastreamLabels", [])
        detected_name = detect_measure(detected_name, alias_idx, datastream_labels)

        if detected_name:
            from frost_utils import get_datastream_config
            ds_config = get_datastream_config(detected_name, datastream_labels)
            
            if ds_config:
                thresholds = ds_config.get("categoryThresholds", [])
                
                if len(thresholds) >= 5:
                    box_colors = [
                        "rgba(0, 208, 182, 0.15)",
                        "rgba(0, 179, 132, 0.15)",
                        "rgba(255, 186, 0, 0.15)",
                        "rgba(255, 0, 78, 0.15)",
                        "rgba(170, 0, 53, 0.15)",
                        "rgba(152, 0, 132, 0.15)"
                    ]
                    
                    import math
                    max_value_for_pessima = thresholds[4] * math.pow(2, 2.5)
                    
                    y_ranges = [
                        (0, thresholds[0]),
                        (thresholds[0], thresholds[1]),
                        (thresholds[1], thresholds[2]),
                        (thresholds[2], thresholds[3]),
                        (thresholds[3], thresholds[4]),
                        (thresholds[4], max_value_for_pessima)
                    ]
                    
                    for i, (y0, y1) in enumerate(y_ranges):
                        if i < len(box_colors):
                            fig.add_shape(
                                type="rect",
                                xref="paper", yref="y",
                                x0=0, x1=1,
                                y0=y0, y1=y1,
                                fillcolor=box_colors[i],
                                layer="below",
                                line_width=0
                            )

        # Linea dati principale
        fig.add_trace(go.Scatter(
            x=times,
            y=values,
            mode='lines',
            name=ds_info['name'],
            line=dict(color='#2563eb', width=2),
            fill='tozeroy',
            fillcolor='rgba(37, 99, 235, 0.1)',
            connectgaps=False
        ))
        
        max_value = max([v for v in values if v is not None], default=0)
        y_max = max_value * 1.1 if max_value > 0 else 100

        fig.update_layout(
            height=350,
            margin=dict(l=40, r=40, t=30, b=50),
            xaxis=dict(
                showgrid=True,
                gridcolor='#e2e8f0',
                title=None
            ),
            yaxis=dict(
                showgrid=True,
                gridcolor='#e2e8f0',
                title=f"{ds_info['unit']}",
                range=[0, y_max]
            ),
            hovermode='x unified',
            plot_bgcolor='white',
            paper_bgcolor='white',
            font=dict(family='Inter', size=11)
        )
        
        return fig
        
    except Exception as e:
        st.error(f"Errore creazione grafico: {e}")
        return None
    

if st.session_state.selected_classe:
    thing_id = st.session_state.selected_classe
    
    with st.spinner(f"Caricamento dati {CLASSI_NAMES.get(thing_id, thing_id)}..."):
        # Carica Thing
        thing = client.get_thing(thing_id)
        
        if not thing:
            st.error(f"Thing '{thing_id}' non trovato")
            st.stop()
        
        # Carica datastreams
        datastreams = client.get_datastreams_with_latest(thing['@iot.id'])
        
        if not datastreams:
            st.warning("Nessun datastream disponibile")
            st.stop()

        # Calcola reference timeper solo questo thoing specifico
        with st.spinner("Calculating reference time..."):
            first_ds = client.get_datastreams(thing['@iot.id'])
            if first_ds:
                first_obs = client.get_latest_observation(first_ds[0].get('@iot.id'))
                if first_obs:
                    ref_time = parse_time(first_obs.get('phenomenonTime'))
                    if not pd.isna(ref_time):
                        ref_time_dt = ref_time.to_pydatetime().replace(tzinfo=None)

                        # NON sovrascrivere se siamo in custom range
                        if not st.session_state.use_custom_window:
                            st.session_state.reference_times[thing_id] = ref_time_dt
                        
                        # Aggiorna sempre il backup originale
                        st.session_state.original_reference_times[thing_id] = ref_time_dt
                            

        #  USA original_reference_time se non sei in modalità costume
        if not st.session_state.use_custom_window:
            reference_time = st.session_state.original_reference_times.get(thing_id)
        else:
            reference_time = st.session_state.reference_times.get(thing_id)

        reference_time = st.session_state.reference_times.get(thing_id)

        #  costruisci latest_data usando stesso metodo di process_datastreams_data
        from frost_utils import build_alias_index, detect_measure
        alias_idx = build_alias_index(standard)
        datastream_labels = standard.get("datastreamLabels", [])

        latest_data = {}
        for ds in datastreams:
            ds_id = ds.get('@iot.id')
            name = ds.get('name') or ""
            
            detected_name = detect_measure(name, alias_idx, datastream_labels)
            if not detected_name:
                op_name = (ds.get("ObservedProperty") or {}).get("name") or ""
                detected_name = detect_measure(op_name, alias_idx, datastream_labels)
            
            if not detected_name:
                continue
            
            # cerca osservazione più vicina a reference_time
            if reference_time:
                ref_obs = client.get_observations_timerange(
                    ds_id, 
                    hours_back=2,
                    limit=50,
                    reference_time=reference_time,
                    select_clause='phenomenonTime,result'
                )
                
                if ref_obs:
                    # Filtra solo <= reference_time
                    ref_time_naive = reference_time.replace(tzinfo=None) if hasattr(reference_time, 'tzinfo') else reference_time
                    
                    valid_obs = []
                    for o in ref_obs:
                        obs_time = parse_time(o.get('phenomenonTime'))
                        if not pd.isna(obs_time):
                            obs_time_naive = obs_time.to_pydatetime().replace(tzinfo=None)
                            if obs_time_naive <= ref_time_naive:
                                valid_obs.append(o)
                    
                    if valid_obs:
                        # Trova la più vicina
                        ref_obs_sorted = sorted(
                            valid_obs,
                            key=lambda o: abs((parse_time(o.get('phenomenonTime')).to_pydatetime().replace(tzinfo=None) - ref_time_naive).total_seconds())
                        )
                        obs = ref_obs_sorted[0]
                        latest_data[detected_name] = obs.get('result')


        # Calcola indice qualità aria
        #  utilizza custom_reference_time SE DISPONIBILE
        air_index, air_category, has_null_values, worst_pollutant = calculate_air_quality_index( 
            datastreams, 
            client, 
            standard, 
            reference_time, 
            latest_data=latest_data if 'latest_data' in locals() else None
        )


        # Wrapper CSS per racchiudere le colonne Streamlit
        st.markdown("""
            <style>
            /* Targetizza SOLO le colonne che contengono .air-quality-content */
            div[data-testid="stHorizontalBlock"]:has(.air-quality-content) {
                background: white !important;
                border-radius: 20px !important;
                padding: 30px !important;
                box-shadow: 0 2px 12px rgba(0,0,0,0.06) !important;
                gap: 30px !important;
                margin-bottom: 0px !important;
                margin-top: 0px; !important;
            }
            
            /* Rimuovi padding extra dalle singole colonne dentro questo contenitore */
            div[data-testid="stHorizontalBlock"]:has(.air-quality-content) div[data-testid="column"] {
                padding: 0 !important;
                margin-bottom: 0px !important;
                margin-top: 0px; !important;
            }
            </style>
        """, unsafe_allow_html=True)

        # Layout 2 colonne
        col_left, col_right = st.columns(2)
    
    col_left, col_right = st.columns(2)
        
# CSS aggiuntivo per layout single-class
if len(thing_ids) == 1:
    st.markdown("""
        <style>
        /* Riduci tutti i margini per single-class */
        div[data-testid="stHorizontalBlock"]:has(.air-quality-content) {
            margin-top: -40px !important;
        }
        
        div[data-testid="stHorizontalBlock"]:has(.advanced-single-column) {
            margin-top: -40px !important;
        }
        </style>
    """, unsafe_allow_html=True)

# render condizionale BASATO SU advanced_view
if st.session_state.advanced_view:
    
    with st.container():

        st.markdown("""
            <style>
            /* CSS per visualizzazione avanzata con colonna unica */                   
            div[data-testid="stHorizontalBlock"]:has(.advanced-single-column) {
                background: white !important;
                border-radius: 12px !important;
                padding: 30px !important;
                border: 1px solid #cccccc !important;
                box-shadow: none !important;
                margin-bottom: 0px !important;
                padding-bottom: 0px !important;
            }
            
            /* RIDUCI SPAZIO TRA BOTTONI CLASSI E SELECT BOX TEMPORALE */
            div[data-testid="stHorizontalBlock"]:has(.custom-select-label) {
                margin-top: -20px !important;
                margin-bottom: -30px !important;
                padding-top: 0px !important;
            }

            /* FORZA SPOSTAMENTO PER SINGLE-CLASS in advanced view */
            .force-single-class-spacing {
                margin-top: -80px !important;
                margin-bottom: 0px !important;
            }
            
    
            
            /* Riduci padding interno delle colonne contenenti il select */
            div[data-testid="column"]:has(.custom-select-label) {
                padding-top: 0px !important;
                margin-top: 0px !important;
            }
            </style>
        """, unsafe_allow_html=True)
        
        ##################################################################################################
        # viz avanzata a colonna unica 
        
        # select box finestra temporale in alto
        col_select_time, col_label_classe = st.columns([7, 2.5])
        
        with col_select_time:
            col_1, col_2, col_3 = st.columns([3.5, 1.5, 3])
            with col_1:
                st.markdown('<div class="custom-select-label">', unsafe_allow_html=True)

                # Definizione opzioni e mapping in ore
                time_options = ["24 ore", "7 giorni", "Finestra temporale custom"]
                time_to_hours = {
                    "24 ore": 24,
                    "7 giorni": 7 * 24,
                }

                # Determina l'indice di default
                if st.session_state.use_custom_window:
                    default_index = 2 
                else:
                    _default_hours = st.session_state.get("time_window", 24)
                    _default_label = next((lbl for lbl, hrs in time_to_hours.items() if hrs == _default_hours), "24 ore")
                    default_index = time_options.index(_default_label)

                selected_window_label = st.selectbox(   
                    "Finestra temporale",
                    options=time_options,
                    index=default_index,
                    label_visibility="collapsed",
                    key="time_select_adv",
                )

                st.markdown('</div>', unsafe_allow_html=True)

                if selected_window_label == "Finestra temporale custom":
                    st.session_state.use_custom_window = True
                    
                    thing_id = st.session_state.selected_classe
                    if thing_id not in st.session_state.available_time_range:
                        with st.spinner("Calcolo dati disponibili..."):
                            avail_start, avail_end = get_available_time_range_from_datastreams(client, thing_id)
                            st.session_state.available_time_range[thing_id] = (avail_start, avail_end)
                    
                    avail_start, avail_end = st.session_state.available_time_range.get(thing_id, (None, None))
                    
                    if avail_start and avail_end:
                        # Converti al timezone configurato per display
                        timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
                        if timezone_cfg == 'local':
                            import tzlocal
                            try:
                                timezone_cfg = str(tzlocal.get_localzone())
                            except:
                                timezone_cfg = 'Europe/Rome'
                        
                        import pytz
                        local_tz = pytz.timezone(timezone_cfg)
                        
                        avail_start_local = pd.Timestamp(avail_start, tz='UTC').tz_convert(local_tz)
                        avail_end_local = pd.Timestamp(avail_end, tz='UTC').tz_convert(local_tz)
                        
                        avail_start_str = avail_start_local.strftime("%d/%m/%Y")
                        avail_end_str = avail_end_local.strftime("%d/%m/%Y")
                        
                        st.markdown(f"""
                            <div style="font-size:12px; color:#4a5568; margin-bottom:10px; margin-top:5px;">
                                Dati disponibili: {avail_start_str} → {avail_end_str}
                            </div>
                        """, unsafe_allow_html=True)
                    
                    # Mostra i date pickers e il pulsante
                    col_start, col_end, col_btn = st.columns([2, 2, 1])

                    with col_start:
                        start_date = st.date_input(
                            "Data inizio",
                            value=st.session_state.custom_start_date if st.session_state.custom_start_date else datetime.now().date() - pd.Timedelta(days=7),
                            key="custom_start_date_input",
                            label_visibility="collapsed",
                            format="DD/MM/YYYY"
                        )

                    with col_end:
                        end_date = st.date_input(
                            "Data fine",
                            value=st.session_state.custom_end_date if st.session_state.custom_end_date else datetime.now().date(),
                            key="custom_end_date_input",
                            label_visibility="collapsed",
                            format="DD/MM/YYYY"
                        )

                    with col_btn:
                        button_clicked = st.button("📊", key="apply_custom_window", use_container_width=True, help="Applica e mostra grafico")

                    # crea un placeolder che utilizza uno spazio fisso 
                    # placeholder con altezza fissa per evitare shift
                    status_placeholder = st.empty()
                    
                    # contenitore con altezza fissa minima
                    with status_placeholder.container():
                        st.markdown('<div style="min-height: 60px;"></div>', unsafe_allow_html=True)

                    # gestione click bottone
                    if button_clicked:
                        status_placeholder.empty()  # Pulisci prima
                        if start_date >= end_date:
                            with status_placeholder.container():
                                st.error("⚠️ La data di inizio deve essere precedente alla data di fine")
                        else:
                            # spinner di caricamneto nel placeholder
                            with status_placeholder.container():
                                with st.spinner("🔄 Creazione grafici in corso..."):
                                    # Validazione contor range disponibile
                                    if avail_start and avail_end:
                                        # Converti date selezionate in datetime naive per confronto
                                        selected_start = datetime.combine(start_date, datetime.min.time())
                                        selected_end = datetime.combine(end_date, datetime.max.time())
                                        
                                        # Confronta con range disponibile (già naive)
                                        if selected_start < avail_start or selected_end > avail_end:
                                            st.error("⚠️ Dati non disponibili per la finestra temporale selezionata")
                                            st.stop()
                                    
                                    days_diff = (end_date - start_date).days
                                    
                                    if days_diff > 40:
                                        st.error("⚠️ Finestra temporale troppo ampia, massimo 40 giorni")
                                        st.stop()
                                    
                                    # gestisci timezone da config
                                    timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
                                    if timezone_cfg == 'local':
                                        import tzlocal
                                        try:
                                            timezone_cfg = str(tzlocal.get_localzone())
                                        except:
                                            timezone_cfg = 'Europe/Rome'
                                    
                                    # Crea datetime naive nel timezone locale
                                    start_datetime = datetime.combine(start_date, datetime.min.time())
                                    end_datetime = datetime.combine(end_date, datetime.max.time())
                                    
                                    # Localizza nel timezone configurato, poi converti a UTC
                                    import pytz
                                    local_tz = pytz.timezone(timezone_cfg)
                                    start_datetime_local = local_tz.localize(start_datetime)
                                    end_datetime_local = local_tz.localize(end_datetime)
                                    
                                    # Converti a UTC e rimuovi tzinfo per storage
                                    start_datetime_utc = start_datetime_local.astimezone(pytz.UTC).replace(tzinfo=None)
                                    end_datetime_utc = end_datetime_local.astimezone(pytz.UTC).replace(tzinfo=None)
                                    
                                    # Calcola ore tra start e end UTC
                                    time_diff = (end_datetime_utc - start_datetime_utc).total_seconds() / 3600
                                    st.session_state.time_window = int(time_diff) + 1
                                    
                                    # sovrascrivi ma solo il reference_time con l'end custom
                                    thing_id = st.session_state.selected_classe
                                    st.session_state.reference_times[thing_id] = end_datetime_utc
                                    st.session_state.custom_reference_time = end_datetime_utc
                                    
                                    st.session_state.custom_start_date = start_date
                                    st.session_state.custom_end_date = end_date
                                    st.session_state.custom_range_ready = True
                                    
                                    # Clear cache solo per il thing corrente (più veloce)
                                    thing_id = st.session_state.selected_classe
                                    datastreams = client.get_datastreams(thing_id)
                                    
                                    for ds in datastreams:
                                        ds_id = ds.get('@iot.id')
                                        if ds_id in st.session_state.obs_cache.cache:
                                            del st.session_state.obs_cache.cache[ds_id]
                                    
                                    # verifica dati disponibili
                                    datastreams = client.get_datastreams_with_latest(thing['@iot.id'])
                                    has_any_data = False
                                    
                                    for ds in datastreams:
                                        obs = st.session_state.obs_cache.get_or_fetch(
                                            client, ds.get('@iot.id'), 
                                            hours_needed=int(time_diff) + 1,
                                            reference_time=end_datetime_utc,
                                            select_clause='phenomenonTime,result'
                                        )
                                        
                                        if obs and len(obs) > 0:
                                            # Verifica se almeno un'osservazione ha valore valido
                                            for o in obs:
                                                val = o.get('result')
                                                if val is not None and val >= 0:
                                                    has_any_data = True
                                                    break
                                        
                                        if has_any_data:
                                            break
                                    
                                    if not has_any_data:
                                        status_placeholder.empty()
                                        with status_placeholder.container():
                                            st.error("⚠️ Nessun dato disponibile per la finestra temporale scelta")
                                        st.stop()
                                    
                                    st.rerun()
                    else:
                        # modifica spazio quando non c'è il bottone cliccato
                        with status_placeholder.container():
                            st.markdown("<div style='height: 1px;'></div>", unsafe_allow_html=True)
                    if not st.session_state.custom_range_ready:
                        st.info("Seleziona le date di inizio e di fine poi premi '📊' per visualizzare i grafici")
                        st.stop()
                else:
                    # Finestra predefinita selezionata
                    st.session_state.use_custom_window = False
                    st.session_state.custom_range_ready = False 
                    
                    st.session_state.custom_start_date = None
                    st.session_state.custom_end_date = None
                    st.session_state.custom_reference_time = None
                    
                    new_hours = time_to_hours[selected_window_label]
                    if new_hours != st.session_state.time_window:
                        st.session_state.time_window = new_hours
                        
                        # ripristina reference_time originale
                        thing_id = st.session_state.selected_classe
                        if thing_id in st.session_state.original_reference_times:
                            st.session_state.reference_times[thing_id] = st.session_state.original_reference_times[thing_id]
                        
                        try:
                            st.session_state.obs_cache.clear()
                        except Exception:
                            from frost_client import ObservationsCache
                            st.session_state.obs_cache = ObservationsCache()
                        st.rerun()
            with col_2:
                st.markdown(f"""
                <div style="text-align:right; margin-top:20px;"></div>
                    """, unsafe_allow_html=True)
                if st.button("Info", key="info_thresholds", use_container_width=True):
                    show_thresholds_info()
            
            with col_3:
                pass

                    ##########
                        


        with col_label_classe:

            # Label classe + range temporale allineate a destra
            if reference_time:
                import locale
                
                if isinstance(reference_time, datetime):
                    ref_ts = pd.Timestamp(reference_time, tz='UTC')
                else:
                    ref_ts = pd.Timestamp(reference_time).tz_localize('UTC')
                
                timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
                if timezone_cfg == 'local':
                    import tzlocal
                    try:
                        timezone_cfg = str(tzlocal.get_localzone())
                    except:
                        timezone_cfg = 'Europe/Rome'

                ref_ts = ref_ts.tz_convert(timezone_cfg)
                
                #  calcol odello start time in base alla finestra temporale
                if st.session_state.use_custom_window and st.session_state.custom_start_date and st.session_state.custom_reference_time:
                    #  Usa data inizio custom nel timezone configurato
                    timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
                    if timezone_cfg == 'local':
                        import tzlocal
                        try:
                            timezone_cfg = str(tzlocal.get_localzone())
                        except:
                            timezone_cfg = 'Europe/Rome'
                    
                    # Crea datetime naive alla mezzanotte locale
                    start_datetime = datetime.combine(st.session_state.custom_start_date, datetime.min.time())
                    
                    # Localizza e converti per display
                    import pytz
                    local_tz = pytz.timezone(timezone_cfg)
                    start_ts = pd.Timestamp(start_datetime, tz=local_tz)
                else:
                    # Usa hours_back
                    hours_back = st.session_state.time_window
                    start_ts = ref_ts - pd.Timedelta(hours=hours_back)
                    
                # Formatta entrambe le date
                start_formatted = start_ts.strftime("%a, %d %B %H:%M").capitalize()
                start_formatted = start_formatted.replace("Mon", "Lun").replace("Tue", "Mar").replace("Wed", "Mer")
                start_formatted = start_formatted.replace("Thu", "Gio").replace("Fri", "Ven").replace("Sat", "Sab").replace("Sun", "Dom")
                start_formatted = start_formatted.replace("January", "gennaio").replace("February", "febbraio").replace("March", "marzo")
                start_formatted = start_formatted.replace("April", "aprile").replace("May", "maggio").replace("June", "giugno")
                start_formatted = start_formatted.replace("July", "luglio").replace("August", "agosto").replace("September", "settembre")
                start_formatted = start_formatted.replace("October", "ottobre").replace("November", "novembre").replace("December", "dicembre")
                
                ref_time_formatted = ref_ts.strftime("%a, %d %B %H:%M").capitalize()
                ref_time_formatted = ref_time_formatted.replace("Mon", "Lun").replace("Tue", "Mar").replace("Wed", "Mer")
                ref_time_formatted = ref_time_formatted.replace("Thu", "Gio").replace("Fri", "Ven").replace("Sat", "Sab").replace("Sun", "Dom")
                ref_time_formatted = ref_time_formatted.replace("January", "gennaio").replace("February", "febbraio").replace("March", "marzo")
                ref_time_formatted = ref_time_formatted.replace("April", "aprile").replace("May", "maggio").replace("June", "giugno")
                ref_time_formatted = ref_time_formatted.replace("July", "luglio").replace("August", "agosto").replace("September", "settembre")
                ref_time_formatted = ref_time_formatted.replace("October", "ottobre").replace("November", "novembre").replace("December", "dicembre")
                
                time_range = f"{start_formatted} → {ref_time_formatted}"

            selected_label = CLASSI_NAMES.get(st.session_state.selected_classe, st.session_state.selected_classe)
            
            st.markdown(f"""
                <div style="text-align:right; margin-top:10px;">
                    <div class="selected-class-label2" style="margin:0; padding:0; font-weight:700; color:#444;margin-right:15px;">
                        <strong>{selected_label}</strong>
                    </div>
                    <div style="font-size:12px; color:#444; margin-top: 2px;margin-bottom:10px; margin-right:15px;">
                        {time_range}
                    </div>
                </div>
            """, unsafe_allow_html=True)

        # Formatta reference_time per visualizzazione avanzata
        if reference_time:
            import locale
            
            # Converti reference_time in pandas Timestamp con timezone
            if isinstance(reference_time, datetime):
                ref_ts = pd.Timestamp(reference_time, tz='UTC')
            else:
                ref_ts = pd.Timestamp(reference_time).tz_localize('UTC')
            

            timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')

            if timezone_cfg == 'local':
                import tzlocal
                try:
                    timezone_cfg = str(tzlocal.get_localzone())
                except:
                    timezone_cfg = 'Europe/Rome'

            ref_ts = ref_ts.tz_convert(timezone_cfg)
            
            try:
                locale.setlocale(locale.LC_TIME, 'it_IT.UTF-8')
            except:
                try:
                    locale.setlocale(locale.LC_TIME, 'Italian')
                except:
                    pass
            
            ref_time_formatted = ref_ts.strftime("%a, %d %B %H:%M").capitalize()
            # Traduzioni manuali se locale non disponibile
            ref_time_formatted = ref_time_formatted.replace("Mon", "Lun").replace("Tue", "Mar").replace("Wed", "Mer")
            ref_time_formatted = ref_time_formatted.replace("Thu", "Gio").replace("Fri", "Ven").replace("Sat", "Sab").replace("Sun", "Dom")
            ref_time_formatted = ref_time_formatted.replace("January", "gennaio").replace("February", "febbraio").replace("March", "marzo")
            ref_time_formatted = ref_time_formatted.replace("April", "aprile").replace("May", "maggio").replace("June", "giugno")
            ref_time_formatted = ref_time_formatted.replace("July", "luglio").replace("August", "agosto").replace("September", "settembre")
            ref_time_formatted = ref_time_formatted.replace("October", "ottobre").replace("November", "novembre").replace("December", "dicembre")
        else:
            ref_time_formatted = "N/A"

            # Label classe + data INSIEME, allineate a destra
            selected_label = CLASSI_NAMES.get(st.session_state.selected_classe, st.session_state.selected_classe)

            # verifica se la classe ha dati vecchi (oltre 6 ore)
            thing_id = st.session_state.selected_classe
            aqi_category = classes_aqi.get(thing_id)
            has_null = classes_has_null_with_data.get(thing_id, False)
            warning_icon_html = ""

            # Mostra esclamativo solo se dati vecchi (non se ci sono valori nulli con dati recenti)
            if aqi_category is None and not has_null:
                # Verifica se i dati sono vecchi
                ref_time = st.session_state.reference_times.get(thing_id)
                if ref_time:
                    now = datetime.now()
                    time_diff = (now - ref_time).total_seconds() / 3600
                    
                    if time_diff > 6:  # Dati più vecchi di 6 ore
                        warning_path = IMAGES.get('esclamativo')
                        if warning_path and Path(warning_path).exists():
                            warning_b64 = get_image_base64(warning_path)
                            if warning_b64:
                                warning_icon_html = f'<img src="data:image/png;base64,{warning_b64}" style="width:50px; height:50px; margin-right:5px; vertical-align:middle;">'
                        st.markdown(f"""
                <div style="text-align:right; margin-top:10px;">
                    <div class="selected-class-label2" style="margin:0; padding:0; font-weight:700; color:#444;margin-right:15px;">
                        <strong>{selected_label}</strong>
                    </div>
                    <div style="font-size:12px; color:#444; margin-top: 2px;margin-bottom:10px; margin-right:15px; display:flex; align-items:center; justify-content:flex-end;">
                        {warning_icon_html}{time_range}
                    </div>
                </div>
            """, unsafe_allow_html=True)
        #st.markdown('<div class="air-quality-content"></div>', unsafe_allow_html=True)
        # Processa datastreams
        with_thresh, without_thresh, ds_map, latest = process_datastreams_data(
            client, thing['@iot.id'], datastreams, standard, reference_time
        )

        # Combina items
        all_items = []
        for item in with_thresh:
            all_items.append({
                'type': 'threshold',
                'name': item['name'],
                'value': item['value'],
                'unit': item['unit'],
                'cat_name': item['cat_name'],
                'color': item['color'],
                'progress': item['progress'],
                'ds_idx': item['ds_idx']
            })
        all_items = all_items[:10]  # Limita a 10 inquinanti

        import streamlit.components.v1 as components
        from frost_utils import format_value

        charts_html_parts = []
        timezone = CONFIG.get('timezone', 'Europe/Rome')
        if timezone == 'local':
            import tzlocal
            try:
                timezone = str(tzlocal.get_localzone())
            except:
                timezone = 'Europe/Rome'


        # Calcola indici normalizzati per tutti gli inquinanti
        ref_time_for_charts = reference_time
        pollutant_indices = calculate_normalized_indices(datastreams, client, standard, ref_time_for_charts)

        # Calcola margine dinamico basato su custom_range_ready
        margin_top_value = "50px" if st.session_state.custom_range_ready else "0px"

        st.markdown(f"""
            <style>
            /* Applica margine top al blocco intero che contiene toggle e label */
            div[data-testid="stHorizontalBlock"]:has([data-testid="stCheckbox"]) {{
                margin-top: {margin_top_value} !important;
            }}
            </style>
        """, unsafe_allow_html=True)

        col_a, col_b = st.columns([0.03, 1])

        with col_a:
            st.markdown(
                """
                <div style="margin-bottom:5px;">
                """,
                unsafe_allow_html=True
            )
            show_heatmap = st.toggle("", value=st.session_state.heatmap_toggle, key="heatmap_toggle")
            st.markdown("</div>", unsafe_allow_html=True)
            
        with col_b:
            st.markdown(
                """
                <div style="margin-top:5px;">
                """,
                unsafe_allow_html=True
            )
            st.markdown("<small>Mostra Heatmap</small>", unsafe_allow_html=True)

        # Margine top anche per il blocco dei grafici
        st.markdown(f"""
            <style>
            .scroll-container-wrapper {{
                margin-top: {margin_top_value} !important;
            }}
            </style>
        """, unsafe_allow_html=True)


        # Scegli quale grafico creare in base al toggle
        if show_heatmap:
            overview_fig = create_categorical_heatmap(pollutant_indices, None, timezone)
        else:
            overview_fig = create_aqi_overview_chart(pollutant_indices, timezone)

        

        if overview_fig:
            overview_html = overview_fig.to_html(
                include_plotlyjs="cdn",
                full_html=False,
                config={"displayModeBar": True}
            )
            
            # Determina colori per la barra verticale

            if air_index is not None and air_category is not None:
                # Caso normale
                bg_color, text_color = get_colors_for_category(air_category)
                index_display = f"{air_index:.0f}"
                cat_name = AIR_QUALITY_LABELS[air_category]
                # Scala logaritmica: cap a 100
                normalized_index = min(air_index, 100)
                bar_height = (normalized_index / 100.0) * 170
            else:
                # Caso nullo: barra grigia
                bg_color = "#e5e7eb"
                text_color = "#9ca3af"
                index_display = "--"
                cat_name = "Non disponibile"
                bar_height = 0

            # Formatta timestamp per display sotto le barre
            if ref_time_for_charts:
                import pytz
                timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
                if timezone_cfg == 'local':
                    import tzlocal
                    try:
                        timezone_cfg = str(tzlocal.get_localzone())
                    except:
                        timezone_cfg = 'Europe/Rome'
                
                local_tz = pytz.timezone(timezone_cfg)
                
                if isinstance(ref_time_for_charts, datetime):
                    ref_ts = pd.Timestamp(ref_time_for_charts, tz='UTC')
                else:
                    ref_ts = pd.Timestamp(ref_time_for_charts).tz_localize('UTC')
                
                ref_ts_local = ref_ts.tz_convert(local_tz)
                ts_display = ref_ts_local.strftime("%d/%m/%Y %H:%M")
            else:
                ts_display = "N/A"
            
            # Determina label timezone
            timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
            if timezone_cfg == 'local':
                import tzlocal
                try:
                    detected_tz = str(tzlocal.get_localzone())
                    tz_label = f"Timezone client local ({detected_tz})"
                except:
                    tz_label = "Timezone client local (Europe/Rome)"
            elif timezone_cfg == 'Europe/Rome':
                tz_label = "Ora locale Ferrara"
            elif timezone_cfg == 'UTC':
                tz_label = "Timezone UTC"
            else:
                tz_label = f"Timezone {timezone_cfg}"

            # Calcola worst_pollutant per overview (solo se indice valido)
            cause_text = ""
            if worst_pollutant and air_index is not None:
                cause_text = f'<div style="font-size:12px; color:{text_color}; margin-top:4px; opacity:0.8;">(a causa di {worst_pollutant})</div>'

            # agiungi legenda solo se stai visualizzando un heatmap

            charts_html_parts.append(f"""
            <div class="chart-section" id="chart-overview">
            <div style="display: grid; grid-template-columns: 15% 85%; gap: 20px;">
                <div class="advanced-pollutant-left">
                    <div class="advanced-pollutant-name">IAQ</div>
                    <div class="advanced-pollutant-value">{index_display}</div>
                
                    <!-- Barra verticale US AQI -->
                    <div class="advanced-bar-vertical"
                        style="width:80px;height:170px;background:{bg_color};border-radius:6px;
                                overflow:hidden;position:relative;margin:10px auto;">
                        <div class="advanced-bar-fill-vertical"
                            style="position:absolute;bottom:0;width:100%;
                                    height:{bar_height:.1f}px;
                                    background:{text_color};
                                    border-radius:6px;transition:height 0.5s ease;">
                        </div>
                    </div>

                    <div class="advanced-pollutant-category" style="color:{text_color};">
                        {cat_name}
                    </div>
                    {cause_text}
                    <div style="font-size:10px; color:#999; margin-top:8px; text-align:center;">
                        {ts_display}
                    </div>
                </div>

                <div class="advanced-pollutant-right">
                {overview_html}
                <div style="text-align:center; font-size:13px; color:#bbb; margin-top:-1px;">
                    {tz_label}
                </div>
                </div>
            </div>
            </div>
            """)

        # grafici singolo inquiannte
        for idx, item in enumerate(all_items):
            ds_info = ds_map.get(item['ds_idx'])
            if not ds_info:
                continue

            ref_time_for_chart = st.session_state.custom_reference_time if st.session_state.use_custom_window else reference_time
            
            #  scarica i dati per ottenere l'ultimo timestam reale
            hours_needed = st.session_state.time_window
            obs = st.session_state.obs_cache.get_or_fetch(
                client, ds_info['id'], hours_needed=hours_needed,
                reference_time=ref_time_for_chart,
                select_clause='phenomenonTime,result'
            )
            
            #  calcola timestamo reale dell'ultimo dato
            actual_last_time = None
            if obs:
                valid_times = []
                for o in obs:
                    t = parse_time(o.get('phenomenonTime'))
                    if not pd.isna(t):
                        valid_times.append(t)
                if valid_times:
                    actual_last_time = max(valid_times)
            
            # Formatta timestamp per display (usa il timestamp reale)
            if actual_last_time:
                timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
                if timezone_cfg == 'local':
                    import tzlocal
                    try:
                        timezone_cfg = str(tzlocal.get_localzone())
                    except:
                        timezone_cfg = 'Europe/Rome'
                
                import pytz
                local_tz = pytz.timezone(timezone_cfg)
                actual_last_time_local = actual_last_time.tz_convert(local_tz)
                ts_display = actual_last_time_local.strftime("%d/%m/%Y %H:%M")
            else:
                ts_display = "N/A"
    
            fig = create_pollutant_chart(client, ds_info, ref_time_for_chart, timezone)
            from frost_utils import format_value
            value_at_ref_time = item.get('value')
            
            #  solo se non ci sono dati: barra grigia + "-- unità"
            if fig is None or not obs:
                value_str = "--"
                unit_str = item['unit']
                
                fig_html = f"""
                <div style="display:flex;align-items:center;justify-content:center;width:100%;height:350px;
                border:1px dashed #e5e7eb;border-radius:12px;background:white;color:#666;font-size:16px;font-weight:600;">
                    Dati non disponibili
                </div>
                """
                
                charts_html_parts.append(f"""
                <div class="chart-section" id="chart-{idx}">
                <div style="display: grid; grid-template-columns: 15% 85%; gap: 20px;">
                    <div class="advanced-pollutant-left">
                        <div class="advanced-pollutant-name">{item['name']}</div>
                        <div class="advanced-pollutant-value" style="color:#9ca3af;">{value_str}</div>
                        <span class="advanced-pollutant-unit" style="color:#9ca3af;">{unit_str}</span>
                        
                        <!-- Barra verticale GRIGIA -->
                        <div class="advanced-bar-vertical"
                            style="width:80px;height:170px;background:#e5e7eb;border-radius:6px;
                                    overflow:hidden;position:relative;margin:10px auto;">
                        </div>
                        
                        <div class="advanced-pollutant-category" style="color:#9ca3af; font-size:14px; margin-top:20px;">
                            Dati non disponibili
                        </div>
                        <div style="font-size:10px; color:#999; margin-top:8px; text-align:center;">
                            {ts_display}
                        </div>
                    </div>

                    <div class="advanced-pollutant-right">
                    {overview_html}
                    <div style="text-align:center; font-size:13px; color:#bbb; margin-top:8px;">
                        {tz_label}
                    </div>
                    </div>
                </div>
                </div>
                """)
                continue  # Salta il resto e vai al prossimo inquinante
            
            #  genera fig_html per il caso CON DATI
            else:
                fig_html = fig.to_html(
                    include_plotlyjs=False, 
                    full_html=False,
                    config={"displayModeBar": True}
                )
                
                # valore valido: Mostra normalmente con barra
                value_str = format_value(value_at_ref_time)
                
                try:
                    cat_idx = AIR_QUALITY_LABELS.index(item['cat_name'])
                except ValueError:
                    cat_idx = 0
                
                bar_bg_color = BOX_COLOR[cat_idx]
                bar_fill_color = item['color']

                # PROGRESS e BAR_BG_COLOR usando la stessa logica viz semplice
                ds_info = ds_map.get(item['ds_idx'])
                if ds_info:
                    detected_name = ds_info.get('name', '')
                    from frost_utils import build_alias_index, detect_measure, get_datastream_config
                    alias_idx = build_alias_index(standard)
                    datastream_labels = standard.get("datastreamLabels", [])
                    detected_name = detect_measure(detected_name, alias_idx, datastream_labels)
                    
                    if detected_name:
                        ds_config = get_datastream_config(detected_name, datastream_labels)
                        if ds_config:
                            thresholds = ds_config.get("categoryThresholds", [])
                            greater_worse = ds_config.get("greaterIsWorse", True)
                            
                            val = value_at_ref_time
                            if greater_worse:
                                if val <= thresholds[4]:
                                    progress = item['progress']
                                else:
                                    import math
                                    log_component = 8 * math.log2(val / thresholds[4])
                                    normalized = min(80 + log_component, 100)
                                    progress = normalized / 100.0
                            else:
                                if val >= thresholds[4]:
                                    progress = item['progress']
                                else:
                                    import math
                                    if val > 0:
                                        ratio = thresholds[4] / val
                                        log_component = 8 * math.log2(1 / ratio)
                                        normalized = min(80 + log_component, 100)
                                        progress = normalized / 100.0
                                    else:
                                        progress = 1.0
                        else:
                            progress = item['progress']
                    else:
                        progress = item['progress']
                else:
                    progress = item['progress']

                # Converti progress in altezza barra (170px max)
                bar_fill_height = progress * 170

                # Ottieni colore di sfondo basato sulla categoria
                try:
                    cat_idx = AIR_QUALITY_LABELS.index(item['cat_name'])
                except ValueError:
                    cat_idx = 0
                bar_bg_color = BOX_COLOR[cat_idx]
                cat_name_display = item['cat_name']
                cat_color_display = item['color']

                # Determina label timezone
                timezone_cfg = CONFIG.get('timezone', 'Europe/Rome')
                if timezone_cfg == 'local':
                    import tzlocal
                    try:
                        detected_tz = str(tzlocal.get_localzone())
                        tz_label = f"Timezone client local ({detected_tz})"
                    except:
                        tz_label = "Timezone client local (Europe/Rome)"
                elif timezone_cfg == 'Europe/Rome':
                    tz_label = "Ora locale Ferrara"
                elif timezone_cfg == 'UTC':
                    tz_label = "Timezone UTC"
                else:
                    tz_label = f"Timezone {timezone_cfg}"

                charts_html_parts.append(f"""
                <div class="chart-section" id="chart-{idx}">
                <div style="display: grid; grid-template-columns: 15% 85%; gap: 20px;">
                    <div class="advanced-pollutant-left">
                    <div class="advanced-pollutant-name">{item['name']}</div>
                    <div class="advanced-pollutant-value">{value_str}</div>
                    <span class="advanced-pollutant-unit">{item['unit']}</span>

                    <!-- Barra verticale con SFONDO dinamico da BOX_COLOR (come viz semplice) -->
                    <div class="advanced-bar-vertical"
                        style="width:80px;height:170px;background:{bar_bg_color};border-radius:6px;
                                overflow:hidden;position:relative;margin:10px auto;">
                        <div class="advanced-bar-fill-vertical"
                            style="position:absolute;bottom:0;width:100%;
                                    height:{bar_fill_height:.1f}px;
                                    background:{bar_fill_color};
                                    border-radius:6px;transition:height 0.5s ease;">
                        </div>
                    </div>

                    <div class="advanced-pollutant-category" style="color:{cat_color_display};">
                        {cat_name_display}
                    </div>
                    <div style="font-size:10px; color:#999; margin-top:8px; text-align:center;">
                        {ts_display}
                    </div>
                    </div>

                    <div class="advanced-pollutant-right">
                    {fig_html}
                    <div style="text-align:center; font-size:13px; color:#bbb; margin-top:-1px;">
                        {tz_label}
                    </div>
                    </div>
                </div>
                </div>
                """)

        # CSS + WRAPPER SCROLLABILE contenente TUTTE le sezioni (barra + grafico affiancati)
        container_css = """
        <style>
            .scroll-container-wrapper {
                margin-top: 10px;
                margin-left: 0;       
                margin-right: 0;      
                background: white;
                border-radius: 20px;
                box-shadow: 0 2px 12px rgba(0,0,0,0.06);
                max-height: 85vh;
                overflow-y: auto;
                scroll-behavior: smooth;
                scroll-snap-type: y mandatory;
                font-family: "Source Sans Pro", sans-serif;
                width: 100%;          
            }
            
            /* applica font a tutti gli elementi figli */
            .scroll-container-wrapper * {
                font-family: "Source Sans Pro", sans-serif !important;
            }
            .scroll-container-wrapper::-webkit-scrollbar { width: 10px; }
            .scroll-container-wrapper::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 10px; }
            .scroll-container-wrapper::-webkit-scrollbar-thumb { background: #888; border-radius: 10px; }
            .scroll-container-wrapper::-webkit-scrollbar-thumb:hover { background: #555; }

            .chart-section {
                margin-bottom: 0;
                padding: 30px;        
                background: white;
                border-radius: 0;
                border-bottom: 1px solid #e5e7eb;
                scroll-snap-align: start;
                min-height: 400px;
            }

            .chart-section:last-child {
                border-bottom: none;  
            }
            .advanced-pollutant-left {
                display: flex;
                flex-direction: column;
                justify-content: center;
                gap: 10px;
                text-align: center;
            }
            .advanced-pollutant-name {
                font-size: 16px;
                font-weight: 700;
                color: #1a1a1a;
            }
            .advanced-pollutant-value {
                font-size: 24px;
                font-weight: 600;
                color: #333;
            }
            .advanced-bar-vertical {
                width: 80px;     
                height: 170px;
                background: #e2e8f0;
                border-radius: 6px;  
                overflow: hidden;
                position: relative;
                margin: 10px auto;
            }

            .advanced-bar-fill-vertical {
                position: absolute;
                bottom: 0;
                width: 100%;
                border-radius: 6px;  
                transition: height 0.5s ease;
            }
            .advanced-pollutant-category {
                font-size: 20px;
                font-weight: 700;
                text-transform: capitalize;
            }

            /* Garantisce che i grafici riempiano la colonna destra */
            .advanced-pollutant-right .plotly-graph-div {
                width: 100% !important;
                height: 350px !important;
            }
        </style>
        """
        
        components.html(
            f"""{container_css}
            <div class="scroll-container-wrapper">
                {''.join(charts_html_parts)}
            </div>
            """,
            height=600,
            scrolling=False
        )

else:
    # visualizzazione standard
    col_left, col_right = st.columns(2)
      
    # colonna sinistra - Qualità Aria
    with col_left:
        # Header: Classe + Data e Orario
        if reference_time:
            timezone = CONFIG.get('timezone', 'Europe/Rome')
            if timezone == 'local':
                import tzlocal
                try:
                    timezone = str(tzlocal.get_localzone())
                except:
                    timezone = 'Europe/Rome'
            date_str = format_timestamp(reference_time, timezone)
            date_only = date_str.split(',')[0] if ',' in date_str else date_str.split(' ')[0]
            time_only = date_str.split(', ')[-1] if ', ' in date_str else ''

            # Aggiungi timezone label solo per Europe/Rome
            timezone_label = ""
            if timezone == 'Europe/Rome':
                timezone_label = '<span style="font-size:10px; color:#999; margin-left:8px;">(ora locale Ferrara)</span>'

            selected_label = CLASSI_NAMES.get(thing_id, thing_id)

            # verifica se la classe abbia dati vecchI (oltre 6 ore)
            aqi_category = classes_aqi.get(thing_id)
            has_null = classes_has_null_with_data.get(thing_id, False)
            warning_icon_html = ""

            # Mostra esclamativo selo se dati vecchi (non se ci sono valori nulli con dati recenti)
            if aqi_category is None and not has_null:
                # Verifica se i dati sono vecchi
                ref_time = st.session_state.reference_times.get(thing_id)
                if ref_time:
                    now = datetime.now()
                    time_diff = (now - ref_time).total_seconds() / 3600
                    
                    if time_diff > 6:  # Dati più vecchi di 6 ore
                        warning_path = IMAGES.get('esclamativo')
                        if warning_path and Path(warning_path).exists():
                            warning_b64 = get_image_base64(warning_path)
                            if warning_b64:
                                warning_icon_html = f'<img src="data:image/png;base64,{warning_b64}" style="width:20px; height:20px; margin-right:5px; vertical-align:middle;">'
            st.markdown(f"""
                <div style="text-align:left; margin-bottom:30px;">
                    <div class="selected-class-label2" style="margin:0; padding:0; font-weight:700;margin-bottom:5; color:#444;">
                        <strong>{selected_label}</strong>
                    </div>
                    <div class="timestamp-outside" style="margin:2px 0 0 0; font-size:14px; color:#444; display:flex; align-items:center;">
                        {warning_icon_html}{date_only} {time_only} {timezone_label}
                    </div>
                </div>
            """, unsafe_allow_html=True)

        # Box qualità aria - caso normale o nullo
        if air_category is not None and not has_null_values:
            # caso normale: mostra indice
            bg_color, text_color = get_colors_for_category(air_category)
            
            img_key = f'air_quality_{air_category + 1}'
            img_path = IMAGES.get(img_key)
            
            img_html = ""
            if img_path and Path(img_path).exists():
                img_b64 = get_image_base64(img_path)
                if img_b64:
                    img_html = f'<div class="air-quality-image-container"><img src="data:image/png;base64,{img_b64}" class="air-quality-image"></div>'
            
            if not img_html:
                emoji_map = ['😊', '🙂', '😐', '😕', '😷', '☠️']
                emoji = emoji_map[air_category] if air_category < len(emoji_map) else '❓'
                img_html = f'<div style="font-size:80px;line-height:1;">{emoji}</div>'
            
            air_text = AIR_QUALITY_TEXTS.get(air_category, AIR_QUALITY_TEXTS[0])
            status_word = air_text['title'].split()[-1]
            
            # aggiungi causa inquinante se disponibile
            cause_text = ""
            if worst_pollutant:
                cause_text = f'<div style="font-size:14px; color:{text_color}; margin-top:8px; opacity:0.8;">(a causa di {worst_pollutant})</div>'
            
            st.markdown(f"""
                <div class="air-quality-content" style="background: {bg_color}; min-height: 180px; padding: 40px;">
                    <div class="air-quality-left" style="justify-content: center; align-items: center; flex:1;">
                        <div class="air-quality-status" style="color: {text_color}; font-size: 24px; margin-bottom: -8px;">{status_word}</div>
                        {img_html}
                        {cause_text}
                    </div>
                    <div class="air-quality-right" style="flex:2;">
                        <div class="air-quality-desc" style="font-size:15px;">{air_text["description"]}</div>
                    </div>
                </div>
            """, unsafe_allow_html=True)
        else:
            # caso nullo: Mostra immagine nullo.png
            bg_color = "#f3f4f6"
            text_color = "#6b7280"
            
            # Carica immagine nullo invece della foto qualità aria
            img_path = IMAGES.get('air_quality_nullo')
            img_html = ""
            if img_path and Path(img_path).exists():
                img_b64 = get_image_base64(img_path)
                if img_b64:
                    img_html = f'<div class="air-quality-image-container"><img src="data:image/png;base64,{img_b64}" class="air-quality-image"></div>'
            
            if not img_html:
                img_html = f'<div style="font-size:80px;line-height:1;">❓</div>'
            
            air_text = {"description": "a causa di uno o più parametri non disponibili"}
            status_word = "NON DISPONIBILE"
            
            st.markdown(f"""
                <div class="air-quality-content" style="background: {bg_color}; min-height: 180px; padding: 40px;">
                    <div class="air-quality-left" style="justify-content: center; align-items: center; flex:1;">
                        <div class="air-quality-status" style="color: {text_color}; font-size: 24px; margin-bottom: -8px;text-align: center">{status_word}</div>
                        {img_html}
                    </div>
                    <div class="air-quality-right" style="flex:2;">
                        <div class="air-quality-desc" style="font-size:15px;">{air_text["description"]}</div>
                    </div>
                </div>
            """, unsafe_allow_html=True)
        
        # barra qualità dellaria (mostrata sia per caso normale che nullo)
        if standard and 'categoryLabels' in standard:
            cat_labels = standard.get('categoryLabels', [])
            
            categories = []
            for i, cat_name in enumerate(cat_labels):
                color = '#cccccc'
                
                for ds_label in standard.get('datastreamLabels', []):
                    colors = ds_label.get('categoryColors', [])
                    if i < len(colors):
                        color = colors[i]
                        break
                
                categories.append({'name': cat_name, 'color': color})
            
            if not categories or all(c['color'] == '#cccccc' for c in categories):
                default_colors = ['#00f9e5', '#00d5a7', '#ffb700', '#ff004f', '#9e0035']
                categories = [
                    {'name': cat_labels[i], 'color': default_colors[i] if i < len(default_colors) else '#890082'}
                    for i in range(len(cat_labels))
                ]
            
            num_cats = 6
            gradient_stops = []
            for i, color in enumerate(AIR_QUALITY_COLORS):
                start_pct = (i / num_cats) * 100
                end_pct = ((i + 1) / num_cats) * 100
                gradient_stops.append(f"{color} {start_pct:.2f}%, {color} {end_pct:.2f}%")
            gradient = ", ".join(gradient_stops)

            ranges = [(0, 16), (16, 32), (32, 48), (48, 64), (64, 80), (80, 100)]
            segment_width = 100 / len(ranges)
            idx = float(air_index or 0.0)
            idx = min(idx, 100)  # Cap a 100

            marker_pos = 0.0
            for i, (low, high) in enumerate(ranges):
                if idx <= high:
                    rel = (idx - low) / (high - low)
                    marker_pos = (i * segment_width) + (rel * segment_width)
                    break
            else:
                marker_pos = 100.0
            
            import streamlit.components.v1 as components

            labels_html = ''.join(
                f'<span style="flex: 1; text-align: center;">{lbl}</span>'
                for lbl in AIR_QUALITY_LABELS
            )

            components.html(f"""
                <div style="background: white; padding: 15px; border-radius: 12px; margin-top: 20px;
                            box-shadow: 0 2px 8px rgba(0,0,0,0.06); font-family: 'Inter', sans-serif;">
                    <div style="position: relative; height: 14px; background: linear-gradient(to right, {gradient});
                                border-radius: 7px; margin-bottom: 10px;">
                        <div style="position: absolute; top: -3px; left: {marker_pos:.2f}%; transform: translateX(-50%);
                                    width: 18px; height: 18px; background: white; border: 3px solid #1a1a1a;
                                    border-radius: 50%; box-shadow: 0 2px 6px rgba(0,0,0,0.4);">
                   
                        </div>
                    </div>
                    <div style="display: flex; justify-content: space-between; font-size: 9px; color: #777; margin-top: 8px;">
                        {labels_html}
                    </div>
                </div>
            """, height=130)
    # Ccolonna destra - Inquinanti
    with col_right:
        # Inizia il riquadro
        # st.markdown('<div class="pollutants-card">', unsafe_allow_html=True)
        
        # Processa datastreams
        #  USA SEMPRE custom_reference_time quando use_custom_window è True
        with_thresh, without_thresh, ds_map, latest = process_datastreams_data(
            client,
            thing['@iot.id'],
            datastreams,
            standard,
            reference_time
        )

        # Mappa icone per inquinante
        POLLUTANT_ICONS = {
            'PM2.5': 'icon_pm25',
            'PM10': 'icon_pm10',
            'NO₂': 'icon_no2',
            'CO₂': 'icon_co2',
            'CO': 'icon_co',
            'O₃': 'icon_o3',
            'Temperature': 'icon_temp',
            'Humidity': 'icon_humidity',
            'Pressure': 'icon_pressure',
            'TVOCs': 'icon_tvoc'
        }
        
        # Combina e ordina datastreams
        all_items = []

        for item in with_thresh:
            all_items.append({
                'type': 'threshold',
                'name': item['name'],
                'value': item['value'],
                'unit': item['unit'],
                'cat_name': item['cat_name'],
                'color': item['color'],
                'progress': item['progress'],
                'ds_idx': item['ds_idx']
            })
        
        # Limita a 20 inquinanti
        all_items = all_items[:20] 
        
        # HTML per yuyyi gli inquinanti in una volta sola
        pollutants_html = """
        <style>
            .pollutants-card {
                background: transparent;
                max-height: 600px;
                overflow-y: auto;
                margin-top: 60px;
                font-family: 'Inter', sans-serif;
            }
            
            .pollutant-row {
                display: flex;
                align-items: flex-start;
                gap: 12px;
                margin-bottom: 24px;
                padding-bottom: 1px;
            }
            
            .pollutant-row:last-child {
                border-bottom: none;
                margin-bottom: 0;
                padding-bottom: 0;
            }
            
            .pollutant-icon {
                width: 32px;
                height: 32px;
                object-fit: contain;
                flex-shrink: 0;
                margin-top: 2px;
            }
            
            .pollutant-content {
                flex: 1;
                display: flex;
                flex-direction: column;
                gap: 8px;
            }
            
            .pollutant-header {
                display: flex;
                align-items: center;
                gap: 20px;
                width: 100%;
            }

            .pollutant-info {
                display: flex;
                flex-direction: column;
                min-width: 120px;
                flex-shrink: 0;
            }

            .pollutant-bar-container {
                display: flex;
                align-items: center;
                gap: 32px;
                flex: 1;
                max-width: 400px;
            }

            .pollutant-bar-bg {
                flex: 1;
                min-width: 200px;
                max-width: 350px;
                height: 15px;
                background: #e2e8f0; 
                border-radius: 8px;
                overflow: hidden;
            }

            .pollutant-bar-fill {
                height: 100%;
                border-radius: 8px;
                transition: width 0.5s ease;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
            }

            .pollutant-category {
                font-size: 15px;
                font-weight: 700;
                text-transform: capitalize;
                white-space: nowrap;
                min-width: 100px;
                text-align: left;
            }
            
            .pollutant-name {
                font-size: 15px;
                font-weight: 600;
                color: #1a1a1a;
                line-height: 1.2;
            }

            .pollutant-value {
                font-size: 11px;
                font-weight: 500;
                color: #666;
            }
        </style>
        <div class="pollutants-card">
        """
        for item in all_items:
            # Trova icona
            icon_key = None
            for poll_name, icon_name in POLLUTANT_ICONS.items():
                if poll_name.lower() in item['name'].lower():
                    icon_key = icon_name
                    break
            
            # Icona con fallback emoji
            icon_html = ""
            if icon_key:
                icon_path = IMAGES.get(icon_key)
                if icon_path and Path(icon_path).exists():
                    icon_b64 = get_image_base64(icon_path)
                    if icon_b64:
                        icon_html = f'<img src="data:image/png;base64,{icon_b64}" class="pollutant-icon">'
                
                if not icon_html:
                    emoji_icons = {
                        'icon_pm25': '🌫️', 'icon_pm10': '💨', 'icon_no2': '🏭',
                        'icon_co2': '🌍', 'icon_co': '⚠️', 'icon_o3': '☀️',
                        'icon_temp': '🌡️', 'icon_humidity': '💧', 'icon_pressure': '🔽',
                        'icon_tvoc': '🧪'
                    }
                    emoji = emoji_icons.get(icon_key, '📊')
                    icon_html = f'<span style="font-size:32px;display:block;text-align:left;">{emoji}</span>'
            
            from frost_utils import format_value
            value_at_ref_time = item.get('value')
            
            # gestione valori nulli
            if value_at_ref_time is None or (isinstance(value_at_ref_time, float) and value_at_ref_time < 0):
                # nullo: Solo testo, NESSUNA barra
                pollutants_html += f"""
                    <div class="pollutant-row">
                        {icon_html}
                        <div class="pollutant-content">
                            <div class="pollutant-header">
                                <div class="pollutant-info">
                                    <span class="pollutant-name">{item['name']}</span>
                                    <span class="pollutant-value" style="color:#656565;">-- {item['unit']}</span>
                                </div>
                                <div class="pollutant-bar-container">
                                    <span class="pollutant-category" style="color:#9ca3af; font-size:14px; margin-left:-20px">
                                        Valore non disponibile
                                    </span>
                                </div>
                            </div>
                        </div>
                    </div>
                """
            else:
                # valore valido: Mostra normalmente con barra
                value_str = format_value(value_at_ref_time)

                try:
                    cat_idx = AIR_QUALITY_LABELS.index(item['cat_name'])
                except ValueError:
                    cat_idx = 0

                bar_color = item['color']

                # calcolo progress con soglia fittizia
                ds_info = ds_map.get(item['ds_idx'])
                if ds_info:
                    detected_name = ds_info.get('name', '')
                    from frost_utils import build_alias_index, detect_measure, get_datastream_config
                    alias_idx = build_alias_index(standard)
                    datastream_labels = standard.get("datastreamLabels", [])
                    detected_name = detect_measure(detected_name, alias_idx, datastream_labels)
                    
                    if detected_name:
                        ds_config = get_datastream_config(detected_name, datastream_labels)
                        if ds_config:
                            thresholds = ds_config.get("categoryThresholds", [])
                            fictional_threshold = thresholds[4] * 2 if len(thresholds) >= 5 else thresholds[4]
                            
                            # Ricalcola progress con soglia fittizia
                            if ds_config:
                                thresholds = ds_config.get("categoryThresholds", [])
                                greater_worse = ds_config.get("greaterIsWorse", True)
                                
                                val = value_at_ref_time
                                if greater_worse:
                                    if val <= thresholds[4]:
                                        progress = item['progress']
                                    else:
                                        import math
                                        log_component = 8 * math.log2(val / thresholds[4])
                                        normalized = min(80 + log_component, 100)
                                        progress = normalized / 100.0
                                else:
                                    if val >= thresholds[4]:
                                        progress = item['progress']
                                    else:
                                        import math
                                        if val > 0:
                                            ratio = thresholds[4] / val
                                            log_component = 8 * math.log2(1 / ratio)
                                            normalized = min(80 + log_component, 100)
                                            progress = normalized / 100.0
                                        else:
                                            progress = 1.0
                        else:
                            progress = item['progress']
                    else:
                        progress = item['progress']
                else:
                    progress = item['progress']
                # Ottieni colore sfondo dalla categoria
                bar_bg_color = BOX_COLOR[cat_idx] 

                category_html = f'<span class="pollutant-category" style="color:{item["color"]};">{item["cat_name"]}</span>'

                
                pollutants_html += f"""
                <div class="pollutant-row">
                {icon_html}
                <div class="pollutant-content">
                    <div class="pollutant-header">
                    <div class="pollutant-info" style="margin-right: 0;">
                        <span class="pollutant-name">{item['name']}</span>
                        <span class="pollutant-value">{value_str} {item['unit']}</span>
                    </div>
                    <div class="pollutant-bar-container" style="margin-left: -24px;">
                        <!-- Usa il background dinamico basato su BOX_COLOR -->
                        <div class="pollutant-bar-bg" style="height: 14px; background:{bar_bg_color};">
                        <div class="pollutant-bar-fill" style="width:{progress*100:.1f}%; background:{bar_color};"></div>
                        </div>
                        {category_html}
                    </div>
                    </div>
                </div>
                </div>
                """
        # Chiudi il div del riquadro
        pollutants_html += '</div>'
            
        # Inserisci tutti gli inquinanti in una volta sola dentro il riquadro
        import streamlit.components.v1 as components
        components.html(pollutants_html, height=550, scrolling=False)
        
        # # qui sotto chiudo il riquado, applicazione finita :)))
        # st.markdown('</div>', unsafe_allow_html=True)

    


# Chiudi wrapper single-class se aperto
if is_single_class:
    st.markdown('</div>', unsafe_allow_html=True)

# footer

st.markdown("---")
st.caption(f"Endpoint: `{CONFIG['endpoint']}` | Ultimo aggiornamento: {datetime.now().strftime('%d/%m/%Y %H:%M')}")













# :)