/**
 * 애니메이션 유틸리티 모듈 (AnimationUtils.js)
 * ========================================
 * 페이드, 슬라이드, 카운트업 등 애니메이션을 제공합니다.
 *
 * [초보자용] AnimationUtils.fade(el, 'in', 300) / AnimationUtils.fade(el, 'out', 300)
 *
 * @module AnimationUtils
 */
class AnimationUtils {
  /**
   * 순차적 요소 애니메이션
   * @param {Array|NodeList} elements - 요소 배열
   * @param {string} className - 추가할 클래스
   * @param {number} delay - 각 요소 간 지연 시간 (ms)
   * @param {Function} callback - 각 요소 애니메이션 후 콜백
   */
  static async sequentialAnimate(elements, className = "show", delay = 50, callback = null) {
    const items = Array.isArray(elements) ? elements : Array.from(elements);

    for (let i = 0; i < items.length; i++) {
      await new Promise((resolve) => {
        setTimeout(() => {
          items[i].classList.add(className);
          if (callback) callback(items[i], i);
          resolve();
        }, delay * i);
      });
    }
  }

  /**
   * 요소 페이드 인/아웃
   * @param {Element} element - 대상 요소
   * @param {string} type - "in" 또는 "out"
   * @param {number} duration - 지속 시간 (ms)
   * @returns {Promise}
   */
  static async fade(element, type = "in", duration = 300) {
    if (!element) return;

    return new Promise((resolve) => {
      // 기존 display 값 저장 (grid, flex 등 유지)
      const originalDisplay = element.style.display || window.getComputedStyle(element).display;
      const isGridOrFlex = originalDisplay === "grid" || originalDisplay === "flex" || 
                          originalDisplay.includes("grid") || originalDisplay.includes("flex");

      if (type === "in") {
        // grid/flex인 경우 display를 설정하지 않음
        if (!isGridOrFlex) {
          element.style.display = "block";
        }
        element.style.opacity = "0";
        element.style.transition = `opacity ${duration}ms ease`;

        requestAnimationFrame(() => {
          element.style.opacity = "1";
          setTimeout(() => {
            element.style.transition = "";
            // grid/flex인 경우 display 스타일 제거
            if (isGridOrFlex) {
              element.style.display = "";
            }
            resolve();
          }, duration);
        });
      } else {
        element.style.opacity = "1";
        element.style.transition = `opacity ${duration}ms ease`;

        requestAnimationFrame(() => {
          element.style.opacity = "0";
          setTimeout(() => {
            // grid/flex인 경우 display를 none으로 설정하지 않음
            if (!isGridOrFlex) {
              element.style.display = "none";
            }
            element.style.transition = "";
            element.style.opacity = ""; // 재오픈 시 opacity 초기화
            resolve();
          }, duration);
        });
      }
    });
  }

  /**
   * 요소 슬라이드
   * @param {Element} element - 대상 요소
   * @param {string} type - "up", "down", "left", "right"
   * @param {number} duration - 지속 시간 (ms)
   * @returns {Promise}
   */
  static async slide(element, type = "down", duration = 300) {
    if (!element) return;

    return new Promise((resolve) => {
      const isShow = type === "down" || type === "right";
      const property = type === "up" || type === "down" ? "height" : "width";
      const overflow = element.style.overflow;

      element.style.overflow = "hidden";

      if (isShow) {
        element.style.display = "block";
        const size = element[property === "height" ? "scrollHeight" : "scrollWidth"];
        element.style[property] = "0";
        element.style.transition = `${property} ${duration}ms ease`;

        requestAnimationFrame(() => {
          element.style[property] = `${size}px`;
          setTimeout(() => {
            element.style[property] = "";
            element.style.overflow = overflow;
            element.style.transition = "";
            resolve();
          }, duration);
        });
      } else {
        const size = element[property === "height" ? "offsetHeight" : "offsetWidth"];
        element.style[property] = `${size}px`;
        element.style.transition = `${property} ${duration}ms ease`;

        requestAnimationFrame(() => {
          element.style[property] = "0";
          setTimeout(() => {
            element.style.display = "none";
            element.style[property] = "";
            element.style.overflow = overflow;
            element.style.transition = "";
            resolve();
          }, duration);
        });
      }
    });
  }

  /**
   * 숫자 카운팅 애니메이션
   * @param {Element} element - 대상 요소
   * @param {number} start - 시작 값
   * @param {number} end - 종료 값
   * @param {number} duration - 지속 시간 (ms)
   * @param {Function} format - 포맷 함수
   * @returns {Promise}
   */
  static async countUp(element, start, end, duration = 1000, format = null) {
    if (!element) return;

    return new Promise((resolve) => {
      const startTime = performance.now();
      const range = end - start;

      const update = (currentTime) => {
        const elapsed = currentTime - startTime;
        const progress = Math.min(elapsed / duration, 1);

        // Ease-out 효과
        const easeProgress = 1 - Math.pow(1 - progress, 3);
        const current = start + range * easeProgress;

        element.textContent = format ? format(current) : Math.round(current);

        if (progress < 1) {
          requestAnimationFrame(update);
        } else {
          element.textContent = format ? format(end) : end;
          resolve();
        }
      };

      requestAnimationFrame(update);
    });
  }

  /**
   * 스크롤 애니메이션
   * @param {Element|string} target - 대상 요소 또는 선택자
   * @param {Object} options - 옵션
   * @returns {Promise}
   */
  static async scrollTo(target, options = {}) {
    const {
      duration = 500,
      offset = 0,
      easing = "ease-in-out",
      container = window,
    } = options;

    const element = typeof target === "string" ? document.querySelector(target) : target;

    if (!element) return;

    return new Promise((resolve) => {
      const targetPosition =
        element.getBoundingClientRect().top +
        (container === window ? window.pageYOffset : container.scrollTop) +
        offset;

      const startPosition = container === window ? window.pageYOffset : container.scrollTop;
      const distance = targetPosition - startPosition;
      const startTime = performance.now();

      const easingFunctions = {
        linear: (t) => t,
        "ease-in": (t) => t * t,
        "ease-out": (t) => t * (2 - t),
        "ease-in-out": (t) => (t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t),
      };

      const easingFunc = easingFunctions[easing] || easingFunctions["ease-in-out"];

      const animation = (currentTime) => {
        const elapsed = currentTime - startTime;
        const progress = Math.min(elapsed / duration, 1);
        const easedProgress = easingFunc(progress);

        const position = startPosition + distance * easedProgress;

        if (container === window) {
          window.scrollTo(0, position);
        } else {
          container.scrollTop = position;
        }

        if (progress < 1) {
          requestAnimationFrame(animation);
        } else {
          resolve();
        }
      };

      requestAnimationFrame(animation);
    });
  }

  /**
   * 흔들기 애니메이션
   * @param {Element} element - 대상 요소
   * @param {number} intensity - 강도
   * @param {number} duration - 지속 시간 (ms)
   * @returns {Promise}
   */
  static async shake(element, intensity = 5, duration = 500) {
    if (!element) return;

    return new Promise((resolve) => {
      const startTime = performance.now();
      const originalTransform = element.style.transform;

      const animation = (currentTime) => {
        const elapsed = currentTime - startTime;
        const progress = elapsed / duration;

        if (progress < 1) {
          const x = Math.sin(progress * Math.PI * 4) * intensity * (1 - progress);
          element.style.transform = `translateX(${x}px)`;
          requestAnimationFrame(animation);
        } else {
          element.style.transform = originalTransform;
          resolve();
        }
      };

      requestAnimationFrame(animation);
    });
  }

  /**
   * 펄스 애니메이션
   * @param {Element} element - 대상 요소
   * @param {number} scale - 스케일
   * @param {number} duration - 지속 시간 (ms)
   * @returns {Promise}
   */
  static async pulse(element, scale = 1.1, duration = 500) {
    if (!element) return;

    return new Promise((resolve) => {
      const originalTransform = element.style.transform;
      element.style.transition = `transform ${duration / 2}ms ease-in-out`;

      element.style.transform = `scale(${scale})`;

      setTimeout(() => {
        element.style.transform = originalTransform;
        setTimeout(() => {
          element.style.transition = "";
          resolve();
        }, duration / 2);
      }, duration / 2);
    });
  }

  /**
   * 바운스 애니메이션
   * @param {Element} element - 대상 요소
   * @param {number} height - 바운스 높이
   * @param {number} duration - 지속 시간 (ms)
   * @returns {Promise}
   */
  static async bounce(element, height = 20, duration = 600) {
    if (!element) return;

    return new Promise((resolve) => {
      const startTime = performance.now();
      const originalTransform = element.style.transform;

      const animation = (currentTime) => {
        const elapsed = currentTime - startTime;
        const progress = elapsed / duration;

        if (progress < 1) {
          const bounceProgress = Math.sin(progress * Math.PI);
          const y = -height * bounceProgress;
          element.style.transform = `translateY(${y}px)`;
          requestAnimationFrame(animation);
        } else {
          element.style.transform = originalTransform;
          resolve();
        }
      };

      requestAnimationFrame(animation);
    });
  }

  /**
   * 회전 애니메이션
   * @param {Element} element - 대상 요소
   * @param {number} degrees - 회전 각도
   * @param {number} duration - 지속 시간 (ms)
   * @returns {Promise}
   */
  static async rotate(element, degrees = 360, duration = 500) {
    if (!element) return;

    return new Promise((resolve) => {
      element.style.transition = `transform ${duration}ms ease`;
      element.style.transform = `rotate(${degrees}deg)`;

      setTimeout(() => {
        element.style.transition = "";
        resolve();
      }, duration);
    });
  }

  /**
   * 타이핑 효과
   * @param {Element} element - 대상 요소
   * @param {string} text - 타이핑할 텍스트
   * @param {number} speed - 타이핑 속도 (ms)
   * @returns {Promise}
   */
  static async typing(element, text, speed = 50) {
    if (!element) return;

    return new Promise((resolve) => {
      let index = 0;
      element.textContent = "";

      const type = () => {
        if (index < text.length) {
          element.textContent += text.charAt(index);
          index++;
          setTimeout(type, speed);
        } else {
          resolve();
        }
      };

      type();
    });
  }

  /**
   * 프로그레스 바 애니메이션
   * @param {Element} element - 대상 요소
   * @param {number} percent - 진행률 (0-100)
   * @param {number} duration - 지속 시간 (ms)
   * @returns {Promise}
   */
  static async progressBar(element, percent, duration = 500) {
    if (!element) return;

    return new Promise((resolve) => {
      element.style.transition = `width ${duration}ms ease-out`;
      element.style.width = `${percent}%`;

      setTimeout(() => {
        element.style.transition = "";
        resolve();
      }, duration);
    });
  }

  /**
   * 파티클 효과
   * @param {Element} container - 컨테이너 요소
   * @param {Object} options - 옵션
   */
  static particles(container, options = {}) {
    const {
      count = 30,
      color = "#4CAF50",
      size = 5,
      duration = 2000,
      spread = 100,
    } = options;

    const rect = container.getBoundingClientRect();
    const centerX = rect.width / 2;
    const centerY = rect.height / 2;

    for (let i = 0; i < count; i++) {
      const particle = document.createElement("div");
      particle.style.cssText = `
        position: absolute;
        width: ${size}px;
        height: ${size}px;
        background: ${color};
        border-radius: 50%;
        left: ${centerX}px;
        top: ${centerY}px;
        pointer-events: none;
      `;

      container.appendChild(particle);

      const angle = (Math.PI * 2 * i) / count;
      const velocity = spread * (0.5 + Math.random() * 0.5);
      const x = Math.cos(angle) * velocity;
      const y = Math.sin(angle) * velocity;

      particle.animate(
        [
          { transform: "translate(0, 0) scale(1)", opacity: 1 },
          { transform: `translate(${x}px, ${y}px) scale(0)`, opacity: 0 },
        ],
        {
          duration: duration,
          easing: "cubic-bezier(0, 0.5, 0.5, 1)",
        }
      ).onfinish = () => {
        particle.remove();
      };
    }
  }

  /**
   * 리플 효과
   * @param {Element} element - 대상 요소
   * @param {Event} event - 클릭 이벤트
   * @param {Object} options - 옵션
   */
  static ripple(element, event, options = {}) {
    const { color = "rgba(255, 255, 255, 0.6)", duration = 600 } = options;

    const rect = element.getBoundingClientRect();
    const size = Math.max(rect.width, rect.height);
    const x = event.clientX - rect.left - size / 2;
    const y = event.clientY - rect.top - size / 2;

    const ripple = document.createElement("span");
    ripple.style.cssText = `
      position: absolute;
      width: ${size}px;
      height: ${size}px;
      border-radius: 50%;
      background: ${color};
      left: ${x}px;
      top: ${y}px;
      transform: scale(0);
      pointer-events: none;
    `;

    // 상대 위치 설정
    const position = window.getComputedStyle(element).position;
    if (position !== "relative" && position !== "absolute") {
      element.style.position = "relative";
    }

    element.style.overflow = "hidden";
    element.appendChild(ripple);

    ripple.animate(
      [
        { transform: "scale(0)", opacity: 1 },
        { transform: "scale(2)", opacity: 0 },
      ],
      {
        duration: duration,
        easing: "ease-out",
      }
    ).onfinish = () => {
      ripple.remove();
    };
  }
}

// ES6 모듈 내보내기
if (typeof module !== "undefined" && module.exports) {
  module.exports = AnimationUtils;
}
