// Pattern Matching 2D - frontend (UI semplificata operator-friendly)

// Parametri avanzati (sezione collassabile)
const ADV_PARAMS = [
  ["num_features", "Num feature", "int", ""],
  ["weak_grad", "Weak grad", "float", ""],
  ["strong_grad", "Strong grad", "float", ""],
  ["spread_radius", "Spread radius", "int", ""],
  ["pyramid_levels", "Pyramid levels", "int", ""],
  ["verify_threshold", "Verify NCC thr", "float", 0.4],
  ["nms_radius", "NMS radius (0=auto)", "int", 0],
];

const PALETTE = [
  "#00ff00", "#ffc800", "#ff6464", "#ffc800", "#c800ff",
  "#64ffc8", "#ff0000", "#00ffff",
];

const state = {
  model: null, scene: null, roi: null, drag: null,
  matches: [], annotatedImg: null,
};

// ---------- Forms ----------
function buildAdvancedForm() {
  const form = document.getElementById("adv-form");
  form.innerHTML = "";
  for (const [key, label, , def] of ADV_PARAMS) {
    const lbl = document.createElement("label");
    lbl.textContent = label;
    const inp = document.createElement("input");
    inp.id = `adv-${key}`;
    inp.type = "text";
    inp.placeholder = "auto";
    inp.value = def === "" ? "" : String(def);
    inp.addEventListener("keydown", (e) => {
      if (e.key === "Enter") doMatch();
    });
    form.appendChild(lbl);
    form.appendChild(inp);
  }
}

function readUserParams() {
  return {
    tipo: document.getElementById("p-tipo").value,
    simmetria: document.getElementById("p-simmetria").value,
    scala: document.getElementById("p-scala").value,
    precisione: document.getElementById("p-precisione").value,
    filtro_fp: document.getElementById("p-filtro-fp").value,
    penalita_scala: parseFloat(
      document.getElementById("p-penalita-scala").value),
    min_score: parseFloat(document.getElementById("p-min-score").value),
    max_matches: parseInt(document.getElementById("p-max-matches").value, 10),
  };
}

function readAdvancedOverrides() {
  const out = {};
  for (const [key, , type] of ADV_PARAMS) {
    const v = document.getElementById(`adv-${key}`).value.trim();
    if (v === "") continue;
    out[key] = type === "int" ? parseInt(v, 10) : parseFloat(v);
  }
  return out;
}

// ---------- Image loading from folder ----------
async function loadFromFolder(filename) {
  const r = await fetch("/load_from_folder", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ filename }),
  });
  if (!r.ok) throw new Error(await r.text());
  return await r.json();
}

async function fetchImagesList() {
  const r = await fetch("/images");
  if (!r.ok) return { files: [], dir: "" };
  return await r.json();
}

async function uploadToFolder(file) {
  const fd = new FormData();
  fd.append("file", file);
  const r = await fetch("/upload_to_folder", { method: "POST", body: fd });
  if (!r.ok) throw new Error(await r.text());
  return await r.json();
}

async function refreshPickers() {
  const {files, dir} = await fetchImagesList();
  buildThumbPicker("picker-model", files, onSelectModel);
  buildThumbPicker("picker-scene", files, onSelectScene);
  return {files, dir};
}

function buildThumbPicker(pickerId, files, onSelect) {
  const picker = document.getElementById(pickerId);
  const current = picker.querySelector(".picker-current");
  const list = picker.querySelector(".picker-list");
  const text = current.querySelector(".picker-text");
  // Rimuovi eventuale vecchia thumbnail
  const oldImg = current.querySelector("img");
  if (oldImg) oldImg.remove();
  list.innerHTML = "";

  files.forEach((f) => {
    const item = document.createElement("div");
    item.className = "picker-item";
    const img = document.createElement("img");
    img.src = `/folder_image/${encodeURIComponent(f)}?w=120`;
    img.loading = "lazy";
    const name = document.createElement("span");
    name.className = "name"; name.textContent = f;
    item.appendChild(img); item.appendChild(name);
    item.addEventListener("click", () => {
      // Aggiorna la visual del "current"
      let thumb = current.querySelector("img");
      if (!thumb) {
        thumb = document.createElement("img");
        current.insertBefore(thumb, text);
      }
      thumb.src = `/folder_image/${encodeURIComponent(f)}?w=80`;
      text.textContent = f;
      picker.classList.remove("open");
      onSelect(f);
    });
    list.appendChild(item);
  });

  current.onclick = () => {
    // Chiudi altri picker aperti
    document.querySelectorAll(".thumb-picker.open")
      .forEach((p) => { if (p !== picker) p.classList.remove("open"); });
    picker.classList.toggle("open");
  };
}

// Close picker on outside click
document.addEventListener("click", (e) => {
  if (!e.target.closest(".thumb-picker")) {
    document.querySelectorAll(".thumb-picker.open")
      .forEach((p) => p.classList.remove("open"));
  }
});

function loadImage(src) {
  return new Promise((res, rej) => {
    const img = new Image();
    img.onload = () => res(img);
    img.onerror = rej;
    img.src = src;
  });
}

async function onSelectModel(filename) {
  if (!filename) return;
  setStatus("Caricamento modello...");
  try {
    const meta = await loadFromFolder(filename);
    const img = await loadImage(`/image/${meta.id}/raw`);
    state.model = { id: meta.id, w: meta.width, h: meta.height, img };
    state.roi = null;
    document.getElementById("roi-info").textContent = "ROI: (nessuna)";
    setStatus(`Modello: ${filename} ${meta.width}x${meta.height} — trascina ROI`);
    renderModel();
  } catch (e) {
    setStatus(`Errore modello: ${e.message}`);
  }
}

async function onSelectScene(filename) {
  if (!filename) return;
  setStatus("Caricamento scena...");
  try {
    const meta = await loadFromFolder(filename);
    const img = await loadImage(`/image/${meta.id}/raw`);
    state.scene = { id: meta.id, w: meta.width, h: meta.height, img };
    state.matches = []; state.annotatedImg = null;
    setStatus(`Scena: ${filename} ${meta.width}x${meta.height}`);
    renderScene();
  } catch (e) {
    setStatus(`Errore scena: ${e.message}`);
  }
}

// ---------- Rendering ----------
function fitToCanvas(img, cw, ch) {
  const sc = Math.min(cw / img.width, ch / img.height);
  const dw = img.width * sc, dh = img.height * sc;
  return { sc, ox: (cw - dw) / 2, oy: (ch - dh) / 2, dw, dh };
}

function renderModel() {
  const cnv = document.getElementById("c-model");
  const ctx = cnv.getContext("2d");
  ctx.fillStyle = "#141414";
  ctx.fillRect(0, 0, cnv.width, cnv.height);
  if (!state.model) return;
  const fit = fitToCanvas(state.model.img, cnv.width, cnv.height);
  state.model.scale = fit.sc;
  state.model.ox = fit.ox; state.model.oy = fit.oy;
  ctx.drawImage(state.model.img, fit.ox, fit.oy, fit.dw, fit.dh);
  if (state.roi) {
    const [x, y, w, h] = state.roi;
    ctx.strokeStyle = "#00ff80"; ctx.lineWidth = 2;
    ctx.strokeRect(fit.ox + x * fit.sc, fit.oy + y * fit.sc,
                   w * fit.sc, h * fit.sc);
  }
  if (state.drag) {
    ctx.strokeStyle = "#ffff00";
    ctx.setLineDash([4, 2]); ctx.lineWidth = 2;
    ctx.strokeRect(
      Math.min(state.drag.x0, state.drag.x1),
      Math.min(state.drag.y0, state.drag.y1),
      Math.abs(state.drag.x1 - state.drag.x0),
      Math.abs(state.drag.y1 - state.drag.y0));
    ctx.setLineDash([]);
  }
}

function renderScene() {
  const cnv = document.getElementById("c-scene");
  const ctx = cnv.getContext("2d");
  ctx.fillStyle = "#141414";
  ctx.fillRect(0, 0, cnv.width, cnv.height);
  const img = state.annotatedImg || (state.scene && state.scene.img);
  if (!img) return;
  const fit = fitToCanvas(img, cnv.width, cnv.height);
  ctx.drawImage(img, fit.ox, fit.oy, fit.dw, fit.dh);
}

// ---------- ROI drag ----------
function canvasPos(cnv, ev) {
  const r = cnv.getBoundingClientRect();
  return { x: ev.clientX - r.left, y: ev.clientY - r.top };
}

function setupROI() {
  const cnv = document.getElementById("c-model");
  cnv.addEventListener("mousedown", (e) => {
    if (!state.model) return;
    const p = canvasPos(cnv, e);
    state.drag = { x0: p.x, y0: p.y, x1: p.x, y1: p.y };
    renderModel();
  });
  cnv.addEventListener("mousemove", (e) => {
    if (!state.drag) return;
    const p = canvasPos(cnv, e);
    state.drag.x1 = p.x; state.drag.y1 = p.y;
    renderModel();
  });
  cnv.addEventListener("mouseup", () => {
    if (!state.drag || !state.model) return;
    const d = state.drag; state.drag = null;
    if (Math.abs(d.x1 - d.x0) < 4 || Math.abs(d.y1 - d.y0) < 4) return;
    const m = state.model;
    const ix0 = Math.round((Math.min(d.x0, d.x1) - m.ox) / m.scale);
    const iy0 = Math.round((Math.min(d.y0, d.y1) - m.oy) / m.scale);
    const iw = Math.round(Math.abs(d.x1 - d.x0) / m.scale);
    const ih = Math.round(Math.abs(d.y1 - d.y0) / m.scale);
    const cx0 = Math.max(0, Math.min(ix0, m.w - 1));
    const cy0 = Math.max(0, Math.min(iy0, m.h - 1));
    const cw = Math.max(1, Math.min(iw, m.w - cx0));
    const ch = Math.max(1, Math.min(ih, m.h - cy0));
    state.roi = [cx0, cy0, cw, ch];
    document.getElementById("roi-info").textContent =
      `ROI: ${cw}x${ch} @ (${cx0}, ${cy0})`;
    renderModel();
  });
}

// ---------- Match action ----------
async function doMatch() {
  if (!state.model) { setStatus("Carica modello"); return; }
  if (!state.scene) { setStatus("Carica scena"); return; }
  if (!state.roi) { setStatus("Seleziona ROI sul modello"); return; }
  const user = readUserParams();
  const adv = readAdvancedOverrides();
  setStatus("Match in corso...");

  // Se utente ha fornito override avanzati → usa /match (tecnico)
  // altrimenti /match_simple (operator mode)
  const hasAdv = Object.keys(adv).length > 0;
  const url = hasAdv ? "/match" : "/match_simple";
  let body;
  if (hasAdv) {
    // Merge simple → tecnici base, poi overrides
    const SYM_MAP = {invariante:0, nessuna:360, bilaterale:180, rot_3:120,
                     rot_4:90, rot_6:60, rot_8:45};
    const SCALE_MAP = {fissa:[1,1,0.1], mini:[0.9,1.1,0.05],
                       medio:[0.75,1.25,0.05], max:[0.5,1.5,0.05]};
    const PREC_MAP = {veloce:10, normale:5, preciso:2};
    // Allineato a FILTRO_FP_MAP server-side (server.py)
    const FP_MAP = {off:0, leggero:0.30, medio:0.50, forte:0.70};
    const [smin, smax, sstep] = SCALE_MAP[user.scala];
    // NB: SYM_MAP[invariante]=0 e' valido (zero rotazioni). Uso ?? per
    // distinguere "chiave mancante" da "valore zero": altrimenti 0 || 360
    // collassa invariante a 360 = bug "simmetria non ha effetto".
    const angMax = SYM_MAP[user.simmetria] ?? 360;
    body = {
      model_id: state.model.id, scene_id: state.scene.id, roi: state.roi,
      angle_min: 0, angle_max: angMax,
      angle_step: PREC_MAP[user.precisione] ?? 5,
      scale_min: smin, scale_max: smax, scale_step: sstep,
      min_score: user.min_score, max_matches: user.max_matches,
      num_features: adv.num_features ?? 96,
      weak_grad: adv.weak_grad ?? 30,
      strong_grad: adv.strong_grad ?? 60,
      spread_radius: adv.spread_radius ?? 5,
      pyramid_levels: adv.pyramid_levels ?? 3,
      verify_threshold: adv.verify_threshold ?? (FP_MAP[user.filtro_fp] ?? 0.50),
      nms_radius: adv.nms_radius ?? 0,
    };
  } else {
    body = {
      model_id: state.model.id, scene_id: state.scene.id, roi: state.roi,
      ...user,
    };
  }

  const r = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  if (!r.ok) {
    setStatus(`Errore: ${await r.text()}`); return;
  }
  const data = await r.json();
  state.matches = data.matches;
  state.annotatedImg = await loadImage(
    `/image/${data.annotated_id}/raw?t=${Date.now()}`);
  renderScene();
  renderLegend();
  document.getElementById("t-train").textContent = `${data.train_time.toFixed(2)}s`;
  document.getElementById("t-find").textContent = `${data.find_time.toFixed(2)}s`;
  document.getElementById("t-var").textContent = data.num_variants;
  document.getElementById("t-match").textContent = data.matches.length;
  setStatus(`${data.matches.length} match trovati${hasAdv ? " (avanzato)" : ""}`);
}

function renderLegend() {
  const el = document.getElementById("legend");
  el.innerHTML = "";
  state.matches.forEach((m, i) => {
    const div = document.createElement("div");
    div.className = "legend-item";
    const dot = document.createElement("span");
    dot.className = "legend-dot";
    dot.style.background = PALETTE[i % PALETTE.length];
    div.appendChild(dot);
    const txt = document.createElement("span");
    txt.textContent = `#${i+1}  cx=${Math.round(m.cx)} cy=${Math.round(m.cy)}  `
      + `${m.angle_deg.toFixed(1)}°  s=${m.scale.toFixed(2)}  `
      + `score=${m.score.toFixed(3)}`;
    div.appendChild(txt);
    el.appendChild(div);
  });
}

function setStatus(s) {
  document.getElementById("status").textContent = s;
}

// ---------- Init ----------
window.addEventListener("DOMContentLoaded", async () => {
  buildAdvancedForm();
  setupROI();
  // Popola picker immagini da IMAGES_DIR (con thumbnail)
  const {files, dir} = await refreshPickers();
  if (files.length === 0) {
    setStatus(`Nessuna immagine in ${dir} (carica file o configura IMAGES_DIR)`);
  } else {
    setStatus(`${files.length} immagini in ${dir}`);
  }

  // Upload file nella folder
  const upEl = document.getElementById("file-upload");
  upEl.addEventListener("change", async (e) => {
    const f = e.target.files[0];
    if (!f) return;
    setStatus(`Caricamento ${f.name} nella cartella...`);
    try {
      const res = await uploadToFolder(f);
      await refreshPickers();
      setStatus(`Salvato come ${res.saved_as} (${res.files.length} file totali)`);
    } catch (err) {
      setStatus(`Errore upload: ${err.message}`);
    }
    e.target.value = "";  // consente re-upload stesso file
  });
  document.getElementById("btn-match").addEventListener("click", doMatch);
  const slider = document.getElementById("p-min-score");
  slider.addEventListener("input", (e) => {
    document.getElementById("v-score").textContent =
      parseFloat(e.target.value).toFixed(2);
  });
  renderModel();
  renderScene();
});
