content: module P2PWallet.GUI.Widgets.Lending.Lend.OfferHistory 
    ( 
      transactionsWidget
    , offerTxInspectionWidget
    ) where
  
  import Monomer hiding (duration)
  import Data.Map.Strict qualified as Map
  import Data.Time.Format qualified as Time
  
  import P2PWallet.Data.AppModel
  import P2PWallet.Data.Core.AssetMaps
  import P2PWallet.Data.Core.Internal
  import P2PWallet.Data.Core.Wallets.LoanWallet
  import P2PWallet.Data.Core.Transaction
  import P2PWallet.Data.DeFi.CardanoLoans qualified as Loans
  import P2PWallet.GUI.Colors
  import P2PWallet.GUI.HelpMessages
  import P2PWallet.GUI.Icons
  import P2PWallet.GUI.MonomerOptics()
  import P2PWallet.GUI.Widgets.Internal.Custom
  import P2PWallet.Plutus
  import P2PWallet.Prelude
  
  transactionsWidget :: AppModel -> AppNode
  transactionsWidget model@AppModel{lendingModel} = do
      zstack 
        [ noTransactions `nodeVisible` null offerTransactions
        , mainWidget model `nodeVisible` (offerTransactions /= [])
        ]
    where
      LoanWallet{offerTransactions} = lendingModel ^. #selectedWallet 
  
      noTransactions :: AppNode
      noTransactions = centerWidget $
        label "This wallet does not have any transactions yet"
          `styleBasic` [textFont "Italics"]
  
  mainWidget :: AppModel -> AppNode
  mainWidget model@AppModel{lendingModel=LendingModel{..},config,reverseTickerMap} =
      zstack
        [ cushionWidgetH $ vstack
            [ centerWidgetH $ hstack
                [ label shownDateRange
                    `styleBasic` 
                      [ padding 0
                      , textMiddle
                      , textFont "Italics"
                      ]
                , spacer_ [width 5]
                , tooltip_ "Filter/Search" [tooltipDelay 0] $
                    toggleButton_ menuSearchIcon
                      (toLensVL $ #lendingModel % #lendModel % #showTransactionFilter)
                      [toggleButtonOffStyle toggleOffStyle]
                      `styleBasic`
                        [ border 0 transparent
                        , radius 20
                        , paddingT 0
                        , paddingB 0
                        , paddingL 5
                        , paddingR 5
                        , bgColor transparent
                        , textColor customBlue
                        , textMiddle
                        , textFont "Remix"
                        ]
                      `styleHover` [bgColor customGray2, cursorIcon CursorHand]
                ]
            , widgetIf (sample /= []) $ flip styleBasic [padding 5] $ box $ vscroll_ [wheelRate 50] $ 
                vstack_ [childSpacing] (map txRow sample)
                  `styleBasic` [padding 10]
            , widgetIf (null sample) $ 
                centerWidget $
                  label "No transactions found"
                   `styleBasic` [textFont "Italics"]
            ]
        , txFilterWidget model `nodeVisible` (model ^. #lendingModel % #lendModel % #showTransactionFilter)
        ]
    where
      toggleOffStyle :: Style
      toggleOffStyle = 
        def `styleBasic` [ bgColor transparent , textColor customBlue ]
            `styleHover` [ bgColor customGray1 ]
  
      timeZone :: TimeZone
      timeZone = config ^. #timeZone
  
      today :: Day
      today = config ^. #currentDay
  
      (mLowerDay,mUpperDay) = lendModel ^. #txFilterModel % #dateRange
  
      startTime :: POSIXTime
      startTime = maybe 0 (localTimeToPosixTime timeZone . beginningOfDay) mLowerDay
  
      endTime :: POSIXTime
      endTime = localTimeToPosixTime timeZone 
              $ endOfDay
              $ fromMaybe today mUpperDay
  
      shownDateRange :: Text
      shownDateRange = unwords
        [ maybe "Beginning" (const $ showLocalDate timeZone startTime) mLowerDay 
        , "-"
        , maybe "Present" (const $ showLocalDate timeZone endTime) mUpperDay 
        ]
  
      withinDateRange :: Transaction -> Bool
      withinDateRange Transaction{blockTime} =
        blockTime >= startTime && blockTime <= endTime
  
      LoanWallet{stakeCredential,offerTransactions} = selectedWallet
      lenderId = Loans.genLenderId stakeCredential
  
      sample :: [Transaction]
      sample = applySearchFilter reverseTickerMap lenderId (lendModel ^. #txFilterModel)
             $ filter withinDateRange offerTransactions
  
      txRow :: Transaction -> AppNode
      txRow tx@Transaction{..} = do
        let (closedCount,createdCount,acceptedCount) = offerActionCount lenderId tx
        vstack
          [ hstack 
              [ copyableLabelSelf txHash 10 customBlue lightGray
              , spacer_ [width 2]
              , tooltip_ "Inspect Offer Transaction" [tooltipDelay 0] $
                  button inspectIcon (LendingEvent $ LendEvent $ InspectOfferTransaction tx)
                    `styleBasic` 
                      [ textSize 10
                      , textColor customBlue
                      , textFont "Remix"
                      , textMiddle
                      , padding 0
                      , paddingL 2
                      , paddingR 2
                      , bgColor transparent
                      , border 0 transparent
                      ]
                    `styleHover` [bgColor customGray1, cursorIcon CursorHand]
              , filler
              , label calendarIcon
                  `styleBasic` 
                    [ textSize 10
                    , textColor customBlue
                    , textFont "Remix"
                    , paddingT 5
                    ]
              , spacer_ [width 3]
              , label (showLocalDate (config ^. #timeZone) blockTime)
                  `styleBasic` 
                    [ textSize 10
                    , textColor white
                    ]
              , spacer_ [width 5]
              , label clockIcon
                  `styleBasic` 
                    [ textSize 10
                    , textColor customBlue
                    , textFont "Remix"
                    , paddingT 5
                    ]
              , spacer_ [width 3]
              , label (showLocalTime (config ^. #timeZone) blockTime)
                  `styleBasic` 
                    [ textSize 10
                    , textColor white
                    ]
              ]
          , hstack
              [ label (show closedCount <> " Offer(s) Closed")
                  `styleBasic` 
                    [ textSize 8
                    , textColor lightGray
                    ]
              , spacer_ [width 5]
              , label "/"
                  `styleBasic` 
                    [ textSize 8
                    , textColor lightGray
                    ]
              , spacer_ [width 5]
              , label (show createdCount <> " Offer(s) Created")
                  `styleBasic` 
                    [ textSize 8
                    , textColor lightGray
                    ]
              , filler
              , label (show acceptedCount <> " Offer(s) Accepted")
                  `styleBasic` 
                    [ textSize 8
                    , textColor lightGray
                    ]
              ]
          ] `styleBasic` 
              [ padding 10
              , bgColor customGray2
              , radius 5
              , border 1 black
              ]
  
  txFilterWidget :: AppModel -> AppNode
  txFilterWidget model = do
    let currentScene = model ^. #lendingModel % #lendModel % #txFilterScene
        offStyle = def 
          `styleBasic` [ bgColor customGray1 , textColor white ]
          `styleHover` [ textColor lightGray, border 1 customBlue ]
    vstack
      [ centerWidget $ hstack
          [ vstack
              [ vgrid
                  [ optionButton_ "Filter" FilterScene (toLensVL $ #lendingModel % #lendModel % #txFilterScene) 
                      [optionButtonOffStyle offStyle]
                      `styleBasic` 
                        [ bgColor customGray3
                        , textColor customBlue
                        , radiusTL 10
                        , radiusBL 0
                        , radiusTR 0
                        , radiusBR 0
                        , border 1 black
                        ]
                  , optionButton_ "Search" SearchScene (toLensVL $ #lendingModel % #lendModel % #txFilterScene) 
                      [optionButtonOffStyle offStyle]
                      `styleBasic` 
                        [ bgColor customGray3
                        , textColor customBlue
                        , radiusBL 10
                        , radiusTL 0
                        , radiusTR 0
                        , radiusBR 0
                        , border 1 black
                        ]
                  ] `styleBasic` [height 100]
              , filler
              ]
          , vstack
              [ vstack 
                  [ zstack
                      [ widgetIf (currentScene == FilterScene) filterWidget
                      , widgetIf (currentScene == SearchScene) searchWidget
                      ]
                  , spacer
                  , hstack 
                      [ filler
                      , button "Reset" $ LendingEvent $ LendEvent ResetLendTxFilters
                      , spacer
                      , toggleButton "Confirm" (toLensVL $ #lendingModel % #lendModel % #showTransactionFilter)
                      ] `styleBasic` [padding 10]
                  ] `styleBasic`
                      [ bgColor customGray3
                      , radiusTL 0
                      , radiusTR 10
                      , radiusBR 10
                      , radiusBL 10
                      , border 1 black
                      ]
              , filler
              ]
          ]
      ] `styleBasic` 
          [ bgColor $ black & #a .~ 0.4
          , paddingT 50
          , paddingB 50
          , paddingL 30
          , paddingR 30
          , radius 10
          ]
    where
      filterWidget :: AppNode
      filterWidget = do
        let rootLens = #lendingModel % #lendModel % #txFilterModel
        vstack
          [ spacer
          , box_ [alignMiddle] $
              label "Filter Settings"
                `styleBasic` [textSize 14, textFont "Italics"]
          , spacer
          , vstack
              [ hstack 
                  [ spacer_ [width 20]
                  , label "Start Date:"
                  , spacer
                  , textField_ (toLensVL $ rootLens % lowerBoundText) [placeholder "MM-DD-YYYY"]
                      `styleBasic` [width 150, bgColor customGray1, sndColor darkGray]
                      `styleFocus` [border 1 customBlue]
                  , mainButton helpIcon (Alert txStartDateMsg)
                      `styleBasic`
                        [ border 0 transparent
                        , radius 20
                        , bgColor transparent
                        , textColor customBlue
                        , textMiddle
                        , textFont "Remix"
                        ]
                      `styleHover` [bgColor customGray2, cursorIcon CursorHand]
                  ]
              , spacer
              , hstack 
                  [ spacer_ [width 20]
                  , label "End Date:"
                  , spacer
                  , textField_ (toLensVL $ rootLens % upperBoundText) [placeholder "MM-DD-YYYY"]
                      `styleBasic` [width 150, bgColor customGray1, sndColor darkGray]
                      `styleFocus` [border 1 customBlue]
                  , mainButton helpIcon (Alert txEndDateMsg)
                      `styleBasic`
                        [ border 0 transparent
                        , radius 20
                        , bgColor transparent
                        , textColor customBlue
                        , textMiddle
                        , textFont "Remix"
                        ]
                      `styleHover` [bgColor customGray2, cursorIcon CursorHand]
                  ]
              ]
          ]
  
      searchWidget :: AppNode
      searchWidget = do
        cushionWidgetH $ vstack
          [ spacer
          , box_ [alignMiddle] $
              label "Search Settings"
                `styleBasic` [textSize 14, textFont "Italics"]
          , spacer
          , hstack
              [ box_ [alignMiddle, onClick $ Alert filterOfferTxByLoanAssetMsg] $
                  label helpIcon
                    `styleBasic`
                      [ border 0 transparent
                      , radius 20
                      , bgColor transparent
                      , textColor customBlue
                      , textMiddle
                      , textFont "Remix"
                      , textSize 10
                      ]
                    `styleHover` [bgColor customGray2, cursorIcon CursorHand]
              , spacer_ [width 3]
              , label "Loan Asset:"
                  `styleBasic` [textSize 12]
              , spacer_ [width 5]
              , textField_ (toLensVL $ #lendingModel % #lendModel % #txFilterModel % #loanAsset)
                    [placeholder "ADA"]
                  `styleBasic` [textSize 10, width 200, bgColor customGray1, sndColor darkGray]
                  `styleFocus` [border 1 customBlue]
              ]
          , spacer
          , hstack
              [ box_ [alignMiddle, onClick $ Alert filterOfferTxByCollateralMsg] $
                  label helpIcon
                    `styleBasic`
                      [ border 0 transparent
                      , radius 20
                      , bgColor transparent
                      , textColor customBlue
                      , textMiddle
                      , textFont "Remix"
                      , textSize 10
                      ]
                    `styleHover` [bgColor customGray2, cursorIcon CursorHand]
              , spacer_ [width 3]
              , label "Collateral Assets (separated with newlines):"
                  `styleBasic` [textSize 12]
              ]
          , spacer
          , textArea (toLensVL $ #lendingModel % #lendModel % #txFilterModel % #collateral)
              `styleBasic` [height 180, textSize 10, bgColor customGray1]
              `styleFocus` [border 1 customBlue]
          ]
  
  offerTxInspectionWidget :: AppModel -> AppNode
  offerTxInspectionWidget model = do
      flip styleBasic [bgColor $ black & #a .~ 0.4, padding 30, radius 10] $ box $
        vstack
          [ centerWidgetH $ label "Loan Offer Transaction"
              `styleBasic` [ textFont "Italics" ]
          , spacer
          , vscroll_ [wheelRate 50] $ vstack
              [ offerInputs model
              , spacer
              , offerOutputs model
              ]
          , filler
          , hstack
              [ filler
              , button "Close" $ LendingEvent $ LendEvent CloseInspectedOfferTransaction
              ]
          ] `styleBasic` [bgColor customGray3, padding 30, radius 10]
  
  offerInputs :: AppModel -> AppNode
  offerInputs AppModel{..} = do
      vstack
        [ hstack
            [ label "Inputs:"
                `styleBasic` [textSize 12]
            , spacer
            , toggleButton_ horizontalMoreIcon 
                (toLensVL $ #lendingModel % #lendModel % #inspectedOfferTransaction % toggleShow #showInputs)
                [toggleButtonOffStyle moreOffStyle]
                `styleBasic` 
                  [ textSize 10
                  , textColor customRed
                  , textFont "Remix"
                  , textMiddle
                  , radius 20
                  , paddingT 2
                  , paddingB 2
                  , paddingR 5
                  , paddingL 5
                  , bgColor black
                  , border 0 transparent
                  ]
                `styleHover` [bgColor customGray1, cursorIcon CursorHand]
                `nodeVisible` (allInputs /= [])
            , label "none" 
                `styleBasic` [textColor white, textSize 12]
                `nodeVisible` null allInputs
            ]
        , widgetIf showInputs $
            vstack_ [childSpacing] (map inputRow allInputs)
              `styleBasic` [padding 10]
        ]
    where
      Config{timeZone,network} = config
  
      LoanWallet{stakeCredential} = lendingModel ^. #selectedWallet
  
      lenderId = Loans.genLenderId stakeCredential
  
      Transaction{showInputs, plutusContracts} = 
        fromMaybe def $ lendingModel ^. #lendModel % #inspectedOfferTransaction
  
      allInputs = flip filter plutusContracts $ \TransactionPlutusContract{datum} ->
        Just lenderId == (datum >>= preview (_OfferDatum % #lenderId) . parseInlineLoanDatum)
  
      inputRow :: TransactionPlutusContract -> AppNode
      inputRow TransactionPlutusContract{spendsInput,datum,redeemer} =
        case parseInlineLoanDatum <$> datum of
          Just (OfferDatum offerDatum) -> 
            offerRow
              (fromMaybe (TxOutRef "" 0) spendsInput)
              offerDatum
              (fromMaybe Loans.CloseOrUpdateOffer $ decodeData @Loans.LoanRedeemer redeemer)
          _ -> spacer `nodeVisible` False
  
      collateralAssetWidget :: NativeAsset -> (NativeAsset,Rational) -> AppNode
      collateralAssetWidget loanAsset (collateralAsset, price) = do
        let formattedPrice = showPriceFormatted reverseTickerMap collateralAsset loanAsset price
            prettyPrice = mconcat
              [ formattedPrice
              , " "
              , showAssetNameOnly reverseTickerMap collateralAsset
              , " / "
              , showAssetNameOnly reverseTickerMap loanAsset
              ]
        hstack
          [ spacer_ [width 2]
          , label prettyPrice
              `styleBasic` [textSize 8, textColor lightGray]
          , spacer_ [width 2]
          ] `styleBasic` 
              [ bgColor customGray4
              , padding 2
              , paddingT 1
              , paddingT 1
              , radius 3
              , border 1 customGray1
              ]
  
      offerRow :: TxOutRef -> Loans.OfferDatum -> Loans.LoanRedeemer -> AppNode
      offerRow utxoRef Loans.OfferDatum{lenderId=_,..} redeemer = do
        let loanAmount = toNativeAsset loanAsset & #quantity .~ loanPrincipal
            duration = calcDaysInPosixPeriod $ fromPlutusTime loanTerm
            collateralPrices = map (over _1 toNativeAsset . over _2 toRational) 
                             $ collateralization ^. #unCollateralization
            prettyInterest 
              | loanInterest == 0 = "Interest-Free"
              | otherwise = unwords
                  [ if compoundingInterest then "Compounding" else "Non-Compounding"
                  , "Interest:"
                  , displayPercentage (toRational loanInterest) <> "%"
                  ]
            prettyEpochDuration = flip (maybe "No Loan Epochs") epochDuration $ \freq ->
              unwords
                [ "Loan Epoch:"
                , show (calcDaysInPosixPeriod $ fromPlutusTime freq)
                , "Day(s)"
                ]
            prettyMinPayment = unwords
              [ "Minimum Payment:"
              , showAssetBalance True reverseTickerMap $ loanAmount & #quantity .~ minPayment
              ]
            prettyPenalty = case penalty of
              Loans.NoPenalty -> "No Penalty"
              Loans.FixedFee fee -> unwords
                [ "Fee Penalty:"
                , showAssetBalance True reverseTickerMap $ loanAmount & #quantity .~ fee
                ]
              Loans.PercentFee percent -> unwords
                [ "Percent Penalty:"
                , displayPercentage (toRational percent) <> "%"
                ]
            prettyExpirationTime exprTime = unwords
              [ "Expires:"
              , showLocalDate timeZone exprTime
              , showLocalTime timeZone exprTime
              ]
            swapCollateralMsg = "Collateral can be swapped out for other approved collateral"
            payToAddress = either (const "error") fst $ plutusToBech32 network lenderAddress
            mTargetWallet = find ((==payToAddress) . view #paymentAddress) 
                          $ knownWallets ^. #paymentWallets
            addressTip = case mTargetWallet of
              Nothing -> "Payments to: " <> display payToAddress
              Just w -> unwords
                [ "Payments to"
                , w ^. #alias <> ":"
                , display payToAddress
                ]
        vstack
          [ hstack
              [ label ("Offer For " <> showAssetBalance True reverseTickerMap loanAmount)
                  `styleBasic` [textSize 10, textColor customBlue]
              , spacer_ [width 5]
              , let prettyRef = display utxoRef in
                flip styleBasic [textSize 10] $ tooltip_ prettyRef [tooltipDelay 0] $
                  box_ [alignMiddle, onClick $ CopyText $ display utxoRef] $
                    label targetUTxOIcon
                      `styleBasic` 
                        [ bgColor black
                        , textMiddle
                        , textFont "Remix"
                        , textSize 8
                        , textColor customBlue
                        , paddingT 1
                        , paddingB 1
                        , paddingL 3
                        , paddingR 3
                        , radius 5
                        ]
                      `styleHover` [bgColor customGray1, cursorIcon CursorHand]
              , widgetMaybe offerExpiration $ \exprTime -> hstack
                  [ spacer_ [width 5]
                  , flip styleBasic [textSize 10] $ 
                      tooltip_ (prettyExpirationTime $ fromPlutusTime exprTime) [tooltipDelay 0] $
                        label expirationIcon
                          `styleBasic` 
                            [ textMiddle
                            , textFont "Remix"
                            , textSize 10
                            , textColor customRed
                            ]
                  ]
              , spacer_ [width 5]
              , flip styleBasic [textSize 10] $ tooltip_ addressTip [tooltipDelay 0] $
                  box_ [alignMiddle, onClick $ CopyText $ display payToAddress] $
                    label targetAddressIcon
                      `styleBasic` 
                        [ bgColor black
                        , textMiddle
                        , textFont "Remix"
                        , textSize 8
                        , textColor customBlue
                        , paddingT 1
                        , paddingB 1
                        , paddingL 3
                        , paddingR 3
                        , radius 5
                        ]
                      `styleHover` [bgColor customGray1, cursorIcon CursorHand]
              , filler
              , label (display redeemer)
                  `styleBasic` [textSize 12, textColor customRed, textFont "Italics"]
              , filler
              , label "Duration:"
                  `styleBasic` [textSize 10, textColor white]
              , spacer_ [width 3]
              , label (show duration <> " Days")
                  `styleBasic` [textSize 10, textColor white]
              ]
          , spacer_ [width 3]
          , hstack
              [ label prettyInterest
                  `styleBasic` [textSize 8, textColor lightGray]
              , filler
              , label prettyEpochDuration
                  `styleBasic` [textSize 8, textColor lightGray]
              ]
          , widgetIf (isJust epochDuration) $ vstack
              [ spacer_ [width 3]
              , hstack
                  [ label prettyMinPayment
                      `styleBasic` [textSize 8, textColor lightGray]
                  , filler
                  , label prettyPenalty
                      `styleBasic` [textSize 8, textColor lightGray]
                  ]
              ]
          , spacer_ [width 2]
          , hstack
              [ widgetIf collateralIsSwappable $ box_ [alignTop] $ hstack
                  [ flip styleBasic [textSize 10] $ tooltip_ swapCollateralMsg [tooltipDelay 0] $
                      label swappableCollateralIcon
                        `styleBasic` 
                          [ textMiddle
                          , textFont "Remix"
                          , textSize 10
                          , textColor customBlue
                          , paddingT 1
                          ]
                  , spacer_ [width 2]
                  ]
              , box_ [alignTop] $ label "Collateralization:"
                  `styleBasic` [paddingT 3, textSize 8, textColor lightGray]
              , spacer_ [width 3]
              , if null collateralPrices 
                then label "Unsecured" `styleBasic` [paddingT 3, textSize 8, textFont "Bold", textColor customRed]
                else vstack_ [childSpacing_ 3] $ for (groupInto 3 collateralPrices) $ 
                       \col -> hstack_ [childSpacing_ 3] $ map (collateralAssetWidget loanAmount) col
              ]
          ] `styleBasic` 
                [ padding 10
                , bgColor customGray2
                , radius 5
                , border 1 black
                ]
  
  offerOutputs :: AppModel -> AppNode
  offerOutputs AppModel{..} = do
      vstack
        [ hstack
            [ label "Outputs:"
                `styleBasic` [textSize 12]
            , spacer
            , toggleButton_ horizontalMoreIcon 
                (toLensVL $ #lendingModel % #lendModel % #inspectedOfferTransaction % toggleShow #showOutputs)
                [toggleButtonOffStyle moreOffStyle]
                `styleBasic` 
                  [ textSize 10
                  , textColor customRed
                  , textFont "Remix"
                  , textMiddle
                  , radius 20
                  , paddingT 2
                  , paddingB 2
                  , paddingR 5
                  , paddingL 5
                  , bgColor black
                  , border 0 transparent
                  ]
                `styleHover` [bgColor customGray1, cursorIcon CursorHand]
                `nodeVisible` (allOutputs /= [])
            , label "none" 
                `styleBasic` [textColor white, textSize 12]
                `nodeVisible` null allOutputs
            ]
        , widgetIf showOutputs $
            vstack_ [childSpacing] (map outputRow allOutputs)
              `styleBasic` [padding 10]
        ]
    where
      Config{timeZone,network} = config
  
      LoanWallet{stakeCredential} = lendingModel ^. #selectedWallet
  
      lenderId = Loans.genLenderId stakeCredential
  
      Transaction{showOutputs, outputs} = 
        fromMaybe def $ lendingModel ^. #lendModel % #inspectedOfferTransaction
  
      allOutputs = flip filter outputs $ \TransactionUTxO{inlineDatum} ->
        Just lenderId == (inlineDatum >>= preview (_OfferDatum % #lenderId) . parseInlineLoanDatum)
  
      outputRow :: TransactionUTxO -> AppNode
      outputRow TransactionUTxO{utxoRef,inlineDatum} =
        case parseInlineLoanDatum <$> inlineDatum of
            Just (OfferDatum offerDatum) -> offerRow utxoRef offerDatum
            _ -> spacer `nodeVisible` False
  
      collateralAssetWidget :: NativeAsset -> (NativeAsset,Rational) -> AppNode
      collateralAssetWidget loanAsset (collateralAsset, price) = do
        let formattedPrice = showPriceFormatted reverseTickerMap collateralAsset loanAsset price
            prettyPrice = mconcat
              [ formattedPrice
              , " "
              , showAssetNameOnly reverseTickerMap collateralAsset
              , " / "
              , showAssetNameOnly reverseTickerMap loanAsset
              ]
        hstack
          [ spacer_ [width 2]
          , label prettyPrice
              `styleBasic` [textSize 8, textColor lightGray]
          , spacer_ [width 2]
          ] `styleBasic` 
              [ bgColor customGray4
              , padding 2
              , paddingT 1
              , paddingT 1
              , radius 3
              , border 1 customGray1
              ]
  
      offerRow :: TxOutRef -> Loans.OfferDatum -> AppNode
      offerRow utxoRef Loans.OfferDatum{lenderId=_,..} = do
        let loanAmount = toNativeAsset loanAsset & #quantity .~ loanPrincipal
            duration = calcDaysInPosixPeriod $ fromPlutusTime loanTerm
            collateralPrices = map (over _1 toNativeAsset . over _2 toRational) 
                             $ collateralization ^. #unCollateralization
            prettyInterest 
              | loanInterest == 0 = "Interest-Free"
              | otherwise = unwords
                  [ if compoundingInterest then "Compounding" else "Non-Compounding"
                  , "Interest:"
                  , displayPercentage (toRational loanInterest) <> "%"
                  ]
            prettyEpochDuration = flip (maybe "No Loan Epochs") epochDuration $ \freq ->
              unwords
                [ "Loan Epoch:"
                , show (calcDaysInPosixPeriod $ fromPlutusTime freq)
                , "Day(s)"
                ]
            prettyMinPayment = unwords
              [ "Minimum Payment:"
              , showAssetBalance True reverseTickerMap $ loanAmount & #quantity .~ minPayment
              ]
            prettyPenalty = case penalty of
              Loans.NoPenalty -> "No Penalty"
              Loans.FixedFee fee -> unwords
                [ "Fee Penalty:"
                , showAssetBalance True reverseTickerMap $ loanAmount & #quantity .~ fee
                ]
              Loans.PercentFee percent -> unwords
                [ "Percent Penalty:"
                , displayPercentage (toRational percent) <> "%"
                ]
            prettyExpirationTime exprTime = unwords
              [ "Expires:"
              , showLocalDate timeZone exprTime
              , showLocalTime timeZone exprTime
              ]
            swapCollateralMsg = "Collateral can be swapped out for other approved collateral"
            payToAddress = either (const "error") fst $ plutusToBech32 network lenderAddress
            mTargetWallet = find ((==payToAddress) . view #paymentAddress) 
                          $ knownWallets ^. #paymentWallets
            addressTip = case mTargetWallet of
              Nothing -> "Payments to: " <> display payToAddress
              Just w -> unwords
                [ "Payments to"
                , w ^. #alias <> ":"
                , display payToAddress
                ]
        vstack
          [ hstack
              [ label ("Offer For " <> showAssetBalance True reverseTickerMap loanAmount)
                  `styleBasic` [textSize 10, textColor customBlue]
              , spacer_ [width 5]
              , let prettyRef = display utxoRef in
                flip styleBasic [textSize 10] $ tooltip_ prettyRef [tooltipDelay 0] $
                  box_ [alignMiddle, onClick $ CopyText $ display utxoRef] $
                    label targetUTxOIcon
                      `styleBasic` 
                        [ bgColor black
                        , textMiddle
                        , textFont "Remix"
                        , textSize 8
                        , textColor customBlue
                        , paddingT 1
                        , paddingB 1
                        , paddingL 3
                        , paddingR 3
                        , radius 5
                        ]
                      `styleHover` [bgColor customGray1, cursorIcon CursorHand]
              , widgetMaybe offerExpiration $ \exprTime -> hstack
                  [ spacer_ [width 5]
                  , flip styleBasic [textSize 10] $ 
                      tooltip_ (prettyExpirationTime $ fromPlutusTime exprTime) [tooltipDelay 0] $
                        label expirationIcon
                          `styleBasic` 
                            [ textMiddle
                            , textFont "Remix"
                            , textSize 10
                            , textColor customRed
                            ]
                  ]
              , spacer_ [width 5]
              , flip styleBasic [textSize 10] $ tooltip_ addressTip [tooltipDelay 0] $
                  box_ [alignMiddle, onClick $ CopyText $ display payToAddress] $
                    label targetAddressIcon
                      `styleBasic` 
                        [ bgColor black
                        , textMiddle
                        , textFont "Remix"
                        , textSize 8
                        , textColor customBlue
                        , paddingT 1
                        , paddingB 1
                        , paddingL 3
                        , paddingR 3
                        , radius 5
                        ]
                      `styleHover` [bgColor customGray1, cursorIcon CursorHand]
              , filler
              , label "Duration:"
                  `styleBasic` [textSize 10, textColor white]
              , spacer_ [width 3]
              , label (show duration <> " Days")
                  `styleBasic` [textSize 10, textColor white]
              ]
          , spacer_ [width 3]
          , hstack
              [ label prettyInterest
                  `styleBasic` [textSize 8, textColor lightGray]
              , filler
              , label prettyEpochDuration
                  `styleBasic` [textSize 8, textColor lightGray]
              ]
          , widgetIf (isJust epochDuration) $ vstack
              [ spacer_ [width 3]
              , hstack
                  [ label prettyMinPayment
                      `styleBasic` [textSize 8, textColor lightGray]
                  , filler
                  , label prettyPenalty
                      `styleBasic` [textSize 8, textColor lightGray]
                  ]
              ]
          , spacer_ [width 2]
          , hstack
              [ widgetIf collateralIsSwappable $ box_ [alignTop] $ hstack
                  [ flip styleBasic [textSize 10] $ tooltip_ swapCollateralMsg [tooltipDelay 0] $
                      label swappableCollateralIcon
                        `styleBasic` 
                          [ textMiddle
                          , textFont "Remix"
                          , textSize 10
                          , textColor customBlue
                          , paddingT 1
                          ]
                  , spacer_ [width 2]
                  ]
              , box_ [alignTop] $ label "Collateralization:"
                  `styleBasic` [paddingT 3, textSize 8, textColor lightGray]
              , spacer_ [width 3]
              , if null collateralPrices 
                then label "Unsecured" `styleBasic` [paddingT 3, textSize 8, textFont "Bold", textColor customRed]
                else vstack_ [childSpacing_ 3] $ for (groupInto 3 collateralPrices) $ 
                       \col -> hstack_ [childSpacing_ 3] $ map (collateralAssetWidget loanAmount) col
              ]
          ] `styleBasic` 
                [ padding 10
                , bgColor customGray2
                , radius 5
                , border 1 black
                ]
  
  moreOffStyle :: Style
  moreOffStyle = 
    def `styleBasic` 
          [ bgColor black
          , textColor customBlue
          , radius 20
          , paddingT 2
          , paddingB 2
          , paddingR 5
          , paddingL 5
          ]
        `styleHover`
          [ bgColor customGray1]
  
  -------------------------------------------------
  -- Helper Functions
  -------------------------------------------------
  -- | (# Offers Closed, # Offers Created, # Offers Accepted).
  offerActionCount :: Loans.LenderId -> Transaction -> (Int,Int,Int)
  offerActionCount lenderId Transaction{plutusContracts,outputs} =
      (closedCount, createdCount, acceptedCount)
    where
      checkSpend :: (Int,Int) -> TransactionPlutusContract -> (Int,Int)
      checkSpend acc@(closed,accepted) TransactionPlutusContract{..} =
       case decodeData @Loans.LoanRedeemer redeemer of
          Nothing -> acc
          Just r -> case r of
            Loans.CloseOrUpdateOffer -> 
              case datum >>= decodeData @Loans.OfferDatum of
                Nothing -> acc
                Just offerDatum -> 
                  if lenderId == offerDatum ^. #lenderId then
                    (closed + 1, accepted)
                  else 
                    acc
            Loans.AcceptOffer ->
              case datum >>= decodeData @Loans.OfferDatum of
                Nothing -> acc
                Just offerDatum -> 
                  if lenderId == offerDatum ^. #lenderId then
                    (closed, accepted + 1)
                  else 
                    acc
            _ -> acc
  
      (closedCount, acceptedCount) = foldl' checkSpend (0,0) plutusContracts
  
      checkCreation :: Int -> TransactionUTxO -> Int
      checkCreation acc TransactionUTxO{nativeAssets} =
        let hasLenderId = flip any nativeAssets $ \NativeAsset{policyId,tokenName} -> and
              [ policyId == Loans.negotiationBeaconCurrencySymbol
              , tokenName == Loans.unLenderId lenderId
              ]
         in if hasLenderId then acc + 1 else acc
  
      createdCount = foldl' checkCreation 0 outputs
  
  matchesUTxO 
    :: ReverseTickerMap 
    -> Loans.LenderId
    -> LendTxFilterModel
    -> TransactionUTxO 
    -> Bool
  matchesUTxO reverseTickerMap lenderId LendTxFilterModel{..} TransactionUTxO{..} = 
      and
        [ matchesAsset [actualLoanAsset] loanAsset
        , matchesAsset actualCollateral collateral
        , offerDatum ^. #lenderId == lenderId 
        ]
    where
      matchesAsset :: [NativeAsset] -> Text -> Bool
      matchesAsset xs searchTarget
        | searchTarget == "" = True
        | otherwise = flip any xs $ \NativeAsset{..} -> or
            [ display policyId <> "." <> display tokenName == searchTarget
            , Just searchTarget ==
                fmap (display . fst) (Map.lookup (policyId,tokenName) reverseTickerMap) 
            , policyId == "" && searchTarget == "ADA"
            ]
  
      offerDatum = fromMaybe def $ inlineDatum >>= decodeData @Loans.OfferDatum
  
      actualLoanAsset = toNativeAsset $ offerDatum ^. #loanAsset
      actualCollateral =
        map (toNativeAsset . fst) $ Loans.unCollateralization $ offerDatum ^. #collateralization 
  
  applySearchFilter 
    :: ReverseTickerMap 
    -> Loans.LenderId
    -> LendTxFilterModel 
    -> [Transaction] 
    -> [Transaction]
  applySearchFilter reverseTickerMap lenderId filterModel xs
    | filterModel ^. #loanAsset == "" && filterModel ^. #collateral == "" = xs
    | otherwise = flip filter xs $ \Transaction{..} -> or
        [ any (matchesUTxO reverseTickerMap lenderId filterModel) inputs
        , any (matchesUTxO reverseTickerMap lenderId filterModel) outputs
        ]
  
  -------------------------------------------------
  -- Helper Lens
  -------------------------------------------------
  lowerBoundText :: Lens' LendTxFilterModel Text
  lowerBoundText = lens getLowerBoundText setLowerBoundText
    where
      getLowerBoundText :: LendTxFilterModel -> Text
      getLowerBoundText tx = 
        maybe "" (toText . Time.formatTime Time.defaultTimeLocale "%m-%d-%Y") $ tx ^. #dateRange % _1
  
      setLowerBoundText :: LendTxFilterModel -> Text -> LendTxFilterModel
      setLowerBoundText tx date = 
        tx & #dateRange % _1 .~ Time.parseTimeM True Time.defaultTimeLocale "%m-%d-%Y" (toString date)
  
  upperBoundText :: Lens' LendTxFilterModel Text
  upperBoundText = lens getLowerBoundText setLowerBoundText
    where
      getLowerBoundText :: LendTxFilterModel -> Text
      getLowerBoundText tx = 
        maybe "" (toText . Time.formatTime Time.defaultTimeLocale "%m-%d-%Y") $ tx ^. #dateRange % _2
  
      setLowerBoundText :: LendTxFilterModel -> Text -> LendTxFilterModel
      setLowerBoundText tx date = 
        tx & #dateRange % _2 .~ Time.parseTimeM True Time.defaultTimeLocale "%m-%d-%Y" (toString date)
  
  -- | A lens to toggle the `show` field of the `Transaction`.
  toggleShow :: Lens' Transaction Bool -> Lens' (Maybe Transaction) Bool
  toggleShow finalLens = lens getToggleShow setToggleShow
    where
      getToggleShow :: Maybe Transaction -> Bool
      getToggleShow = maybe False (view finalLens)
  
      setToggleShow :: Maybe Transaction -> Bool -> Maybe Transaction
      setToggleShow maybeInfo b = fmap (set finalLens b) maybeInfo
  
  -------------------------------------------------
  -- Helper Widgets
  -------------------------------------------------
  -- | A label button that will copy itself.
  copyableLabelSelf :: Text -> Double -> Color -> Color -> WidgetNode s AppEvent
  copyableLabelSelf caption fontSize mainColor hoverColor = 
    tooltip_ "Copy" [tooltipDelay 0] $ button_ caption (CopyText caption) [resizeFactor 2]
      `styleBasic`
        [ padding 0
        , radius 5
        , textMiddle
        , textLeft
        , textSize fontSize
        , border 0 transparent
        , textColor mainColor
        , bgColor transparent
        ]
      `styleHover` [textColor hoverColor, cursorIcon CursorHand]
  