const active = [];
let rafId = null;

const easings = {
  linear: t => t,
  easeOut: t => 1 - Math.pow(1 - t, 3),
  easeIn: t => t * t * t,
  easeInOut: t => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2,
  spring: t => 1 - Math.cos(t * Math.PI * 0.5) * Math.exp(-t * 3),
  bounce: t => {
    if (t < 1/2.75) return 7.5625 * t * t;
    if (t < 2/2.75) return 7.5625 * (t -= 1.5/2.75) * t + 0.75;
    if (t < 2.5/2.75) return 7.5625 * (t -= 2.25/2.75) * t + 0.9375;
    return 7.5625 * (t -= 2.625/2.75) * t + 0.984375;
  }
};

export function to(target, props, options = {}) {
  const { duration = 300, ease = 'easeOut', delay = 0, onUpdate, onComplete } = options;
  const startValues = {};
  const endValues = {};

  for (const key of Object.keys(props)) {
    if (target.style && key in target.style) {
      startValues[key] = parseFloat(getComputedStyle(target)[key]) || 0;
    } else if (typeof target[key] === 'number') {
      startValues[key] = target[key];
    } else {
      startValues[key] = 0;
    }
    endValues[key] = props[key];
  }

  const tween = {
    target, startValues, endValues,
    duration, ease: easings[ease] || easings.easeOut,
    delay, onUpdate, onComplete,
    startTime: null, done: false
  };

  active.push(tween);
  startLoop();

  return {
    stop: () => { tween.done = true; },
    promise: new Promise(resolve => { tween._resolve = resolve; })
  };
}

export function animate(from, to_val, duration, ease, onUpdate) {
  return new Promise(resolve => {
    const tw = { from, to: to_val, duration, ease: easings[ease] || easings.easeOut, onUpdate, startTime: null, done: false, _resolve: resolve };
    active.push(tw);
    startLoop();
  });
}

function tick(now) {
  for (let i = active.length - 1; i >= 0; i--) {
    const tw = active[i];
    if (tw.done) { active.splice(i, 1); continue; }

    if (!tw.startTime) tw.startTime = now + (tw.delay || 0);
    const elapsed = now - tw.startTime;
    if (elapsed < 0) continue;

    const progress = Math.min(elapsed / tw.duration, 1);
    const eased = tw.ease(progress);

    if (tw.from !== undefined) {
      const val = tw.from + (tw.to - tw.from) * eased;
      if (tw.onUpdate) tw.onUpdate(val);
    } else if (tw.target) {
      for (const key of Object.keys(tw.endValues)) {
        const val = tw.startValues[key] + (tw.endValues[key] - tw.startValues[key]) * eased;
        if (tw.target.style && key in tw.target.style) {
          tw.target.style[key] = val + (key === 'opacity' ? '' : 'px');
        } else {
          tw.target[key] = val;
        }
      }
      if (tw.onUpdate) tw.onUpdate(eased);
    }

    if (progress >= 1) {
      tw.done = true;
      if (tw.onComplete) tw.onComplete();
      if (tw._resolve) tw._resolve();
      active.splice(i, 1);
    }
  }

  if (active.length > 0) {
    rafId = requestAnimationFrame(tick);
  } else {
    rafId = null;
  }
}

function startLoop() {
  if (!rafId) rafId = requestAnimationFrame(tick);
}

export function delay(ms) {
  return new Promise(r => setTimeout(r, ms));
}
