// client.js
// UI: Buttons, Anzeige von Result als JSON + Baum, Fallback für Commands

function appendLog(line) {
  const el = document.getElementById("log");
  if (!el) return;

  const now = new Date().toISOString();
  el.value += `[${now}] ${line}\n`;
  el.scrollTop = el.scrollHeight;
}

function clearTextarea(id) {
  const el = document.getElementById(id);
  if (el) el.value = "";
}

function clearElement(id) {
  const el = document.getElementById(id);
  if (el) el.innerHTML = "";
}

function formatScalar(value) {
  if (value === null) return "null";
  if (value === undefined) return "undefined";
  if (typeof value === "string") return JSON.stringify(value);
  if (typeof value === "number" && Number.isFinite(value)) return String(value);
  if (typeof value === "boolean") return String(value);
  return String(value);
}

function renderTree(container, value, key = "result", open = true) {
  if (!container) return;

  container.innerHTML = "";
  container.appendChild(renderNode(key, value, open));
}

function renderNode(key, value, open = false) {
  const isObject = value !== null && typeof value === "object";

  if (!isObject) {
    const leaf = document.createElement("div");
    leaf.className = "tree-leaf";
    leaf.textContent = `${key}: ${formatScalar(value)}`;
    return leaf;
  }

  const details = document.createElement("details");
  details.open = open;

  const summary = document.createElement("summary");
  summary.textContent = Array.isArray(value)
    ? `${key} [${value.length}]`
    : key;

  details.appendChild(summary);

  const body = document.createElement("div");
  body.style.marginLeft = "16px";

  if (Array.isArray(value)) {
    value.forEach((item, idx) => {
      body.appendChild(renderNode(String(idx), item, false));
    });
  } else {
    Object.entries(value).forEach(([childKey, childVal]) => {
      body.appendChild(renderNode(childKey, childVal, false));
    });
  }

  details.appendChild(body);
  return details;
}

function renderResult(result) {
  const jsonEl = document.getElementById("result-json");
  const treeEl = document.getElementById("result-tree");

  if (jsonEl) {
    jsonEl.value = JSON.stringify(result, null, 2);
  }

  renderTree(treeEl, result, "result", true);
}

dataCache = null
headersCache = null 
rowsCache  = null
timeCache = null
jsonCache  = null

async function fetchCSV(){
  if(dataCache == null || headersCache == null || rowsCache == null || timeCache == null){
      ({ data: dataCache, headers: headersCache, rows: rowsCache } = await fetchCSV_fromServer());
      timeCache = Date.now();
  }
  if (Date.now() - timeCache > 1000){
    
      ({ data: dataCache, headers: headersCache, rows: rowsCache } = await fetchCSV_fromServer());
      timeCache = Date.now();
  }

  return {data: dataCache, headers: headersCache, rows: rowsCache}

}

async function fetchCSV_fromServer() {
  
  const res = await fetch("/api/latest-snapshot");
  if (!res.ok) throw new Error("Fehler beim Laden des Snapshots");


  let data;
  if (res.headers.get("content-type")?.includes("application/json")) {
    data = await res.json();
  } else {
    const csvData = await res.text();
    data = {
      filename: "latest.csv",
      mtime: new Date().toISOString(),
      content: csvData
    };
  }
  
  if (!jsonCache && data.jsonFile) {
    jsonCache = JSON.parse(data.jsonFile.content);
  }

  const lines = data.content.trim().split(/\r?\n/).filter(Boolean);
  if (lines.length < 2) {
    throw new Error("Keine oder unvollständige Daten");
  }

  const headers = lines[0].split(",").map(h => h.trim());

  const rows = lines.slice(1).map(line => {
    const cells = line.split(",");
    const obj = {};
    headers.forEach((h, i) => {
      const raw = (cells[i] ?? "").trim();
      const numeric = Number(raw);
      obj[h] = raw !== "" && Number.isFinite(numeric) ? numeric : raw;
    });
    return obj;
  });

  return { data, headers, rows };
}

async function fetchWebcamSnapshotData() {
  const { data, headers, rows } = await fetchCSV();
  return {
    filename: data.filename,
    mtime: data.mtime,
    headers,
    rows,
    robotIntrinsics: jsonCache,
    imageFile: data.imageFile,
    image2: data.image2
  };
}

async function sendToBodyTracker({imageFile, image2, robotIntrinsics}) {
  const response = await fetch('/api/estimate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ imageFile, image2, robotIntrinsics })
  });

  if (!response.ok) {
    const message = await response.text();
    throw new Error(`BodyTracker fehlgeschlagen (${response.status}): ${message}`);
  }

  return await response.json();
}

async function renderSnapshot() {
  const table = document.getElementById("snapshot-table");
  const pictureEl = document.getElementById("snapshot-info-picture");
  if (!table) return;

  try {
    const { data, headers, rows } = await fetchCSV();

    // Info anzeigen
    const infoEl = document.getElementById("snapshot-info");
    if (infoEl) {
      infoEl.textContent = `Datei: ${data.filename}, Geändert: ${new Date(data.mtime).toLocaleString()}, Zeilen: ${rows.length}`;
    }

    // Bild anzeigen, falls vorhanden
    if (pictureEl) {
      let imagesHtml = '';
      if (data.imageFile) {
        imagesHtml += `<img src="data:${data.imageFile.mimeType};base64,${data.imageFile.contentBase64}" alt="${data.imageFile.filename}" style="max-width: calc(50% - 2.5px); height: auto;">`;
      }
      if (data.image2) {
        imagesHtml += `<img src="data:${data.image2.mimeType};base64,${data.image2.contentBase64}" alt="${data.image2.filename}" style="max-width: calc(50% - 2.5px); height: auto;">`;
      }
      if (imagesHtml) {
        pictureEl.innerHTML = `<div style="display: flex; gap: 5px;">${imagesHtml}</div>`;
      } else {
        pictureEl.innerHTML = "";
      }
    }

    // Tabelle leeren
    table.innerHTML = "";

    // Header
    const thead = document.createElement("thead");
    const headerRow = document.createElement("tr");
    headers.forEach(h => {
      const th = document.createElement("th");
      th.textContent = h;
      headerRow.appendChild(th);
    });
    thead.appendChild(headerRow);
    table.appendChild(thead);

    // Body
    const tbody = document.createElement("tbody");
    rows.forEach(row => {
      const tr = document.createElement("tr");
      headers.forEach(h => {
        const td = document.createElement("td");
        let value = row[h];
        if (typeof value === 'number') {
          if (h === 'id' || h === 'seen_by') {
            value = Math.round(value);
          } else {
            value = value.toFixed(1);
          }
        }
        td.textContent = value;
        tr.appendChild(td);
      });
      tbody.appendChild(tr);
    });
    table.appendChild(tbody);
  } catch (err) {
    console.error("Fehler beim Rendern des Snapshots:", err);
  }
}

async function onCalculateClick() {
  clearTextarea("analysis-log");
  clearTextarea("result-json");
  clearElement("result-tree");
  clearElement("snapshot-table");

  appendLog("Starte Berechnung...");

  try {
    const response = await fetch("robot.json");
    const robot = await response.json();
    const snapshot = await fetchWebcamSnapshotData();

    appendLog(`Snapshot geladen: ${snapshot.filename} (${snapshot.rows.length} Zeilen)`);

    let result = null;
    try {
      result = await sendToBodyTracker(snapshot);
      appendLog("BodyTracker wurde aufgerufen.");
    } catch (err) {
      appendLog(`BodyTracker nicht aufgerufen: ${err.message}`);
    }

    if (!result) {
      appendLog("Fallback: lokale Pose-Berechnung mit calculateAngles.js");
      result = await window.calculate(snapshot.robotIntrinsics, robot);
    }

    renderResult(result);
    await renderSnapshot();
    appendLog("Result angezeigt.");
  } catch (err) {
    appendLog(`Fehler: ${err.message}`);
  }
}

// ── Foto ─────────────────────────────────────────────────────────────────────

/**
 * Foto-Button: holt HD-JPEGs aller 3 Kameras parallel
 * und zeigt sie nebeneinander im Snapshot-Bereich an.
 */
async function onFotoClick() {
  const display = document.getElementById('snapshot-info-picture');
  if (!display) return;

  const camIds = ['cam0', 'cam1', 'cam2'];
  const labels = { cam0: 'front', cam1: 'left', cam2: 'right' };
  appendLog('Foto: alle Kameras …');

  const t = Date.now();

  // Wrapper mit 3 Spalten
  const wrapper = document.createElement('div');
  wrapper.style.cssText = 'display:flex; gap:6px; align-items:flex-start;';

  camIds.forEach(id => {
    const col = document.createElement('div');
    col.style.cssText = 'flex:1 1 0; min-width:0;';

    const caption = document.createElement('div');
    caption.style.cssText = 'font-size:0.8em; margin-bottom:3px; color:#aaa;';
    caption.textContent = `${id} (${labels[id]})`;

    const img = document.createElement('img');
    img.style.cssText = 'width:100%; height:auto; display:block;';
    img.alt = id;
    img.onload  = () => appendLog(`  ${id}: ${img.naturalWidth}×${img.naturalHeight}px`);
    img.onerror = () => appendLog(`  ${id}: Fehler`);
    img.src = `/api/webcam/snapshot/${id}?t=${t}`;

    col.appendChild(caption);
    col.appendChild(img);
    wrapper.appendChild(col);
  });

  display.innerHTML = '';
  display.appendChild(wrapper);
}

// ── Homing ────────────────────────────────────────────────────────────────────

let _homingState = null;

function setHomingStatus(label, cls) {
  const el = document.getElementById('homing-status');
  if (!el) return;
  el.textContent = label;
  el.className   = `status-badge ${cls}`;
}

function setHomingProgress(step, total, text) {
  const wrap = document.getElementById('homing-progress');
  const bar  = document.getElementById('homing-progress-bar');
  const txt  = document.getElementById('homing-progress-text');
  if (!wrap) return;
  wrap.style.display = 'block';
  if (bar) bar.style.width = (total > 0 ? Math.round(step / total * 100) : 0) + '%';
  if (txt) txt.textContent = text || `Schritt ${step} / ${total}`;
}

function writePartialGCode(state) {
  const axisMap = { x: 'X', y: 'Y', z: 'Z', a: 'A', b: 'B', c: 'C', e: 'E' };
  const parts = [];
  for (const [key, axis] of Object.entries(axisMap)) {
    if (state[key] != null) parts.push(`${axis}${Number(state[key]).toFixed(2)}`);
  }
  if (!parts.length) return;
  const el = document.getElementById('gcodePayload');
  if (el) el.value = `G92 ${parts.join(' ')}`;
}

function showHomingResult(state) {
  // Raw JSON
  const jsonEl = document.getElementById('result-json');
  if (jsonEl) jsonEl.value = JSON.stringify(state, null, 2);

  // Tree View: Labels + Einheiten statt generischem renderTree
  const treeEl = document.getElementById('result-tree');
  if (treeEl) {
    const LABELS = {
      x_mm:  'x (Slider)', y_deg: 'y (Arm1)',   z_deg: 'z (Ellbow)',
      a_deg: 'a (Arm2)',   b_deg: 'b (Hand)',   c_deg: 'c (Palm)',
      e_mm:  'e (Greifer)',
    };
    const UNITS = {
      x_mm: 'mm', y_deg: '°', z_deg: '°',
      a_deg: '°', b_deg: '°', c_deg: '°', e_mm: 'mm',
    };
    let html = '';
    for (const [key, val] of Object.entries(state)) {
      const label  = LABELS[key] ?? key;
      const unit   = UNITS[key]  ?? '';
      const valStr = typeof val === 'number' ? val.toFixed(2) : String(val ?? '–');
      html += `<div style="display:flex;gap:8px;padding:3px 0;font-family:ui-monospace,monospace;font-size:13px">
        <span style="min-width:130px;color:#94a3b8">${label}</span>
        <span style="font-weight:600">${valStr}&thinsp;${unit}</span>
      </div>`;
    }
    treeEl.innerHTML = html || '<span style="color:#64748b">–</span>';
  }
}

async function loadHomingImages(runDir) {
  if (!runDir) return;
  try {
    const res = await fetch(`/api/homing/run-data?run=${encodeURIComponent(runDir)}`);
    if (!res.ok) return;
    const data = await res.json();

    // ── Debug-Bilder ──────────────────────────────────────────────────────────
    const display = document.getElementById('snapshot-info-picture');
    if (display) {
      const debugImages = (data.images ?? []).filter(img => /debug/i.test(img.filename));
      let html = '<div style="display:flex;flex-direction:column;gap:12px;margin-top:8px">';
      for (const img of debugImages) {
        html += `<figure style="margin:0">
          <img src="data:image/jpeg;base64,${img.contentBase64}"
            style="width:100%;max-width:760px;height:auto;border:1px solid #1f2937;border-radius:4px;display:block"
            alt="${img.filename}">
          <figcaption style="font-size:11px;color:#64748b;margin-top:3px">
            ${img.filename}
          </figcaption>
        </figure>`;
      }
      html += '</div>';
      display.innerHTML = html;
    }

    // ── Marker-CSV-Tabelle ────────────────────────────────────────────────────
    if (data.csvContent) renderHomingCsv(data.csvContent, runDir);
  } catch { /* nicht kritisch */ }
}

function renderHomingCsv(csvContent, runDir) {
  const table  = document.getElementById('snapshot-table');
  const infoEl = document.getElementById('snapshot-info');
  if (!table) return;

  const lines = csvContent.trim().split(/\r?\n/).filter(Boolean);
  if (lines.length < 2) return;

  const headers = lines[0].split(',').map(h => h.trim());
  const rows = lines.slice(1).map(line => {
    const cells = line.split(',');
    const obj = {};
    headers.forEach((h, i) => {
      const raw = (cells[i] ?? '').trim();
      const num = Number(raw);
      obj[h] = raw !== '' && Number.isFinite(num) ? num : raw;
    });
    return obj;
  });

  if (infoEl) infoEl.textContent = `aruco_marker_poses.csv · ${runDir} · ${rows.length} Marker`;

  table.innerHTML = '';

  const thead = document.createElement('thead');
  const headerRow = document.createElement('tr');
  headers.forEach(h => {
    const th = document.createElement('th');
    th.textContent = h;
    headerRow.appendChild(th);
  });
  thead.appendChild(headerRow);
  table.appendChild(thead);

  const tbody = document.createElement('tbody');
  rows.forEach(row => {
    const tr = document.createElement('tr');
    headers.forEach(h => {
      const td = document.createElement('td');
      let v = row[h];
      if (typeof v === 'number') {
        v = /^(marker_id|id|num_cameras|seen_by)$/.test(h) ? Math.round(v) : v.toFixed(1);
      }
      td.textContent = v ?? '';
      tr.appendChild(td);
    });
    tbody.appendChild(tr);
  });
  table.appendChild(tbody);
}

async function runHoming() {
  // UI zurücksetzen
  clearTextarea('log');
  clearTextarea('analysis-log');
  clearTextarea('result-json');
  clearElement('result-tree');
  clearElement('snapshot-table');
  clearElement('snapshot-info-picture');

  const btnRun  = document.getElementById('btn-homing-run');
  const btnSend = document.getElementById('btn-homing-send');
  _homingState  = null;

  if (btnRun)  btnRun.disabled  = true;
  if (btnSend) {
    btnSend.disabled     = true;
    btnSend.style.opacity = '.4';
    btnSend.style.cursor  = 'not-allowed';
  }
  setHomingStatus('● Läuft …', 'wip');
  setHomingProgress(0, 6, 'Verbinde …');

  try {
    const response = await fetch('/api/homing/run', { method: 'POST' });
    if (!response.ok || !response.body) throw new Error(`HTTP ${response.status}`);

    const reader  = response.body.getReader();
    const decoder = new TextDecoder();
    let buf = '';

    while (true) {
      const { done, value } = await reader.read();
      if (done) break;

      buf += decoder.decode(value, { stream: true });
      const lines = buf.split('\n');
      buf = lines.pop();

      for (const line of lines) {
        if (!line.startsWith('data: ')) continue;
        let evt;
        try { evt = JSON.parse(line.slice(6)); } catch { continue; }

        switch (evt.type) {
          case 'log':
            appendLog(evt.text ?? '');
            break;

          case 'step':
            setHomingProgress(evt.step, evt.total, evt.text);
            appendLog(`[${evt.step}/${evt.total}] ${evt.text || ''}`);
            break;

          case 'analysis': {
            const el = document.getElementById('analysis-log');
            if (el) {
              el.value += `${evt.key}:\n${JSON.stringify(evt.value, null, 2)}\n\n`;
              el.scrollTop = el.scrollHeight;
            }
            // Progressiver Skeleton-Update: accumulated_state nach jedem Gelenk
            if (evt.key?.startsWith('state_') && evt.value) {
              const frame = document.getElementById('board-viewer-frame');
              if (frame?.contentWindow) {
                frame.contentWindow.postMessage({ type: 'homing-state', state: evt.value }, '*');
              }
              writePartialGCode(evt.value);
            }
            break;
          }

          case 'error':
            appendLog(evt.text ?? '');
            setHomingStatus('✗ Fehler', 'open');
            break;

          case 'done':
            if (evt.state) {
              _homingState = evt.state;
              showHomingResult(evt.state);
              if (btnSend) {
                btnSend.disabled     = false;
                btnSend.style.opacity = '';
                btnSend.style.cursor  = '';
              }
              setHomingStatus('✓ Fertig', 'done');
              setHomingProgress(6, 6, 'Homing abgeschlossen');
            } else {
              setHomingStatus('✗ Fehler', 'open');
              setHomingProgress(6, 6, 'Fehler aufgetreten');
            }
            if (evt.runDir) await loadHomingImages(evt.runDir);
            const frame = document.getElementById('board-viewer-frame');
            if (frame?.contentWindow) {
              frame.contentWindow.postMessage({ type: 'load-homing-run', runDir: evt.runDir }, '*');
              if (evt.state) {
                frame.contentWindow.postMessage({ type: 'homing-state', state: evt.state }, '*');
              }
            }
            break;
        }
      }
    }
  } catch (err) {
    appendLog(`❌ Verbindungsfehler: ${err.message}`);
    setHomingStatus('✗ Fehler', 'open');
  } finally {
    if (btnRun) btnRun.disabled = false;
  }
}

async function sendHomingToRobot() {
  if (!_homingState) return;
  const btnSend = document.getElementById('btn-homing-send');
  if (btnSend) btnSend.disabled = true;

  try {
    const res = await fetch('/api/homing/send-state', {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify({ state: _homingState }),
    });
    const data = await res.json();
    if (res.ok) {
      appendLog('✅ State erfolgreich an Roboter gesendet');
      setHomingStatus('✓ Gesendet', 'done');
    } else {
      appendLog(`❌ Fehler beim Senden: ${data.error ?? JSON.stringify(data)}`);
      if (btnSend) btnSend.disabled = false;
    }
  } catch (err) {
    appendLog(`❌ Netzwerkfehler: ${err.message}`);
    if (btnSend) btnSend.disabled = false;
  }
}

async function onCommandClick(btn) {
  const cmd = btn.dataset.cmd;
  const payloadSelector = btn.dataset.payload;
  const payload = payloadSelector
    ? document.querySelector(payloadSelector)?.value ?? ""
    : "";

  if (typeof window.sendCommand === "function") {
    try {
      await window.sendCommand(cmd, payload);
      appendLog(`Command gesendet: ${cmd}${payload ? " " + payload : ""}`);
    } catch (err) {
      appendLog(`Command-Fehler: ${err.message}`);
    }
    return;
  }

  appendLog(`Command (kein Transport definiert): ${cmd}${payload ? " " + payload : ""}`);
}

function setupUi() {
  // Homing-Buttons
  const homingRunBtn  = document.getElementById('btn-homing-run');
  if (homingRunBtn)  homingRunBtn.addEventListener('click', runHoming);

  const homingSendBtn = document.getElementById('btn-homing-send');
  if (homingSendBtn) homingSendBtn.addEventListener('click', sendHomingToRobot);

  // Ältere Buttons (Fallback / Debug)
  const calculateBtn = document.getElementById("btn-calculate");
  if (calculateBtn) {
    calculateBtn.addEventListener("click", onCalculateClick);
  }

  const fotoBtn = document.getElementById("btn-foto");
  if (fotoBtn) {
    fotoBtn.addEventListener("click", onFotoClick);
  }

  document.querySelectorAll("button[data-cmd]").forEach(btn => {
    if (btn.id === "btn-calculate") return;
    btn.addEventListener("click", () => onCommandClick(btn));
  });


  // ===== SECTION COLLAPSE/EXPAND =====
  document.querySelectorAll(".section h2").forEach(heading => {
    heading.addEventListener("click", () => {
      const section = heading.closest(".section");
      if (section) {
        section.classList.toggle("collapsed");
      }
    });
    heading.style.cursor = "pointer";
  });

  // Auto-expand when content is written to a collapsed section
  const observer = new MutationObserver((mutations) => {
    mutations.forEach((mutation) => {
      if (mutation.type === "childList" || mutation.type === "characterData") {
        let el = mutation.target;
        while (el && el !== document.body) {
          const section = el.closest(".section");
          if (section && section.classList.contains("collapsed")) {
            section.classList.remove("collapsed");
            break;
          }
          el = el.parentElement;
        }
      }
    });
  });

  // Observer auf alle Elemente in Sections anwenden
  document.querySelectorAll(".section").forEach(section => {
    observer.observe(section, {
      childList: true,
      subtree: true,
      characterData: true,
      characterDataOldValue: false
    });
  });
}

window.addEventListener("DOMContentLoaded", setupUi);