/**
 * yAxisComputeJs.test.js
 * =======================
 * Unit-Test für public/yAxisCompute.js (reine JS-Berechnungslogik).
 *
 * Dieselben drei Timestamps wie der Python-Test:
 *   test/y-axis-finder-examples/20260612_190019  (Pos A)
 *   test/y-axis-finder-examples/20260612_190104  (Pos B)
 *   test/y-axis-finder-examples/20260612_190241  (Pos C)
 *
 * Erwartete Werte (aus verifiziertem Python-Lauf und boardViewer-Logik):
 *   Marker 197, 218, 219  → rotieren  (3 Marker genutzt)
 *   Marker 201, 204, 242  → gefiltert (Bewegung < 10 mm)
 *   axisDir ≈ [0.9995, 0.029, -0.015]  (fast reine X-Achse des Roboters)
 *
 * Hinweis: Im Gegensatz zum Python-Skript verwendet computeYAxis() nur die
 * Marker-Zentren (position_mm), nicht alle vier Ecken.  Deshalb:
 *   numMarkers = 3  (nicht 5 wie Python)
 *   Residuen etwas größer (Python hat 4× mehr Punkte je Marker)
 */

const fs   = require('fs');
const path = require('path');

const { computeYAxis, DEFAULT_MIN_MOVEMENT_MM } = require('../public/yAxisCompute');

// ── Hilfsfunktionen ───────────────────────────────────────────────────────────

const EXAMPLES = path.join(__dirname, 'y-axis-finder-examples');

/** Lädt aruco_marker_poses.json und gibt die fremd-Marker zurück (link !== 'Board'). */
function loadFremdMarkers(timestamp) {
  const file = path.join(EXAMPLES, timestamp, 'aruco_marker_poses.json');
  const data = JSON.parse(fs.readFileSync(file, 'utf8'));
  return (data.markers ?? []).filter(m => m.link !== 'Board');
}

const fremdA = loadFremdMarkers('20260612_190019');
const fremdB = loadFremdMarkers('20260612_190104');
const fremdC = loadFremdMarkers('20260612_190241');

// ── Haupt-Suite ───────────────────────────────────────────────────────────────

describe('computeYAxis – Arm1-Testdaten (190019 / 190104 / 190241)', () => {

  let r;
  beforeAll(() => {
    r = computeYAxis(fremdA, fremdB, fremdC);
  });

  // Grundstatus
  test('Berechnung erfolgreich (ok: true)', () => {
    expect(r.ok).toBe(true);
  });

  // Marker-Anzahlen
  test('numMarkersCommon erfasst alle gemeinsamen fremd-Marker', () => {
    // Alle drei Timestamps haben dieselben 6 fremd-Marker in common:
    // 197, 201, 204, 218, 219, 242
    expect(r.numMarkersCommon).toBeGreaterThanOrEqual(4);
  });

  test('3 Marker tatsächlich genutzt (197, 218, 219)', () => {
    expect(r.numMarkers).toBe(3);
    const usedIds = r.markerData.map(m => m.markerId).sort((a, b) => a - b);
    expect(usedIds).toEqual([197, 218, 219]);
  });

  // Filterung
  test('kaum-bewegende Marker werden gefiltert (201, 204 mindestens)', () => {
    const skippedIds = r.skipped.map(s => s.id).sort((a, b) => a - b);
    expect(skippedIds).toContain(201);
    expect(skippedIds).toContain(204);
  });

  test('gefilterte Marker haben Bewegung < DEFAULT_MIN_MOVEMENT_MM', () => {
    r.skipped.forEach(s => {
      expect(s.maxMoveMm).toBeLessThan(DEFAULT_MIN_MOVEMENT_MM);
      expect(s.reason).toMatch(/Bewegung zu gering/);
    });
  });

  test('gefilterte Marker enthalten posA (für späteres Base-Zuordnen)', () => {
    r.skipped.forEach(s => {
      expect(Array.isArray(s.posA)).toBe(true);
      expect(s.posA).toHaveLength(3);
    });
  });

  // Achsenrichtung
  test('axisDir ist Einheitsvektor (|axisDir| ≈ 1)', () => {
    const [ax, ay, az] = r.axisDir;
    const len = Math.sqrt(ax * ax + ay * ay + az * az);
    expect(len).toBeCloseTo(1.0, 4);
  });

  test('Achse liegt fast auf X-Achse des Roboters (axisDir[0] > 0.99)', () => {
    // Arm1 schwingt auf/ab → Drehachse = X-Richtung
    expect(r.axisDir[0]).toBeGreaterThan(0.99);
  });

  test('axisDir hat 3 Komponenten', () => {
    expect(r.axisDir).toHaveLength(3);
  });

  test('axisPoint hat 3 Komponenten', () => {
    expect(r.axisPoint).toHaveLength(3);
  });

  // Kippwinkel
  test('tiltXY und tiltYZ sind finite Zahlen', () => {
    expect(Number.isFinite(r.tiltXY)).toBe(true);
    expect(Number.isFinite(r.tiltYZ)).toBe(true);
  });

  // markerData-Struktur
  test('jeder markerData-Eintrag hat posA/posB/posC/circumcenter/normal', () => {
    r.markerData.forEach(md => {
      expect(md.posA).toHaveLength(3);
      expect(md.posB).toHaveLength(3);
      expect(md.posC).toHaveLength(3);
      expect(md.circumcenter).toHaveLength(3);
      expect(md.normal).toHaveLength(3);
    });
  });
});

// ── Parametertest: minMovementMm ──────────────────────────────────────────────

describe('computeYAxis – minMovementMm-Parameter', () => {

  test('minMovementMm = 0 → alle gemeinsamen Marker werden genutzt, keine Skips', () => {
    const r = computeYAxis(fremdA, fremdB, fremdC, { minMovementMm: 0 });
    expect(r.ok).toBe(true);
    // Kein Marker sollte wegen Bewegung gefiltert sein
    const movementSkips = r.skipped.filter(s => s.reason.includes('Bewegung zu gering'));
    expect(movementSkips).toHaveLength(0);
    // Mehr Marker als im Standardfall
    expect(r.numMarkers).toBeGreaterThanOrEqual(3);
  });

  test('minMovementMm = 9999 → alle Marker gefiltert, ok: false', () => {
    const r = computeYAxis(fremdA, fremdB, fremdC, { minMovementMm: 9999 });
    expect(r.ok).toBe(false);
    expect(r.reason).toMatch(/gefiltert|gefunden/i);
    expect(Array.isArray(r.skipped)).toBe(true);
  });

  test('DEFAULT_MIN_MOVEMENT_MM ist 10', () => {
    expect(DEFAULT_MIN_MOVEMENT_MM).toBe(10.0);
  });
});

// ── Edge-Cases ────────────────────────────────────────────────────────────────

describe('computeYAxis – Edge Cases', () => {

  test('leere Eingaben → ok: false', () => {
    const r = computeYAxis([], [], []);
    expect(r.ok).toBe(false);
  });

  test('keine gemeinsamen Marker → ok: false', () => {
    const a = [{ marker_id: 1, position_mm: [0, 0, 0] }];
    const b = [{ marker_id: 2, position_mm: [1, 0, 0] }];
    const c = [{ marker_id: 3, position_mm: [2, 0, 0] }];
    const r = computeYAxis(a, b, c);
    expect(r.ok).toBe(false);
  });

  test('drei kollineare Punkte → degenerat, ok: false (einzelner Marker)', () => {
    // Marker bewegt sich auf einer Linie → Kreisebene undefiniert
    const a = [{ marker_id: 99, position_mm: [0,   0, 100] }];
    const b = [{ marker_id: 99, position_mm: [0, 100, 100] }];  // nur Y ändert sich
    const c = [{ marker_id: 99, position_mm: [0, 200, 100] }];  // weiterhin nur Y
    // Bewegung ist 200 mm (> 10 mm Threshold), aber Punkte sind kollinear
    const r = computeYAxis(a, b, c, { minMovementMm: 0 });
    expect(r.ok).toBe(false);
  });

  test('ein einzelner gültiger Marker → Achse berechenbar', () => {
    // Punkt bewegt sich auf einem Kreisbogen (einfache 2D-Rotation um Z-Achse)
    const R = 100;   // Radius 100 mm
    const angle = (deg) => deg * Math.PI / 180;
    const mk = (deg) => ({
      marker_id: 42,
      position_mm: [R * Math.cos(angle(deg)), R * Math.sin(angle(deg)), 50],
    });
    const r = computeYAxis([mk(0)], [mk(90)], [mk(180)]);
    expect(r.ok).toBe(true);
    expect(r.numMarkers).toBe(1);
    // Achse sollte in Z-Richtung zeigen [0, 0, ±1]
    expect(Math.abs(r.axisDir[2])).toBeGreaterThan(0.99);
  });
});
