import cv2
import time
import serial
import os
from ultralytics import YOLO

# =========================
# PARAMÈTRES FEEDBACK
# =========================

MODE_APPRENTISSAGE = True
TEMPS_REPONSE_HUMAIN = 5  # secondes

# Dossier où se trouve ce fichier Python
DOSSIER_SCRIPT = os.path.dirname(os.path.abspath(__file__))

DOSSIER_FEEDBACK = os.path.join(DOSSIER_SCRIPT, "dataset_feedback")

DOSSIER_EXISTE_DEJA = os.path.join(DOSSIER_FEEDBACK, "existe_deja")
DOSSIER_EXISTE_PAS = os.path.join(DOSSIER_FEEDBACK, "existe_pas")
DOSSIER_A_CLASSER = os.path.join(DOSSIER_FEEDBACK, "a_classer")

os.makedirs(DOSSIER_EXISTE_DEJA, exist_ok=True)
os.makedirs(DOSSIER_EXISTE_PAS, exist_ok=True)
os.makedirs(DOSSIER_A_CLASSER, exist_ok=True)


FENETRE = "Smart Trash"

clic_x = None
clic_y = None
molette = 0


def gestion_clic(event, x, y, flags, param):
    global clic_x, clic_y, molette

    if event == cv2.EVENT_LBUTTONDOWN:
        clic_x = x
        clic_y = y

    elif event == cv2.EVENT_MOUSEWHEEL:
        if flags > 0:
            molette = -1
        else:
            molette = 1


# =========================
# MODÈLE IA
# =========================

model = YOLO(r"runs\detect\runs_perso\V1.1\weights\best.pt")

# =========================
# REGROUPEMENT DES CLASSES
# =========================

classes_papier = [
    "Papier",
]

classes_emballage = [
    "Plastique transparant",
    "Emballage plastique",
    "Emballage papier",
    "Couvert recyclable",
    "Carton",
    "Canette",
]

classes_pas_ici = [
    "Pile",
    "Composant electronique",
]

classes_autre = [
    "Tissu",
    "Stylo",
    "PVC",
    "Plexiglas",
    "Papier a poncer",
    "Mouchoir",
    "Metal",
    "Jouet en plastique",
    "Filtre",
    "Eponge",
    "Colle chaude",
    "Capsule cafe",
    "Caoutchous",
    "Aimant",
]

def classe_est_valide(classe):
    if classe is None:
        return False

    if str(classe).strip() == "":
        return False

    return True

def bac_depuis_classe(classe):
    if classe in classes_papier:
        return "papier"

    elif classe in classes_emballage:
        return "emballage"

    elif classe in classes_pas_ici:
        return "Pas ici"

    elif classe in classes_autre:
        return "autre"

    else:
        return "autre"


def classe_vers_categorie(classe, confiance):
    if confiance < 0.10:
        return "rien"

    if classe in classes_papier and confiance > 0.45:
        return "papier"

    elif classe in classes_emballage and confiance > 0.45:
        return "emballage"

    elif classe in classes_pas_ici and confiance > 0.45:
        return "Pas ici"

    elif classe in classes_autre and confiance > 0.30:
        return "autre"

    elif confiance > 0.15:
        return "autre"

    else:
        return "rien"


def categorie_vers_commande(categorie):
    if categorie == "papier":
        return "P", 2

    elif categorie == "emballage":
        return "E", 4.61

    elif categorie == "Pas ici":
        return "N", 2.21

    elif categorie == "rien":
        return None, 0

    else:
        return "A", 6.55


# =========================
# OUTILS INTERFACE
# =========================

def dessiner_bouton(image, texte, x1, y1, x2, y2):
    cv2.rectangle(image, (x1, y1), (x2, y2), (60, 60, 60), -1)
    cv2.rectangle(image, (x1, y1), (x2, y2), (255, 255, 255), 2)

    cv2.putText(
        image,
        texte,
        (x1 + 10, y1 + 32),
        cv2.FONT_HERSHEY_SIMPLEX,
        0.60,
        (255, 255, 255),
        2
    )


def dessiner_bouton_selectionne(image, texte, x1, y1, x2, y2):
    cv2.rectangle(image, (x1, y1), (x2, y2), (0, 120, 120), -1)
    cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 255), 3)

    cv2.putText(
        image,
        texte,
        (x1 + 10, y1 + 32),
        cv2.FONT_HERSHEY_SIMPLEX,
        0.60,
        (255, 255, 255),
        2
    )


def clic_dans_bouton(x, y, bouton):
    x1, y1, x2, y2, _ = bouton
    return x1 <= x <= x2 and y1 <= y <= y2


def nettoyer_nom(texte):
    texte = str(texte)
    texte = texte.strip()
    texte = texte.replace(" ", "_")
    texte = texte.replace("/", "_")
    texte = texte.replace("\\", "_")
    texte = texte.replace(":", "_")
    texte = texte.replace(";", "_")
    texte = texte.replace("?", "_")
    texte = texte.replace("*", "_")
    texte = texte.replace('"', "_")
    texte = texte.replace("<", "_")
    texte = texte.replace(">", "_")
    texte = texte.replace("|", "_")
    return texte


def nom_affichage_depuis_dossier(nom_dossier):
    return str(nom_dossier).replace("_", " ")


def prochain_numero_image(dossier):
    os.makedirs(dossier, exist_ok=True)

    fichiers = [
        f for f in os.listdir(dossier)
        if f.lower().endswith((".jpg", ".jpeg", ".png"))
    ]

    return len(fichiers) + 1


def recuperer_classes_feedback():
    """
    Lit seulement :
    dataset_feedback/existe_pas/<bac>/<categorie>/

    Ne lit jamais :
    dataset_feedback/a_classer/
    """

    classes_feedback = []

    if not os.path.exists(DOSSIER_EXISTE_PAS):
        return classes_feedback

    for bac in os.listdir(DOSSIER_EXISTE_PAS):
        chemin_bac = os.path.join(DOSSIER_EXISTE_PAS, bac)

        if not os.path.isdir(chemin_bac):
            continue

        for categorie in os.listdir(chemin_bac):
            chemin_categorie = os.path.join(chemin_bac, categorie)

            if not os.path.isdir(chemin_categorie):
                continue

            classe_affichage = nom_affichage_depuis_dossier(categorie)
            bac_affichage = nom_affichage_depuis_dossier(bac)

            if not classe_est_valide(classe_affichage):
                continue

            classes_feedback.append((classe_affichage, bac_affichage))

    return classes_feedback


def construire_options_classes():
    classes_existantes = list(model.names.values())
    classes_nouvelles = recuperer_classes_feedback()

    options = []

    for classe in classes_existantes:
        if not classe_est_valide(classe):
            continue

        bac_auto = bac_depuis_classe(classe)
        options.append((classe, True, bac_auto))

    for classe, bac in classes_nouvelles:
        if not classe_est_valide(classe):
            continue

        deja_presente = False

        for classe_existante in classes_existantes:
            if classe.lower() == classe_existante.lower():
                deja_presente = True
                break

        if not deja_presente:
            options.append((classe, False, bac))

    options_uniques = []
    noms_vus = set()

    for classe, existe_deja, bac_auto in options:
        cle = classe.lower()

        if cle not in noms_vus:
            options_uniques.append((classe, existe_deja, bac_auto))
            noms_vus.add(cle)

    options_uniques.sort(key=lambda x: x[0].lower())

    return options_uniques


# =========================
# INTERFACE FEEDBACK
# =========================

def afficher_question_feedback(image, classe_finale, confiance_finale, categorie):
    global clic_x, clic_y
    clic_x = None
    clic_y = None

    debut = time.time()

    while True:
        copie = image.copy()
        h, w = copie.shape[:2]

        cv2.rectangle(copie, (0, 0), (w, 200), (0, 0, 0), -1)

        temps_restant = max(0, TEMPS_REPONSE_HUMAIN - int(time.time() - debut))

        cv2.putText(
            copie,
            f"IA pense : {classe_finale} ({confiance_finale:.2f})",
            (20, 40),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.8,
            (255, 255, 255),
            2
        )

        cv2.putText(
            copie,
            f"Bac propose : {categorie}",
            (20, 80),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.8,
            (255, 255, 255),
            2
        )

        cv2.putText(
            copie,
            f"Auto dans {temps_restant}s",
            (20, 120),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.8,
            (255, 255, 255),
            2
        )

        boutons = [
            (20, 140, 200, 190, "oui"),
            (230, 140, 410, 190, "non")
        ]

        dessiner_bouton(copie, "OUI", 20, 140, 200, 190)
        dessiner_bouton(copie, "NON", 230, 140, 410, 190)

        cv2.imshow(FENETRE, copie)

        if clic_x is not None and clic_y is not None:
            for bouton in boutons:
                if clic_dans_bouton(clic_x, clic_y, bouton):
                    reponse = bouton[4]
                    clic_x = None
                    clic_y = None
                    return reponse

            clic_x = None
            clic_y = None

        key = cv2.waitKey(50) & 0xFF

        if key == 27:
            return "auto"

        if time.time() - debut >= TEMPS_REPONSE_HUMAIN:
            return "auto"


def saisir_texte_fenetre(image, titre):
    """
    Retourne :
    - "__A_CLASSER__" si Échap
    - "__A_CLASSER__" si Entrée sans texte
    - le texte si un nom est écrit
    """

    texte = ""

    while True:
        fond = image.copy()
        fond = cv2.resize(fond, (900, 600))

        cv2.rectangle(fond, (0, 0), (900, 230), (0, 0, 0), -1)

        cv2.putText(
            fond,
            titre,
            (20, 40),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.85,
            (255, 255, 255),
            2
        )

        cv2.putText(
            fond,
            "Tape le nom puis appuie sur ENTREE",
            (20, 85),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.65,
            (255, 255, 255),
            2
        )

        cv2.putText(
            fond,
            "ECHAP = sauvegarder dans a_classer",
            (20, 125),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.55,
            (0, 255, 255),
            2
        )

        cv2.putText(
            fond,
            texte,
            (20, 215),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.8,
            (0, 255, 255),
            2
        )

        cv2.imshow(FENETRE, fond)

        key = cv2.waitKeyEx(0)

        # Entrée
        if key in [13, 10]:
            if texte.strip() == "":
                return "__A_CLASSER__"
            else:
                return texte.strip()

        # Échap
        elif key == 27:
            return "__A_CLASSER__"

        # Retour arrière
        elif key in [8, 127]:
            texte = texte[:-1]

        # Texte normal
        elif 32 <= key <= 126:
            texte += chr(key)


def choisir_classe_fenetre(image):
    """
    Retourne :
    vraie_classe, existe_deja, bac_auto, mode_special

    mode_special :
    - None : sauvegarde normale
    - "a_classer" : sauvegarde dans dataset_feedback/a_classer
    """

    global clic_x, clic_y, molette
    clic_x = None
    clic_y = None
    molette = 0

    options_classes = construire_options_classes()

    classe_selectionnee = None
    existe_deja_selection = None
    bac_auto_selection = None
    mode_special = None
    scroll_index = 0

    largeur_fenetre = 1100
    largeur_image = 640
    hauteur_image = 720

    x_panel = largeur_image
    largeur_panel = largeur_fenetre - largeur_image

    y_liste_debut = 100
    y_liste_fin = 610
    hauteur_bouton = 40
    espace = 8

    nb_visibles = max(1, (y_liste_fin - y_liste_debut) // (hauteur_bouton + espace))

    while True:
        fond = image.copy()
        fond = cv2.resize(fond, (largeur_image, hauteur_image))

        interface = cv2.copyMakeBorder(
            fond,
            0,
            0,
            0,
            largeur_panel,
            cv2.BORDER_CONSTANT,
            value=(0, 0, 0)
        )

        cv2.putText(
            interface,
            "A quelle classe appartient ce dechet ?",
            (x_panel + 20, 35),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.7,
            (255, 255, 255),
            2
        )

        if classe_selectionnee is None:
            texte_selection = "Selection : aucune"
        else:
            if mode_special == "a_classer":
                texte_selection = "Selection : a_classer"
            elif bac_auto_selection is None:
                texte_selection = f"Selection : {classe_selectionnee}"
            else:
                texte_selection = f"Selection : {classe_selectionnee} -> {bac_auto_selection}"

        cv2.putText(
            interface,
            texte_selection[:45],
            (x_panel + 20, 70),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.50,
            (0, 255, 255),
            2
        )

        if molette != 0:
            scroll_index += molette
            molette = 0

        max_scroll = max(0, len(options_classes) - nb_visibles)
        scroll_index = max(0, min(scroll_index, max_scroll))

        classes_visibles = options_classes[scroll_index:scroll_index + nb_visibles]

        boutons = []

        y = y_liste_debut

        for classe, existe_deja, bac_auto in classes_visibles:
            x1 = x_panel + 20
            y1 = y
            x2 = largeur_fenetre - 35
            y2 = y + hauteur_bouton

            if existe_deja:
                prefixe = ""
            else:
                prefixe = "[NEW] "

            texte = prefixe + classe

            if len(texte) > 32:
                texte = texte[:32] + "..."

            if classe == classe_selectionnee and existe_deja == existe_deja_selection:
                dessiner_bouton_selectionne(interface, texte, x1, y1, x2, y2)
            else:
                dessiner_bouton(interface, texte, x1, y1, x2, y2)

            boutons.append((x1, y1, x2, y2, ("selection_classe", (classe, existe_deja, bac_auto))))

            y += hauteur_bouton + espace

        barre_x1 = largeur_fenetre - 22
        barre_x2 = largeur_fenetre - 10
        barre_y1 = y_liste_debut
        barre_y2 = y_liste_fin

        cv2.rectangle(interface, (barre_x1, barre_y1), (barre_x2, barre_y2), (80, 80, 80), -1)

        if len(options_classes) > nb_visibles:
            proportion = nb_visibles / len(options_classes)
            taille_curseur = max(40, int((barre_y2 - barre_y1) * proportion))

            position = scroll_index / max_scroll if max_scroll > 0 else 0
            curseur_y1 = barre_y1 + int((barre_y2 - barre_y1 - taille_curseur) * position)
            curseur_y2 = curseur_y1 + taille_curseur

            cv2.rectangle(
                interface,
                (barre_x1, curseur_y1),
                (barre_x2, curseur_y2),
                (220, 220, 220),
                -1
            )

        bouton_nouvelle = (x_panel + 20, 630, x_panel + 210, 685, ("nouvelle", None))
        bouton_valider = (x_panel + 230, 630, largeur_fenetre - 35, 685, ("valider", None))

        dessiner_bouton(
            interface,
            "NOUVELLE CLASSE",
            bouton_nouvelle[0],
            bouton_nouvelle[1],
            bouton_nouvelle[2],
            bouton_nouvelle[3]
        )
        boutons.append(bouton_nouvelle)

        if classe_selectionnee is None:
            cv2.rectangle(
                interface,
                (bouton_valider[0], bouton_valider[1]),
                (bouton_valider[2], bouton_valider[3]),
                (40, 40, 40),
                -1
            )
            cv2.rectangle(
                interface,
                (bouton_valider[0], bouton_valider[1]),
                (bouton_valider[2], bouton_valider[3]),
                (120, 120, 120),
                2
            )
            cv2.putText(
                interface,
                "VALIDER",
                (bouton_valider[0] + 20, bouton_valider[1] + 35),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.65,
                (120, 120, 120),
                2
            )
        else:
            dessiner_bouton(
                interface,
                "VALIDER",
                bouton_valider[0],
                bouton_valider[1],
                bouton_valider[2],
                bouton_valider[3]
            )
            boutons.append(bouton_valider)

        cv2.putText(
            interface,
            "Molette = defiler | Z/S aussi",
            (x_panel + 20, 710),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.50,
            (180, 180, 180),
            1
        )

        cv2.imshow(FENETRE, interface)

        if clic_x is not None and clic_y is not None:
            for bouton in boutons:
                if clic_dans_bouton(clic_x, clic_y, bouton):
                    action, valeur = bouton[4]

                    clic_x = None
                    clic_y = None

                    if action == "selection_classe":
                        classe_selectionnee, existe_deja_selection, bac_auto_selection = valeur
                        mode_special = None

                    elif action == "nouvelle":
                        nouvelle = saisir_texte_fenetre(image, "Nom nouvelle classe")

                        if nouvelle == "__A_CLASSER__":
                            return "a_classer", False, None, "a_classer"

                        classe_selectionnee = nouvelle
                        existe_deja_selection = False
                        bac_auto_selection = None
                        mode_special = None

                    elif action == "valider":
                        if classe_selectionnee is not None:
                            return classe_selectionnee, existe_deja_selection, bac_auto_selection, mode_special

                    break

            clic_x = None
            clic_y = None

        key = cv2.waitKey(50) & 0xFF

        if key == 27:
            return None, None, None, None

        elif key == ord("s"):
            scroll_index += 1

        elif key == ord("z"):
            scroll_index -= 1


def choisir_bac_fenetre(image):
    global clic_x, clic_y
    clic_x = None
    clic_y = None

    bac_selectionne = None

    while True:
        fond = image.copy()
        fond = cv2.resize(fond, (900, 600))

        cv2.rectangle(fond, (0, 0), (900, 310), (0, 0, 0), -1)

        cv2.putText(
            fond,
            "Dans quel bac doit etre jete le dechet ?",
            (20, 45),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.9,
            (255, 255, 255),
            2
        )

        if bac_selectionne is None:
            texte_selection = "Selection : aucune"
        else:
            texte_selection = f"Selection : {bac_selectionne}"

        cv2.putText(
            fond,
            texte_selection,
            (20, 85),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.65,
            (0, 255, 255),
            2
        )

        boutons = [
            (40, 120, 240, 180, ("selection_bac", "papier")),
            (280, 120, 540, 180, ("selection_bac", "emballage")),
            (580, 120, 820, 180, ("selection_bac", "autre")),
            (280, 205, 540, 265, ("selection_bac", "Pas ici")),
        ]

        for bouton in boutons:
            x1, y1, x2, y2, data = bouton
            _, bac = data

            texte = bac.upper()

            if bac == bac_selectionne:
                dessiner_bouton_selectionne(fond, texte, x1, y1, x2, y2)
            else:
                dessiner_bouton(fond, texte, x1, y1, x2, y2)

        bouton_valider = (300, 340, 600, 410, ("valider", None))

        if bac_selectionne is None:
            cv2.rectangle(fond, (300, 340), (600, 410), (40, 40, 40), -1)
            cv2.rectangle(fond, (300, 340), (600, 410), (120, 120, 120), 2)
            cv2.putText(
                fond,
                "VALIDER",
                (390, 382),
                cv2.FONT_HERSHEY_SIMPLEX,
                0.8,
                (120, 120, 120),
                2
            )
        else:
            dessiner_bouton(fond, "VALIDER", 300, 340, 600, 410)
            boutons.append(bouton_valider)

        cv2.imshow(FENETRE, fond)

        if clic_x is not None and clic_y is not None:
            for bouton in boutons:
                if clic_dans_bouton(clic_x, clic_y, bouton):
                    action, valeur = bouton[4]

                    clic_x = None
                    clic_y = None

                    if action == "selection_bac":
                        bac_selectionne = valeur

                    elif action == "valider":
                        if bac_selectionne is not None:
                            return bac_selectionne

                    break

            clic_x = None
            clic_y = None

        key = cv2.waitKey(50) & 0xFF

        if key == 27:
            return None


def choisir_categorie_fenetre(image):
    vraie_classe, existe_deja, bac_auto, mode_special = choisir_classe_fenetre(image)

    if vraie_classe is None:
        return None, None, None, None

    if mode_special == "a_classer":
        return vraie_classe, None, existe_deja, mode_special

    if bac_auto is not None:
        vraie_categorie = bac_auto
    else:
        vraie_categorie = choisir_bac_fenetre(image)

        if vraie_categorie is None:
            return None, None, None, None

    return vraie_classe, vraie_categorie, existe_deja, mode_special


def sauvegarder_image_a_classer(image_originale, classe_predite, confiance):
    os.makedirs(DOSSIER_A_CLASSER, exist_ok=True)

    classe_predite_clean = nettoyer_nom(classe_predite)

    numero = prochain_numero_image(DOSSIER_A_CLASSER)

    nom_image = (
        f"img_{numero:03d}"
        f"_pred-{classe_predite_clean}"
        f"_conf-{confiance:.2f}"
        f".jpg"
    )

    chemin_image = os.path.join(DOSSIER_A_CLASSER, nom_image)

    ok = cv2.imwrite(chemin_image, image_originale)



def sauvegarder_feedback(image_originale, classe_predite, confiance,
                         vraie_classe, vraie_categorie, existe_deja):

    vraie_classe_clean = nettoyer_nom(vraie_classe)
    vraie_categorie_clean = nettoyer_nom(vraie_categorie)
    classe_predite_clean = nettoyer_nom(classe_predite)

    if existe_deja:
        dossier_base = DOSSIER_EXISTE_DEJA
    else:
        dossier_base = DOSSIER_EXISTE_PAS

    dossier_final = os.path.join(
        dossier_base,
        vraie_categorie_clean,
        vraie_classe_clean
    )

    os.makedirs(dossier_final, exist_ok=True)

    numero = prochain_numero_image(dossier_final)

    nom_image = (
        f"img_{numero:03d}"
        f"_pred-{classe_predite_clean}"
        f"_conf-{confiance:.2f}"
        f".jpg"
    )

    chemin_image = os.path.join(dossier_final, nom_image)

    ok = cv2.imwrite(chemin_image, image_originale)



def afficher_message_pas_ici(image):
    """
    Affiche un message rouge au centre de l'image.
    OpenCV gere mal les accents avec cv2.putText, donc le texte est sans accents.
    """
    texte_attention = "/!\\ Attention, cet objet ne doit pas etre jete ici /!\\"

    police = cv2.FONT_HERSHEY_SIMPLEX
    taille = 0.55
    epaisseur = 2

    taille_texte, _ = cv2.getTextSize(
        texte_attention,
        police,
        taille,
        epaisseur
    )

    largeur_texte = taille_texte[0]
    hauteur_texte = taille_texte[1]

    h, w = image.shape[:2]

    x = (w - largeur_texte) // 2
    y = h // 2

    # Fond noir pour rendre le texte lisible
    cv2.rectangle(
        image,
        (x - 10, y - hauteur_texte - 10),
        (x + largeur_texte + 10, y + 10),
        (0, 0, 0),
        -1
    )

    # Texte rouge
    cv2.putText(
        image,
        texte_attention,
        (x, y),
        police,
        taille,
        (0, 0, 255),
        epaisseur
    )


# =========================
# CONNEXION ARDUINO
# =========================

arduino = serial.Serial("COM5", 9600, timeout=1)
time.sleep(2)

# =========================
# CAMÉRA
# =========================

cap = cv2.VideoCapture(1, cv2.CAP_DSHOW)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)

if not cap.isOpened():
    print("Erreur : caméra non ouverte")
    exit()

cv2.destroyAllWindows()
cv2.namedWindow(FENETRE)
cv2.setMouseCallback(FENETRE, gestion_clic)

# =========================
# PARAMÈTRES STABILITÉ / BLOCAGE
# =========================

temps_fin_blocage = 0
# Message rouge affiche pendant quelques secondes apres une commande N
TEMPS_MESSAGE_PAS_ICI = 3
temps_fin_attention = 0

ancienne_box = None
categorie_stable = None
compteur_stable = 0

frames_necessaires = 10
seuil_mouvement = 20

print("IA + Arduino lancés. Appuie sur Échap pour quitter.")

# =========================
# BOUCLE PRINCIPALE
# =========================

while True:
    ret, frame = cap.read()

    if not ret:
        print("Erreur : image non lue")
        break

    maintenant = time.time()

    results = model(frame, conf=0.10, verbose=False)

    categorie = "rien"
    classe_finale = "rien"
    confiance_finale = 0
    box_actuelle = None

    if len(results[0].boxes) > 0:
        best_box = max(results[0].boxes, key=lambda box: float(box.conf[0]))

        x1, y1, x2, y2 = map(int, best_box.xyxy[0])
        box_actuelle = (x1, y1, x2, y2)

        classe_id = int(best_box.cls[0])
        confiance = float(best_box.conf[0])
        classe = model.names[classe_id]

        classe_finale = classe
        confiance_finale = confiance

        categorie = classe_vers_categorie(classe, confiance)

        if ancienne_box is not None:
            ax1, ay1, ax2, ay2 = ancienne_box

            mouvement = abs(x1 - ax1) + abs(y1 - ay1) + abs(x2 - ax2) + abs(y2 - ay2)

            if mouvement < seuil_mouvement and categorie == categorie_stable:
                compteur_stable += 1
            else:
                compteur_stable = 0
                categorie_stable = categorie
        else:
            compteur_stable = 0
            categorie_stable = categorie

        ancienne_box = box_actuelle

    else:
        ancienne_box = None
        categorie_stable = None
        compteur_stable = 0
        categorie = "rien"

    commande, temps_blocage = categorie_vers_commande(categorie)

    en_blocage = maintenant < temps_fin_blocage
    objet_est_stable = compteur_stable >= frames_necessaires

    annotated_frame = results[0].plot()

    if en_blocage:
        cv2.putText(
            annotated_frame,
            "Mouvement en cours : commandes bloquees",
            (20, 40),
            cv2.FONT_HERSHEY_SIMPLEX,
            0.7,
            (255, 255, 255),
            2
        )
    else : 
        cv2.putText(
        annotated_frame,
        f"Stable: {compteur_stable}/{frames_necessaires}",
        (20, 40),
        cv2.FONT_HERSHEY_SIMPLEX,
        0.7,
        (255, 255, 255),
        2
        )


    # Affichage du message rouge si un objet "Pas ici" vient d'etre detecte/envoye
    if time.time() < temps_fin_attention:
        afficher_message_pas_ici(annotated_frame)

    cv2.imshow(FENETRE, annotated_frame)

    # =========================
    # VALIDATION HUMAINE + ENVOI
    # =========================

    if commande is not None and objet_est_stable and not en_blocage:

        image_originale_decision = frame.copy()
        image_annotee_decision = annotated_frame.copy()

        if MODE_APPRENTISSAGE:
            reponse = afficher_question_feedback(
                image_annotee_decision,
                classe_finale,
                confiance_finale,
                categorie
            )
        else:
            reponse = "auto"

        categorie_finale = categorie
        commande_finale = commande
        temps_blocage_final = temps_blocage

        if reponse == "non":
            vraie_classe, vraie_categorie, existe_deja, mode_special = choisir_categorie_fenetre(
                image_annotee_decision
            )

            if vraie_classe is None:
                print("Correction annulée : aucune image sauvegardée")
                print("Action automatique conservée :", categorie_finale)

            elif mode_special == "a_classer":
                sauvegarder_image_a_classer(
                    image_originale_decision,
                    classe_finale,
                    confiance_finale
                )
                print("Action automatique conservée :", categorie_finale)

            else:
                sauvegarder_feedback(
                    image_originale_decision,
                    classe_finale,
                    confiance_finale,
                    vraie_classe,
                    vraie_categorie,
                    existe_deja
                )

                categorie_finale = vraie_categorie
                commande_finale, temps_blocage_final = categorie_vers_commande(categorie_finale)

        if commande_finale is not None:
            arduino.write(commande_finale.encode())

            print(
                "Envoyé à Arduino :",
                commande_finale,
                "| Classe IA :",
                classe_finale,
                "| Confiance :",
                round(confiance_finale, 2),
                "| Catégorie finale :",
                categorie_finale,
                "| Temps blocage :",
                temps_blocage_final,
                "s"
            )

            if commande_finale == "N":
                temps_fin_attention = time.time() + TEMPS_MESSAGE_PAS_ICI

            temps_fin_blocage = time.time() + temps_blocage_final + 0.05

        compteur_stable = 0
        ancienne_box = None
        categorie_stable = None

    key = cv2.waitKey(30) & 0xFF

    if key == 27:
        break

cap.release()
arduino.close()
cv2.destroyAllWindows()