// Timeline Editor — unifies "Stagger / Sequential / Group / Timeline
// Animation" into one mechanism. Deliberately NOT a new data model: a track's
// bar position/width is a direct visual read/write of the corresponding
// child's own interactions[].settings.delay/duration (via the existing
// addInteraction/updateInteraction store actions). The renderer/runtime never
// need to know "timeline" exists — they just see ordinary per-node rules with
// different delay values, same as if a user had set them one at a time in
// the Animation tab. Editor-only: nothing here reaches the public frontend
// bundle. Playback preview uses the Web Animations API against the live
// canvas DOM nodes (resolved via [data-node-id], the same attribute
// CanvasNode.tsx's own selection/resize code already uses).
import React, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
import { createPortal } from 'react-dom';
import type { CBNode, InteractionTrigger, InteractionRule } from '../../types';
import type { EditorStore } from '../../store/editorStore';
import { buildWaapiPreviewKeyframes } from '../../utils/animationPresets';

const PX_PER_SECOND = 140;
const MIN_TRACK_MS = 100;

const TRACK_TRIGGER_OPTIONS: Array<{ value: InteractionTrigger; label: string }> = [
  { value: 'pageLoad', label: 'On Page Load' },
  { value: 'scrollIntoView', label: 'On Scroll Into View' },
];

function msToPx(ms: number): number { return (ms / 1000) * PX_PER_SECOND; }
function pxToMs(px: number): number { return Math.max(0, (px / PX_PER_SECOND) * 1000); }

function parseSeconds(v: string | undefined, fallback: number): number {
  const n = parseFloat(v || '');
  return Number.isFinite(n) ? n * 1000 : fallback;
}

interface TrackRule { child: CBNode; rule: InteractionRule | null; }

export default function TimelinePanel({
  node,
  store,
  onClose,
}: {
  node: CBNode;
  store: EditorStore;
  onClose: () => void;
}) {
  const [trigger, setTrigger] = useState<InteractionTrigger>('scrollIntoView');
  const [isPlaying, setIsPlaying] = useState(false);
  const [playheadMs, setPlayheadMs] = useState(0);
  const [staggerStep, setStaggerStep] = useState('100');
  const rafRef = useRef<number | null>(null);
  const playStartRef = useRef(0);

  useEffect(() => {
    const h = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); };
    document.addEventListener('keydown', h);
    return () => document.removeEventListener('keydown', h);
  }, [onClose]);

  const children = node.children ?? [];

  // One track per direct child — a track's rule is whichever of that child's
  // interactions matches the timeline's currently selected trigger.
  const tracks: TrackRule[] = useMemo(
    () => children.map((child) => ({
      child,
      rule: (child.interactions ?? []).find((r) => r.trigger === trigger) ?? null,
    })),
    [children, trigger]
  );

  const totalMs = useMemo(() => {
    let max = 2000;
    tracks.forEach(({ rule }) => {
      if (!rule) return;
      const d = parseSeconds(rule.settings?.delay, 0) + parseSeconds(rule.settings?.duration, 600);
      if (d > max) max = d;
    });
    return Math.ceil((max + 500) / 500) * 500;
  }, [tracks]);

  const ensureRule = useCallback((childId: string): void => {
    store.addInteraction(childId, trigger, 'fade', { duration: '0.6s' });
  }, [store, trigger]);

  const setDelay = useCallback((childId: string, ruleId: string, delayMs: number) => {
    store.updateInteraction(childId, ruleId, { settings: { delay: `${Math.max(0, delayMs / 1000).toFixed(2)}s` } } as Partial<InteractionRule>);
  }, [store]);

  const setDuration = useCallback((childId: string, ruleId: string, durationMs: number) => {
    store.updateInteraction(childId, ruleId, { settings: { duration: `${Math.max(MIN_TRACK_MS, durationMs) / 1000}s` } } as Partial<InteractionRule>);
  }, [store]);

  const staggerAll = () => {
    const step = parseFloat(staggerStep) || 0;
    tracks.forEach(({ child, rule }, i) => {
      if (!rule) return;
      setDelay(child.id, rule.id, i * step);
    });
  };

  const stopPlayback = useCallback(() => {
    if (rafRef.current) cancelAnimationFrame(rafRef.current);
    rafRef.current = null;
    setIsPlaying(false);
  }, []);

  const play = () => {
    // Cancel any in-flight preview animations before starting a fresh pass.
    tracks.forEach(({ child }) => {
      const el = document.querySelector(`[data-node-id="${child.id}"]`) as HTMLElement | null;
      el?.getAnimations().forEach((a) => { if (a.id === 'jalpi-timeline-preview') a.cancel(); });
    });

    tracks.forEach(({ child, rule }) => {
      if (!rule || rule.enabled === false) return;
      const el = document.querySelector(`[data-node-id="${child.id}"]`) as HTMLElement | null;
      if (!el || typeof el.animate !== 'function') return;
      const s = rule.settings || {};
      const anim = el.animate(buildWaapiPreviewKeyframes(rule.action, s), {
        duration: parseSeconds(s.duration, 600),
        delay: parseSeconds(s.delay, 0),
        easing: s.easing || 'ease-out',
        fill: 'both',
      });
      anim.id = 'jalpi-timeline-preview';
    });

    setIsPlaying(true);
    playStartRef.current = performance.now();
    const tick = () => {
      const elapsed = performance.now() - playStartRef.current;
      if (elapsed >= totalMs) {
        setPlayheadMs(totalMs);
        stopPlayback();
        return;
      }
      setPlayheadMs(elapsed);
      rafRef.current = requestAnimationFrame(tick);
    };
    rafRef.current = requestAnimationFrame(tick);
  };

  useEffect(() => () => { if (rafRef.current) cancelAnimationFrame(rafRef.current); }, []);

  return createPortal(
    <div className="jalpi-tl-overlay">
      <div className="jalpi-tl-panel" role="dialog" aria-label={`Timeline — ${node.type}`}>
        <div className="jalpi-tl-header">
          <span className="jalpi-tl-title">Timeline</span>
          <select
            className="jalpi-tl-trigger"
            value={trigger}
            onChange={(e) => setTrigger(e.target.value as InteractionTrigger)}
            aria-label="Timeline trigger"
          >
            {TRACK_TRIGGER_OPTIONS.map((o) => <option key={o.value} value={o.value}>{o.label}</option>)}
          </select>

          <button type="button" className="jalpi-tl-btn" onClick={isPlaying ? stopPlayback : play}>
            {isPlaying ? 'Stop' : 'Play'}
          </button>

          <span className="jalpi-tl-stagger">
            <label htmlFor="jalpi-tl-stagger-input">Stagger by</label>
            <input
              id="jalpi-tl-stagger-input"
              type="number"
              min={0}
              step={10}
              value={staggerStep}
              onChange={(e) => setStaggerStep(e.target.value)}
            />
            <span>ms</span>
            <button type="button" className="jalpi-tl-btn jalpi-tl-btn--sm" onClick={staggerAll}>Stagger All</button>
          </span>

          <button type="button" className="jalpi-tl-close" onClick={onClose} aria-label="Close timeline">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
              <path d="M6 6l12 12M18 6L6 18" />
            </svg>
          </button>
        </div>

        <div className="jalpi-tl-ruler" style={{ width: msToPx(totalMs) + 160 }}>
          {Array.from({ length: Math.floor(totalMs / 500) + 1 }, (_, i) => i * 500).map((ms) => (
            <span key={ms} className="jalpi-tl-ruler__tick" style={{ left: 160 + msToPx(ms) }}>{(ms / 1000).toFixed(1)}s</span>
          ))}
          <span className="jalpi-tl-playhead" style={{ left: 160 + msToPx(playheadMs) }} />
        </div>

        <div className="jalpi-tl-tracks">
          {tracks.length === 0 && (
            <p className="jalpi-ia-hint" style={{ padding: 16 }}>This element has no direct children to sequence.</p>
          )}
          {tracks.map(({ child, rule }) => (
            <TimelineTrack
              key={child.id}
              child={child}
              rule={rule}
              totalMs={totalMs}
              onEnsureRule={() => ensureRule(child.id)}
              onSetDelay={(ms) => rule && setDelay(child.id, rule.id, ms)}
              onSetDuration={(ms) => rule && setDuration(child.id, rule.id, ms)}
            />
          ))}
        </div>
      </div>
    </div>,
    document.body
  );
}

function TimelineTrack({
  child, rule, totalMs, onEnsureRule, onSetDelay, onSetDuration,
}: {
  child: CBNode;
  rule: InteractionRule | null;
  totalMs: number;
  onEnsureRule: () => void;
  onSetDelay: (ms: number) => void;
  onSetDuration: (ms: number) => void;
}) {
  const barRef = useRef<HTMLDivElement>(null);
  const drag = useRef<{ mode: 'move' | 'resize'; startX: number; startDelay: number; startDuration: number } | null>(null);

  const delayMs = parseSeconds(rule?.settings?.delay, 0);
  const durationMs = parseSeconds(rule?.settings?.duration, 600);

  const onMouseDown = (mode: 'move' | 'resize') => (e: React.MouseEvent) => {
    e.preventDefault();
    drag.current = { mode, startX: e.clientX, startDelay: delayMs, startDuration: durationMs };
    const onMove = (ev: MouseEvent) => {
      if (!drag.current) return;
      const deltaPx = ev.clientX - drag.current.startX;
      const deltaMs = pxToMs(Math.abs(deltaPx)) * Math.sign(deltaPx);
      if (drag.current.mode === 'move') {
        onSetDelay(Math.max(0, drag.current.startDelay + deltaMs));
      } else {
        onSetDuration(Math.max(MIN_TRACK_MS, drag.current.startDuration + deltaMs));
      }
    };
    const onUp = () => {
      drag.current = null;
      window.removeEventListener('mousemove', onMove);
      window.removeEventListener('mouseup', onUp);
    };
    window.addEventListener('mousemove', onMove);
    window.addEventListener('mouseup', onUp);
  };

  return (
    <div className="jalpi-tl-track">
      <div className="jalpi-tl-track__label">{child.type}{rule?.enabled === false ? ' (disabled)' : ''}</div>
      <div className="jalpi-tl-track__lane" style={{ width: msToPx(totalMs) }}>
        {rule ? (
          <div
            ref={barRef}
            className="jalpi-tl-bar"
            style={{ left: msToPx(delayMs), width: Math.max(16, msToPx(durationMs)) }}
            onMouseDown={onMouseDown('move')}
            title={`Delay ${(delayMs / 1000).toFixed(2)}s · Duration ${(durationMs / 1000).toFixed(2)}s`}
          >
            <span className="jalpi-tl-bar__handle" onMouseDown={(e) => { e.stopPropagation(); onMouseDown('resize')(e); }} />
          </div>
        ) : (
          <button type="button" className="jalpi-tl-track__add" onClick={onEnsureRule}>+ Add to timeline</button>
        )}
      </div>
    </div>
  );
}
