;; Woods Recipe - Using friendly names for all assets
;;
;; Requires uploaded:
;;   - Media: woods-1 through woods-8 (videos), woods-audio (audio)
;;   - Effects: fx-rotate, fx-zoom, fx-blend, fx-ripple, fx-invert, fx-hue-shift
;;   - Templates: tpl-standard-primitives, tpl-standard-effects, tpl-process-pair,
;;                tpl-crossfade-zoom, tpl-scan-spin, tpl-scan-ripple

(stream "woods-recipe"
  :fps 30
  :width 1920
  :height 1080
  :seed 42

  ;; Load standard primitives and effects via friendly names
  (include :name "tpl-standard-primitives")
  (include :name "tpl-standard-effects")

  ;; Load reusable templates
  (include :name "tpl-process-pair")
  (include :name "tpl-crossfade-zoom")

  ;; === SOURCES AS ARRAY (using friendly names) ===
  (def sources [
    (streaming:make-video-source "woods-1" 30)
    (streaming:make-video-source "woods-2" 30)
    (streaming:make-video-source "woods-3" 30)
    (streaming:make-video-source "woods-4" 30)
    (streaming:make-video-source "woods-5" 30)
    (streaming:make-video-source "woods-6" 30)
    (streaming:make-video-source "woods-7" 30)
    (streaming:make-video-source "woods-8" 30)
  ])

  ;; Per-pair config: [rot-dir, rot-a-max, rot-b-max, zoom-a-max, zoom-b-max]
  (def pair-configs [
    {:dir -1 :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5}
    {:dir 1  :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5}
    {:dir 1  :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5}
    {:dir -1 :rot-a -45 :rot-b 45 :zoom-a 0.5 :zoom-b 1.5}
    {:dir -1 :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5}
    {:dir 1  :rot-a 30 :rot-b -30 :zoom-a 1.3 :zoom-b 0.7}
    {:dir -1 :rot-a -45 :rot-b 45 :zoom-a 0.5 :zoom-b 1.5}
    {:dir 1  :rot-a 45 :rot-b -45 :zoom-a 1.5 :zoom-b 0.5}
  ])

  ;; Audio analyzer (using friendly name)
  (def music (streaming:make-audio-analyzer "woods-audio"))

  ;; Audio playback (friendly name resolved by streaming primitives)
  (audio-playback "woods-audio")

  ;; === GLOBAL SCANS ===

  ;; Cycle state: which source is active
  (scan cycle (streaming:audio-beat music t)
    :init {:active 0 :beat 0 :clen 16}
    :step (if (< (+ beat 1) clen)
            (dict :active active :beat (+ beat 1) :clen clen)
            (dict :active (mod (+ active 1) (len sources)) :beat 0
                  :clen (+ 8 (mod (* (streaming:audio-beat-count music t) 7) 17)))))

  ;; Reusable scans from templates
  (include :name "tpl-scan-spin")
  (include :name "tpl-scan-ripple")

  ;; === PER-PAIR STATE ===
  (scan pairs (streaming:audio-beat music t)
    :init {:states (map (core:range (len sources)) (lambda (_)
      {:inv-a 0 :inv-b 0 :hue-a 0 :hue-b 0 :hue-a-val 0 :hue-b-val 0 :mix 0.5 :mix-rem 5 :angle 0 :rot-beat 0 :rot-clen 25}))}
    :step (dict :states (map states (lambda (p)
      (let [new-inv-a (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- (get p :inv-a) 1)))
            new-inv-b (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- (get p :inv-b) 1)))
            old-hue-a (get p :hue-a)
            old-hue-b (get p :hue-b)
            new-hue-a (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- old-hue-a 1)))
            new-hue-b (if (< (core:rand) 0.1) (+ 1 (core:rand-int 1 4)) (core:max 0 (- old-hue-b 1)))
            new-hue-a-val (if (> new-hue-a old-hue-a) (+ 30 (* (core:rand) 300)) (get p :hue-a-val))
            new-hue-b-val (if (> new-hue-b old-hue-b) (+ 30 (* (core:rand) 300)) (get p :hue-b-val))
            mix-rem (get p :mix-rem)
            old-mix (get p :mix)
            new-mix-rem (if (> mix-rem 0) (- mix-rem 1) (+ 1 (core:rand-int 1 10)))
            new-mix (if (> mix-rem 0) old-mix (* (core:rand-int 0 2) 0.5))
            rot-beat (get p :rot-beat)
            rot-clen (get p :rot-clen)
            old-angle (get p :angle)
            new-rot-beat (if (< (+ rot-beat 1) rot-clen) (+ rot-beat 1) 0)
            new-rot-clen (if (< (+ rot-beat 1) rot-clen) rot-clen (+ 20 (core:rand-int 0 10)))
            new-angle (+ old-angle (/ 360 rot-clen))]
        (dict :inv-a new-inv-a :inv-b new-inv-b
              :hue-a new-hue-a :hue-b new-hue-b
              :hue-a-val new-hue-a-val :hue-b-val new-hue-b-val
              :mix new-mix :mix-rem new-mix-rem
              :angle new-angle :rot-beat new-rot-beat :rot-clen new-rot-clen))))))

  ;; === FRAME PIPELINE ===
  (frame
    (let [now t
          e (streaming:audio-energy music now)

          ;; Get cycle state
          active (bind cycle :active)
          beat-pos (bind cycle :beat)
          clen (bind cycle :clen)

          ;; Transition logic
          phase3 (* beat-pos 3)
          fading (and (>= phase3 (* clen 2)) (< phase3 (* clen 3)))
          fade-amt (if fading (/ (- phase3 (* clen 2)) clen) 0)
          next-idx (mod (+ active 1) (len sources))

          ;; Get pair states array
          pair-states (bind pairs :states)

          ;; Process active pair using macro from template
          active-frame (process-pair active)

          ;; Crossfade with zoom during transition
          result (if fading
                   (crossfade-zoom active-frame (process-pair next-idx) fade-amt)
                   active-frame)

          ;; Final: global spin + ripple
          spun (rotate result :angle (bind spin :angle))
          rip-gate (bind ripple-state :gate)
          rip-amp (* rip-gate (core:map-range e 0 1 5 50))]

      (ripple spun
        :amplitude rip-amp
        :center_x (bind ripple-state :cx)
        :center_y (bind ripple-state :cy)
        :frequency 8
        :decay 2
        :speed 5))))
