import * as THREE from "three";

/**
 * Adaptive-quality controller.
 *
 * The single highest-leverage, lowest-risk runtime knob in this codebase is
 * renderer.setPixelRatio: it re-allocates the drawing buffer to
 * floor(clientSize * pixelRatio) without touching CSS layout (onResize already
 * uses updateStyle=false), so there is zero reflow. We walk a tiered ladder of
 * pixel-ratio steps with hysteresis (asymmetric down/up thresholds + sustained
 * windows) so the tier cannot oscillate around the 60fps line.
 *
 * Antialias is deliberately NOT touched: it is fixed at WebGLRenderer
 * construction and changing it would force a new context + KTX2 detectSupport,
 * violating the singleton-loader invariant in src/viewer/loaders.ts.
 */

const TIER_MIN = 0;
const TIER_MAX = 4;
// Sustained-sample windows (in frames at ~60fps): ~1s down, longer to recover.
const DOWN_FRAMES = 60;
const UP_FRAMES = 180;
const DOWN_FPS = 60;
const UP_FPS = 72;

export interface AdaptiveQuality {
  /** Feed the current rolling-average fps; may step the tier. */
  update(avgFps: number): void;
  /** Current tier index (0 = best). */
  tier(): number;
}

export interface AdaptiveQualityOptions {
  /** Override the tier-0 (best) pixel ratio. Default min(devicePixelRatio, 2). */
  baseRatio?: number;
}

/** Create the controller and apply tier 0 immediately. */
export function createAdaptiveQuality(
  renderer: THREE.WebGLRenderer,
  opts: AdaptiveQualityOptions = {},
): AdaptiveQuality {
  const base = opts.baseRatio ?? Math.min(window.devicePixelRatio, 2);
  // Ladder of pixel ratios, highest quality first. Tier 0 = capped DPR.
  const ratios = [base, 1.5, 1, 0.75, 0.5];

  let tier = 0;
  let below = 0;
  let above = 0;

  renderer.setPixelRatio(ratios[tier]);

  function applyTier(next: number): void {
    tier = next;
    below = 0;
    above = 0;
    const pr = ratios[tier];
    renderer.setPixelRatio(pr);
    console.info(`[hmwebviewer] adaptive quality -> tier ${tier} (pr=${pr})`);
  }

  return {
    update(avgFps: number): void {
      // Ignore warm-up / unmeasured frames.
      if (avgFps <= 0) return;

      if (avgFps < DOWN_FPS) {
        below++;
        above = 0;
        if (below >= DOWN_FRAMES && tier < TIER_MAX) applyTier(tier + 1);
      } else if (avgFps > UP_FPS) {
        above++;
        below = 0;
        if (above >= UP_FRAMES && tier > TIER_MIN) applyTier(tier - 1);
      } else {
        // Dead band: reset both so transient blips don't accumulate.
        below = 0;
        above = 0;
      }
    },
    tier(): number {
      return tier;
    },
  };
}
