(local ffi (require :ffi))
(local pxl8 (require :pxl8))
(local effects (require :pxl8.effects))

(local SKY_GRADIENT_START 144)
(local SKY_GRADIENT_COUNT 16)
(local sky-radius 900)
(local sky-segments 16)
(local sky-rings 16)

(local NUM_RANDOM_STARS 300)
(local NUM_TINY_STARS 7000)
(local STAR_SEED 0xDEADBEEF)
(local STAR_CYCLE_PERIOD 7200)

;; Use existing bright palette colors
;; Silver/white: indices 14-15 (brightest grays)
(local IDX_SILVER 14)
;; Warm/torch: indices 216-218 (bright creams/yellows)
(local IDX_TORCH 216)
;; Blue/magic: indices 176-178 (purples - brightest of the range)
(local IDX_MAGIC 176)

(var sky-mesh nil)
(var star-time 0)
(var last-gradient-key nil)
(var random-stars [])
(var tiny-stars [])

(fn generate-sky-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
  (for [i 0 (- SKY_GRADIENT_COUNT 1)]
    (let [t (/ i (- SKY_GRADIENT_COUNT 1))
          r (math.floor (+ zenith-r (* t (- horizon-r zenith-r))))
          g (math.floor (+ zenith-g (* t (- horizon-g zenith-g))))
          b (math.floor (+ zenith-b (* t (- horizon-b zenith-b))))]
      (pxl8.set_palette_rgb (+ SKY_GRADIENT_START i) r g b))))

(fn create-sky-dome []
  (let [verts []
        indices []]

    (for [i 0 (- sky-rings 1)]
      (let [theta0 (* (/ i sky-rings) math.pi 0.5)
            theta1 (* (/ (+ i 1) sky-rings) math.pi 0.5)
            sin-t0 (math.sin theta0)
            cos-t0 (math.cos theta0)
            sin-t1 (math.sin theta1)
            cos-t1 (math.cos theta1)
            y0 (* sky-radius cos-t0)
            y1 (* sky-radius cos-t1)
            r0 (* sky-radius sin-t0)
            r1 (* sky-radius sin-t1)
            t0 (/ i sky-rings)
            t1 (/ (+ i 1) sky-rings)
            c0 (math.floor (+ SKY_GRADIENT_START (* t0 (- SKY_GRADIENT_COUNT 1)) 0.5))
            c1 (math.floor (+ SKY_GRADIENT_START (* t1 (- SKY_GRADIENT_COUNT 1)) 0.5))]

        (for [j 0 (- sky-segments 1)]
          (let [phi0 (* (/ j sky-segments) math.pi 2)
                phi1 (* (/ (+ j 1) sky-segments) math.pi 2)
                cos-p0 (math.cos phi0)
                sin-p0 (math.sin phi0)
                cos-p1 (math.cos phi1)
                sin-p1 (math.sin phi1)
                x00 (* r0 cos-p0) z00 (* r0 sin-p0)
                x01 (* r0 cos-p1) z01 (* r0 sin-p1)
                x10 (* r1 cos-p0) z10 (* r1 sin-p0)
                x11 (* r1 cos-p1) z11 (* r1 sin-p1)
                nx00 (- (* sin-t0 cos-p0)) ny00 (- cos-t0) nz00 (- (* sin-t0 sin-p0))
                nx01 (- (* sin-t0 cos-p1)) ny01 (- cos-t0) nz01 (- (* sin-t0 sin-p1))
                nx10 (- (* sin-t1 cos-p0)) ny10 (- cos-t1) nz10 (- (* sin-t1 sin-p0))
                nx11 (- (* sin-t1 cos-p1)) ny11 (- cos-t1) nz11 (- (* sin-t1 sin-p1))
                base-idx (# verts)]

            (if (= i 0)
                (do
                  (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
                  (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
                  (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
                  (table.insert indices base-idx)
                  (table.insert indices (+ base-idx 2))
                  (table.insert indices (+ base-idx 1)))
                (do
                  (table.insert verts {:x x00 :y y0 :z z00 :nx nx00 :ny ny00 :nz nz00 :color c0 :light 255})
                  (table.insert verts {:x x01 :y y0 :z z01 :nx nx01 :ny ny01 :nz nz01 :color c0 :light 255})
                  (table.insert verts {:x x11 :y y1 :z z11 :nx nx11 :ny ny11 :nz nz11 :color c1 :light 255})
                  (table.insert verts {:x x10 :y y1 :z z10 :nx nx10 :ny ny10 :nz nz10 :color c1 :light 255})
                  (table.insert indices base-idx)
                  (table.insert indices (+ base-idx 3))
                  (table.insert indices (+ base-idx 2))
                  (table.insert indices base-idx)
                  (table.insert indices (+ base-idx 2))
                  (table.insert indices (+ base-idx 1))))))))

    (set sky-mesh (pxl8.create_mesh verts indices))))

(fn update-gradient [zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b]
  (let [key (.. zenith-r "," zenith-g "," zenith-b "," horizon-r "," horizon-g "," horizon-b)]
    (when (not= key last-gradient-key)
      (generate-sky-gradient zenith-r zenith-g zenith-b horizon-r horizon-g horizon-b)
      (set last-gradient-key key))))

(fn galactic-band-factor [dx dy dz]
  (let [band-len (math.sqrt (+ (* 0.6 0.6) (* 0.3 0.3) (* 0.742 0.742)))
        bx (/ 0.6 band-len)
        by (/ 0.3 band-len)
        bz (/ 0.742 band-len)
        dist-from-band (math.abs (+ (* dx bx) (* dy by) (* dz bz)))
        in-band (- 1 (math.min (* dist-from-band 3) 1))]
    (* in-band in-band)))

(fn generate-stars []
  (set random-stars [])
  (set tiny-stars [])

  ;; Generate random stars - use full upper hemisphere (dy > -0.1)
  (for [i 0 (- NUM_RANDOM_STARS 1)]
    (let [h1 (pxl8.hash32 (+ STAR_SEED (* i 5)))
          h2 (pxl8.hash32 (+ STAR_SEED (* i 5) 1))
          h3 (pxl8.hash32 (+ STAR_SEED (* i 5) 2))
          h4 (pxl8.hash32 (+ STAR_SEED (* i 5) 3))
          theta (* (/ h1 0xFFFFFFFF) math.pi 2)
          phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
          sin-phi (math.sin phi)
          cos-phi (math.cos phi)
          dx (* sin-phi (math.cos theta))
          dy cos-phi
          dz (* sin-phi (math.sin theta))
          brightness-raw (/ (% h3 256) 255)
          brightness (math.floor (+ 60 (* brightness-raw brightness-raw 195)))
          color-type (% h4 100)
          color (if (< color-type 8) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
                    (< color-type 16) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
                    (+ IDX_SILVER (% (bit.rshift h4 8) 2)))]

      (when (> dy -0.1)
        (table.insert random-stars {:dx dx :dy dy :dz dz
                                    :brightness brightness
                                    :color color}))))

  (let [tiny-seed (+ STAR_SEED 0xCAFEBABE)]
    (for [i 0 (- NUM_TINY_STARS 1)]
      (let [h1 (pxl8.hash32 (+ tiny-seed (* i 4)))
            h2 (pxl8.hash32 (+ tiny-seed (* i 4) 1))
            h3 (pxl8.hash32 (+ tiny-seed (* i 4) 2))
            h4 (pxl8.hash32 (+ tiny-seed (* i 4) 3))
            theta (* (/ h1 0xFFFFFFFF) math.pi 2)
            phi (math.acos (- 1 (* (/ h2 0xFFFFFFFF) 1.0)))
            sin-phi (math.sin phi)
            cos-phi (math.cos phi)
            dx (* sin-phi (math.cos theta))
            dy cos-phi
            dz (* sin-phi (math.sin theta))
            band-boost (galactic-band-factor dx dy dz)
            base-bright (+ 40 (% h3 50))
            brightness (+ base-bright (math.floor (* band-boost 40)))
            color-shift (% h4 100)
            color (if (< color-shift 3) (+ IDX_TORCH (% (bit.rshift h4 8) 2))
                      (< color-shift 12) (+ IDX_MAGIC (% (bit.rshift h4 8) 2))
                      (+ IDX_SILVER (% (bit.rshift h4 8) 2)))]
        (when (> dy -0.1)
          (table.insert tiny-stars {:dx dx :dy dy :dz dz
                                    :brightness brightness
                                    :color color}))))))

(fn project-direction [dir-x dir-y dir-z yaw pitch cos-rot sin-rot width height]
  (let [rot-x (- (* dir-x cos-rot) (* dir-z sin-rot))
        rot-z (+ (* dir-x sin-rot) (* dir-z cos-rot))
        cos-yaw (math.cos yaw)
        sin-yaw (math.sin yaw)
        cos-pitch (math.cos pitch)
        sin-pitch (math.sin pitch)
        rotated-x (+ (* rot-x cos-yaw) (* rot-z sin-yaw))
        rotated-z (+ (* (- rot-x) sin-yaw) (* rot-z cos-yaw))
        rotated-y (- (* dir-y cos-pitch) (* rotated-z sin-pitch))
        final-z (+ (* dir-y sin-pitch) (* rotated-z cos-pitch))]
    (when (> final-z 0.01)
      (let [fov 1.047
            aspect (/ width height)
            half-fov-tan (math.tan (* fov 0.5))
            ndc-x (/ rotated-x (* final-z half-fov-tan aspect))
            ndc-y (/ rotated-y (* final-z half-fov-tan))]
        (when (and (>= ndc-x -1) (<= ndc-x 1) (>= ndc-y -1) (<= ndc-y 1))
          {:x (math.floor (* (+ 1 ndc-x) 0.5 width))
           :y (math.floor (* (- 1 ndc-y) 0.5 height))})))))

(fn render-stars [yaw pitch intensity dt]
  (set star-time (+ star-time (or dt 0)))
  (when (> intensity 0)
    (let [width (pxl8.get_width)
          height (pxl8.get_height)
          glows []
          fade-in (* intensity intensity)
          time-factor (/ star-time 60)
          star-rotation (/ (* star-time math.pi 2) STAR_CYCLE_PERIOD)
          cos-rot (math.cos star-rotation)
          sin-rot (math.sin star-rotation)]

      (each [i star (ipairs tiny-stars)]
        (let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)]
          (when screen
            (let [int (math.floor (* star.brightness fade-in))]
              (when (> int 8)
                (table.insert glows {:x screen.x :y screen.y
                                     :radius 1
                                     :intensity int
                                     :color star.color
                                     :shape effects.GLOW_CIRCLE}))))))

      (each [i star (ipairs random-stars)]
        (let [screen (project-direction star.dx star.dy star.dz yaw pitch cos-rot sin-rot width height)]
          (when screen
            (let [phase (+ (* i 2.137) (* time-factor 3.0))
                  twinkle (+ 0.75 (* 0.25 (math.sin (* phase 6.28))))
                  int (math.floor (* star.brightness fade-in twinkle))]
              (if (> star.brightness 220)
                  (do
                    (table.insert glows {:x screen.x :y screen.y
                                         :radius 3
                                         :intensity (math.floor (* int 1.5))
                                         :color star.color
                                         :shape effects.GLOW_DIAMOND})
                    (table.insert glows {:x screen.x :y screen.y
                                         :radius 5
                                         :intensity (math.floor (/ int 2))
                                         :color star.color
                                         :shape effects.GLOW_CIRCLE}))
                  (> star.brightness 180)
                  (do
                    (table.insert glows {:x screen.x :y screen.y
                                         :radius 2
                                         :intensity int
                                         :color star.color
                                         :shape effects.GLOW_DIAMOND})
                    (table.insert glows {:x screen.x :y screen.y
                                         :radius 4
                                         :intensity (math.floor (/ int 3))
                                         :color star.color
                                         :shape effects.GLOW_CIRCLE}))
                  (> star.brightness 120)
                  (do
                    (table.insert glows {:x screen.x :y screen.y
                                         :radius 2
                                         :intensity (math.floor (* int 0.67))
                                         :color star.color
                                         :shape effects.GLOW_DIAMOND})
                    (table.insert glows {:x screen.x :y screen.y
                                         :radius 3
                                         :intensity (math.floor (/ int 4))
                                         :color star.color
                                         :shape effects.GLOW_CIRCLE}))
                  (table.insert glows {:x screen.x :y screen.y
                                       :radius 2
                                       :intensity (math.floor (* int 0.5))
                                       :color star.color
                                       :shape effects.GLOW_CIRCLE}))))))

      (when (> (length glows) 0)
        (effects.glows glows)))))

(fn render [cam-x cam-y cam-z wireframe]
  (when (not sky-mesh) (create-sky-dome))
  (when sky-mesh
    (pxl8.draw_mesh sky-mesh {:x cam-x :y cam-y :z cam-z :passthrough true :wireframe wireframe})))

{:render render
 :render-stars render-stars
 :generate-stars generate-stars
 :update-gradient update-gradient
 :SKY_GRADIENT_START SKY_GRADIENT_START
 :SKY_GRADIENT_COUNT SKY_GRADIENT_COUNT}
