import './style.css';

/**
 * Applies a scroll-driven text fill effect to configured inner text nodes.
 */
const PRESET_SELECTORS = {
  heading: 'h1, h2, h3, h4, h5, h6',
  text: 'p, span, li, blockquote',
  links: 'a',
};

const GRADIENT_PRESETS = {
  ocean: ['#2563eb', '#38bdf8'],
  sunset: ['#f97316', '#ec4899'],
  mint: ['#10b981', '#84cc16'],
};

const TIMING_PRESETS = {
  subtle: 0.03,
  balanced: 0.07,
  dramatic: 0.12,
};

const clamp = (value, min = 0, max = 1) => Math.min(max, Math.max(min, value));

const applyEasing = (progress, easing) => {
  switch (easing) {
    case 'ease-in':
      return progress * progress;
    case 'ease-in-out':
      return progress < 0.5
        ? 2 * progress * progress
        : 1 - ((-2 * progress + 2) ** 2) / 2;
    case 'ease-out':
      return 1 - ((1 - progress) ** 2);
    case 'linear':
    default:
      return progress;
  }
};

const buildSegmentProgress = (progress, index, total, stagger, easing) => {
  if (total <= 1 || stagger <= 0) {
    return applyEasing(clamp(progress), easing);
  }

  const totalOffset = clamp(stagger);
  const offset = total === 1 ? 0 : (index / (total - 1)) * totalOffset;
  const normalized = clamp((progress - offset) / Math.max(0.0001, 1 - totalOffset));

  return applyEasing(normalized, easing);
};

const createSegmentElement = (text, skin, direction) => {
  const segment = document.createElement('span');
  segment.className = 'calio-scroll-fill-target calio-scroll-fill-segment';
  segment.classList.add(`is-skin-${skin}`);
  if (direction === 'rtl') {
    segment.classList.add('is-rtl');
  }
  segment.textContent = text;

  return segment;
};

const getSourceText = (target) => {
  if (!target.dataset.calioScrollFillSourceText) {
    target.dataset.calioScrollFillSourceText = target.textContent || '';
  }

  return target.dataset.calioScrollFillSourceText;
};

const buildWordSegments = (target, sourceText, skin, direction) => {
  const tokens = sourceText.match(/(\s+|\S+)/g) || [];
  const fragment = document.createDocumentFragment();
  const segments = [];

  tokens.forEach((token) => {
    if (/^\s+$/.test(token)) {
      fragment.appendChild(document.createTextNode(token));
      return;
    }

    const segment = createSegmentElement(token, skin, direction);
    fragment.appendChild(segment);
    segments.push(segment);
  });

  if (!segments.length) {
    return [];
  }

  target.textContent = '';
  target.classList.add('calio-scroll-fill-scope', 'is-mode-words');
  target.classList.remove('is-mode-lines');
  target.appendChild(fragment);

  return segments;
};

const buildLineSegments = (target, sourceText, skin, direction) => {
  const words = sourceText.trim().split(/\s+/).filter(Boolean);
  if (!words.length) {
    return [];
  }

  const measureFragment = document.createDocumentFragment();
  words.forEach((word, index) => {
    const measureWord = document.createElement('span');
    measureWord.className = 'calio-scroll-fill-measure-word';
    measureWord.textContent = word;
    measureFragment.appendChild(measureWord);

    if (index < words.length - 1) {
      measureFragment.appendChild(document.createTextNode(' '));
    }
  });

  target.textContent = '';
  target.classList.add('calio-scroll-fill-scope', 'is-mode-lines', 'is-measuring-lines');
  target.classList.remove('is-mode-words');
  target.appendChild(measureFragment);

  const measureWords = Array.from(target.querySelectorAll('.calio-scroll-fill-measure-word'));
  const lines = [];
  let currentTop = null;

  measureWords.forEach((word) => {
    const top = word.offsetTop;
    if (currentTop === null || Math.abs(top - currentTop) > 2) {
      lines.push([]);
      currentTop = top;
    }

    lines[lines.length - 1].push(word.textContent || '');
  });

  target.textContent = '';
  target.classList.remove('is-measuring-lines');

  const fragment = document.createDocumentFragment();
  const segments = lines.map((lineWords) => createSegmentElement(lineWords.join(' '), skin, direction));
  segments.forEach((segment) => {
    fragment.appendChild(segment);
  });
  target.appendChild(fragment);

  return segments;
};

const prepareTarget = (target, mode, skin, direction, forceRebuild = false) => {
  if (mode === 'element') {
    target.classList.add('calio-scroll-fill-target');
    target.classList.add(`is-skin-${skin}`);
    if (direction === 'rtl') {
      target.classList.add('is-rtl');
    }

    return { segments: [target], mode: 'element' };
  }

  if (!forceRebuild && target.dataset.calioScrollFillPrepared === 'yes') {
    const existingSegments = Array.from(target.querySelectorAll('.calio-scroll-fill-segment'));
    if (existingSegments.length) {
      return { segments: existingSegments, mode };
    }
  }

  if (target.childElementCount > 0 && target.dataset.calioScrollFillPrepared !== 'yes') {
    target.classList.add('calio-scroll-fill-target');
    target.classList.add(`is-skin-${skin}`);
    if (direction === 'rtl') {
      target.classList.add('is-rtl');
    }

    return { segments: [target], mode: 'element' };
  }

  const sourceText = getSourceText(target);
  if (!sourceText.trim()) {
    target.classList.add('calio-scroll-fill-target');
    target.classList.add(`is-skin-${skin}`);
    if (direction === 'rtl') {
      target.classList.add('is-rtl');
    }

    return { segments: [target], mode: 'element' };
  }

  let segments = [];
  if (mode === 'words') {
    segments = buildWordSegments(target, sourceText, skin, direction);
  } else if (mode === 'lines') {
    segments = buildLineSegments(target, sourceText, skin, direction);
  }

  if (!segments.length) {
    target.classList.add('calio-scroll-fill-target');
    target.classList.add(`is-skin-${skin}`);
    if (direction === 'rtl') {
      target.classList.add('is-rtl');
    }

    return { segments: [target], mode: 'element' };
  }

  target.dataset.calioScrollFillPrepared = 'yes';

  return { segments, mode };
};

const initScrollFillEffect = (element) => {
  if (element.dataset.calioScrollFillInit === 'yes') {
    return;
  }

  element.dataset.calioScrollFillInit = 'yes';

  const preset = element.dataset.calioScrollFillTargetPreset || 'heading';
  const selector = preset === 'custom'
    ? (element.dataset.calioScrollFillSelector || 'h1, h2, h3, h4, h5, h6, p, span, a')
    : (PRESET_SELECTORS[preset] || PRESET_SELECTORS.heading);
  const start = Number.parseFloat(element.dataset.calioScrollFillStart || '85') / 100;
  const end = Number.parseFloat(element.dataset.calioScrollFillEnd || '20') / 100;
  const direction = element.dataset.calioScrollFillDirection === 'rtl' ? 'rtl' : 'ltr';
  const easing = element.dataset.calioScrollFillEasing || 'ease-out';
  const targetMode = element.dataset.calioScrollFillTargetMode || 'element';
  const timingPreset = element.dataset.calioScrollFillTimingPreset || 'balanced';
  const stagger = timingPreset === 'custom'
    ? Number.parseFloat(element.dataset.calioScrollFillStagger || '0') / 100
    : (TIMING_PRESETS[timingPreset] || TIMING_PRESETS.balanced);
  const skin = element.dataset.calioScrollFillSkin || 'solid';
  const gradientPreset = element.dataset.calioScrollFillGradientPreset || 'ocean';
  const completeOnce = element.dataset.calioScrollFillOnce === 'yes';
  const targets = Array.from(element.querySelectorAll(selector));

  if (!targets.length) {
    return;
  }

  const preparedTargets = targets.map((target) => ({
    target,
    ...prepareTarget(target, targetMode, skin, direction),
  }));

  if (skin === 'gradient' && gradientPreset !== 'custom' && GRADIENT_PRESETS[gradientPreset]) {
    const [primary, secondary] = GRADIENT_PRESETS[gradientPreset];
    element.style.setProperty('--calio-scroll-fill-fill', primary);
    element.style.setProperty('--calio-scroll-fill-fill-secondary', secondary);
  }

  let locked = false;
  let ticking = false;

  const update = () => {
    if (locked) {
      ticking = false;
      return;
    }

    const rect = element.getBoundingClientRect();
    const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 1;
    const startPosition = viewportHeight * start;
    const endPosition = viewportHeight * end;
    const denominator = Math.max(1, startPosition - endPosition);
    const rawProgress = Math.max(0, Math.min(1, (startPosition - rect.top) / denominator));

    preparedTargets.forEach(({ segments }) => {
      const total = segments.length;

      segments.forEach((segment, index) => {
        const segmentProgress = buildSegmentProgress(rawProgress, index, total, stagger, easing);
        segment.style.setProperty('--calio-scroll-fill-progress', `${Math.round(segmentProgress * 100)}%`);
      });
    });

    if (completeOnce && rawProgress >= 1) {
      locked = true;

      preparedTargets.forEach(({ segments }) => {
        segments.forEach((segment) => {
          segment.style.setProperty('--calio-scroll-fill-progress', '100%');
        });
      });
    }

    ticking = false;
  };

  const requestUpdate = () => {
    if (ticking) {
      return;
    }

    ticking = true;
    window.requestAnimationFrame(update);
  };

  update();
  window.addEventListener('scroll', requestUpdate, { passive: true });
  window.addEventListener('resize', () => {
    preparedTargets.forEach((preparedTarget, index) => {
      if (preparedTarget.mode !== 'lines') {
        return;
      }

      preparedTargets[index] = {
        target: preparedTarget.target,
        ...prepareTarget(preparedTarget.target, 'lines', skin, direction, true),
      };
    });

    requestUpdate();
  });
};

const initAllScrollFillEffects = (scope = document) => {
  scope
    .querySelectorAll('[data-calio-scroll-fill="yes"]')
    .forEach(initScrollFillEffect);
};

document.addEventListener('DOMContentLoaded', () => {
  initAllScrollFillEffects(document);
});

let calioScrollFillHookBound = false;
const bindElementorScrollFill = () => {
  if (calioScrollFillHookBound) {
    return true;
  }

  if (!window.elementorFrontend?.hooks) {
    return false;
  }

  window.elementorFrontend.hooks.addAction('frontend/element_ready/global', ($scope) => {
    const root = $scope?.[0] || null;
    if (root) {
      initAllScrollFillEffects(root);
    }
  });

  document
    .querySelectorAll('[data-calio-scroll-fill="yes"]')
    .forEach(initScrollFillEffect);

  calioScrollFillHookBound = true;
  return true;
};

window.addEventListener('elementor/frontend/init', bindElementorScrollFill);
if (!bindElementorScrollFill()) {
  let attempts = 0;
  const timer = window.setInterval(() => {
    attempts += 1;
    if (bindElementorScrollFill() || attempts >= 40) {
      window.clearInterval(timer);
    }
  }, 250);
}
