/*
 * Wave-Layout Label-Fabriken (Knoten-, Serien-End- und Annotations-Labels).
 * Registriert unter `window.__CE_PD_SCENE_EXT__.labels` (merge, kein Replace).
 * Helfer aus `window.__CE_PD_LABEL_HELPERS__` (gesetzt von groupedBarLabels.js / init).
 */

const w = (typeof window !== 'undefined') ? window : globalThis;
if (!w.__CE_PD_SCENE_EXT__) w.__CE_PD_SCENE_EXT__ = {};

const WAVE_NODE_LABEL_X_GAP       = 0.04;
const WAVE_NODE_LABEL_Z_OFFSET    = 0.06;
const WAVE_SERIES_END_LABEL_Y_DROP = 0.12;
const WAVE_SERIES_LABEL_X_GAP     = 0.12;
const ANNOTATION_TITLE_FONT_RATIO = 37 / 42;
const ANNOTATION_DESC_FONT_RATIO  = 27 / 42;
const ANNOTATION_MAX_WIDTH_WORLD  = 1.7;
const ANNOTATION_LINE_LENGTH      = 0.25;
const ANNOTATION_LINE_THICKNESS   = 0.006;
const ANNOTATION_LABEL_GAP        = 0.02;

function mergeWaveLabels(fns) {
  const prev = w.__CE_PD_SCENE_EXT__.labels && typeof w.__CE_PD_SCENE_EXT__.labels === 'object'
    ? w.__CE_PD_SCENE_EXT__.labels
    : {};
  w.__CE_PD_SCENE_EXT__.labels = { ...prev, ...fns };
}

function tryBindAndRegister() {
  const H = w.__CE_PD_LABEL_HELPERS__;
  if (!H) return false;

  const {
    STRAIGHT_XLABEL_PLANE_H,
    HEADLINE_FONT_RATIO,
    resolveFormatChartNumber,
    dataLabelFs,
    hexForBarLabel,
    rawTextFor,
    clampTextChars,
    buildBarTopLabel,
    updateBarValueSegmentGroup,
    makeLabelGroup,
    isEmptyBar,
    getBarValueLabelParts,
    getFallbackChartNumberFormatter,
    createTroikaLabel,
    createOverlayLineMaterial,
    updateTroikaLabel,
    getLabelBounds,
  } = H;

  function applyWaveNodeLabelPose(mesh, sphere) {
    const pw = mesh.userData._planeW;
    const sphereR = mesh.userData._waveNodeSphereRadius ?? 0.05;
    if (pw == null || !sphere) return;
    const cx = sphere.position.x + sphereR + WAVE_NODE_LABEL_X_GAP;
    const yDrop = sphere.userData._isSeriesEnd ? WAVE_SERIES_END_LABEL_Y_DROP : 0;
    const cy = sphere.position.y - yDrop;
    const cz = sphere.position.z + WAVE_NODE_LABEL_Z_OFFSET;
    mesh.position.set(cx, cy, cz);
    mesh.quaternion.identity();
  }

  function createWaveNodeLabels(ctx) {
    const {
      THREE, DATA, wavePoints,
      barLabelColor, rulerColor,
      mode = 'headline', axisYPrefix = '', axisYSuffix = '',
    } = ctx;
    const ft = resolveFormatChartNumber(ctx);
    const fs = dataLabelFs(ctx);
    const rulerColorHex = hexForBarLabel(barLabelColor, rulerColor);
    const group = makeLabelGroup(THREE);
    const meshes = [];
    DATA.forEach((item, i) => {
      if (isEmptyBar(item.value)) return;
      const pt = wavePoints[i];
      if (!pt) return;
      const built = buildBarTopLabel(THREE, rulerColorHex, item, mode, axisYPrefix, axisYSuffix, ft, fs, { anchorMode: 'left' });
      if (!built) return;
      const { mesh, planeW, planeH } = built;
      mesh.userData._dataIndex = i;
      mesh.userData._waveNodeLabelMode = mode === 'value' ? 'value' : 'headline';
      const sr = ctx.waveSphereRadius;
      mesh.userData._waveNodeSphereRadius = typeof sr === 'number' ? sr : 0.05;
      mesh.userData._frustumHalfW = planeW * 0.55 + WAVE_NODE_LABEL_X_GAP;
      mesh.userData._frustumHalfH = planeH * 0.55;
      mesh.userData._afterSized = (m) => {
        m.userData._frustumHalfW = m.userData._planeW * 0.55 + WAVE_NODE_LABEL_X_GAP;
        m.userData._frustumHalfH = m.userData._planeH * 0.55;
      };
      meshes.push(mesh);
      group.add(mesh);
      const instD = ctx.waveSphereInstances && ctx.waveSphereInstances[i];
      if (instD) {
        const proxy = { position: instD.position, userData: { _isSeriesEnd: instD.isSeriesEnd } };
        applyWaveNodeLabelPose(mesh, proxy);
      }
    });
    return { group, meshes };
  }

  function createWaveSeriesLabels(ctx) {
    const { THREE, seriesEndInfo } = ctx;
    const fs = dataLabelFs(ctx);
    const fontSize = STRAIGHT_XLABEL_PLANE_H * HEADLINE_FONT_RATIO * fs;
    const group = makeLabelGroup(THREE);
    const meshes = [];
    for (const info of seriesEndInfo) {
      const text = String(info.groupKey || '').trim();
      if (!text) continue;
      const mesh = createTroikaLabel({
        THREE, text: clampTextChars(text, 36), color: info.color, fontSize,
        anchorX: 'left', anchorY: 'middle', weight: 'bold',
        renderOrder: 1000,
        onSync: (m) => {
          const b = getLabelBounds(m);
          m.userData._planeW = b.width;
          m.userData._planeH = b.height;
        },
      });
      if (mesh.material) mesh.material.opacity = 1;
      mesh.position.set(info.position.x + WAVE_SERIES_LABEL_X_GAP, info.position.y, info.position.z + 0.02);
      mesh.quaternion.identity();
      const est = getLabelBounds(mesh);
      mesh.userData._planeW = est.width;
      mesh.userData._planeH = est.height;
      meshes.push(mesh);
      group.add(mesh);
    }
    return { group, meshes };
  }

  function createWaveAnnotations(ctx) {
    const {
      THREE, DATA, wavePoints, waveSphereInstances,
      barLabelColor, rulerColor,
      annotationLineLength, annotationFontScale,
    } = ctx;
    const fs = dataLabelFs(ctx);
    const rulerColorHex = hexForBarLabel(barLabelColor, rulerColor);
    const lineColorNum = new THREE.Color(rulerColorHex).getHex();
    const lineLen = typeof annotationLineLength === 'number' ? annotationLineLength : ANNOTATION_LINE_LENGTH;
    const fontScale = typeof annotationFontScale === 'number' ? annotationFontScale : 1;
    const titleFontSize = STRAIGHT_XLABEL_PLANE_H * ANNOTATION_TITLE_FONT_RATIO * fs * fontScale;
    const descFontSize  = STRAIGHT_XLABEL_PLANE_H * ANNOTATION_DESC_FONT_RATIO  * fs * fontScale;
    const dx = Math.cos(Math.PI / 4) * lineLen;
    const dy = Math.sin(Math.PI / 4) * lineLen;
    const group = new THREE.Group();
    group.layers.set(1);
    group.renderOrder = 1002;
    const meshes = [];
    const lineMeshes = [];
    DATA.forEach((item, i) => {
      const title = String(item.annotation || '').trim();
      if (!title) return;
      const desc = String(item.annotationDesc || '').trim();
      const instD = waveSphereInstances && waveSphereInstances[i];
      const pt = instD ? instD.position : (wavePoints && wavePoints[i]);
      if (!pt) return;
      const sx = pt.x; const sy = pt.y; const sz = pt.z;
      const ex = sx - dx;
      const ey = sy + dy;
      const mx = (sx + ex) / 2;
      const my = (sy + ey) / 2;
      const lineGeo = new THREE.BoxGeometry(lineLen, ANNOTATION_LINE_THICKNESS, ANNOTATION_LINE_THICKNESS);
      const lineMat = createOverlayLineMaterial(THREE, lineColorNum, 0);
      const lineMesh = new THREE.Mesh(lineGeo, lineMat);
      lineMesh.position.set(mx, my, sz);
      lineMesh.rotation.z = Math.PI * 3 / 4;
      lineMesh.renderOrder = 1002;
      lineMesh.userData._dataIndex = i;
      lineMeshes.push(lineMesh);
      group.add(lineMesh);
      const titleMesh = createTroikaLabel({
        THREE, text: title, color: rulerColorHex, fontSize: titleFontSize,
        anchorX: 'right', anchorY: 'bottom', weight: 'bold',
        maxWidth: ANNOTATION_MAX_WIDTH_WORLD * fontScale,
        textAlign: 'right', renderOrder: 1002,
      });
      if (titleMesh.material) titleMesh.material.opacity = 0;
      titleMesh.userData._dataIndex = i;
      titleMesh.position.set(ex - ANNOTATION_LABEL_GAP, ey, sz);
      titleMesh.quaternion.identity();
      meshes.push(titleMesh);
      group.add(titleMesh);
      if (desc) {
        const descMesh = createTroikaLabel({
          THREE, text: desc, color: rulerColorHex, fontSize: descFontSize,
          anchorX: 'right', anchorY: 'top', weight: 'light',
          maxWidth: ANNOTATION_MAX_WIDTH_WORLD * fontScale,
          textAlign: 'right', renderOrder: 1002,
        });
        if (descMesh.material) {
          descMesh.material.opacity = 0;
          descMesh.fillOpacity = 0.72;
        }
        descMesh.userData._dataIndex = i;
        descMesh.userData._isAnnotationDesc = true;
        descMesh.position.set(ex - ANNOTATION_LABEL_GAP, ey, sz);
        descMesh.quaternion.identity();
        meshes.push(descMesh);
        group.add(descMesh);
      }
    });
    return { group, meshes, lineMeshes };
  }

  function _updateTopLabelColorsCore(meshes, barLabelColor, THREE, DATA, axisYPrefix, axisYSuffix, dataLabelFontScale, modeKey, formatChartNumber) {
    if (!meshes?.length) return;
    const ft = typeof formatChartNumber === 'function' ? formatChartNumber : getFallbackChartNumberFormatter().formatTick;
    const fs = dataLabelFs({ dataLabelFontScale });
    const fontSize = STRAIGHT_XLABEL_PLANE_H * HEADLINE_FONT_RATIO * fs;
    const rulerColorHex = hexForBarLabel(barLabelColor, null);
    meshes.forEach((mesh) => {
      const i = mesh.userData._dataIndex;
      const mode = mesh.userData[modeKey];
      if (i == null || DATA[i] == null || (mode !== 'headline' && mode !== 'value')) return;
      if (mode === 'value' && mesh.isGroup && mesh.userData._tickPair) {
        const parts = getBarValueLabelParts(DATA[i], axisYPrefix, axisYSuffix, ft);
        if (!parts || !parts.numberStr) return;
        updateBarValueSegmentGroup(mesh, parts, rulerColorHex, fontSize);
        return;
      }
      const rawText = rawTextFor(DATA[i], mode, axisYPrefix, axisYSuffix, ft);
      if (!rawText) return;
      updateTroikaLabel(mesh, {
        text: clampTextChars(rawText, 36),
        color: rulerColorHex,
        fontSize,
      });
    });
  }

  function updateWaveNodeLabelColors(meshes, barLabelColor, THREE, DATA, axisYPrefix, axisYSuffix, dataLabelFontScale, formatChartNumber) {
    _updateTopLabelColorsCore(meshes, barLabelColor, THREE, DATA, axisYPrefix, axisYSuffix, dataLabelFontScale, '_waveNodeLabelMode', formatChartNumber);
  }

  mergeWaveLabels({
    createWaveNodeLabels,
    createWaveSeriesLabels,
    createWaveAnnotations,
    updateWaveNodeLabelColors,
    applyWaveNodeLabelPose,
  });
  return true;
}

const WAVE_API = [
  'createWaveNodeLabels',
  'createWaveSeriesLabels',
  'createWaveAnnotations',
  'updateWaveNodeLabelColors',
  'applyWaveNodeLabelPose',
];

if (!tryBindAndRegister()) {
  const lazy = {};
  for (const name of WAVE_API) {
    lazy[name] = (...args) => {
      if (tryBindAndRegister()) {
        const fn = w.__CE_PD_SCENE_EXT__.labels[name];
        if (typeof fn === 'function') return fn(...args);
      }
      return null;
    };
  }
  mergeWaveLabels(lazy);
}
