;; sf-marketplace-v2

(use-trait nft-trait 'ST3R40A4Q12JWFX6PAGRNA408EWMAQ96F4YJMFTAJ.sf-v3-nft-trait.nft-trait)

;; bids map
;; if nft-id is `none`, the bid is a collection bid
;;    and can be accepted by anyone holding a token from
;;    that collection.

(define-map bids
  {
    collection: principal, 
    nft-id: uint,
  }
  {  
    bid-amount: uint, 
    buyer: principal, 
    seller: (optional principal), 
    ;; expiration-block: uint, 
    action-event-index: uint,
    memo: (optional (string-ascii 256))
  }
)

(define-constant contract-address (as-contract tx-sender))
(define-constant contract-owner tx-sender)
(define-constant blocks-per-day u144)
(define-constant err-contract-not-authorized u101)
(define-constant err-placing-bids-disabled u102)
(define-constant err-accepting-bids-disabled u103)
(define-constant err-withdrawing-bids-disabled u104)
(define-constant err-user-not-authorized u105)
(define-constant err-no-bid-found u106)
(define-constant err-bid-expired u107)
(define-constant err-wrong-collection u108)
(define-constant err-wrong-nft-id u109)
(define-constant err-not-enough-bid-expiry u110)
(define-constant err-not-enough-bid-amount u111)

(define-data-var placing-bids-enabled bool true)
(define-data-var accepting-bids-enabled bool true)
(define-data-var commission uint u200)
(define-data-var id uint u0)

;; #[allow(unchecked_data)]
(define-public (place-bid (collection <nft-trait>) (nft-id uint) (amount uint) (memo (optional (string-ascii 256))))
  (let ((block block-height)
        (nft-owner (get-owner collection nft-id))  
        (nft { bid-amount: amount, buyer: tx-sender, seller: nft-owner, action-event-index: u0, memo: memo,
         })
        )
    
    (asserts! (var-get placing-bids-enabled) (err err-placing-bids-disabled))

    (match (map-get? bids {collection: (contract-of collection), nft-id: nft-id})
        bid (begin
                (asserts! (>= amount (+ (get bid-amount bid) u1000000)) (err err-not-enough-bid-amount))
                
                ;; Review for social engineering attack
                (map-delete bids {collection: (contract-of collection), nft-id: nft-id})
                (try! (as-contract (stx-transfer? (get bid-amount bid) contract-address (get buyer bid))))
                (unwrap-panic (private-place-bid collection nft-id nft))
            )
            (begin
                (asserts! (>= amount u1000000)
                          (err err-not-enough-bid-amount))
                (unwrap-panic (private-place-bid collection nft-id nft))
            )
            
    )
    (ok "Bid placed")
  )
)

;; Removed (expiration-block uint) from nft tuple
(define-private (private-place-bid (collection <nft-trait>) (nft-id uint)

    (nft (tuple
         (bid-amount uint) (buyer principal)
          
           (seller (optional principal)) 
         (action-event-index uint) (memo (optional (string-ascii 256)))
    ))
)
    (begin
        (map-set bids {collection: (contract-of collection), nft-id: nft-id} nft)
        (try! (stx-transfer? (get bid-amount nft) tx-sender contract-address))
        (print { 
            action: "place-bid",
            payload: {
                action_event_index: (get action-event-index nft),
                collection_id: (contract-of collection),
                token_id: nft-id,
                bidder_address: tx-sender,
                seller_address: (get seller nft),
                bid_amount: (get bid-amount nft), 
                memo: (get memo nft)
            }
        })
    (ok true)
    )
)

;; #[allow(unchecked_data)]
(define-public (accept-bid (collection <nft-trait>) (nft-id uint))
  (let ((bid (get-bid collection nft-id))
        (bid-nft-id nft-id)
        (bid-collection (contract-of collection))
        (bidder (get buyer bid))
        (bid-amount (get bid-amount bid))
        (bid-action-event-index (get action-event-index bid))
        (nft-owner (unwrap! (get-owner collection nft-id) (err err-user-not-authorized)))
        (royalty (get-royalty (contract-of collection)))
        (royalty-address (get address royalty))
        (commission-amount (/ (* bid-amount (var-get commission)) u10000))
        (royalty-amount (/ (* bid-amount (get percent royalty)) u10000))
        (to-owner-amount (- (- bid-amount commission-amount) royalty-amount))
        (block block-height))
    (asserts! (var-get accepting-bids-enabled) 
              (err err-accepting-bids-disabled))
    (asserts! (> bid-amount u0) (err err-no-bid-found))
    (asserts! (is-eq (contract-of collection) bid-collection) (err err-wrong-collection))
    (asserts! (is-eq tx-sender nft-owner) (err err-user-not-authorized))

    (map-delete bids {collection: (contract-of collection), nft-id: nft-id})
    (try! (contract-call? collection transfer nft-id tx-sender bidder))
    (and (> to-owner-amount u0)
        (try! (as-contract (stx-transfer? to-owner-amount contract-address nft-owner))))
    (and (> commission-amount u0)
        (try! (as-contract (stx-transfer? commission-amount contract-address contract-owner))))
    (and (> royalty-amount u0)
        (try! (as-contract (stx-transfer? royalty-amount contract-address royalty-address))))

    (print { 
      action: "accept-bid",
      payload: {
        action_event_index: (+ u1 bid-action-event-index),
        collection_id: (contract-of collection),
        token_id: nft-id,
        bidder_address: bidder,
        seller_address: nft-owner,
        bid_amount: bid-amount, 
        royalty: {
          recipient_address: royalty-address,
          percent: (get percent royalty),
        }
      }
    })

    (ok true)
  )
)


;; #[allow(unchecked_data)]
(define-public (set-placing-bids-enabled (enabled bool))
    (begin
        (asserts! (is-eq tx-sender contract-owner) (err err-user-not-authorized))
        (ok (var-set placing-bids-enabled enabled))
    )
)

;; #[allow(unchecked_data)]
(define-public (set-accepting-bids-enabled (enabled bool))
    (begin
        (asserts! (is-eq tx-sender contract-owner) (err err-user-not-authorized))
        (ok (var-set accepting-bids-enabled enabled))
    )
)

;; #[allow(unchecked_data)]
(define-public (set-commission (comm uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-user-not-authorized))
    (ok (var-set commission comm))))

(define-read-only (get-bid (collection <nft-trait>) (nft-id uint))
  (default-to
    {buyer: contract-owner, seller: none, bid-amount: u0,
      action-event-index: u0}
    (map-get? bids {collection: (contract-of collection), nft-id: nft-id})
  )
)

(define-private (get-owner (nft <nft-trait>) (nft-id uint))
  (unwrap-panic (contract-call? nft get-owner nft-id))
)

(define-private (get-royalty (collection principal))
  ;; (default-to
    { address: contract-owner, percent: u0 }
    ;; (contract-call? .nft-oracle-v2 get-royalty-amount collection))
)
