;; Events page-level components (slots, ticket types, buy form, cart, posts nav)

(defcomp ~page/slot-days-pills (&key days-inner)
  (div :class "flex flex-wrap gap-1" days-inner))

(defcomp ~page/slot-day-pill (&key day)
  (span :class "px-2 py-0.5 rounded-full text-xs bg-slate-200" day))

(defcomp ~page/slot-no-days ()
  (span :class "text-xs text-slate-400" "No days"))

(defcomp ~page/slot-panel (&key slot-id list-container days flexible time-str cost-str pre-action edit-url)
  (section :id (str "slot-" slot-id) :class list-container
    (div :class "flex flex-col"
      (div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" "Days")
      (div :class "mt-1" days))
    (div :class "flex flex-col"
      (div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" "Flexible")
      (div :class "mt-1" flexible))
    (div :class "grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm"
      (div :class "flex flex-col"
        (div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" "Time")
        (div :class "mt-1" time-str))
      (div :class "flex flex-col"
        (div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" "Cost")
        (div :class "mt-1" cost-str)))
    (button :type "button" :class pre-action :sx-get edit-url
            :sx-target (str "#slot-" slot-id) :sx-swap "outerHTML" "Edit")))

(defcomp ~page/slot-description-oob (&key description)
  (div :id "slot-description-title" :sx-swap-oob "outerHTML"
       :class "text-base font-normal break-words whitespace-normal min-w-0 break-all w-full text-center block"
    description))

(defcomp ~page/slots-empty-row ()
  (tr (td :colspan "5" :class "p-3 text-stone-500" "No slots yet.")))

(defcomp ~page/slots-row (&key tr-cls slot-href pill-cls hx-select slot-name description
                                  flexible days time-str cost-str action-btn del-url csrf-hdr)
  (tr :class tr-cls
    (td :class "p-2 align-top w-1/6"
      (div :class "font-medium"
        (a :href slot-href :class pill-cls :sx-get slot-href :sx-target "#main-panel"
           :sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true" slot-name))
      (p :class "text-stone-500 whitespace-pre-line break-all w-full" description))
    (td :class "p-2 align-top w-1/6" flexible)
    (td :class "p-2 align-top w-1/6" days)
    (td :class "p-2 align-top w-1/6" time-str)
    (td :class "p-2 align-top w-1/6" cost-str)
    (td :class "p-2 align-top w-1/6"
      (button :class action-btn :type "button"
              :data-confirm "true" :data-confirm-title "Delete slot?"
              :data-confirm-text "This action cannot be undone."
              :data-confirm-icon "warning" :data-confirm-confirm-text "Yes, delete it"
              :data-confirm-cancel-text "Cancel" :data-confirm-event "confirmed"
              :sx-delete del-url :sx-target "#slots-table" :sx-select "#slots-table"
              :sx-swap "outerHTML" :sx-headers csrf-hdr :sx-trigger "confirmed"
        (i :class "fa-solid fa-trash")))))

(defcomp ~page/slots-table (&key list-container rows pre-action add-url)
  (section :id "slots-table" :class list-container
    (table :class "w-full text-sm border table-fixed"
      (thead :class "bg-stone-100"
        (tr (th :class "p-2 text-left w-1/6" "Name")
            (th :class "p-2 text-left w-1/6" "Flexible")
            (th :class "text-left p-2 w-1/6" "Days")
            (th :class "text-left p-2 w-1/6" "Time")
            (th :class "text-left p-2 w-1/6" "Cost")
            (th :class "text-left p-2 w-1/6" "Actions")))
      (tbody rows))
    (div :id "slot-add-container" :class "mt-4"
      (button :type "button" :class pre-action
              :sx-get add-url :sx-target "#slot-add-container" :sx-swap "innerHTML"
        "+ Add slot"))))

;; ---------------------------------------------------------------------------
;; Composition defcomps — receive data, compose slot/table trees
;; ---------------------------------------------------------------------------

;; Days pills from data — replaces Python loop
(defcomp ~page/days-pills-from-data (&key days)
  (if (empty? (or days (list)))
    (~page/slot-no-days)
    (~page/slot-days-pills
      :days-inner (<> (map (lambda (d) (~page/slot-day-pill :day d)) days)))))

;; Slot panel from data
(defcomp ~page/slot-panel-from-data (&key slot-id list-container days
                                              flexible time-str cost-str
                                              pre-action edit-url description oob)
  (<>
    (~page/slot-panel
      :slot-id slot-id :list-container list-container
      :days (~page/days-pills-from-data :days days)
      :flexible flexible :time-str time-str :cost-str cost-str
      :pre-action pre-action :edit-url edit-url)
    (when oob
      (~page/slot-description-oob :description (or description "")))))

;; Slots table from data
(defcomp
  ~page/slots-table-from-data
  (&key
    list-container
    slots
    pre-action
    add-url
    tr-cls
    pill-cls
    action-btn
    hx-select
    csrf-hdr)
  (~page/slots-table
    :list-container list-container
    :rows (if
      (empty? (or slots (list)))
      (~page/slots-empty-row)
      (<>
        (map
          (lambda
            (s)
            (let-match
              {:slot-name slot-name :time-str time-str :flexible flexible :description description :days days :cost-str cost-str :del-url del-url :slot-href slot-href}
              s
              (~page/slots-row
                :tr-cls tr-cls
                :slot-href slot-href
                :pill-cls pill-cls
                :hx-select hx-select
                :slot-name slot-name
                :description description
                :flexible flexible
                :days (~page/days-pills-from-data :days days)
                :time-str time-str
                :cost-str cost-str
                :action-btn action-btn
                :del-url del-url
                :csrf-hdr csrf-hdr)))
          (or slots (list)))))
    :pre-action pre-action
    :add-url add-url))

(defcomp ~page/ticket-type-col (&key label value)
  (div :class "flex flex-col"
    (div :class "text-xs font-semibold uppercase tracking-wide text-stone-500" label)
    (div :class "mt-1" value)))

(defcomp ~page/ticket-type-panel (&key ticket-id list-container c1 c2 c3 pre-action edit-url)
  (section :id (str "ticket-" ticket-id) :class list-container
    (div :class "grid grid-cols-1 sm:grid-cols-3 gap-4 text-sm"
      c1 c2 c3)
    (button :type "button" :class pre-action :sx-get edit-url
            :sx-target (str "#ticket-" ticket-id) :sx-swap "outerHTML" "Edit")))

(defcomp ~page/ticket-types-empty-row ()
  (tr (td :colspan "4" :class "p-3 text-stone-500" "No ticket types yet.")))

(defcomp ~page/ticket-types-row (&key tr-cls tt-href pill-cls hx-select tt-name cost-str count
                                         action-btn del-url csrf-hdr)
  (tr :class tr-cls
    (td :class "p-2 align-top w-1/3"
      (div :class "font-medium"
        (a :href tt-href :class pill-cls :sx-get tt-href :sx-target "#main-panel"
           :sx-select hx-select :sx-swap "outerHTML" :sx-push-url "true" tt-name)))
    (td :class "p-2 align-top w-1/4" cost-str)
    (td :class "p-2 align-top w-1/4" count)
    (td :class "p-2 align-top w-1/6"
      (button :class action-btn :type "button"
              :data-confirm "true" :data-confirm-title "Delete ticket type?"
              :data-confirm-text "This action cannot be undone."
              :data-confirm-icon "warning" :data-confirm-confirm-text "Yes, delete it"
              :data-confirm-cancel-text "Cancel" :data-confirm-event "confirmed"
              :sx-delete del-url :sx-target "#tickets-table" :sx-select "#tickets-table"
              :sx-swap "outerHTML" :sx-headers csrf-hdr :sx-trigger "confirmed"
        (i :class "fa-solid fa-trash")))))

(defcomp ~page/ticket-types-table (&key list-container rows action-btn add-url)
  (section :id "tickets-table" :class list-container
    (table :class "w-full text-sm border table-fixed"
      (thead :class "bg-stone-100"
        (tr (th :class "p-2 text-left w-1/3" "Name")
            (th :class "text-left p-2 w-1/4" "Cost")
            (th :class "text-left p-2 w-1/4" "Count")
            (th :class "text-left p-2 w-1/6" "Actions")))
      (tbody rows))
    (div :id "ticket-add-container" :class "mt-4"
      (button :class action-btn :sx-get add-url :sx-target "#ticket-add-container" :sx-swap "innerHTML"
        (i :class "fa fa-plus") " Add ticket type"))))

(defcomp ~page/ticket-config-display (&key price-str count-str show-js)
  (div :class "space-y-2"
    (div :class "flex items-center gap-2"
      (span :class "text-sm font-medium text-stone-700" "Price:")
      (span :class "font-medium text-green-600" price-str))
    (div :class "flex items-center gap-2"
      (span :class "text-sm font-medium text-stone-700" "Available:")
      (span :class "font-medium text-blue-600" count-str))
    (button :type "button" :class "text-xs text-blue-600 hover:text-blue-800 underline"
            :onclick show-js "Edit ticket config")))

(defcomp ~page/ticket-config-none (&key show-js)
  (div :class "space-y-2"
    (span :class "text-sm text-stone-400" "No tickets configured")
    (button :type "button" :class "block text-xs text-blue-600 hover:text-blue-800 underline"
            :onclick show-js "Configure tickets")))

(defcomp ~page/ticket-config-form (&key entry-id hidden-cls update-url csrf price-val count-val hide-js)
  (form :id (str "ticket-form-" entry-id) :class (str hidden-cls " space-y-3 mt-2 p-3 border rounded bg-stone-50")
        :sx-post update-url :sx-target (str "#entry-tickets-" entry-id) :sx-swap "innerHTML"
    (input :type "hidden" :name "csrf_token" :value csrf)
    (div (label :for (str "ticket-price-" entry-id) :class "block text-sm font-medium text-stone-700 mb-1"
           "Ticket Price (£)")
      (input :type "number" :id (str "ticket-price-" entry-id) :name "ticket_price"
             :step "0.01" :min "0" :value price-val
             :class "w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
             :placeholder "e.g., 5.00"))
    (div (label :for (str "ticket-count-" entry-id) :class "block text-sm font-medium text-stone-700 mb-1"
           "Total Tickets")
      (input :type "number" :id (str "ticket-count-" entry-id) :name "ticket_count"
             :min "0" :value count-val
             :class "w-full px-3 py-2 border rounded focus:outline-none focus:ring-2 focus:ring-blue-500"
             :placeholder "Leave empty for unlimited"))
    (div :class "flex gap-2"
      (button :type "submit" :class "px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm" "Save")
      (button :type "button" :class "px-4 py-2 bg-stone-200 text-stone-700 rounded hover:bg-stone-300 text-sm"
              :onclick hide-js "Cancel"))))

;; Data-driven buy form — Python passes pre-resolved data, .sx does layout + iteration
(defcomp
  ~page/buy-form
  (&key
    entry-id
    info-sold
    info-remaining
    info-basket
    ticket-types
    user-ticket-counts-by-type
    user-ticket-count
    price-str
    adjust-url
    csrf
    state
    my-tickets-href)
  (if
    (!= state "confirmed")
    (~page/buy-not-confirmed :entry-id (str entry-id))
    (let
      ((eid-s (str entry-id)) (target (str "#ticket-buy-" entry-id)))
      (div
        :id (str "ticket-buy-" entry-id)
        :class "rounded-xl border border-stone-200 bg-white p-4"
        (h3
          :class "text-sm font-semibold text-stone-700 mb-3"
          (i :class "fa fa-ticket mr-1" :aria-hidden "true")
          "Tickets")
        (when
          (or info-sold info-remaining info-basket)
          (div
            :class "flex items-center gap-3 mb-3 text-xs text-stone-500"
            (when info-sold (span (str info-sold " sold")))
            (when info-remaining (span (str info-remaining " remaining")))
            (when
              info-basket
              (span
                :class "text-emerald-600 font-medium"
                (i
                  :class "fa fa-shopping-cart text-[0.6rem]"
                  :aria-hidden "true")
                (str " " info-basket " in basket")))))
        (if
          (and ticket-types (not (empty? ticket-types)))
          (div
            :class "space-y-2"
            (map
              (fn
                (tt)
                (let-match
                  {:cost_str cost-str :id tt-id :name tt-name}
                  tt
                  (let
                    ((tt-count (if user-ticket-counts-by-type (get user-ticket-counts-by-type (str tt-id) 0) 0)))
                    (div
                      :class "flex items-center justify-between p-3 rounded-lg bg-stone-50 border border-stone-100"
                      (div
                        (div :class "font-medium text-sm" tt-name)
                        (div :class "text-xs text-stone-500" cost-str))
                      (~page/adjust-inline
                        :csrf csrf
                        :adjust-url adjust-url
                        :target target
                        :entry-id eid-s
                        :count tt-count
                        :ticket-type-id tt-id
                        :my-tickets-href my-tickets-href)))))
              ticket-types))
          (<>
            (div
              :class "flex items-center justify-between mb-4"
              (div
                (span :class "font-medium text-green-600" price-str)
                (span :class "text-sm text-stone-500 ml-2" "per ticket")))
            (~page/adjust-inline
              :csrf csrf
              :adjust-url adjust-url
              :target target
              :entry-id eid-s
              :count (if user-ticket-count user-ticket-count 0)
              :ticket-type-id nil
              :my-tickets-href my-tickets-href)))))))

;; Inline +/- controls (used by both default and per-type)
(defcomp ~page/adjust-inline (&key csrf adjust-url target entry-id count ticket-type-id my-tickets-href)
  (if (= count 0)
    (form :sx-post adjust-url :sx-target target :sx-swap "outerHTML" :class "flex items-center"
      (input :type "hidden" :name "csrf_token" :value csrf)
      (input :type "hidden" :name "entry_id" :value entry-id)
      (when ticket-type-id (input :type "hidden" :name "ticket_type_id" :value (str ticket-type-id)))
      (input :type "hidden" :name "count" :value "1")
      (button :type "submit"
              :class "relative inline-flex items-center justify-center text-sm font-medium text-stone-500 hover:bg-emerald-50 rounded p-1"
        (i :class "fa fa-cart-plus text-2xl" :aria-hidden "true")))
    (div :class "flex items-center gap-2"
      (form :sx-post adjust-url :sx-target target :sx-swap "outerHTML"
        (input :type "hidden" :name "csrf_token" :value csrf)
        (input :type "hidden" :name "entry_id" :value entry-id)
        (when ticket-type-id (input :type "hidden" :name "ticket_type_id" :value (str ticket-type-id)))
        (input :type "hidden" :name "count" :value (str (- count 1)))
        (button :type "submit"
                :class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
          "-"))
      (a :class "relative inline-flex items-center justify-center text-emerald-700" :href my-tickets-href
        (span :class "relative inline-flex items-center justify-center"
          (i :class "fa-solid fa-shopping-cart text-2xl" :aria-hidden "true")
          (span :class "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
            (span :class "flex items-center justify-center bg-black text-white rounded-full w-4 h-4 text-xs font-bold" (str count)))))
      (form :sx-post adjust-url :sx-target target :sx-swap "outerHTML"
        (input :type "hidden" :name "csrf_token" :value csrf)
        (input :type "hidden" :name "entry_id" :value entry-id)
        (when ticket-type-id (input :type "hidden" :name "ticket_type_id" :value (str ticket-type-id)))
        (input :type "hidden" :name "count" :value (str (+ count 1)))
        (button :type "submit"
                :class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl"
          "+")))))

(defcomp ~page/buy-not-confirmed (&key entry-id)
  (div :id (str "ticket-buy-" entry-id) :class "rounded-xl border border-stone-200 bg-stone-50 p-4 text-sm text-stone-500"
    (i :class "fa fa-ticket mr-1" :aria-hidden "true")
    "Tickets available once this event is confirmed."))


(defcomp
  ~page/buy-result
  (&key entry-id tickets remaining my-tickets-href)
  (let
    ((count (len tickets)) (suffix (if (= count 1) "" "s")))
    (div
      :id (str "ticket-buy-" entry-id)
      :class "rounded-xl border border-emerald-200 bg-emerald-50 p-4"
      (div
        :class "flex items-center gap-2 mb-3"
        (i :class "fa fa-check-circle text-emerald-600" :aria-hidden "true")
        (span
          :class "font-semibold text-emerald-800"
          (str count " ticket" suffix " reserved")))
      (div
        :class "space-y-2 mb-4"
        (map
          (fn
            (t)
            (let-match
              {:href href :code_short code-short}
              t
              (a
                :href href
                :class "flex items-center justify-between p-2 rounded-lg bg-white border border-emerald-100 hover:border-emerald-300 transition text-sm"
                (div
                  :class "flex items-center gap-2"
                  (i
                    :class "fa fa-ticket text-emerald-500"
                    :aria-hidden "true")
                  (span :class "font-mono text-xs text-stone-500" code-short))
                (span
                  :class "text-xs text-emerald-600 font-medium"
                  "View ticket"))))
          tickets))
      (when
        (not (nil? remaining))
        (let
          ((r-suffix (if (= remaining 1) "" "s")))
          (p
            :class "text-xs text-stone-500"
            (str remaining " ticket" r-suffix " remaining"))))
      (div
        :class "mt-3 flex gap-2"
        (a
          :href my-tickets-href
          :class "text-sm text-emerald-700 hover:text-emerald-900 underline"
          "View all my tickets")))))

;; Single response wrappers for POST routes (include OOB cart icon)
(defcomp ~page/buy-response (&key entry-id tickets remaining my-tickets-href
                                     cart-count blog-href cart-href logo)
  (<>
    (~page/cart-icon :cart-count cart-count :blog-href blog-href :cart-href cart-href :logo logo)
    (~page/buy-result :entry-id entry-id :tickets tickets :remaining remaining
                        :my-tickets-href my-tickets-href)))

(defcomp ~page/adjust-response (&key cart-count blog-href cart-href logo
                                        entry-id info-sold info-remaining info-basket
                                        ticket-types user-ticket-counts-by-type
                                        user-ticket-count price-str adjust-url csrf state
                                        my-tickets-href)
  (<>
    (~page/cart-icon :cart-count cart-count :blog-href blog-href :cart-href cart-href :logo logo)
    (~page/buy-form :entry-id entry-id :info-sold info-sold :info-remaining info-remaining
                      :info-basket info-basket :ticket-types ticket-types
                      :user-ticket-counts-by-type user-ticket-counts-by-type
                      :user-ticket-count user-ticket-count :price-str price-str
                      :adjust-url adjust-url :csrf csrf :state state
                      :my-tickets-href my-tickets-href)))

;; Unified OOB cart icon — picks logo or badge based on count
(defcomp ~page/cart-icon (&key cart-count blog-href cart-href logo)
  (if (= cart-count 0)
    (~page/cart-icon-logo :blog-href blog-href :logo logo)
    (~page/cart-icon-badge :cart-href cart-href :count (str cart-count))))

(defcomp ~page/cart-icon-logo (&key blog-href logo)
  (div :id "cart-mini" :sx-swap-oob "true"
    (div :class "h-12 w-12 rounded-full overflow-hidden border border-stone-300 flex-shrink-0"
      (a :href blog-href :class "h-full w-full font-bold text-5xl flex-shrink-0 flex flex-row items-center gap-1"
        (img :src logo :class "h-full w-full rounded-full object-cover border border-stone-300 flex-shrink-0")))))

(defcomp ~page/cart-icon-badge (&key cart-href count)
  (div :id "cart-mini" :sx-swap-oob "true"
    (a :href cart-href :class "relative inline-flex items-center justify-center text-stone-700 hover:text-emerald-700"
      (i :class "fa fa-shopping-cart text-5xl" :aria-hidden "true")
      (span :class "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 inline-flex items-center justify-center rounded-full bg-emerald-600 text-white text-sm w-5 h-5"
        count))))

;; Inline ticket widget (for all-events/page-summary cards)
(defcomp ~page/tw-form (&key ticket-url target csrf entry-id count-val btn)
  (form :action ticket-url :method "post" :sx-post ticket-url :sx-target target :sx-swap "outerHTML"
    (input :type "hidden" :name "csrf_token" :value csrf)
    (input :type "hidden" :name "entry_id" :value entry-id)
    (input :type "hidden" :name "count" :value count-val)
    btn))

(defcomp ~page/tw-cart-plus ()
  (button :type "submit" :class "relative inline-flex items-center justify-center text-stone-500 hover:bg-emerald-50 rounded p-1"
    (i :class "fa fa-cart-plus text-2xl" :aria-hidden "true")))

(defcomp ~page/tw-minus ()
  (button :type "submit" :class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl" "-"))

(defcomp ~page/tw-plus ()
  (button :type "submit" :class "inline-flex items-center justify-center w-8 h-8 text-sm font-medium rounded-full border border-emerald-600 text-emerald-700 hover:bg-emerald-50 text-xl" "+"))

(defcomp ~page/tw-cart-icon (&key qty)
  (span :class "relative inline-flex items-center justify-center text-emerald-700"
    (span :class "relative inline-flex items-center justify-center"
      (i :class "fa-solid fa-shopping-cart text-xl" :aria-hidden "true")
      (span :class "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none"
        (span :class "flex items-center justify-center bg-black text-white rounded-full w-4 h-4 text-xs font-bold" qty)))))

(defcomp ~page/tw-widget (&key entry-id price inner)
  (div :id (str "page-ticket-" entry-id) :class "flex items-center gap-2"
    (span :class "text-green-600 font-medium text-sm" price)
    inner))

;; Entry posts panel
(defcomp ~page/entry-posts-panel (&key posts search-url entry-id)
  (div :class "space-y-2"
    posts
    (div :class "mt-3 pt-3 border-t"
      (label :class "block text-xs font-medium text-stone-700 mb-1" "Add Post")
      (input :type "text" :placeholder "Search posts..."
             :class "w-full px-3 py-2 border rounded text-sm"
             :sx-get search-url :sx-trigger "keyup changed delay:300ms, load"
             :sx-target (str "#post-search-results-" entry-id) :sx-swap "innerHTML" :name "q")
      (div :id (str "post-search-results-" entry-id) :class "mt-2 max-h-96 overflow-y-auto border rounded"))))

(defcomp ~page/entry-posts-list (&key items)
  (div :class "space-y-2" items))

(defcomp ~page/entry-posts-none ()
  (p :class "text-sm text-stone-400" "No posts associated"))

(defcomp ~page/entry-post-item (&key img title del-url entry-id csrf-hdr)
  (div :class "flex items-center justify-between gap-3 p-2 bg-stone-50 rounded border"
    img (span :class "text-sm flex-1" title)
    (button :type "button" :class "text-xs text-red-600 hover:text-red-800 flex-shrink-0"
            :data-confirm "true" :data-confirm-title "Remove post?"
            :data-confirm-text (str "This will remove " title " from this entry")
            :data-confirm-icon "warning" :data-confirm-confirm-text "Yes, remove it"
            :data-confirm-cancel-text "Cancel" :data-confirm-event "confirmed"
            :sx-delete del-url :sx-trigger "confirmed"
            :sx-target (str "#entry-posts-" entry-id) :sx-swap "innerHTML"
            :sx-headers csrf-hdr
      (i :class "fa fa-times") " Remove")))

(defcomp ~page/post-img (&key src alt)
  (img :src src :alt alt :class "w-8 h-8 rounded-full object-cover flex-shrink-0"))

(defcomp ~page/post-img-placeholder ()
  (div :class "w-8 h-8 rounded-full bg-stone-200 flex-shrink-0"))

;; Entry posts nav OOB
(defcomp ~page/entry-posts-nav-oob-empty ()
  (div :id "entry-posts-nav-wrapper" :sx-swap-oob "true"))

(defcomp ~page/entry-posts-nav-oob (&key items)
  (div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
       :id "entry-posts-nav-wrapper" :sx-swap-oob "true"
    (div :class "flex overflow-x-auto gap-1 scrollbar-thin" items)))

(defcomp ~page/entry-nav-post (&key href nav-btn img title)
  (a :href href :class nav-btn img (div :class "flex-1 min-w-0" (div :class "font-medium truncate" title))))

;; Post nav entries OOB
(defcomp ~page/post-nav-oob-empty ()
  (div :id "entries-calendars-nav-wrapper" :sx-swap-oob "true"))

(defcomp ~page/post-nav-entry (&key href nav-btn name time-str)
  (a :href href :class nav-btn
    (div :class "w-8 h-8 rounded bg-stone-200 flex-shrink-0")
    (div :class "flex-1 min-w-0"
      (div :class "font-medium truncate" name)
      (div :class "text-xs text-stone-600 truncate" time-str))))

(defcomp ~page/post-nav-calendar (&key href nav-btn name)
  (a :href href :class nav-btn
    (i :class "fa fa-calendar" :aria-hidden "true")
    (div name)))

(defcomp ~page/post-nav-wrapper (&key items hyperscript)
  (div :class "flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
       :id "entries-calendars-nav-wrapper" :sx-swap-oob "true"
    (button :class "entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
            :aria-label "Scroll left"
            :_ "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft - 200"
      (i :class "fa fa-chevron-left"))
    (div :id "associated-items-container"
         :class "overflow-y-auto sm:overflow-x-auto sm:overflow-y-visible scrollbar-hide max-h-[50vh] sm:max-h-none"
         :style "scroll-behavior: smooth;" :_ hyperscript
      (div :class "flex flex-col sm:flex-row gap-1" items))
    (style ".scrollbar-hide::-webkit-scrollbar { display: none; } .scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }")
    (button :class "entries-nav-arrow hidden flex-shrink-0 p-2 hover:bg-stone-200 rounded"
            :aria-label "Scroll right"
            :_ "on click set #associated-items-container.scrollLeft to #associated-items-container.scrollLeft + 200"
      (i :class "fa fa-chevron-right"))))

;; Entry nav post link (with image)
(defcomp ~page/entry-nav-post-link (&key href img title)
  (a :href href :class "flex items-center gap-2 px-3 py-2 hover:bg-stone-100 rounded transition text-sm border sm:whitespace-nowrap sm:flex-shrink-0"
    img (div :class "flex-1 min-w-0" (div :class "font-medium truncate" title))))


;; ---------------------------------------------------------------------------
;; Composition defcomps — nav OOB, posts panel from data
;; ---------------------------------------------------------------------------

;; Post image helper from data
(defcomp ~page/post-img-from-data (&key src alt)
  (if src
    (~page/post-img :src src :alt alt)
    (~page/post-img-placeholder)))

;; Entry posts nav OOB from data
(defcomp
  ~page/entry-posts-nav-oob-from-data
  (&key nav-btn posts)
  (if
    (empty? (or posts (list)))
    (~page/entry-posts-nav-oob-empty)
    (~page/entry-posts-nav-oob
      :items (<>
        (map
          (lambda
            (p)
            (let-match
              {:href href :title title :img img}
              p
              (~page/entry-nav-post
                :href href
                :nav-btn nav-btn
                :img (~page/post-img-from-data :src img :alt title)
                :title title)))
          posts)))))

;; Entry posts nav (non-OOB) from data — for desktop nav embedding
(defcomp
  ~page/entry-posts-nav-inner-from-data
  (&key posts)
  (when
    (not (empty? (or posts (list))))
    (~page/entry-posts-nav-oob
      :items (<>
        (map
          (lambda
            (p)
            (let-match
              {:href href :title title :img img}
              p
              (~page/entry-nav-post-link
                :href href
                :img (~page/post-img-from-data :src img :alt title)
                :title title)))
          posts)))))

;; Post nav entries+calendars OOB from data
(defcomp ~page/post-nav-wrapper-from-data (&key nav-btn entries calendars hyperscript)
  (if (and (empty? (or entries (list))) (empty? (or calendars (list))))
    (~page/post-nav-oob-empty)
    (~page/post-nav-wrapper
      :items (<>
        (map (lambda (e)
          (~page/post-nav-entry
            :href (get e "href") :nav-btn nav-btn
            :name (get e "name") :time-str (get e "time-str")))
          (or entries (list)))
        (map (lambda (c)
          (~page/post-nav-calendar
            :href (get c "href") :nav-btn nav-btn :name (get c "name")))
          (or calendars (list))))
      :hyperscript hyperscript)))

;; Entry posts panel from data
(defcomp ~page/entry-posts-panel-from-data (&key entry-id posts search-url)
  (~page/entry-posts-panel
    :posts (if (empty? (or posts (list)))
      (~page/entry-posts-none)
      (~page/entry-posts-list
        :items (<> (map (lambda (p)
          (~page/entry-post-item
            :img (~page/post-img-from-data :src (get p "img") :alt (get p "title"))
            :title (get p "title")
            :del-url (get p "del-url") :entry-id entry-id
            :csrf-hdr (get p "csrf-hdr")))
          posts))))
    :search-url search-url :entry-id entry-id))

;; CRUD list/panel from data — shared by calendars + markets
(defcomp ~page/crud-list-from-data (&key items empty-msg list-id)
  (if (empty? (or items (list)))
    (~shared:misc/empty-state :message empty-msg :cls "text-gray-500 mt-4")
    (<> (map (lambda (item)
      (~shared:misc/crud-item
        :href (get item "href") :name (get item "name") :slug (get item "slug")
        :del-url (get item "del-url") :csrf-hdr (get item "csrf-hdr")
        :list-id list-id
        :confirm-title (get item "confirm-title")
        :confirm-text (get item "confirm-text")))
      items))))

(defcomp ~page/crud-panel-from-data (&key can-create create-url csrf errors-id list-id
                                             placeholder btn-label items empty-msg)
  (~shared:misc/crud-panel
    :form (when can-create
      (~shared:misc/crud-create-form
        :create-url create-url :csrf csrf :errors-id errors-id
        :list-id list-id :placeholder placeholder :btn-label btn-label))
    :list (~page/crud-list-from-data :items items :empty-msg empty-msg :list-id list-id)
    :list-id list-id))

;; Post nav admin cog
(defcomp ~page/post-nav-admin-cog (&key href aclass)
  (div :class "relative nav-group"
    (a :href href :class aclass
      (i :class "fa fa-cog" :aria-hidden "true"))))

;; Post nav from data — calendar links + container nav + admin
(defcomp ~page/post-nav-from-data (&key calendars container-nav select-colours
                                          has-admin admin-href aclass)
  (<>
    (map (lambda (c)
      (~shared:layout/nav-link :href (get c "href") :icon "fa fa-calendar"
        :label (get c "name") :select-colours select-colours
        :is-selected (get c "is-selected")))
      (or calendars (list)))
    (when container-nav container-nav)
    (when has-admin
      (~page/post-nav-admin-cog :href admin-href :aclass aclass))))

;; Calendar nav from data — slots + admin link
(defcomp ~page/calendar-nav-from-data (&key slots-href admin-href select-colours is-admin)
  (<>
    (~shared:layout/nav-link :href slots-href :icon "fa fa-clock"
      :label "Slots" :select-colours select-colours)
    (when is-admin
      (~shared:layout/nav-link :href admin-href :icon "fa fa-cog"
        :select-colours select-colours))))

;; Calendar admin nav from data
(defcomp ~page/calendar-admin-nav-from-data (&key links select-colours)
  (<> (map (lambda (l)
    (~shared:layout/nav-link :href (get l "href") :label (get l "label")
      :select-colours select-colours))
    (or links (list)))))

;; Day nav from data — confirmed entries + admin link
(defcomp ~page/day-nav-from-data (&key entries is-admin admin-href)
  (<>
    (when (not (empty? (or entries (list))))
      (~day/entries-nav
        :inner (<> (map (lambda (e)
          (~day/entry-link
            :href (get e "href") :name (get e "name") :time-str (get e "time-str")))
          entries))))
    (when is-admin
      (~shared:layout/nav-link :href admin-href :icon "fa fa-cog"))))

;; Post search results from data
(defcomp
  ~page/post-search-results-from-data
  (&key items page next-url has-more)
  (<>
    (map
      (lambda
        (item)
        (let-match
          {:csrf csrf :entry-id entry-id :post-url post-url :title title :img img :post-id post-id}
          item
          (~forms/post-search-item
            :post-url post-url
            :entry-id entry-id
            :csrf csrf
            :post-id post-id
            :img (~page/post-img-from-data :src img :alt title)
            :title title)))
      (or items (list)))
    (cond
      (has-more (~forms/post-search-sentinel :page page :next-url next-url))
      ((not (empty? (or items (list)))) (~forms/post-search-end))
      (true ""))))

;; Entry options from data — state-driven button composition
(defcomp
  ~page/entry-options-from-data
  (&key entry-id state buttons)
  (~admin/entry-options
    :entry-id entry-id
    :buttons (<>
      (map
        (lambda
          (b)
          (let-match
            {:csrf csrf :confirm-title confirm-title :url url :btn-type btn-type :action-btn action-btn :confirm-text confirm-text :label label :is-btn is-btn}
            b
            (~admin/entry-option-button
              :url url
              :target (str "#calendar_entry_options_" entry-id)
              :csrf csrf
              :btn-type btn-type
              :action-btn action-btn
              :confirm-title confirm-title
              :confirm-text confirm-text
              :label label
              :is-btn is-btn)))
        (or buttons (list))))))
