;; cycle-crossfade template
;;
;; Generalized cycling zoom-crossfade for any number of video layers.
;; Cycles through videos with smooth zoom-based crossfade transitions.
;;
;; Parameters:
;;   beat-data     - beat analysis node (drives timing)
;;   input-videos  - list of video nodes to cycle through
;;   init-clen     - initial cycle length in beats
;;
;; NOTE: The parameter is named "input-videos" (not "videos") because
;; template substitution replaces param names everywhere in the AST.
;; The planner's _expand_slice_on injects env["videos"] at plan time,
;; so (len videos) inside the lambda references that injected value.

(deftemplate cycle-crossfade
  (beat-data input-videos init-clen)

  (slice-on beat-data
    :videos input-videos
    :init {:cycle 0 :beat 0 :clen init-clen}
    :fn (lambda [acc i start end]
      (let [beat   (get acc "beat")
            clen   (get acc "clen")
            active (get acc "cycle")
            n      (len videos)
            phase3 (* beat 3)
            wt (lambda [p]
              (let [prev (mod (+ p (- n 1)) n)]
                (if (= active p)
                  (if (< phase3 clen) 1.0
                    (if (< phase3 (* clen 2))
                      (- 1.0 (* (/ (- phase3 clen) clen) 1.0))
                      0.0))
                  (if (= active prev)
                    (if (< phase3 clen) 0.0
                      (if (< phase3 (* clen 2))
                        (* (/ (- phase3 clen) clen) 1.0)
                        1.0))
                    0.0))))
            zm (lambda [p]
              (let [prev (mod (+ p (- n 1)) n)]
                (if (= active p)
                  ;; Active video: normal -> zoom out during transition -> tiny
                  (if (< phase3 clen) 1.0
                    (if (< phase3 (* clen 2))
                      (+ 1.0 (* (/ (- phase3 clen) clen) 1.0))
                      0.1))
                  (if (= active prev)
                    ;; Incoming video: tiny -> zoom in during transition -> normal
                    (if (< phase3 clen) 0.1
                      (if (< phase3 (* clen 2))
                        (+ 0.1 (* (/ (- phase3 clen) clen) 0.9))
                        1.0))
                    0.1))))
            new-acc (if (< (+ beat 1) clen)
                      (dict :cycle active :beat (+ beat 1) :clen clen)
                      (dict :cycle (mod (+ active 1) n) :beat 0
                            :clen (+ 40 (mod (* i 7) 41))))]
        {:layers (map (lambda [p]
                   {:video p :effects [{:effect zoom :amount (zm p)}]})
                 (range 0 n))
         :compose {:effect blend_multi :mode "alpha"
                   :weights (map (lambda [p] (wt p)) (range 0 n))}
         :acc new-acc}))))
