"""GUI standalone OpenCV per Pattern Matching 2D.

Flusso:
1. Apri immagine modello (file dialog tk)
2. Selezione ROI con cv2.selectROI
3. Apri immagine scena
4. Esegui matching
5. Visualizza risultati (baricentro, angolo, score, bbox)

Tutta la logica algoritmica vive in pm2d.matcher.EdgeShapeMatcher.
"""

from __future__ import annotations

import sys
from pathlib import Path
from tkinter import Tk, filedialog
import tkinter as tk
from tkinter import ttk

import cv2
import numpy as np

from pm2d.matcher import EdgeShapeMatcher
from pm2d.line_matcher import LineShapeMatcher, Match
from pm2d.auto_tune import auto_tune, summarize as tune_summary


# Schema campi form parametri: (key, label, type, initial)
PARAM_SCHEMA: list[tuple[str, str, type]] = [
    ("backend", "Backend (line | edge)", str),
    ("angle_min", "Angolo min [deg]", float),
    ("angle_max", "Angolo max [deg]", float),
    ("angle_step", "Angolo step [deg]", float),
    ("scale_min", "Scala min", float),
    ("scale_max", "Scala max", float),
    ("scale_step", "Scala step", float),
    ("min_score", "Score minimo [0..1]", float),
    ("max_matches", "Max match", int),
    ("nms_radius", "NMS radius [px] (0=auto)", int),
    ("num_features", "Num feature (line)", int),
    ("weak_grad", "Weak grad (line)", float),
    ("strong_grad", "Strong grad (line)", float),
    ("spread_radius", "Spread radius (line)", int),
    ("pyramid_levels", "Pyramid levels", int),
    ("verify_threshold", "Verify NCC threshold", float),
]


def edit_params(defaults: dict, template_bgr: np.ndarray | None = None) -> dict | None:
    """Dialog tkinter per modificare i parametri.

    Se `template_bgr` fornito, mostra bottone "Auto-tune" che analizza il template
    e pre-popola i campi con valori suggeriti.
    """
    root = tk.Tk()
    root.title("Parametri Pattern Matching 2D")
    root.resizable(True, True)
    root.minsize(360, 420)
    try:
        root.attributes("-topmost", True)
    except Exception:
        pass

    # Grid root: fa espandere la cella (0, 0)
    root.columnconfigure(0, weight=1)
    root.rowconfigure(0, weight=1)

    result: dict = {}
    entries: dict[str, tk.Entry] = {}

    frame = ttk.Frame(root, padding=12)
    frame.grid(row=0, column=0, sticky="nsew")
    frame.columnconfigure(0, weight=1)
    frame.columnconfigure(1, weight=2)
    for i, (key, label, _typ) in enumerate(PARAM_SCHEMA):
        ttk.Label(frame, text=label).grid(row=i, column=0, sticky="w", padx=4, pady=3)
        e = ttk.Entry(frame)
        e.insert(0, str(defaults.get(key, "")))
        e.grid(row=i, column=1, padx=4, pady=3, sticky="ew")
        entries[key] = e

    hint_var = tk.StringVar(value="")
    hint_lbl = ttk.Label(frame, textvariable=hint_var, foreground="#0088bb",
                         wraplength=280)
    hint_lbl.grid(row=len(PARAM_SCHEMA), column=0, columnspan=2,
                  sticky="w", pady=(6, 0))

    def apply_tune():
        if template_bgr is None:
            hint_var.set("Auto-tune non disponibile (template mancante)")
            return
        tune = auto_tune(template_bgr)
        for key, _label, _typ in PARAM_SCHEMA:
            if key in tune:
                entries[key].delete(0, tk.END)
                entries[key].insert(0, str(tune[key]))
        hint_var.set("Auto-tune: " + tune_summary(tune))

    state = {"ok": False}
    def on_ok():
        try:
            for key, _label, typ in PARAM_SCHEMA:
                val = entries[key].get().strip()
                if typ is int:
                    result[key] = int(float(val))
                elif typ is float:
                    result[key] = float(val)
                else:
                    result[key] = val
            state["ok"] = True
            root.destroy()
        except ValueError as ex:
            hint_var.set(f"Errore parametri: {ex}")

    def on_cancel():
        root.destroy()

    btns = ttk.Frame(frame)
    btns.grid(row=len(PARAM_SCHEMA) + 1, column=0, columnspan=2, pady=(10, 0))
    if template_bgr is not None:
        ttk.Button(btns, text="Auto-tune", command=apply_tune).pack(side="left", padx=6)
    ttk.Button(btns, text="Annulla", command=on_cancel).pack(side="left", padx=6)
    ttk.Button(btns, text="OK", command=on_ok).pack(side="left", padx=6)

    root.bind("<Return>", lambda _e: on_ok())
    root.bind("<Escape>", lambda _e: on_cancel())
    root.mainloop()
    return result if state["ok"] else None


WINDOW_MODEL = "Modello (selezionare ROI - INVIO conferma, c annulla)"
WINDOW_RESULT = "Risultato matching"


def pick_file(title: str, initialdir: str | None = None) -> str | None:
    """Tk file picker (root nascosto)."""
    root = Tk()
    root.withdraw()
    path = filedialog.askopenfilename(
        title=title,
        initialdir=initialdir,
        filetypes=[
            ("Immagini", "*.png *.jpg *.jpeg *.bmp *.tif *.tiff"),
            ("Tutti i file", "*.*"),
        ],
    )
    root.destroy()
    return path or None


def load_image(path: str) -> np.ndarray:
    img = cv2.imread(path, cv2.IMREAD_COLOR)
    if img is None:
        raise FileNotFoundError(f"Impossibile leggere immagine: {path}")
    return img


def select_roi(image: np.ndarray) -> np.ndarray | None:
    """Apre finestra di selezione ROI. Ritorna ROI BGR o None se annullato."""
    disp = _fit_for_display(image, max_side=1200)
    scale = disp.shape[1] / image.shape[1]
    r = cv2.selectROI(WINDOW_MODEL, disp, showCrosshair=True, fromCenter=False)
    cv2.destroyWindow(WINDOW_MODEL)
    x, y, w, h = r
    if w == 0 or h == 0:
        return None
    # Riporta a coordinate immagine originale
    x0 = int(round(x / scale))
    y0 = int(round(y / scale))
    w0 = int(round(w / scale))
    h0 = int(round(h / scale))
    x0 = max(0, x0); y0 = max(0, y0)
    w0 = max(1, min(w0, image.shape[1] - x0))
    h0 = max(1, min(h0, image.shape[0] - y0))
    return image[y0:y0 + h0, x0:x0 + w0].copy()


def _fit_for_display(image: np.ndarray, max_side: int = 1200) -> np.ndarray:
    h, w = image.shape[:2]
    m = max(h, w)
    if m <= max_side:
        return image
    s = max_side / m
    return cv2.resize(image, (int(w * s), int(h * s)), interpolation=cv2.INTER_AREA)


def _warp_template_edges_to_scene(
    template_gray: np.ndarray,
    cx: float, cy: float, angle_deg: float, scale: float,
    scene_shape: tuple[int, int],
    canny_low: int = 50, canny_high: int = 150,
) -> np.ndarray:
    """Ritorna mask edge del template ruotato+scalato posizionato in scena."""
    h, w = template_gray.shape
    edge = cv2.Canny(template_gray, canny_low, canny_high)
    # Matrice affine: scala + rotazione attorno al centro template, poi traslazione
    Ht, Wt = h, w
    # Centro coerente con la convenzione train (center = w / 2.0, no -1):
    # (Wt-1)/2 introduceva uno shift di 0.5px per template di lato pari.
    cx_t = Wt / 2.0
    cy_t = Ht / 2.0
    M = cv2.getRotationMatrix2D((cx_t, cy_t), angle_deg, scale)
    # Traslazione per portare centro template a (cx, cy) della scena
    M[0, 2] += cx - cx_t
    M[1, 2] += cy - cy_t
    warped = cv2.warpAffine(
        edge, M, (scene_shape[1], scene_shape[0]),
        flags=cv2.INTER_NEAREST, borderValue=0,
    )
    return warped


def draw_matches(
    scene: np.ndarray,
    matches: list[Match],
    template_gray: np.ndarray | None = None,
    overlay_alpha: float = 0.7,
    canny_for_overlay: tuple[int, int] = (50, 150),
) -> np.ndarray:
    """Disegna bbox orientato + overlay edge template + baricentro + etichetta."""
    out = scene.copy()
    H, W = scene.shape[:2]
    for i, m in enumerate(matches):
        color = _color_for(i)
        # Overlay edge template nella pose del match (se template disponibile)
        if template_gray is not None:
            emap = _warp_template_edges_to_scene(
                template_gray, m.cx, m.cy, m.angle_deg, m.scale, (H, W),
                canny_low=canny_for_overlay[0], canny_high=canny_for_overlay[1],
            )
            mask = emap > 0
            if mask.any():
                overlay_color = np.zeros_like(out)
                overlay_color[mask] = color
                out[mask] = (
                    (1 - overlay_alpha) * out[mask]
                    + overlay_alpha * overlay_color[mask]
                ).astype(np.uint8)
        # Bbox orientato (poligono)
        poly = m.bbox_poly.astype(np.int32).reshape(-1, 1, 2)
        cv2.polylines(out, [poly], isClosed=True,
                      color=color, thickness=2, lineType=cv2.LINE_AA)
        # Lato top evidenziato per leggere orientamento
        p0 = tuple(m.bbox_poly[0].astype(int))
        p1 = tuple(m.bbox_poly[1].astype(int))
        cv2.line(out, p0, p1, color, 4, cv2.LINE_AA)
        # Baricentro
        cx, cy = int(round(m.cx)), int(round(m.cy))
        cv2.drawMarker(out, (cx, cy), color, cv2.MARKER_CROSS, 22, 2, cv2.LINE_AA)
        cv2.circle(out, (cx, cy), 4, color, -1, cv2.LINE_AA)
        # Asse orientamento
        L = int(np.linalg.norm(m.bbox_poly[1] - m.bbox_poly[0])) // 2
        a = np.deg2rad(m.angle_deg)
        ex = int(round(cx + L * np.cos(a)))
        ey = int(round(cy - L * np.sin(a)))
        cv2.arrowedLine(out, (cx, cy), (ex, ey), color, 2,
                        cv2.LINE_AA, tipLength=0.2)
        # Etichetta
        label = f"#{i+1} {m.angle_deg:.0f}d s={m.scale:.2f} {m.score:.2f}"
        cv2.putText(out, label, (cx + 8, cy - 8),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2, cv2.LINE_AA)
    return out


def _put_text(img: np.ndarray, s: str, x: int, y: int,
              size: float = 0.5, color: tuple = (220, 220, 220),
              thick: int = 1) -> None:
    cv2.putText(img, s, (x, y), cv2.FONT_HERSHEY_SIMPLEX,
                size, color, thick, cv2.LINE_AA)


def build_left_panel(
    template_bgr: np.ndarray,
    matches: list[Match],
    panel_width: int = 300,
    panel_height: int = 900,
) -> np.ndarray:
    """Pannello sinistro: thumbnail modello + legenda risultati (senza parametri)."""
    panel = np.full((panel_height, panel_width, 3), 28, dtype=np.uint8)
    pad = 12
    y = pad

    # Titolo MODELLO
    _put_text(panel, "MODELLO", pad, y + 18, size=0.7,
              color=(0, 200, 255), thick=2)
    y += 34

    # Thumbnail
    th_h, th_w = template_bgr.shape[:2]
    max_tw = panel_width - 2 * pad
    max_th = 160
    sc = min(max_tw / th_w, max_th / th_h)
    tw = max(1, int(th_w * sc)); th = max(1, int(th_h * sc))
    thumb = cv2.resize(template_bgr, (tw, th), interpolation=cv2.INTER_AREA)
    if thumb.ndim == 2:
        thumb = cv2.cvtColor(thumb, cv2.COLOR_GRAY2BGR)
    tx = (panel_width - tw) // 2
    panel[y:y + th, tx:tx + tw] = thumb
    cv2.rectangle(panel, (tx - 1, y - 1), (tx + tw, y + th),
                  (90, 90, 90), 1, cv2.LINE_AA)
    y += th + 16

    # Risultati
    _put_text(panel, f"RISULTATI ({len(matches)})", pad, y,
              size=0.6, color=(0, 200, 255), thick=2)
    y += 22
    if matches:
        scores = [m.score for m in matches]
        scales = [m.scale for m in matches]
        _put_text(panel, f"score: {min(scores):.2f}..{max(scores):.2f}",
                  pad, y, size=0.42); y += 16
        if max(scales) != min(scales):
            _put_text(panel, f"scale: {min(scales):.2f}..{max(scales):.2f}",
                      pad, y, size=0.42); y += 16
        y += 4
        max_rows = max(1, (panel_height - y - 12) // 16)
        shown = matches[:max_rows]
        for i, m in enumerate(shown):
            color = _color_for(i)
            cv2.circle(panel, (pad + 6, y - 4), 5, color, -1, cv2.LINE_AA)
            txt = (f"#{i+1} ({int(m.cx)},{int(m.cy)}) "
                   f"{m.angle_deg:.0f}d s={m.scale:.2f} {m.score:.3f}")
            _put_text(panel, txt, pad + 18, y, size=0.40)
            y += 16
        if len(matches) > len(shown):
            _put_text(panel, f"... +{len(matches) - len(shown)} altri",
                      pad, y, size=0.40, color=(150, 150, 150))
    return panel


def build_right_panel(
    params: dict,
    panel_width: int = 320,
    panel_height: int = 900,
) -> np.ndarray:
    """Pannello destro: parametri correnti + hotkey."""
    panel = np.full((panel_height, panel_width, 3), 28, dtype=np.uint8)
    pad = 12
    y = pad

    _put_text(panel, "PARAMETRI", pad, y + 18, size=0.7,
              color=(0, 200, 255), thick=2)
    y += 36

    # Tempi in alto, evidenziati
    for k in ("train_time", "find_time", "num_variants"):
        if k in params:
            v = params[k]
            _put_text(panel, f"{k}:", pad, y,
                      size=0.45, color=(160, 220, 160))
            _put_text(panel, str(v), pad + 140, y,
                      size=0.45, color=(200, 255, 200), thick=2)
            y += 18
    y += 6

    # Altri parametri
    skip = {"train_time", "find_time", "num_variants"}
    for k, v in params.items():
        if k in skip:
            continue
        _put_text(panel, f"{k}:", pad, y, size=0.42, color=(180, 180, 180))
        _put_text(panel, str(v), pad + 140, y, size=0.42,
                  color=(220, 220, 220))
        y += 16

    # Hotkey in fondo
    footer_y = panel_height - 110
    _put_text(panel, "HOTKEY", pad, footer_y, size=0.6,
              color=(0, 200, 255), thick=2)
    fy = footer_y + 22
    for line in [
        "r  modifica parametri",
        "o  nuovo ROI",
        "m  nuovo modello",
        "s  nuova scena",
        "q / Esc  esci",
    ]:
        _put_text(panel, line, pad, fy, size=0.42, color=(200, 200, 200))
        fy += 16
    return panel


def _fit_scene_center(
    scene: np.ndarray, target_w: int, target_h: int,
) -> np.ndarray:
    """Scala scena a fit (target_w, target_h) mantenendo aspect; padding bg."""
    h, w = scene.shape[:2]
    sc = min(target_w / w, target_h / h)
    new_w = max(1, int(w * sc)); new_h = max(1, int(h * sc))
    resized = cv2.resize(scene, (new_w, new_h), interpolation=cv2.INTER_AREA)
    out = np.full((target_h, target_w, 3), 20, dtype=np.uint8)
    y0 = (target_h - new_h) // 2
    x0 = (target_w - new_w) // 2
    out[y0:y0 + new_h, x0:x0 + new_w] = resized
    return out


def compose_fixed_layout(
    scene_annotated: np.ndarray,
    left_panel: np.ndarray,
    right_panel: np.ndarray,
    window_w: int = 1600,
    window_h: int = 900,
) -> np.ndarray:
    """Layout fisso: [left | scena fit-scaled | right]."""
    lH, lW = left_panel.shape[:2]
    rH, rW = right_panel.shape[:2]
    # Altezza uniforme (pannelli dovrebbero essere già window_h)
    if lH != window_h:
        left_panel = cv2.resize(left_panel, (lW, window_h),
                                interpolation=cv2.INTER_AREA)
    if rH != window_h:
        right_panel = cv2.resize(right_panel, (rW, window_h),
                                 interpolation=cv2.INTER_AREA)
    center_w = window_w - lW - rW
    center = _fit_scene_center(scene_annotated, center_w, window_h)
    out = np.concatenate([left_panel, center, right_panel], axis=1)
    return out


def _color_for(i: int) -> tuple[int, int, int]:
    palette = [
        (0, 255, 0), (0, 200, 255), (255, 100, 100),
        (255, 200, 0), (200, 0, 255), (100, 255, 200),
        (255, 0, 0), (0, 255, 255),
    ]
    return palette[i % len(palette)]


def show_results(
    scene: np.ndarray,
    matches: list[Match],
    template_bgr: np.ndarray | None = None,
    params: dict | None = None,
    window_w: int = 1600,
    window_h: int = 900,
) -> str:
    """Visualizza risultati in layout fisso [SX panel | scena scalata | DX panel].

    Ritorna 'rematch'/'new_roi'/'new_model'/'new_scene'/'quit'.
    """
    print(f"\n=== {len(matches)} match trovati ===")
    for i, m in enumerate(matches):
        print(f"  #{i+1}: cx={m.cx:.1f} cy={m.cy:.1f} "
              f"angle={m.angle_deg:.1f}d scale={m.scale:.2f} score={m.score:.3f}")

    template_gray = None
    if template_bgr is not None:
        template_gray = (template_bgr if template_bgr.ndim == 2
                         else cv2.cvtColor(template_bgr, cv2.COLOR_BGR2GRAY))
    annotated = draw_matches(scene, matches, template_gray=template_gray)

    if template_bgr is not None:
        left = build_left_panel(template_bgr, matches, panel_height=window_h)
    else:
        left = np.full((window_h, 300, 3), 28, dtype=np.uint8)
    if params is not None:
        right = build_right_panel(params, panel_height=window_h)
    else:
        right = np.full((window_h, 320, 3), 28, dtype=np.uint8)

    composed = compose_fixed_layout(
        annotated, left, right, window_w=window_w, window_h=window_h,
    )
    cv2.namedWindow(WINDOW_RESULT, cv2.WINDOW_AUTOSIZE)
    cv2.imshow(WINDOW_RESULT, composed)
    print("\n[r] parametri  [o] nuovo ROI  [m] nuovo modello  [s] nuova scena  [q/Esc] chiudi")
    action = "quit"
    while True:
        k = cv2.waitKey(0) & 0xFF
        if k in (ord("r"), ord("R")):
            action = "rematch"; break
        if k in (ord("o"), ord("O")):
            action = "new_roi"; break
        if k in (ord("m"), ord("M")):
            action = "new_model"; break
        if k in (ord("s"), ord("S")):
            action = "new_scene"; break
        if k in (27, ord("q"), ord("Q")):
            action = "quit"; break
        if k != 255:
            action = "quit"; break
    cv2.destroyAllWindows()
    return action


def run(
    initial_dir: str | None = None,
    angle_step_deg: float = 5.0,
    angle_range_deg: tuple[float, float] = (0.0, 360.0),
    scale_range: tuple[float, float] = (1.0, 1.0),
    scale_step: float = 0.1,
    num_features: int = 96,
    weak_grad: float = 30.0,
    strong_grad: float = 60.0,
    # 4 allineato col default del matcher: raggio 5 peggiora la precisione
    # di rotazione (spread troppo largo appiattisce il picco angolare).
    spread_radius: int = 4,
    pyramid_levels: int = 3,
    min_score: float = 0.55,
    max_matches: int = 25,
    nms_radius: int = 0,
    verify_threshold: float = 0.4,
    backend: str = "line",
) -> None:
    """Entry-point GUI completo."""
    print("[1] Selezionare immagine MODELLO...")
    model_path = pick_file("Immagine MODELLO", initialdir=initial_dir)
    if not model_path:
        print("Annullato."); return
    model_img = load_image(model_path)
    print(f"    caricato: {model_path}  shape={model_img.shape}")

    print("[2] Selezionare ROI sul modello (trascinare, INVIO conferma).")
    roi = select_roi(model_img)
    if roi is None:
        print("ROI vuota, annullato."); return
    print(f"    ROI: {roi.shape[1]}x{roi.shape[0]} px")

    print("[3] Selezionare immagine SCENA...")
    scene_path = pick_file("Immagine SCENA",
                           initialdir=str(Path(model_path).parent))
    if not scene_path:
        print("Annullato."); return
    scene = load_image(scene_path)
    print(f"    caricato: {scene_path}  shape={scene.shape}")

    # Valori iniziali del form parametri
    cur = {
        "backend": backend,
        "angle_min": angle_range_deg[0],
        "angle_max": angle_range_deg[1],
        "angle_step": angle_step_deg,
        "scale_min": scale_range[0],
        "scale_max": scale_range[1],
        "scale_step": scale_step,
        "min_score": min_score,
        "max_matches": max_matches,
        "nms_radius": nms_radius,
        "num_features": num_features,
        "weak_grad": weak_grad,
        "strong_grad": strong_grad,
        "spread_radius": spread_radius,
        "pyramid_levels": pyramid_levels,
        "verify_threshold": verify_threshold,
    }

    while True:
        print("[4/?] Dialog parametri (OK=conferma, Annulla=esci)...")
        new = edit_params(cur, template_bgr=roi)
        if new is None:
            print("Annullato."); return
        cur = new

        print(f"      Train + match (backend={cur['backend']})...")
        if cur["backend"] == "edge":
            matcher: EdgeShapeMatcher | LineShapeMatcher = EdgeShapeMatcher(
                angle_step_deg=cur["angle_step"],
                angle_range_deg=(cur["angle_min"], cur["angle_max"]),
                scale_range=(cur["scale_min"], cur["scale_max"]),
                scale_step=cur["scale_step"],
            )
        else:
            matcher = LineShapeMatcher(
                num_features=cur["num_features"],
                weak_grad=cur["weak_grad"], strong_grad=cur["strong_grad"],
                angle_step_deg=cur["angle_step"],
                angle_range_deg=(cur["angle_min"], cur["angle_max"]),
                scale_range=(cur["scale_min"], cur["scale_max"]),
                scale_step=cur["scale_step"],
                spread_radius=cur["spread_radius"],
                pyramid_levels=cur["pyramid_levels"],
            )
        import time
        t0 = time.time()
        n = matcher.train(roi)
        t_train = time.time() - t0
        print(f"      train: {n} varianti in {t_train:.2f}s")
        t0 = time.time()
        nms = cur["nms_radius"] if cur["nms_radius"] > 0 else None
        if cur["backend"] == "line":
            matches = matcher.find(
                scene, min_score=cur["min_score"],
                max_matches=cur["max_matches"], nms_radius=nms,
                verify_threshold=cur.get("verify_threshold", 0.4),
            )
        else:
            matches = matcher.find(
                scene, min_score=cur["min_score"],
                max_matches=cur["max_matches"], nms_radius=nms,
            )
        t_find = time.time() - t0
        print(f"      find:  {len(matches)} match in {t_find:.2f}s")

        params = {
            "backend": cur["backend"],
            "angle_range": f"{cur['angle_min']:.0f}..{cur['angle_max']:.0f}d",
            "angle_step": f"{cur['angle_step']:.1f}d",
            "scale_range": f"{cur['scale_min']:.2f}..{cur['scale_max']:.2f}",
            "scale_step": f"{cur['scale_step']:.2f}",
            "min_score": f"{cur['min_score']:.2f}",
            "max_matches": str(cur["max_matches"]),
            "nms_radius": str(nms if nms else "auto"),
            "num_variants": str(n),
            "train_time": f"{t_train:.2f}s",
            "find_time": f"{t_find:.2f}s",
        }
        if cur["backend"] == "line":
            params["num_features"] = str(cur["num_features"])
            params["weak/strong"] = f"{cur['weak_grad']:.0f}/{cur['strong_grad']:.0f}"
            params["spread_radius"] = str(cur["spread_radius"])
            params["pyramid_levels"] = str(cur["pyramid_levels"])
        action = show_results(scene, matches, template_bgr=roi, params=params)
        if action == "rematch":
            continue
        if action == "new_roi":
            new_roi = select_roi(model_img)
            if new_roi is None:
                print("ROI annullata, esco.")
                break
            roi = new_roi
            print(f"    nuovo ROI: {roi.shape[1]}x{roi.shape[0]} px")
            continue
        if action == "new_model":
            p = pick_file("Nuovo MODELLO",
                          initialdir=str(Path(model_path).parent))
            if not p:
                print("Annullato."); break
            model_path = p
            model_img = load_image(model_path)
            print(f"    modello: {model_path}  shape={model_img.shape}")
            new_roi = select_roi(model_img)
            if new_roi is None:
                print("ROI annullata, esco."); break
            roi = new_roi
            print(f"    nuovo ROI: {roi.shape[1]}x{roi.shape[0]} px")
            continue
        if action == "new_scene":
            p = pick_file("Nuova SCENA",
                          initialdir=str(Path(scene_path).parent))
            if not p:
                print("Annullato."); break
            scene_path = p
            scene = load_image(scene_path)
            print(f"    scena: {scene_path}  shape={scene.shape}")
            continue
        # quit
        break


if __name__ == "__main__":
    test_dir = "/home/adriano/Documenti/Git_XYZ/VisionSuite/Shape_model_2d/Test"
    run(initial_dir=test_dir if Path(test_dir).is_dir() else None)
