/**
 * Raycaster-basierte Pick-Logik, Hover-Feedback und Farb-Reset.
 *
 * pickAtCurrentMouse() — liest `mouse`-NDC, feuert Raycaster gegen die Layout-relevanten Targets
 * und liefert `{ obj, hovered, hoveredSphereIndex, hoveredInstanceIndex, hitPoint }` oder null.
 * resetAllMeshColors() — setzt Ziel-Farben aller Free-Chart-Typen auf default.
 * updateSurfaceHoverHighlight() — delegiert an Pro-Handler (Surface-only Feature).
 *
 * Dieses Modul enthaelt die Pick-Logik fuer die Standard-Layouts (straight,
 * stacked, pie, wave). Add-on-Layouts werden ueber einen optionalen Handler
 * abgewickelt, den Add-on-Plugins via
 * `window.__CE_PD_SCENE_EXT__.hitTest.createHandler(ctx)` bereitgestellt wird.
 */
export function createHitTesting(ctx) {
  const {
    state,
    getActiveCamera, raycaster, mouse,
    alwaysColorful,
    bars, barInstMeshes,
    pieMeshes,
    waveMeshes = [], waveAreaMeshes = [], waveSphereInstances = [],
    getWaveSphereInstMesh,
    radialMeshes = [],
    getBubblePackInstMesh, bubblePackInstances = [],
    getSurfaceMesh,
  } = ctx;

  const proExt = (typeof window !== 'undefined' && window.__CE_PD_SCENE_EXT__ && window.__CE_PD_SCENE_EXT__.hitTest) || null;
  const proHandler = proExt && typeof proExt.createHandler === 'function' ? proExt.createHandler(ctx) : null;

  let _waveHitTargetsCache = null;

  function resetWaveMeshColors() {
    waveSphereInstances.forEach(inst => {
      inst.targetColor = alwaysColorful ? inst.defaultColor : inst.grayColor;
      inst.targetScale = 1;
    });
    waveMeshes.forEach(wm => { wm.userData._targetSaturation = alwaysColorful ? 1 : 0; });
    waveAreaMeshes.forEach(am => { am.userData._targetSaturation = alwaysColorful ? 1 : 0; });
  }

  function applyWaveHoverColors(hoverInfo) {
    const hoveredSphereIndex = hoverInfo && typeof hoverInfo.hoveredSphereIndex === 'number' ? hoverInfo.hoveredSphereIndex : -1;
    const hoveredSeries = hoveredSphereIndex >= 0 ? (waveSphereInstances[hoveredSphereIndex]?.seriesIndex ?? -1) : -1;
    waveSphereInstances.forEach((inst, i) => {
      const sameSeries = inst.seriesIndex === hoveredSeries;
      inst.targetColor = (alwaysColorful || sameSeries) ? inst.defaultColor : inst.grayColor;
      inst.targetScale = (i === hoveredSphereIndex) ? 1.5 : 1;
    });
    waveMeshes.forEach(wm => {
      const sameSeries = wm.userData._seriesIndex === hoveredSeries;
      wm.userData._targetSaturation = (alwaysColorful || sameSeries) ? 1 : 0;
    });
    waveAreaMeshes.forEach(am => {
      const sameSeries = am.userData._seriesIndex === hoveredSeries;
      am.userData._targetSaturation = (alwaysColorful || sameSeries) ? 1 : 0;
    });
  }

  function resolveWavePick(intersects, obj) {
    if (!intersects || intersects.length === 0) return null;
    const waveSphereInstMesh = typeof getWaveSphereInstMesh === 'function' ? getWaveSphereInstMesh() : null;
    let hoveredData = null;
    let hoveredSphereIndex = -1;
    if (waveSphereInstMesh && obj === waveSphereInstMesh) {
      const iid = intersects[0].instanceId;
      if (iid != null && waveSphereInstances[iid]) {
        const inst = waveSphereInstances[iid];
        hoveredData = inst._DATA[inst.dataIndex];
        hoveredSphereIndex = iid;
      }
    }
    if (!hoveredData && waveMeshes.includes(obj)) {
      const hitPt = intersects[0].point;
      if (waveSphereInstances.length > 0) {
        let minDist = Infinity;
        for (let i = 0; i < waveSphereInstances.length; i++) {
          const d = hitPt.distanceTo(waveSphereInstances[i].position);
          if (d < minDist) { minDist = d; hoveredSphereIndex = i; }
        }
        if (hoveredSphereIndex >= 0) {
          const inst = waveSphereInstances[hoveredSphereIndex];
          hoveredData = inst._DATA[inst.dataIndex];
        }
      } else {
        const pts = obj.userData._dataPoints;
        let minDist = Infinity;
        let localIdx = -1;
        for (let i = 0; i < pts.length; i++) { const d = hitPt.distanceTo(pts[i]); if (d < minDist) { minDist = d; localIdx = i; } }
        if (localIdx >= 0) hoveredData = obj.userData._DATA[localIdx];
      }
    }
    return hoveredData ? { hoveredData, hoveredSphereIndex } : null;
  }

  function getDiagramHitTargets() {
    if (state.layout === 'wave') {
      const waveSphereInstMesh = typeof getWaveSphereInstMesh === 'function' ? getWaveSphereInstMesh() : null;
      if (!_waveHitTargetsCache) {
        _waveHitTargetsCache = waveSphereInstMesh ? [...waveMeshes, waveSphereInstMesh] : [...waveMeshes];
      }
      return _waveHitTargetsCache;
    }
    _waveHitTargetsCache = null;
    if (proHandler && typeof proHandler.getHitTargets === 'function') {
      const t = proHandler.getHitTargets(state.layout);
      if (t) return t;
    }
    if (state.layout === 'pie' || state.layout === 'pieStairs' || state.layout === 'pyramid') return pieMeshes;
    return barInstMeshes;
  }

  /**
   * @param {THREE.Intersection[]} intersects
   * @returns {{ obj: THREE.Object3D, hovered: THREE.Object3D | { userData: object }, hoveredSphereIndex: number, hoveredInstanceIndex: number, hitPoint: THREE.Vector3 } | null}
   */
  function resolvePickFromIntersects(intersects) {
    if (intersects.length === 0) return null;
    const hitPoint = intersects[0].point.clone();
    const obj = intersects[0].object;
    let hoveredData = null;
    let hoveredSphereIndex = -1;
    let hoveredInstanceIndex = -1;

    if (state.layout === 'wave') {
      const res = resolveWavePick(intersects, obj);
      if (res) {
        hoveredData = res.hoveredData;
        hoveredSphereIndex = res.hoveredSphereIndex;
      }
    } else if (proHandler && typeof proHandler.resolvePick === 'function') {
      const res = proHandler.resolvePick(state.layout, intersects, obj);
      if (res && typeof res === 'object') {
        hoveredData = res.hoveredData != null ? res.hoveredData : null;
        if (typeof res.hoveredSphereIndex === 'number') hoveredSphereIndex = res.hoveredSphereIndex;
        if (typeof res.hoveredInstanceIndex === 'number') hoveredInstanceIndex = res.hoveredInstanceIndex;
      }
    }

    if (!hoveredData && (state.layout === 'pie' || state.layout === 'pieStairs' || state.layout === 'pyramid') && pieMeshes.includes(obj)) {
      const di = obj.userData._dataIndex;
      if (state.hiddenDataIndices && state.hiddenDataIndices.has(di)) return null;
      hoveredData = obj.userData._DATA[di];
    }

    if (!hoveredData && barInstMeshes.length && barInstMeshes.includes(obj)) {
      const iid = intersects[0].instanceId;
      const gi = obj.userData._barIndices?.[iid];
      if (gi != null && state.hiddenDataIndices && state.hiddenDataIndices.has(gi)) return null;
      if (gi != null && bars[gi]) { hoveredData = bars[gi].meshUserData; hoveredInstanceIndex = gi; }
    }
    const hovered = hoveredData ? { userData: hoveredData } : obj;
    return { obj, hovered, hoveredSphereIndex, hoveredInstanceIndex, hitPoint };
  }

  function pickAtCurrentMouse() {
    const activeCamera = getActiveCamera();
    activeCamera.updateMatrixWorld(true);
    raycaster.setFromCamera(mouse, activeCamera);
    const targets = getDiagramHitTargets();
    if (state.layout === 'stacked') {
      for (const im of barInstMeshes) im.boundingSphere = null;
    }
    const hits = raycaster.intersectObjects(targets, true);
    return resolvePickFromIntersects(hits);
  }

  function resetAllMeshColors() {
    bars.forEach(bar => {
      if (bar.meshUserData) {
        bar.meshUserData._targetColor = alwaysColorful ? bar.meshUserData._defaultColor : bar.meshUserData.color;
      }
    });
    pieMeshes.forEach(m => { m.userData._targetColor = alwaysColorful ? m.userData._defaultColor : m.userData.color; });
    if (state.layout === 'wave' || waveSphereInstances.length > 0 || waveMeshes.length > 0) {
      resetWaveMeshColors();
    }
    if (proHandler && typeof proHandler.resetMeshColors === 'function') {
      proHandler.resetMeshColors();
    }
  }

  function updateSurfaceHoverHighlight(pick) {
    if (proHandler && typeof proHandler.updateSurfaceHoverHighlight === 'function') {
      proHandler.updateSurfaceHoverHighlight(pick);
    }
  }

  function layoutIsNonBar() {
    if (state.layout === 'pie' || state.layout === 'pieStairs' || state.layout === 'pyramid' || state.layout === 'wave') return true;
    if (proHandler && typeof proHandler.layoutIsNonBar === 'function') return proHandler.layoutIsNonBar(state.layout) === true;
    return false;
  }

  function applyHoverColors(hoverInfo) {
    if (state.layout === 'pie' || state.layout === 'pieStairs' || state.layout === 'pyramid') {
      const obj = hoverInfo && hoverInfo.obj;
      pieMeshes.forEach(m => { m.userData._targetColor = alwaysColorful ? m.userData._defaultColor : (m === obj) ? m.userData._defaultColor : m.userData.color; });
      return;
    }
    if (state.layout === 'wave') {
      applyWaveHoverColors(hoverInfo);
      return;
    }
    if (proHandler && typeof proHandler.applyHoverColors === 'function') {
      proHandler.applyHoverColors(state.layout, hoverInfo);
    }
  }

  return { pickAtCurrentMouse, resetAllMeshColors, updateSurfaceHoverHighlight, layoutIsNonBar, applyHoverColors };
}
