/*
 * Hibernate/Wake, Deep-Dispose, Context-Loss, Leftover-Reinit-Snapshot (PLAN 9C).
 * `mountDiagramViewportCanvas` vor OrbitControls; `createDiagramViewportLifecycle` danach.
 */

import {
  IS_MOBILE_GLOBAL,
  cePdDebugWebgl,
  enqueueSerializedWake,
  getDeepDisposeDelayMs,
  registerPendingDeepDispose,
  unregisterPendingDeepDispose,
  noteSharedRendererMaybeLost,
} from './rendererPool.js';
import {
  HIBERNATE_SNAPSHOT_MAX_SIDE_MOBILE,
  HIBERNATE_SNAPSHOT_WEBP_QUALITY,
} from '../shared/config.js';
import { onAllLabelsSynced, getTroikaDebugStats } from './troikaLabel.js';
import {
  applyPostSnapshotOverlayCamera,
  CE_PD_SNAPSHOT_OVERLAY_CLASS,
  flashOwnCanvasCompositingLayer,
  scheduleSnapshotOverlayDomRemoval,
} from './snapshotOverlayFinish.js';

const SNAPSHOT_OVERLAY_TROIKA_MS = 4000;
const CTX_RECOVER_TIMEOUT_MS = 3400;

export function mountDiagramViewportCanvas({ container, wrapper, ownCanvas }) {
  const leftoverSnapshots = [...container.querySelectorAll('img.ce-diagram-hibernate-snapshot')];
  if (leftoverSnapshots.length && leftoverSnapshots[0].parentNode === container) {
    container.insertBefore(ownCanvas, leftoverSnapshots[0]);
  } else container.appendChild(ownCanvas);
  if (leftoverSnapshots.length) {
    try { wrapper.classList.add(CE_PD_SNAPSHOT_OVERLAY_CLASS); } catch (_) { /* ignore */ }
  }
  return { leftoverSnapshots };
}

function createRendererLifecycle(cfg) {
  const {
    OrbitControls, wrapper, container, ownerDoc, scene, ownCanvas, state, camCtl,
    IS_IOS_GLOBAL, isDiagramFullscreen, blitToOwnCanvas, markDirty,
    acquireRenderer, releaseRenderer, registerActiveRenderer, unregisterActiveRenderer,
    getRenderer, setRenderer, getControls, setControls, getActiveCamera,
    getAnimate, getCleanup, getRecalcFit, attachControlsEventListeners,
    onWakeSkipCameraRefit, tapToInteractMobile = false, renderLifecycleOps,
    invalidateLayoutFrameCache = () => {},
  } = cfg;

  function renderCompositeOrFallback(r) {
    try {
      if (renderLifecycleOps && typeof renderLifecycleOps.renderComposite === 'function') {
        renderLifecycleOps.renderComposite(r);
      } else {
        r.render(scene, getActiveCamera());
        blitToOwnCanvas();
      }
    } catch (_) { /* best-effort */ }
  }

  let hibernateControlsTarget = null;
  let hibernateCameraPos = null;
  let snapshotImg = null;
  let ctxRecoverTimer = null;

  function swapCanvasToSnapshot() {
    if (snapshotImg) return;
    if (!ownCanvas.parentNode) return;
    let dataUrl = '';
    let snapSrc = ownCanvas;
    let snapTmp = null;
    if (IS_MOBILE_GLOBAL) {
      const maxSide = HIBERNATE_SNAPSHOT_MAX_SIDE_MOBILE;
      const ow = ownCanvas.width;
      const oh = ownCanvas.height;
      if (ow > 0 && oh > 0) {
        const scale = Math.min(1, maxSide / ow, maxSide / oh);
        if (scale < 1) {
          snapTmp = ownerDoc.createElement('canvas');
          snapTmp.width = Math.max(1, Math.round(ow * scale));
          snapTmp.height = Math.max(1, Math.round(oh * scale));
          const tctx = snapTmp.getContext('2d');
          if (tctx) {
            tctx.imageSmoothingEnabled = true;
            tctx.drawImage(ownCanvas, 0, 0, snapTmp.width, snapTmp.height);
            snapSrc = snapTmp;
          }
        }
      }
    }
    try {
      dataUrl = snapSrc.toDataURL('image/webp', HIBERNATE_SNAPSHOT_WEBP_QUALITY);
      if (!dataUrl || dataUrl.length < 32 || dataUrl.startsWith('data:,')) dataUrl = snapSrc.toDataURL('image/png');
    } catch (_) {
      try { dataUrl = snapSrc.toDataURL('image/png'); } catch (__) { return; }
    }
    if (!dataUrl || dataUrl.length < 32) return;
    const img = ownerDoc.createElement('img');
    img.src = dataUrl;
    img.alt = '';
    img.decoding = 'sync';
    img.setAttribute('aria-hidden', 'true');
    Object.assign(img.style, { position: 'absolute', inset: '0', width: '100%', height: '100%', pointerEvents: 'none' });
    img.className = 'ce-diagram-hibernate-snapshot';
    container.insertBefore(img, ownCanvas);
    snapshotImg = img;
    try { ownCanvas.width = 1; ownCanvas.height = 1; } catch (_) { /* ignore */ }
    if (ownCanvas.parentNode) ownCanvas.parentNode.removeChild(ownCanvas);
  }

  function swapSnapshotToCanvas() {
    if (!snapshotImg) return;
    if (!ownCanvas.parentNode) container.insertBefore(ownCanvas, snapshotImg);
    const img = snapshotImg;
    snapshotImg = null;
    try { markDirty(12); } catch (_) { /* ignore */ }
    scheduleSnapshotOverlayDomRemoval([img], () => {
      try { wrapper.classList.remove('ce-diagram-wrapper--hibernating', CE_PD_SNAPSHOT_OVERLAY_CLASS); } catch (_) { /* ignore */ }
    });
  }

  function bindRendererEvents(r, on) {
    const fn = on ? 'addEventListener' : 'removeEventListener';
    r.domElement[fn]('webglcontextlost', onContextLost, false);
    r.domElement[fn]('webglcontextrestored', onContextRestored, false);
  }

  function hibernate(forceForBudget) {
    if (state.hibernated) return;
    // Budget-Eviction: kein Blockieren bei Fullscreen/Interaktion (iOS max. 1 Kontext).
    if (!forceForBudget && isDiagramFullscreen()) return;
    if (!forceForBudget && state.isInteracting) return;
    if (state.contextLost && !forceForBudget) return;
    if (forceForBudget && state.isInteracting) state.isInteracting = false;
    const renderer = getRenderer();
    const controls = getControls();
    try { invalidateLayoutFrameCache(); } catch (_) { /* best-effort */ }
    try { camCtl.collapseSmoothing(); } catch (_) { /* best-effort */ }
    renderCompositeOrFallback(renderer);
    if (state.animId != null) { cancelAnimationFrame(state.animId); state.animId = null; }
    bindRendererEvents(renderer, false);
    hibernateControlsTarget = controls.target.clone();
    hibernateCameraPos = getActiveCamera().position.clone();
    controls.dispose();
    releaseRenderer(renderer);
    unregisterActiveRenderer(wrapper);
    state.contextLost = false;
    state.hibernated = true;
    wrapper.classList.add('ce-diagram-wrapper--hibernating', CE_PD_SNAPSHOT_OVERLAY_CLASS, 'loaded');
    try { swapCanvasToSnapshot(); } catch (_) { /* ignore */ }
    if (state.deepDisposeTimer != null) clearTimeout(state.deepDisposeTimer);
    const deepDelay = getDeepDisposeDelayMs(IS_IOS_GLOBAL, ownerDoc);
    state.deepDisposeTimer = setTimeout(() => {
      state.deepDisposeTimer = null;
      performDeepDispose();
    }, deepDelay);
    registerPendingDeepDispose(wrapper, {
      getRatio: () => (typeof state.intersectionRatio === 'number' ? state.intersectionRatio : 0),
      performDeepDispose: () => { performDeepDispose(); },
    });
  }

  function performDeepDispose() {
    if (state.disposed) { unregisterPendingDeepDispose(wrapper); return; }
    if (!state.hibernated) { unregisterPendingDeepDispose(wrapper); return; }
    if (isDiagramFullscreen()) return;
    if (state.blockVisible) return;
    if (state.deepDisposeTimer != null) { clearTimeout(state.deepDisposeTimer); state.deepDisposeTimer = null; }
    unregisterPendingDeepDispose(wrapper);
    try {
      if (wrapper._cePdReinitIo && typeof wrapper._cePdReinitIo.disconnect === 'function') wrapper._cePdReinitIo.disconnect();
      wrapper._cePdReinitIo = null;
    } catch (_) { /* ignore */ }
    const resume = {
      camera: {
        position: hibernateCameraPos
          ? { x: hibernateCameraPos.x, y: hibernateCameraPos.y, z: hibernateCameraPos.z }
          : null,
        target: hibernateControlsTarget
          ? { x: hibernateControlsTarget.x, y: hibernateControlsTarget.y, z: hibernateControlsTarget.z }
          : null,
        isOrthographic: state.isOrthographic === true,
        userZoomFactorPersp: state.userZoomFactorPersp,
        // PT1-Seed (layoutDistancePersp) — vermeidet harten Sprung nach Re-Init bei neuer desired-Pose.
        fitDistPersp: state.layoutDistancePersp,
      },
    };
    const cleanup = getCleanup();
    try { if (cleanup) cleanup({ keepSnapshot: true }); } catch (_) { /* ignore */ }
    try { wrapper._ceDiagramCleanup = null; } catch (_) { /* ignore */ }
    wrapper._cePdResumeState = resume;
    wrapper.removeAttribute('data-ce-init');
    if (tapToInteractMobile) {
      wrapper.setAttribute('data-ce-pd-await-tap', '1');
      try {
        const ta = wrapper.querySelector('.ce-diagram-top-actions');
        if (ta) {
          ta.classList.add('ce-diagram-top-actions--ce-suppressed');
          ta.setAttribute('aria-hidden', 'true');
        }
      } catch (_) { /* ignore */ }
      try {
        const snap = wrapper.querySelector('.ce-diagram-hibernate-snapshot');
        if (snap) { snap.style.pointerEvents = 'auto'; snap.style.cursor = 'pointer'; }
      } catch (_) { /* ignore */ }
      try {
        const w = ownerDoc.defaultView || window;
        if (typeof w.__cePdEnsureDeepDisposeTapListener === 'function') w.__cePdEnsureDeepDisposeTapListener(ownerDoc);
      } catch (_) { /* ignore */ }
    } else {
      try {
        const w = ownerDoc.defaultView || window;
        const IOCtor = w.IntersectionObserver;
        if (IOCtor && typeof w.__cePdReInit === 'function') {
          const reinitIo = new IOCtor((entries) => {
            if (!entries || !entries.length) return;
            if (!entries[0].isIntersecting) return;
            try { reinitIo.disconnect(); } catch (__) { /* ignore */ }
            wrapper._cePdReinitIo = null;
            try { w.__cePdReInit(wrapper); } catch (__) { /* ignore */ }
          }, { rootMargin: '1000px 0px', threshold: 0 });
          reinitIo.observe(wrapper);
          wrapper._cePdReinitIo = reinitIo;
        }
      } catch (_) { /* ignore */ }
    }
  }

  function wake() {
    if (!state.hibernated) return;
    if (state.deepDisposeTimer != null) { clearTimeout(state.deepDisposeTimer); state.deepDisposeTimer = null; }
    let createdRenderer = null;
    let createdControls = null;
    try {
      createdRenderer = acquireRenderer();
      if (!createdRenderer) {
        cePdDebugWebgl('wake-skip', 'wake: acquireRenderer returned null (budget)', {});
        return;
      }
      bindRendererEvents(createdRenderer, true);
      if (!ownCanvas.parentNode) container.insertBefore(ownCanvas, snapshotImg || null);
      createdControls = new OrbitControls(getActiveCamera(), ownCanvas);
      if (hibernateControlsTarget) { createdControls.target.copy(hibernateControlsTarget); hibernateControlsTarget = null; }
      if (hibernateCameraPos) { getActiveCamera().position.copy(hibernateCameraPos); hibernateCameraPos = null; }
      camCtl.configureControls(createdControls);
      camCtl.attachControls(createdControls);
      camCtl.setZoomLimits();
      attachControlsEventListeners(createdControls);
      setRenderer(createdRenderer);
      setControls(createdControls);
      registerActiveRenderer(wrapper, { hibernate, isFullscreen: isDiagramFullscreen, isInteracting: () => state.isInteracting, ratio: state.blockVisible ? 1 : 0 });
      state.hibernated = false;
      state.lastTimestamp = 0;
      onWakeSkipCameraRefit(true);
      getRecalcFit()();
      onWakeSkipCameraRefit(false);
      createdControls.update();
      /* Nach Hibernate ist der Layout-Frame-Cache invalid — desired.target weicht evtl. ab. */
      try { camCtl.syncUserPanOffsetFromRestoredPose(); } catch (_) { /* best-effort */ }
      try { camCtl.seedPerspectiveSmoothingToCurrentPose(); } catch (_) { /* best-effort */ }
      renderCompositeOrFallback(createdRenderer);
      unregisterPendingDeepDispose(wrapper);
      getAnimate()(performance.now());
      onAllLabelsSynced(() => {
        try { invalidateLayoutFrameCache(); } catch (_) { /* ignore */ }
        try { renderCompositeOrFallback(createdRenderer); } catch (_) { /* best-effort */ }
        try { camCtl.syncUserPanOffsetFromRestoredPose(); } catch (_) { /* best-effort */ }
        try { camCtl.seedPerspectiveSmoothingToCurrentPose(); } catch (_) { /* best-effort */ }
        markDirty();
        const finishOverlay = () => {
          try { applyPostSnapshotOverlayCamera(camCtl, state); } catch (_) { /* ignore */ }
          try { flashOwnCanvasCompositingLayer(ownCanvas); } catch (_) { /* ignore */ }
          swapSnapshotToCanvas();
        };
        if (typeof requestAnimationFrame === 'function') {
          requestAnimationFrame(() => { requestAnimationFrame(finishOverlay); });
        } else setTimeout(finishOverlay, 0);
      }, SNAPSHOT_OVERLAY_TROIKA_MS);
    } catch (_) {
      state.hibernated = true;
      if (createdRenderer) {
        try { bindRendererEvents(createdRenderer, false); } catch (_) { /* ignore */ }
        releaseRenderer(createdRenderer);
      }
      if (createdControls) try { createdControls.dispose(); } catch (_) { /* ignore */ }
    }
  }

  function recoverContextImpl() {
    if (ctxRecoverTimer != null) { clearTimeout(ctxRecoverTimer); ctxRecoverTimer = null; }
    if (!state.contextLost || state.hibernated) return;
    state.contextLost = false;
    const oldRenderer = getRenderer();
    const oldControls = getControls();
    const oldTarget = oldControls.target.clone();
    oldControls.dispose();
    bindRendererEvents(oldRenderer, false);
    releaseRenderer(oldRenderer); // inkl. forceContextLoss — sonst Safari „too many contexts“.
    const newRenderer = acquireRenderer();
    if (!newRenderer) {
      state.contextLost = true;
      cePdDebugWebgl('recover-fail', 'recoverContext: acquireRenderer null after context loss', {});
      return;
    }
    bindRendererEvents(newRenderer, true);
    const newControls = new OrbitControls(getActiveCamera(), ownCanvas);
    newControls.target.copy(oldTarget);
    camCtl.configureControls(newControls);
    camCtl.attachControls(newControls);
    camCtl.setZoomLimits();
    attachControlsEventListeners(newControls);
    setRenderer(newRenderer);
    setControls(newControls);
    try { newControls.update(); } catch (_) { /* ignore */ }
    try { camCtl.syncUserPanOffsetFromRestoredPose(); } catch (_) { /* best-effort */ }
    try { camCtl.seedPerspectiveSmoothingToCurrentPose(); } catch (_) { /* best-effort */ }
    renderCompositeOrFallback(newRenderer);
    wrapper.classList.add('loaded');
    const loader = wrapper.querySelector('.ce-diagram-loader');
    if (loader) {
      loader.classList.remove('ce-diagram-loader-error', 'ce-diagram-loader-recovering');
      loader.setAttribute('aria-busy', 'false');
      const errEl = loader.querySelector('.ce-diagram-loader-error-msg');
      if (errEl) errEl.remove();
      const recEl = loader.querySelector('.ce-diagram-loader-recover-msg');
      if (recEl) recEl.remove();
    }
    getAnimate()(performance.now());
  }

  function recoverContext() {
    enqueueSerializedWake(() => { recoverContextImpl(); });
  }

  function onContextLost(e) {
    e.preventDefault();
    if (state.contextLost || state.hibernated) return;
    state.contextLost = true;
    if (state.animId != null) { cancelAnimationFrame(state.animId); state.animId = null; }
    try { noteSharedRendererMaybeLost(); } catch (_) { /* ignore */ }
    ctxRecoverTimer = setTimeout(recoverContext, CTX_RECOVER_TIMEOUT_MS);
  }

  function onContextRestored() { recoverContext(); }

  function clearCtxRecoverTimer() {
    if (ctxRecoverTimer != null) { clearTimeout(ctxRecoverTimer); ctxRecoverTimer = null; }
  }

  function removeSnapshotImg() {
    if (snapshotImg && snapshotImg.parentNode) snapshotImg.parentNode.removeChild(snapshotImg);
    snapshotImg = null;
    try { wrapper.classList.remove('ce-diagram-wrapper--hibernating', CE_PD_SNAPSHOT_OVERLAY_CLASS); } catch (_) { /* ignore */ }
  }

  return {
    hibernate,
    wake,
    bindRendererEvents,
    clearCtxRecoverTimer,
    removeSnapshotImg,
  };
}

export function createDiagramViewportLifecycle(full) {
  const {
    leftoverSnapshots, ownCanvas, ownCtx, scene, camCtl, markDirty, getRenderer,
    renderLifecycleOps, wrapper, container, ownerDoc, state,
    ...rl
  } = full;

  let leftoverScheduled = false;

  function blitToOwnCanvas() {
    const renderer = getRenderer();
    const src = renderer.domElement;
    if (ownCanvas.width !== src.width || ownCanvas.height !== src.height) {
      ownCanvas.width = src.width; ownCanvas.height = src.height;
    }
    ownCtx.globalCompositeOperation = 'copy';
    ownCtx.drawImage(src, 0, 0);
    if (leftoverSnapshots.length > 0 && !leftoverScheduled) {
      leftoverScheduled = true;
      const snaps = leftoverSnapshots.slice();
      leftoverSnapshots.length = 0;
      const finalizeOverlay = () => {
        try { renderLifecycleOps.renderComposite(renderer); } catch (_) { /* ignore */ }
        try { camCtl.syncUserPanOffsetFromRestoredPose(); } catch (_) { /* ignore */ }
        try { camCtl.seedPerspectiveSmoothingToCurrentPose(); } catch (_) { /* ignore */ }
        markDirty();
        try { applyPostSnapshotOverlayCamera(camCtl, state); } catch (_) { /* ignore */ }
        try { flashOwnCanvasCompositingLayer(ownCanvas); } catch (_) { /* ignore */ }
        scheduleSnapshotOverlayDomRemoval(snaps, () => {
          try { wrapper.classList.remove(CE_PD_SNAPSHOT_OVERLAY_CLASS); } catch (_) { /* ignore */ }
          try { markDirty(12); } catch (_) { /* ignore */ }
        });
      };
      if (getTroikaDebugStats().pendingSyncs > 0) {
        onAllLabelsSynced(finalizeOverlay, SNAPSHOT_OVERLAY_TROIKA_MS);
      } else finalizeOverlay();
    }
  }

  renderLifecycleOps.renderComposite = (r) => {
    r.render(scene, camCtl.activeCamera);
    blitToOwnCanvas();
  };

  const rendererLifecycle = createRendererLifecycle({
    ...rl, wrapper, container, ownerDoc, scene, ownCanvas, state, camCtl,
    blitToOwnCanvas, markDirty, getRenderer, renderLifecycleOps,
  });

  return {
    blitToOwnCanvas,
    rendererLifecycle,
    bindRendererEvents: rendererLifecycle.bindRendererEvents,
  };
}
