(ns fuseme.core
  (:require [clojure.string :as str]))

;; ── fuse bead conversion logic ──────────────────────────────────────────────

(def black-text-threshold 186)
(def threshold-multipliers [0.299 0.587 0.114])

(defn- get-symbol [index]
  (if (< index 26)
    (str (char (+ 65 index)))
    (let [hi (quot index 26)
          lo (rem index 26)]
      (str (char (+ 65 (dec hi))) (char (+ 65 lo))))))

(defn- hex-pad [n]
  (let [s (.toString n 16)]
    (if (= (count s) 1) (str "0" s) s)))

(defn- pixel->color [image-data x y image-width]
  (let [idx (* 4 (+ (* y image-width) x))
        a (aget (.-data image-data) (+ idx 3))]
    (if (= a 0)
      "#FFFFFF"
      (let [r (aget (.-data image-data) idx)
            g (aget (.-data image-data) (+ idx 1))
            b (aget (.-data image-data) (+ idx 2))]
        (str "#" (hex-pad r) (hex-pad g) (hex-pad b))))))

(defn- luminance [hex-color]
  (as-> hex-color $
    (str/replace $ #"#" "")
    (seq $)
    (partition 2 $)
    (map #(js/parseInt (apply str %) 16) $)
    (map-indexed (fn [idx val] (* val (get threshold-multipliers idx))) $)
    (reduce + $)))

(defn- text-color-for [hex-color]
  (if (> (luminance hex-color) black-text-threshold) "#000000" "#FFFFFF"))

(defn- rect-svg [x y size fill]
  (str "<rect x=\"" x "\" y=\"" y "\" width=\"" size
       "\" height=\"" size "\" fill=\"" fill
       "\" stroke=\"#000000\" stroke-width=\"1\" />"))

(defn- text-svg [x y font-size fill symbol]
  (str "<text x=\"" x "\" y=\"" y
       "\" font-size=\"" font-size "px\""
       " fill=\"" fill "\" stroke=\"" fill
       "\" stroke-width=\"0.3\""
       " dominant-baseline=\"middle\""
       " text-anchor=\"middle\""
       " font-family=\"monospace\""
       " font-weight=\"bold\">"
       symbol "</text>"))

(defn- extract-colors [image-data image-width image-height]
  (->> (for [y (range image-height)
             x (range image-width)]
         (pixel->color image-data x y image-width))
       (distinct)
       (sort)))

(defn- build-color-symbols [colors]
  (->> colors
       (map-indexed (fn [idx color] [color (get-symbol idx)]))
       (into {})))

(defn- colors-summary [colors symbols]
  (str/join "\n"
            (map (fn [[color symbol]]
                   (str symbol ": " color))
                 (sort-by second (map (fn [c] [c (get symbols c)]) colors)))))

(defn- pixels->fused-svg
  [image-data image-width image-height pixel-multiplier]
  (let [colors (extract-colors image-data image-width image-height)
        symbols (build-color-symbols colors)
        font-padding 2
        rect-size pixel-multiplier
        elements
        (for [y (range image-height)
              x (range image-width)
              :let [color (pixel->color image-data x y image-width)
                    scaled-x (* x pixel-multiplier)
                    scaled-y (* y pixel-multiplier)
                    text-color (text-color-for color)
                    center-x (+ scaled-x (/ rect-size 2))
                    text-y (- (+ scaled-y rect-size) font-padding)
                    font-size (- rect-size font-padding)
                    symbol (get symbols color)]]
          (str (rect-svg scaled-x scaled-y rect-size color)
               (when (and symbol (not= color "#FFFFFF"))
                 (text-svg center-x text-y font-size text-color symbol))))]
    (str "<svg viewBox=\"0 0 " (* image-width pixel-multiplier) " " (* image-height pixel-multiplier)
         "\" xmlns=\"http://www.w3.org/2000/svg\""
         " shape-rendering=\"crispEdges\""
         " style=\"width:100%;height:auto;display:block\">"
         (apply str elements)
         "</svg>")))

(defn process-data-url!
  "takes a png data-url string and produces a fused SVG.
   calls on-result with {:svg fused-svg-string, :original-width, :original-height, :colors-summary}
   or calls on-error with an error message string."
  [data-url on-result on-error]
  (let [img (js/Image.)]
    (set! (.-onload img)
          (fn []
            (let [canvas (.createElement js/document "canvas")
                  width (.-naturalWidth img)
                  height (.-naturalHeight img)]
              (set! (.-width canvas) width)
              (set! (.-height canvas) height)
              (let [ctx (.getContext canvas "2d")]
                (.drawImage ctx img 0 0)
                (let [image-data (.getImageData ctx 0 0 width height)
                      svg (pixels->fused-svg image-data width height 10)
                      colors (extract-colors image-data width height)
                      symbols (build-color-symbols colors)
                      summary (colors-summary colors symbols)]
                  (on-result {:svg svg
                              :original-width width
                              :original-height height
                              :colors-summary summary}))))))
    (set! (.-onerror img) (fn [_] (on-error "failed to load image")))
    (set! (.-src img) data-url)))