// Curated, named animation library ("Fade Up", "Zoom In Left", "Jack In The
// Box", etc.) sitting on top of the existing Interaction Engine's 12 actions
// (see types/index.ts's InteractionAction/InteractionTrigger doc comment).
// This file is deliberately editor-only metadata — applying a preset just
// writes an ordinary InteractionRule via editorStore's addInteraction(), so
// the sanitizer/renderer/runtime never need to know "presets" exist at all.
//
// Almost every preset reuses an existing action + a curated settings bag:
//  - Fade*  -> action 'slide'     (opacity + transform, per direction)
//  - Slide* -> action 'translate' (transform only, no fade — the engine
//              already keeps these as two distinct actions for exactly this)
//  - Zoom In/Out -> action 'zoom'; the four directional Zoom-In variants and
//    Roll In / Light Speed use 'customTransform' (the engine's one composite-
//    transform escape hatch) rather than growing the core action list.
//  - Back In* -> action 'slide' + an overshoot easing curve.
//  - Jack In The Box is the one genuinely new action (see class-jalpi-
//    renderer.php's interaction_css_vars()/assets/css/frontend.css).

import type { InteractionAction, InteractionSettings } from '../types';

export type AnimationCategory =
  | 'Fade' | 'Slide' | 'Zoom' | 'Rotate' | 'Flip' | 'Bounce' | 'Back' | 'Special'
  // Library extension — five new families (Blur/Scale/Skew/Elastic get their
  // own category; Reveal covers the Reveal/Clip Path/Wipe sub-families too).
  // The existing "Blur In" preset stays in 'Special' exactly as it was —
  // only its new siblings live in the new 'Blur' category.
  | 'Blur' | 'Scale' | 'Skew' | 'Elastic' | 'Reveal';

export interface AnimationPreset {
  id: string;
  label: string;
  category: AnimationCategory;
  action: InteractionAction;
  settings: InteractionSettings;
}

export const ANIMATION_CATEGORIES: AnimationCategory[] = [
  'Fade', 'Slide', 'Zoom', 'Rotate', 'Flip', 'Bounce', 'Back', 'Special',
  'Blur', 'Scale', 'Skew', 'Elastic', 'Reveal',
];

// The overshoot/"spring" curve already used elsewhere in this codebase's own
// editor CSS (toast entrance) — reused here so Back In* presets feel like
// part of the same visual language rather than a new one-off.
const BACK_EASING = 'cubic-bezier(0.34, 1.56, 0.64, 1)';

const fade = (label: string, id: string, direction?: string): AnimationPreset => ({
  id, label, category: 'Fade',
  action: direction ? 'slide' : 'fade',
  settings: direction ? { direction, duration: '0.6s', easing: 'ease-out' } : { duration: '0.6s', easing: 'ease-out' },
});

const slide = (label: string, id: string, direction: string): AnimationPreset => ({
  id, label, category: 'Slide', action: 'translate',
  settings: { direction, duration: '0.6s', easing: 'ease-out' },
});

const zoomDir = (label: string, id: string, transform: string): AnimationPreset => ({
  id, label, category: 'Zoom', action: 'customTransform',
  settings: { customTransform: transform, duration: '0.6s', easing: 'ease-out' },
});

const back = (label: string, id: string, direction: string): AnimationPreset => ({
  id, label, category: 'Back', action: 'slide',
  settings: { direction, distance: '80px', duration: '0.7s', easing: BACK_EASING },
});

const bounceDir = (label: string, id: string, direction?: string): AnimationPreset => ({
  id, label, category: 'Bounce', action: 'bounce',
  settings: direction ? { direction, duration: '0.9s' } : { duration: '0.9s' },
});

// ── Library extension helpers (Flip/Rotate/Blur/Scale/Reveal/Wipe/Elastic) ──

const customT = (
  label: string, id: string, category: AnimationCategory, transform: string,
  opts: Partial<InteractionSettings> = {}
): AnimationPreset => ({
  id, label, category, action: 'customTransform',
  settings: { customTransform: transform, duration: '0.7s', easing: 'ease-out', ...opts },
});

const scaleOrigin = (label: string, id: string, transformOrigin: string): AnimationPreset => ({
  id, label, category: 'Scale', action: 'scale',
  settings: { scaleValue: '0.5', transformOrigin, duration: '0.5s', easing: 'ease-out' },
});

const revealDir = (label: string, id: string, action: 'reveal' | 'wipe', direction: string): AnimationPreset => ({
  id, label, category: 'Reveal', action,
  settings: { clipShape: 'edge', direction, duration: action === 'reveal' ? '0.6s' : '0.5s', easing: action === 'reveal' ? 'ease-out' : 'ease-in-out' },
});

const elasticDir = (label: string, id: string, direction?: string, distance?: string): AnimationPreset => ({
  id, label, category: 'Elastic', action: 'elastic',
  settings: direction ? { direction, distance: distance ?? '50px', duration: '0.9s' } : { duration: '0.9s' },
});

export const ANIMATION_PRESETS: AnimationPreset[] = [
  // Fade
  fade('Fade In', 'fadeIn'),
  fade('Fade Up', 'fadeInUp', 'up'),
  fade('Fade Down', 'fadeInDown', 'down'),
  fade('Fade Left', 'fadeInLeft', 'left'),
  fade('Fade Right', 'fadeInRight', 'right'),

  // Slide (position only, no opacity fade — distinct from Fade Up/Down/Left/Right)
  slide('Slide Up', 'slideInUp', 'up'),
  slide('Slide Down', 'slideInDown', 'down'),
  slide('Slide Left', 'slideInLeft', 'left'),
  slide('Slide Right', 'slideInRight', 'right'),

  // Zoom
  { id: 'zoomIn', label: 'Zoom In', category: 'Zoom', action: 'zoom', settings: { scaleValue: '0.3', duration: '0.6s', easing: 'ease-out' } },
  { id: 'zoomOut', label: 'Zoom Out', category: 'Zoom', action: 'zoom', settings: { scaleValue: '1.5', duration: '0.6s', easing: 'ease-out' } },
  zoomDir('Zoom In Up', 'zoomInUp', 'scale(0.3) translateY(60px)'),
  zoomDir('Zoom In Down', 'zoomInDown', 'scale(0.3) translateY(-60px)'),
  zoomDir('Zoom In Left', 'zoomInLeft', 'scale(0.3) translateX(-60px)'),
  zoomDir('Zoom In Right', 'zoomInRight', 'scale(0.3) translateX(60px)'),

  // Rotate
  { id: 'rotateIn', label: 'Rotate In', category: 'Rotate', action: 'rotate', settings: { rotationDeg: '-15', duration: '0.7s', easing: 'ease-out' } },
  { id: 'rotateInLeft', label: 'Rotate Left', category: 'Rotate', action: 'rotate', settings: { rotationDeg: '-90', transformOrigin: 'left center', duration: '0.7s', easing: 'ease-out' } },
  { id: 'rotateInRight', label: 'Rotate Right', category: 'Rotate', action: 'rotate', settings: { rotationDeg: '90', transformOrigin: 'right center', duration: '0.7s', easing: 'ease-out' } },

  // Flip
  { id: 'flipInX', label: 'Flip X', category: 'Flip', action: 'flip', settings: { direction: 'up', duration: '0.7s', easing: 'ease-out' } },
  { id: 'flipInY', label: 'Flip Y', category: 'Flip', action: 'flip', settings: { direction: 'left', duration: '0.7s', easing: 'ease-out' } },

  // Bounce
  bounceDir('Bounce In', 'bounceIn'),
  bounceDir('Bounce Up', 'bounceInUp', 'up'),
  bounceDir('Bounce Down', 'bounceInDown', 'down'),
  bounceDir('Bounce Left', 'bounceInLeft', 'left'),
  bounceDir('Bounce Right', 'bounceInRight', 'right'),

  // Back (overshoot easing)
  back('Back In Up', 'backInUp', 'up'),
  back('Back In Down', 'backInDown', 'down'),
  back('Back In Left', 'backInLeft', 'left'),
  back('Back In Right', 'backInRight', 'right'),

  // Special
  { id: 'rollIn', label: 'Roll In', category: 'Special', action: 'customTransform', settings: { customTransform: 'translateX(-100%) rotate(-120deg)', duration: '0.7s', easing: 'ease-out' } },
  { id: 'blurIn', label: 'Blur In', category: 'Special', action: 'blur', settings: { blurAmount: '10px', duration: '0.6s', easing: 'ease-out' } },
  { id: 'lightSpeedLeft', label: 'Light Speed Left', category: 'Special', action: 'customTransform', settings: { customTransform: 'translateX(-100%) skewX(30deg)', duration: '0.6s', easing: 'ease-out' } },
  { id: 'lightSpeedRight', label: 'Light Speed Right', category: 'Special', action: 'customTransform', settings: { customTransform: 'translateX(100%) skewX(-30deg)', duration: '0.6s', easing: 'ease-out' } },
  { id: 'jackInTheBox', label: 'Jack In The Box', category: 'Special', action: 'jackInTheBox', settings: { scaleValue: '0.1', duration: '0.8s' } },

  // ── Library extension: 50 additional presets ──────────────────────────
  // Everything below is new. Nothing above this line was reordered or
  // edited — every existing id/label/action/settings pair is untouched.

  // Flip (existing Flip X/Y are pure single-axis rotate; these combine a
  // translate so they read as visually distinct rather than re-labeled
  // duplicates).
  customT('Flip', 'flip', 'Flip', 'perspective(500px) rotateY(180deg)', { duration: '0.8s' }),
  customT('Flip In Top', 'flipInTop', 'Flip', 'perspective(600px) translateY(-40px) rotateX(90deg)'),
  customT('Flip In Bottom', 'flipInBottom', 'Flip', 'perspective(600px) translateY(40px) rotateX(-90deg)'),
  customT('Flip In Left', 'flipInLeft', 'Flip', 'perspective(600px) translateX(-40px) rotateY(90deg)'),
  customT('Flip In Right', 'flipInRight', 'Flip', 'perspective(600px) translateX(40px) rotateY(-90deg)'),

  // Rotate (bare = quicker/subtler than existing "Rotate In"; corners are
  // diagonal translate+rotate combos the plain 'rotate' action can't express).
  { id: 'rotate', label: 'Rotate', category: 'Rotate', action: 'rotate', settings: { rotationDeg: '-25', duration: '0.5s', easing: 'ease-out' } },
  customT('Rotate In Up Left', 'rotateInUpLeft', 'Rotate', 'translate(-50px, -50px) rotate(-45deg)'),
  customT('Rotate In Up Right', 'rotateInUpRight', 'Rotate', 'translate(50px, -50px) rotate(45deg)'),
  customT('Rotate In Down Left', 'rotateInDownLeft', 'Rotate', 'translate(-50px, 50px) rotate(45deg)'),
  customT('Rotate In Down Right', 'rotateInDownRight', 'Rotate', 'translate(50px, 50px) rotate(-45deg)'),

  // Bounce (bare + "In" directional variants — deliberately snappier
  // distance/duration than the existing Bounce Up/Down/Left/Right so they
  // aren't byte-identical to their existing siblings).
  bounceDir('Bounce', 'bounce'),
  { id: 'bounceUpIn', label: 'Bounce In Up', category: 'Bounce', action: 'bounce', settings: { direction: 'up', distance: '45px', duration: '0.75s' } },
  { id: 'bounceDownIn', label: 'Bounce In Down', category: 'Bounce', action: 'bounce', settings: { direction: 'down', distance: '45px', duration: '0.75s' } },
  { id: 'bounceLeftIn', label: 'Bounce In Left', category: 'Bounce', action: 'bounce', settings: { direction: 'left', distance: '45px', duration: '0.75s' } },
  { id: 'bounceRightIn', label: 'Bounce In Right', category: 'Bounce', action: 'bounce', settings: { direction: 'right', distance: '45px', duration: '0.75s' } },

  // Light Speed (generic variants alongside the existing Left/Right, each
  // adding a slight rotate so they're a distinct combo, not a relabeling).
  customT('Light Speed', 'lightSpeed', 'Special', 'translateX(-60%) skewX(20deg) rotate(-5deg)', { duration: '0.6s' }),
  customT('Light Speed In', 'lightSpeedIn', 'Special', 'translateX(80%) skewX(-20deg) rotate(5deg)', { duration: '0.6s' }),

  // Blur (bare reuses the existing 'blur' action with different numbers than
  // "Blur In"; directional variants combine a translate with the new
  // customFilter setting since the dedicated blur keyframe has no position
  // component).
  { id: 'blur', label: 'Blur', category: 'Blur', action: 'blur', settings: { blurAmount: '6px', duration: '0.5s', easing: 'ease-out' } },
  customT('Blur Up', 'blurUp', 'Blur', 'translateY(30px)', { customFilter: 'blur(10px)', duration: '0.6s' }),
  customT('Blur Down', 'blurDown', 'Blur', 'translateY(-30px)', { customFilter: 'blur(10px)', duration: '0.6s' }),
  customT('Blur Left', 'blurLeft', 'Blur', 'translateX(30px)', { customFilter: 'blur(10px)', duration: '0.6s' }),
  customT('Blur Right', 'blurRight', 'Blur', 'translateX(-30px)', { customFilter: 'blur(10px)', duration: '0.6s' }),

  // Scale (bare/In use the 'scale' action directly — a distinct action from
  // Zoom's 'zoom' action even though visually similar; directional variants
  // reuse the existing transformOrigin setting, no new engine code at all).
  { id: 'scale', label: 'Scale', category: 'Scale', action: 'scale', settings: { scaleValue: '0.5', duration: '0.5s', easing: 'ease-out' } },
  { id: 'scaleIn', label: 'Scale In', category: 'Scale', action: 'scale', settings: { scaleValue: '0.6', duration: '0.6s', easing: 'ease-out' } },
  scaleOrigin('Scale Up', 'scaleUp', 'bottom center'),
  scaleOrigin('Scale Down', 'scaleDown', 'top center'),
  scaleOrigin('Scale Left', 'scaleLeft', 'right center'),
  scaleOrigin('Scale Right', 'scaleRight', 'left center'),

  // Reveal (clip-path + fade — "soft" wipe).
  revealDir('Reveal', 'reveal', 'reveal', 'left'),
  revealDir('Reveal Left', 'revealLeft', 'reveal', 'left'),
  revealDir('Reveal Right', 'revealRight', 'reveal', 'right'),
  revealDir('Reveal Up', 'revealUp', 'reveal', 'up'),
  revealDir('Reveal Down', 'revealDown', 'reveal', 'down'),

  // Clip Path family — generic entry + the three center-outward shapes.
  revealDir('Clip Path', 'clipPath', 'wipe', 'left'),
  { id: 'circleReveal', label: 'Circle Reveal', category: 'Reveal', action: 'reveal', settings: { clipShape: 'circle', duration: '0.7s', easing: 'ease-out' } },
  { id: 'squareReveal', label: 'Square Reveal', category: 'Reveal', action: 'reveal', settings: { clipShape: 'square', duration: '0.7s', easing: 'ease-out' } },
  { id: 'diamondReveal', label: 'Diamond Reveal', category: 'Reveal', action: 'reveal', settings: { clipShape: 'diamond', duration: '0.7s', easing: 'ease-out' } },

  // Wipe (clip-path only, no fade — "hard" edge, the harder-edged counterpart
  // to the Reveal family above).
  revealDir('Wipe Left', 'wipeLeft', 'wipe', 'left'),
  revealDir('Wipe Right', 'wipeRight', 'wipe', 'right'),
  revealDir('Wipe Up', 'wipeUp', 'wipe', 'up'),
  revealDir('Wipe Down', 'wipeDown', 'wipe', 'down'),

  // Skew (bare combines skew+translate via customTransform for a richer
  // generic feel; Left/Right use the dedicated 'skew' action directly).
  customT('Skew', 'skew', 'Skew', 'skew(15deg, 5deg) translateX(20px)', { duration: '0.6s' }),
  { id: 'skewLeft', label: 'Skew Left', category: 'Skew', action: 'skew', settings: { rotationDeg: '-15', duration: '0.6s', easing: 'ease-out' } },
  { id: 'skewRight', label: 'Skew Right', category: 'Skew', action: 'skew', settings: { rotationDeg: '15', duration: '0.6s', easing: 'ease-out' } },

  // Elastic — new multi-oscillation action, distinct from Bounce's single
  // overshoot (see frontend.css's jalpi-ia-elastic/elasticX keyframes).
  elasticDir('Elastic', 'elastic'),
  elasticDir('Elastic In', 'elasticIn', 'down', '60px'),
  elasticDir('Elastic Up', 'elasticUp', 'up'),
  elasticDir('Elastic Down', 'elasticDown', 'down', '55px'),
  elasticDir('Elastic Left', 'elasticLeft', 'left'),
  elasticDir('Elastic Right', 'elasticRight', 'right'),
];

export function searchPresets(query: string, category?: AnimationCategory | 'All'): AnimationPreset[] {
  const q = query.trim().toLowerCase();
  return ANIMATION_PRESETS.filter((p) => {
    if (category && category !== 'All' && p.category !== category) return false;
    if (!q) return true;
    return p.label.toLowerCase().includes(q) || p.category.toLowerCase().includes(q);
  });
}

/**
 * Editor-only WAAPI preview keyframes for one (action, settings) pair.
 * Mirrors Jalpi_Renderer::interaction_css_vars()/frontend.css's @keyframes
 * closely enough for a truthful "what will this look like" preview — used
 * by both the preset picker's hover swatches and the configured-rule
 * restart-preview button (PropertiesPanel.tsx), so there is exactly one
 * implementation of "action + settings -> keyframes" instead of two.
 * Never touches the public frontend bundle.
 */
export function buildWaapiPreviewKeyframes(action: InteractionAction, settings: InteractionSettings): Keyframe[] {
  const s = settings || {};
  const dist = parseFloat(s.distance || '30') || 30;
  const dir = s.direction || 'up';
  const from: Record<string, string> = { opacity: '0' };
  const to: Record<string, string> = { opacity: '1' };

  switch (action) {
    case 'fade':
      break;
    case 'slide':
    case 'translate': {
      const map: Record<string, [number, number]> = {
        up: [0, dist], down: [0, -dist], left: [-dist, 0], right: [dist, 0],
      };
      const [tx, ty] = map[dir] || map.up;
      from.transform = `translate(${tx}px, ${ty}px)`;
      to.transform = 'translate(0, 0)';
      if (action === 'translate') { delete from.opacity; delete to.opacity; }
      break;
    }
    case 'zoom':
    case 'scale': {
      const scaleFrom = parseFloat(s.scaleValue || '0.3') || 0.3;
      from.transform = `scale(${scaleFrom})`;
      to.transform = 'scale(1)';
      break;
    }
    case 'rotate': {
      const rot = parseFloat(s.rotationDeg || '-15') || -15;
      from.transform = `rotate(${rot}deg)`;
      to.transform = 'rotate(0deg)';
      break;
    }
    case 'flip': {
      const isX = dir === 'up' || dir === 'down';
      const deg = dir === 'up' || dir === 'left' ? -90 : 90;
      from.transform = isX ? `perspective(600px) rotateX(${deg}deg)` : `perspective(600px) rotateY(${deg}deg)`;
      to.transform = 'perspective(600px) rotateX(0deg) rotateY(0deg)';
      break;
    }
    case 'blur':
      from.filter = `blur(${s.blurAmount || '10px'})`;
      to.filter = 'blur(0)';
      break;
    case 'bounce': {
      const bDist = parseFloat(s.distance || '30') || 30;
      const horiz = dir === 'left' || dir === 'right';
      if (horiz) {
        const tx = dir === 'left' ? -bDist : bDist;
        return [
          { opacity: 0, transform: `translateX(${tx}px)` },
          { opacity: 1, transform: `translateX(${-tx * 0.27}px)`, offset: 0.6 },
          { transform: `translateX(${tx * 0.13}px)`, offset: 0.8 },
          { opacity: 1, transform: 'translateX(0)' },
        ];
      }
      const ty = dir === 'up' ? bDist : -bDist;
      return [
        { opacity: 0, transform: `translateY(${ty}px)` },
        { opacity: 1, transform: 'translateY(8px)', offset: 0.6 },
        { transform: 'translateY(-4px)', offset: 0.8 },
        { opacity: 1, transform: 'translateY(0)' },
      ];
    }
    case 'jackInTheBox': {
      const scaleFrom = parseFloat(s.scaleValue || '0.1') || 0.1;
      return [
        { opacity: 0, transform: `scale(${scaleFrom}) rotate(30deg)` },
        { opacity: 1, transform: 'scale(1.1) rotate(-10deg)', offset: 0.5 },
        { transform: 'scale(0.9) rotate(5deg)', offset: 0.7 },
        { opacity: 1, transform: 'scale(1) rotate(0deg)' },
      ];
    }
    case 'customTransform':
      from.transform = s.customTransform || 'none';
      to.transform = 'none';
      if (s.customFilter) { from.filter = s.customFilter; to.filter = 'none'; }
      break;
    case 'elastic': {
      // Mirrors frontend.css's jalpi-ia-elastic/elasticX 5-stage shape.
      const eDist = parseFloat(s.distance || '50') || 50;
      const horiz = dir === 'left' || dir === 'right';
      const start = horiz ? (dir === 'left' ? -eDist : eDist) : (dir === 'down' ? -eDist : eDist);
      const axis = horiz ? 'X' : 'Y';
      const t = (v: number) => `translate${axis}(${v}px)`;
      return [
        { opacity: 0, transform: t(start) },
        { opacity: 1, transform: t(start * -0.15), offset: 0.4 },
        { transform: t(start * 0.08), offset: 0.6 },
        { transform: t(start * -0.04), offset: 0.8 },
        { opacity: 1, transform: t(0) },
      ];
    }
    case 'reveal':
    case 'wipe': {
      const shape = s.clipShape || 'edge';
      let clipFrom = 'inset(0 100% 0 0)';
      let clipTo = 'inset(0 0 0 0)';
      if (shape === 'circle') { clipFrom = 'circle(0% at 50% 50%)'; clipTo = 'circle(150% at 50% 50%)'; }
      else if (shape === 'diamond') { clipFrom = 'polygon(50% 50%, 50% 50%, 50% 50%, 50% 50%)'; clipTo = 'polygon(50% -50%, 150% 50%, 50% 150%, -50% 50%)'; }
      else if (shape === 'square') { clipFrom = 'inset(50% 50% 50% 50%)'; }
      else {
        const edgeMap: Record<string, string> = {
          left: 'inset(0 100% 0 0)', right: 'inset(0 0 0 100%)',
          up: 'inset(100% 0 0 0)', down: 'inset(0 0 100% 0)',
        };
        clipFrom = edgeMap[dir] || edgeMap.left;
      }
      from.clipPath = clipFrom;
      to.clipPath = clipTo;
      if (action === 'wipe') { delete from.opacity; delete to.opacity; }
      break;
    }
    default:
      break;
  }

  return [from as Keyframe, to as Keyframe];
}
