Untitled

mail@pastecode.io avatar
unknown
plain_text
a year ago
39 kB
2
Indexable
import UIKit

/// Cell for displaying position, holding and order data
///
/// ![ScripListPHOCell](../img/DocumentationSnapshot.ScripListPHOCell.png)

public protocol ScripListPHOCellDelegate: AnyObject {
  func didTapDetailsBottomView(at index: Int)
  func didTapCancelBottomView(at index: Int)
  func didTapModifyBottomView(at index: Int)
  func didTapRetryBottomView(at index: Int)
}

open class ScripListPHOCell: UITableViewCell {
  
  // MARK: - Properties
  
  public weak var delegate: ScripListPHOCellDelegate?
  
  private let leadingCheckbox: Checkbox = {
    let checkbox = Checkbox()
    checkbox.isHidden = true
    checkbox.isUserInteractionEnabled = false
    return checkbox
  }()
  
  private let rootStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.axis = .vertical
    return stackView
  }()
  
  private let mainHorizontalStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.isLayoutMarginsRelativeArrangement = true
    stackView.directionalLayoutMargins = Constants.mainHorizontalStackViewDefaultMargins
    return stackView
  }()
  
  private let bottomView: ScripListPHOView = {
    let view = ScripListPHOView(size: .small, isTapNeededForLegs: false)
    view.setContentHuggingPriority(.defaultHigh, for: .vertical)
    view.isHidden = true
    return view
  }()
  
  private let failureBottomView: BottomFailureView = {
    let view = BottomFailureView()
    view.isHidden = true
    return view
  }()
  
  private let mainVerticalStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.axis = .vertical
    stackView.spacing = .halfOfQuarterOfDefaultMargin
    return stackView
  }()
  
  private let leadingPrimaryLabel = ScripName()
  
  private let trailingPrimaryLabel: Label = {
    let label = Label(.bodyBold)
    label.textAlignment = .right
    return label
  }()
  
  private let trailingPrimaryImageView: UIImageView = {
    let imageView = UIImageView()
    imageView.contentMode = .center
    imageView.isHidden = true
    return imageView
  }()
  
  private let trailingPrimaryStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.spacing = .halfOfThreeQuartersOfDefaultMargin
    return stackView
  }()
  
  private let primaryStackView = UIStackView()
  
  private let leadingSecondaryLabel: Label = {
    let label = Label(.meta)
    label.textColor = .appUI5Alt
    return label
  }()
  
  private let trailingStackViewForTopLabel: UIStackView = {
    let stackView = UIStackView()
    stackView.spacing = .halfOfDefaultMargin
    stackView.alignment = .center
    return stackView
  }()
  
  private let warningIcon: UIImageView = {
    let imageView = UIImageView()
    imageView.image = .appIconErrorCircle16.withColor(.appBackgroundYellow)
    imageView.contentMode = .scaleAspectFit
    imageView.setContentHuggingPriority(.required, for: .horizontal)
    imageView.isHidden = true
    return imageView
  }()
  
  private let trailingSecondaryLabel: Label = {
    let label = Label(.meta)
    label.textColor = .appUI5Alt
    label.textAlignment = .right
    return label
  }()
  
  private let secondaryStackView = UIStackView()
  
  private let leadingTertiaryLabel: Label = {
    let label = Label(.meta)
    label.textColor = .appUI5Alt
    return label
  }()
  
  private let trailingTertiaryLabel: Label = {
    let label = Label(.meta)
    label.textAlignment = .right
    label.textColor = .appUI5Alt
    return label
  }()
  private let tertiaryStackView = UIStackView()
  
  private let bottomButtonsStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.backgroundColor = .appUi1
    stackView.distribution = .fillEqually
    stackView.isHidden = true
    return stackView
  }()
  
  private lazy var detailsBottomView = makeCustomButtonView(image: .appIconFileList2Line, text: L10n.Other.details, shouldAddVerticalSeparator: false)
  private lazy var modifyBottomView = makeCustomButtonView(image: .appIconEdit2Line, text: L10n.Other.modify)
  private lazy var cancelBottomView = makeCustomButtonView(image: .appIconCloseCircleLine, text: L10n.Other.cancel)
  private lazy var retryBottomView = makeCustomButtonView(image: .appIconReloadLine, text: L10n.Other.retry)
  
  private let separatorStackView: UIStackView = {
    let stackView = UIStackView()
    stackView.isLayoutMarginsRelativeArrangement = true
    stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: .zero,
                                                                 leading: .defaultMargin,
                                                                 bottom: .zero,
                                                                 trailing: .defaultMargin)
    return stackView
  }()
  
  private let separator = Divider().withBackgroundColor(.appUi1)
  private lazy var separatorHeightConstraint = separator.heightAnchor.constraint(equalToConstant: Constants.defaultSeparatorHeight)
  
  // MARK: - Lifecycle
  
  override public init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    
    backgroundColor = .appBackgroundDefault
    selectedBackgroundView = UIView().withBackgroundColor(.appInteractiveUIPressed)
    
    rootStackView.addArrangedSubviews(
      mainHorizontalStackView.addingArrangedSubviews([
        leadingCheckbox,
        mainVerticalStackView.addingArrangedSubviews([
          primaryStackView.addingArrangedSubviews([
            leadingPrimaryLabel,
            trailingPrimaryStackView.addingArrangedSubviews([
              trailingStackViewForTopLabel.addingArrangedSubviews([trailingPrimaryLabel, warningIcon]),
              trailingPrimaryImageView
            ])
          ]),
          secondaryStackView.addingArrangedSubviews([
            leadingSecondaryLabel,
            trailingSecondaryLabel
          ]),
          tertiaryStackView.addingArrangedSubviews([
            leadingTertiaryLabel,
            trailingTertiaryLabel
          ]),
          bottomView,
          failureBottomView
        ])
      ]),
      separatorStackView.addingArrangedSubviews(separator),
      bottomButtonsStackView.addingArrangedSubviews(detailsBottomView, modifyBottomView, cancelBottomView, retryBottomView)
    )
    contentView.addSubviewsForAutoLayout([rootStackView])
    
    NSLayoutConstraint.activate([
      leadingCheckbox.widthAnchor.constraint(equalToConstant: .threeAndHalfOfDefaultMargin),
      separatorHeightConstraint,
      rootStackView.topAnchor.constraint(equalTo: contentView.topAnchor),
      rootStackView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
      rootStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
      rootStackView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
      trailingPrimaryImageView.widthAnchor.constraint(equalToConstant: .defaultMargin)
    ])
    
    detailsBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapDetailsBottomView(_:)))))
    cancelBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapCancelBottomView(_:)))))
    modifyBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapModifyBottomView(_:)))))
    retryBottomView.addGestureRecognizer((UITapGestureRecognizer(target: self, action: #selector(didTapRetryBottomView(_:)))))
  }
  
  @available(*, unavailable)
  public required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
  
  override open func prepareForReuse() {
    super.prepareForReuse()
    failureBottomView.isHidden = true
    trailingPrimaryImageView.isHidden = true
    warningIcon.isHidden = true
    bottomView.layoutIfNeeded()
  }
  
  override open func setEditing(_ editing: Bool, animated: Bool) {
    // We need hide the default leading accessory, so will send editing as false here
    super.setEditing(false, animated: animated)
  }
  
  override open func setSelected(_ selected: Bool, animated: Bool) {
    super.setSelected(selected, animated: animated)
    leadingCheckbox.setIsSelected(selected)
  }
  
  public func isHiddenSeparator(_ value: Bool) {
    separator.isHidden = value
  }
  
  public func configure(name: String = "", PHO: PHO) {
    switch PHO {
    case let .orders(orderCategory):
      updateForOrder(orderCategory)
    case let .holdings(data):
      updateForHolding(data: data)
    case let .positions(data):
      updateForPosition(data: data)
    case let .basketOrder(data):
      updateForBasketItem(data: data)
    }
  }
  
  public func updateConditionalOrderLTP(with attributedText: NSAttributedString?) {
    trailingPrimaryLabel.isHidden = attributedText == nil
    trailingPrimaryLabel.attributedText = attributedText
  }
  
  public func updateBasketOrder(withLTP ltp: String, isBasketOrderExecuted: Bool, side: OrderSide) {
    if isBasketOrderExecuted {
      let trailingSecondaryAttributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: L10n.Lists.ltpSuffix(ltp), attributes: [.foregroundColor: UIColor.appUI5Alt]))
      trailingSecondaryAttributedText.append(NSAttributedString(string: " \(L10n.Lists.interpunct) "))
      let sideText = side == .buy ? L10n.Lists.buy : L10n.Lists.sell
      let sideTextColor = side == .buy ? UIColor.appTextPositiveTrend : UIColor.appTextNegativeTrend
      let sideAttributedText = sideText.attributedString(textStyle: .meta, originalColor: sideTextColor, subStrings: (sideText, .appText2))
      trailingSecondaryAttributedText.append(sideAttributedText)
      trailingSecondaryLabel.attributedText = trailingSecondaryAttributedText
    } else {
      trailingSecondaryLabel.text = L10n.Lists.ltpSuffix(ltp)
    }
  }
    
  public func updateConditionalOrderPNL(with pnlText: String?, pnlColor: UIColor) {
    bottomView.updatePNL(pnlText, pnlColor: pnlColor)
  }
  
  public func updateWarningIconVisibility(to shouldShow: Bool) {
    warningIcon.isHidden = !shouldShow
  }
  
}

// MARK: - UpdateUIHelper

private typealias UpdateUIHelper = ScripListPHOCell
private extension UpdateUIHelper {
  
  func setDefaultSeparator() {
    separatorStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: .zero,
                                                                          leading: .defaultMargin,
                                                                          bottom: .zero,
                                                                          trailing: .defaultMargin)
    separatorHeightConstraint.constant = Constants.defaultSeparatorHeight
    layoutIfNeeded()
  }
  
  func setConditionalSeparator() {
    separatorStackView.directionalLayoutMargins = .zero
    separatorHeightConstraint.constant = Constants.conditionalSeparatorHeight
    layoutIfNeeded()
  }
  
  func updateForOrder(_ order: PHO.OrderCategory) {
    switch order {
    case .regular(let (order, filling, trailingImage)):
      trailingPrimaryLabel.isHidden = false
      leadingSecondaryLabel.isHidden = false
      trailingSecondaryLabel.isHidden = false
      leadingTertiaryLabel.isHidden = false
      trailingTertiaryLabel.isHidden = false
      tertiaryStackView.isHidden = false
      failureBottomView.isHidden = true
      bottomView.isHidden = true
      trailingPrimaryImageView.isHidden = true
      backgroundColor = order.status == .failed ? .appDataNegativeSubtle : backgroundColor
      
      leadingPrimaryLabel.configure(.scripName(title: order.name, tag: nil, titleFont: .bodyBold, trailingImage: trailingImage))
      leadingSecondaryLabel.text = additionalScripInfo(index: order.index, instrumentType: order.instrumentType, date: order.date)
      
      let productTypeAttributedText = NSAttributedString(string: "\(order.productType.title) ", attributes: [.foregroundColor: UIColor.appText1])
      leadingTertiaryLabel.attributedText = productTypeAttributedText
      
      trailingPrimaryLabel.textColor = order.status == .failed ? .appSupportError : .appText1
      trailingPrimaryLabel.text = !order.isScheduled ? titleForOrderStatus(order.status) : L10n.Lists.scheduled
      
      switch order.side {
      case .buy:
        trailingSecondaryLabel.textColor = .appTextPositiveTrend
        trailingSecondaryLabel.text = L10n.Lists.buy
      case .sell:
        trailingSecondaryLabel.textColor = .appTextNegativeTrend
        trailingSecondaryLabel.text = L10n.Lists.sell
      }
      
      let price: String = {
        if case let .limit(limit) = filling {
          return order.isCurrency ? FormattedNumber.currencyFourFractionals(limit).stringValue : FormattedNumber.currency(limit).stringValue
        } else {
          return L10n.Lists.market
        }
      }()
      trailingTertiaryLabel.textColor = .appText2
      trailingTertiaryLabel.text = "\(order.quantityFilled)/\(order.quantityOrdered) \(L10n.Other.qty)." + " @ " + price
      separator.backgroundColor = order.isOpen ? .appUi1 : .appUi3
      setDefaultSeparator()
      if order.status == .failed {
        trailingPrimaryImageView.isHidden = false
        trailingPrimaryImageView.image = .appIconErrorCircle16
        trailingPrimaryImageView.tintColor = .appSupportError
        if !order.message.isEmpty {
          failureBottomView.isHidden = false
          failureBottomView.configure(with: .regularError(order.message))
        }
      }
      warningIcon.isHidden = !order.shouldShowWarningIcon
    case .conditional(let(parent, childLegs, scripDetails, message)):
      warningIcon.isHidden = true
      bottomView.isHidden = false
      trailingPrimaryLabel.isHidden = false
      leadingSecondaryLabel.isHidden = false
      trailingSecondaryLabel.isHidden = false
      leadingTertiaryLabel.isHidden = true
      trailingTertiaryLabel.isHidden = true
      tertiaryStackView.isHidden = true
      failureBottomView.isHidden = true
      trailingPrimaryImageView.isHidden = true
      trailingPrimaryLabel.attributedText = nil
      leadingPrimaryLabel.configure(.scripName(title: scripDetails.scripName,
                                               tag: nil,
                                               titleFont: .bodyBold))
      leadingSecondaryLabel.text = additionalScripInfo(index: scripDetails.scripIndex, instrumentType: scripDetails.instrumentType, date: scripDetails.scripDate)
      trailingSecondaryLabel.text = scripDetails.scripProductType.title
      trailingSecondaryLabel.textColor = .appUi5
      trailingPrimaryLabel.textColor = .appText2
      
      updateConditionalOrderLTP(with: Self.getAttributedLTP(ltpValue: parent.ltp, ltpColor: parent.ltpColor))
      updateConditionalOrderPNL(with: parent.pnlValue, pnlColor: parent.pnlColor)
      setConditionalSeparator()
      separator.backgroundColor = .appUi3
      if let errorMessage = message, !errorMessage.isEmpty {
        failureBottomView.isHidden = false
        failureBottomView.configure(with: .conditionalError(errorMessage))
      }
      bottomView.configureParentStatusView(parent: parent)
      bottomView.configureConditionalChildsViews(childs: childLegs, parentInfo: parent)
    }
  }
  
  func updateForHolding(data: HoldingDataModel) {
    warningIcon.isHidden = true
    leadingPrimaryLabel.configure(.scripName(title: data.name, tag: nil, titleFont: .bodyBold))
    updateOverall(overall: data.overall, percentage: data.percentageChange, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
    
    leadingSecondaryLabel.text = L10n.Lists.invested(FormattedNumber.currency(data.amountInvested).stringValue)
    updateAveragePrice(price: data.averagePrice, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
    
    let quantityString = NSMutableAttributedString()
    let quantity = NSAttributedString(
      string: L10n.Lists.quantityPrefix(data.quantity),
      attributes: [.foregroundColor: UIColor.appUI5Alt]
    )
    quantityString.append(quantity)
    quantityString.append(getAdditionalQuantityInfo(additionalInfo: data.quantityAdditionalInfo))
    leadingTertiaryLabel.attributedText = quantityString
    if !data.isIlliquid {
      updateTextForLTP(ltp: data.ltp, ltpPercentage: data.ltpPercentage, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
    } else {
      trailingTertiaryLabel.text = L10n.UIElements.illiquid
    }
    
    separator.backgroundColor = data.isActive ? .appUi1 : .appUi3
  }
  
  func updateForPosition(data: PositionDataModel) {
    warningIcon.isHidden = true
    leadingPrimaryLabel.configure(.scripName(title: data.name, tag: nil, titleFont: .bodyBold))
    updateOverall(overall: data.overall, showFourFractionalsForPrice: false)
    
    leadingSecondaryLabel.text = additionalScripInfo(index: data.index, instrumentType: data.instrumentType, date: data.date)
    updateAveragePrice(price: data.averagePriceOfShare, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
    
    updateQuantity(productType: data.productType.title, quantity: data.quantityOfShares)
    updateTextForLTP(ltp: data.lastTradedPrice, ltpPercentage: data.ltpPercentage, showFourFractionalsForPrice: data.showFourFractionalsForPrice)
    
    separator.backgroundColor = data.isOpen ? .appUi1 : .appUi3
    bottomView.isHidden = true
  }
  
  func updateForBasketItem(data: BasketOrderDataModel) {
    mainHorizontalStackView.directionalLayoutMargins = data.isEditing ? NSDirectionalEdgeInsets(top: .halfOfDefaultMargin, leading: .zero, bottom: .halfOfDefaultMargin, trailing: .defaultMargin) : Constants.mainHorizontalStackViewDefaultMargins
    leadingCheckbox.isHidden = !data.isEditing
    trailingPrimaryLabel.textAlignment = data.isEditing ? .left : .right
    mainVerticalStackView.spacing = data.isEditing ? .halfOfThreeQuartersOfDefaultMargin : .halfOfQuarterOfDefaultMargin
    trailingSecondaryLabel.isHidden = data.isEditing
    tertiaryStackView.isHidden = data.isEditing
    leadingPrimaryLabel.isTitleExpanded = data.expansionDetails.isExpanded
    leadingPrimaryLabel.configure(.scripName(title: data.name,
                                             shouldAddSeparatorAfterTitle: data.isEditing,
                                             tag: nil,
                                             titleFont: .bodyBold,
                                             titleColor: data.isExecuted ? .appTextLink : .appText1,
                                             isExpandable: data.isExecuted,
                                             shouldShowTagDate: !data.isEditing))
    leadingSecondaryLabel.text = additionalScripInfo(index: data.index, instrumentType: data.segment, date: nil)
    
    let productTypeAttributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(data.productType.title)", attributes: [.foregroundColor: UIColor.appText1]))
    productTypeAttributedText.append(NSAttributedString(string: " \(L10n.Lists.interpunct) "))
    
    let quantityAttributedText = data.orderType.attributedString(textStyle: .meta, originalColor: .appUI5Alt, subStrings: (data.orderType, .appText2))
    productTypeAttributedText.append(quantityAttributedText)
    
    leadingTertiaryLabel.attributedText = productTypeAttributedText
    
    if data.isExecuted {
      trailingPrimaryLabel.textColor = data.orderStatus.color
      trailingPrimaryLabel.text = !data.isScheduled ? titleForOrderStatus(data.orderStatus) : L10n.Lists.scheduled
    } else {
      switch data.side {
      case .buy:
        trailingPrimaryLabel.textColor = .appTextPositiveTrend
        trailingPrimaryLabel.text = L10n.Lists.buy
      case .sell:
        trailingPrimaryLabel.textColor = .appTextNegativeTrend
        trailingPrimaryLabel.text = L10n.Lists.sell
      }
    }
      
    trailingSecondaryLabel.textColor = .appUi5
    
    trailingTertiaryLabel.textColor = .appText2
    let priceString = (data.shouldShowFourFractionalsForPrice ? FormattedNumber.currencyFourFractionals(data.price) : FormattedNumber.currency(data.price)).stringValue
    let quantityText = data.isLimitPrice ? "\(data.quantity) \(L10n.Other.qty). @ \(priceString)" : "\(data.quantity) \(L10n.Other.qty)."
    trailingTertiaryLabel.text = quantityText
    
    bottomButtonsStackView.isHidden = !data.expansionDetails.isExpanded
    guard data.isExecuted else { return }
    detailsBottomView.isHidden = false
    switch data.expansionDetails.buttonsConfig {
    case .detailsModifyCancel:
      modifyBottomView.isHidden = false
      cancelBottomView.isHidden = false
      retryBottomView.isHidden = true
    case .detailsRetry:
      retryBottomView.isHidden = false
      modifyBottomView.isHidden = true
      cancelBottomView.isHidden = true
    case .details:
      modifyBottomView.isHidden = true
      cancelBottomView.isHidden = true
      retryBottomView.isHidden = true
    }
  }
  
  func titleForOrderStatus(_ orderStatus: OrderStatus) -> String {
    switch orderStatus {
    case .triggerPending:
      return L10n.Lists.triggerPendingOrders
    default:
      return orderStatus.title
    }
  }
  
  func additionalScripInfo(index: String, instrumentType: String?, date: Date?) -> String {
    var string = "\(index)"
    if let date = date {
      string.append(" " + L10n.Lists.interpunct + " ")
      string.append("\(DateFormatter.scripInlineSpacedFormatter.string(from: date).uppercased())")
    }
    if let instrumentType = instrumentType {
      string.append(" \(instrumentType)")
    }
    return string
  }
  
  func getAdditionalQuantityInfo(additionalInfo: OrderQuantityAdditionalInformation?) -> NSAttributedString {
    guard let additionalInfo = additionalInfo else {
      return NSAttributedString(string: "")
    }
    let attributedString: NSAttributedString
    switch additionalInfo {
    case let .t1(quantity):
      attributedString = NSAttributedString(string: L10n.Lists.t1PendingText(quantity), attributes: [.foregroundColor: UIColor.appSupportProcessing])
    case .delisted:
      attributedString = NSAttributedString(string: L10n.Lists.delistedSuffix, attributes: [.foregroundColor: UIColor.appSupportWarning])
    case .suspended:
      attributedString = NSAttributedString(string: L10n.Lists.suspendedSuffix, attributes: [.foregroundColor: UIColor.appSupportWarning])
    }
    return attributedString
  }
  
  func updateQuantity(productType: String, quantity: Int) {
    let attributedText = NSMutableAttributedString()
    let productTypeAttributedText = NSAttributedString(string: "\(productType) ", attributes: [.foregroundColor: UIColor.appText2])
    attributedText.append(productTypeAttributedText)
    
    let seperatorAttachment = NSTextAttachment()
    seperatorAttachment.image = .appIconSeperator
    attributedText.append(NSAttributedString(attachment: seperatorAttachment))
    attributedText.append(NSAttributedString(string: " "))
    let quantityInDouble = Double(quantity)
    let quantityFormattedText = FormattedNumber.decimalNoFractionalWithSign(quantityInDouble).stringValue
    let quantityTextColor = quantityInDouble.currencyColor(defaultColor: .appText2)
    let quantityAttributedText = L10n.Lists.quantityPrefix(quantityFormattedText).attributedString(textStyle: .meta, originalColor: .appUI5Alt, subStrings: (quantityFormattedText, quantityTextColor))
    attributedText.append(quantityAttributedText)
    
    leadingTertiaryLabel.attributedText = attributedText
  }
  
  func updateOverall(overall: Double?, percentage: Double? = nil, showFourFractionalsForPrice: Bool) {
    let overallText = showFourFractionalsForPrice ? FormattedNumber.currencyWithSignFourFractionals(overall).stringValue : FormattedNumber.currencyWithSign(overall).stringValue
    let overallTextColor = overall?.currencyColor(defaultColor: .appText2) ?? .appText2
    guard let percentage = percentage else {
      trailingPrimaryLabel.textColor = overallTextColor
      trailingPrimaryLabel.text = overallText
      return
    }
    let percentChangeText = FormattedNumber.percentWithSign(percentage).stringValue
    trailingPrimaryLabel.attributedText = "\(overallText) (\(percentChangeText))".attributedString(textStyle: .meta, originalColor: overallTextColor, subStrings: (overallText, overallTextColor, .bodyBold))
  }
  
  func updateAveragePrice(price: Double?, showFourFractionalsForPrice: Bool) {
    let avgPriceFormatter: FormattedNumber = showFourFractionalsForPrice ? FormattedNumber.currencyFourFractionals(price) : FormattedNumber.currency(price)
    trailingSecondaryLabel.text = L10n.Lists.averageSuffix(avgPriceFormatter.stringValue)
  }
  
  func updateTextForLTP(ltp: Double?, ltpPercentage: Double?, showFourFractionalsForPrice: Bool) {
    let formattedLTPText = showFourFractionalsForPrice ? FormattedNumber.currencyFourFractionals(ltp).stringValue : FormattedNumber.currency(ltp).stringValue
    let formattedLTPChangeText = "(\(FormattedNumber.percentWithSignWithMultiplier(ltpPercentage).stringValue))"
    let trailingTertiaryLabelText = L10n.Lists.ltpSuffix("\(formattedLTPText) \(formattedLTPChangeText)")
    let formattedLTPChangeTextColor = ltpPercentage?.currencyColor(defaultColor: .appText2) ?? .appText2
    trailingTertiaryLabel.attributedText = trailingTertiaryLabelText.attributedString(textStyle: .meta, originalColor: .appUI5Alt, subStrings: (formattedLTPChangeText, formattedLTPChangeTextColor))
  }
  
}

public extension ScripListPHOCell {
  
  public static func getAttributedLTP(ltpValue: String?, ltpColor: UIColor) -> NSAttributedString? {
    guard let ltpValue = ltpValue else { return nil }
    let attributedText: NSAttributedString = L10n.Lists.ltp(ltpValue).attributedString(textStyle: .meta,
                                                                                       originalColor: .appUI5Alt,
                                                                                       subStrings: (ltpValue, ltpColor, .metaBold))
    return attributedText
  }
  
}

// MARK: Substructures

private typealias Substructures = ScripListPHOCell
private extension Substructures {
  
  public struct HoldingDataModel {
    
    let name: String
    let amountInvested: Double?
    let overall: Double?
    let percentageChange: Double?
    let quantity: Int
    let quantityAdditionalInfo: OrderQuantityAdditionalInformation?
    let averagePrice: Double?
    let showFourFractionalsForPrice: Bool
    let ltp: Double?
    let ltpPercentage: Double?
    let isActive: Bool
    let isIlliquid: Bool
    
    public init(name: String,
                amountInvested: Double?,
                overall: Double?,
                percentageChange: Double?,
                quantity: Int,
                quantityAdditionalInfo: OrderQuantityAdditionalInformation?,
                averagePrice: Double?,
                showFourFractionalsForPrice: Bool,
                ltp: Double?,
                ltpPercentage: Double?,
                isActive: Bool,
                isIlliquid: Bool) {
      self.name = name
      self.amountInvested = amountInvested
      self.overall = overall
      self.percentageChange = percentageChange
      self.quantity = quantity
      self.quantityAdditionalInfo = quantityAdditionalInfo
      self.averagePrice = averagePrice
      self.showFourFractionalsForPrice = showFourFractionalsForPrice
      self.ltp = ltp
      self.ltpPercentage = ltpPercentage
      self.isActive = isActive
      self.isIlliquid = isIlliquid
    }
    
  }
  
  public struct BasketOrderDataModel {
    
    public struct ExpansionDetails {
      
      public enum ButtonsConfig {
        
        case detailsModifyCancel
        case detailsRetry
        case details
        
      }
      
      let isExpanded: Bool
      let buttonsConfig: ButtonsConfig
      
      public init(isExpanded: Bool, buttonsConfig: ButtonsConfig) {
        self.isExpanded = isExpanded
        self.buttonsConfig = buttonsConfig
      }
      
    }
    
    let name: String
    let side: OrderSide
    let productType: ProductType
    let quantity: Int
    let orderType: String
    let price: Double
    let index: String
    let segment: String
    let isLimitPrice: Bool
    let isEditing: Bool
    let isExecuted: Bool
    let expansionDetails: ExpansionDetails
    let orderStatus: OrderStatus
    let isScheduled: Bool
    let shouldShowFourFractionalsForPrice: Bool
    
    public init(name: String,
                side: OrderSide,
                productType: ProductType,
                quantity: Int,
                orderType: String,
                price: Double,
                index: String,
                segment: String,
                isLimitPrice: Bool,
                isEditing: Bool,
                isExecuted: Bool,
                expansionDetails: ExpansionDetails,
                orderStatus: OrderStatus,
                isScheduled: Bool,
                shouldShowFourFractionalsForPrice: Bool) {
      self.name = name
      self.side = side
      self.productType = productType
      self.quantity = quantity
      self.orderType = orderType
      self.price = price
      self.index = index
      self.segment = segment
      self.isLimitPrice = isLimitPrice
      self.isEditing = isEditing
      self.isExecuted = isExecuted
      self.expansionDetails = expansionDetails
      self.orderStatus = orderStatus
      self.isScheduled = isScheduled
      self.shouldShowFourFractionalsForPrice = shouldShowFourFractionalsForPrice
    }
    
  }
  
  public struct PositionDataModel {
    
    let name: String
    let index: String
    let date: Date?
    let productType: ProductType
    let overall: Double?
    let lastTradedPrice: Double?
    let quantityOfShares: Int
    let averagePriceOfShare: Double
    let showFourFractionalsForPrice: Bool
    let ltpPercentage: Double
    let isOpen: Bool
    let instrumentType: String?
    
    public init(name: String,
                index: String,
                date: Date?,
                productType: ProductType,
                overall: Double?,
                lastTradedPrice: Double?,
                quantityOfShares: Int,
                averagePriceOfShare: Double,
                showFourFractionalsForPrice: Bool = false,
                ltpPercentage: Double,
                isOpen: Bool,
                instrumentType: String? = nil) {
      self.name = name
      self.index = index
      self.date = date
      self.productType = productType
      self.overall = overall
      self.lastTradedPrice = lastTradedPrice
      self.quantityOfShares = quantityOfShares
      self.averagePriceOfShare = averagePriceOfShare
      self.showFourFractionalsForPrice = showFourFractionalsForPrice
      self.ltpPercentage = ltpPercentage
      self.isOpen = isOpen
      self.instrumentType = instrumentType
    }
    
  }
  
  public enum PHO {
    
    case positions(PositionDataModel)
    case holdings(HoldingDataModel)
    case orders(OrderCategory)
    case basketOrder(BasketOrderDataModel)
    
    public enum OrderCategory {
      case regular(order: Order, filling: OrderFilling, trailingImage: UIImage? = nil)
      case conditional(
        parent: ScripListPHOView.ParentViewInfo,
        childLegs: [ScripListPHOView.ConditionalViewInfo],
        scripDetails: ScripDetails,
        errorMessage: String?
      )
    }
    
  }
  
  public enum OrderSide {
    
    case buy
    case sell
    
    var title: String {
      switch self {
      case .buy: return L10n.Lists.buy
      case .sell: return L10n.Lists.sell
      }
    }
    
  }
  
  public struct ScripDetails {
    
    let scripName: String
    let scripIndex: String
    let scripDate: Date?
    let scripProductType: ProductType
    let scripQuantity: Int
    let scripSegment: String?
    let instrumentType: String?
    
    public init(scripName: String,
                scripIndex: String,
                scripDate: Date?,
                scripProductType: ProductType,
                scripQuantity: Int,
                scripSegment: String?,
                instrumentType: String?) {
      self.scripName = scripName
      self.scripIndex = scripIndex
      self.scripDate = scripDate
      self.scripProductType = scripProductType
      self.scripQuantity = scripQuantity
      self.scripSegment = scripSegment
      self.instrumentType = instrumentType
    }
    
  }
  
  public struct Order {
    
    let name: String
    let index: String
    let date: Date?
    let productType: ProductType
    let status: OrderStatus
    let side: OrderSide
    let quantityFilled: Int
    let quantityOrdered: Int
    let isScheduled: Bool
    let isCurrency: Bool
    let price: OrderPrice
    let instrumentType: String?
    let isOpen: Bool
    let message: String
    let shouldShowWarningIcon: Bool
    
    public init(name: String,
                index: String,
                date: Date?,
                productType: ProductType,
                status: OrderStatus,
                side: OrderSide,
                quantityFilled: Int,
                quantityOrdered: Int,
                isScheduled: Bool,
                isCurrency: Bool,
                price: OrderPrice,
                instrumentType: String?,
                isOpen: Bool,
                message: String,
                shouldShowWarningIcon: Bool) {
      self.name = name
      self.index = index
      self.date = date
      self.productType = productType
      self.status = status
      self.side = side
      self.quantityFilled = quantityFilled
      self.quantityOrdered = quantityOrdered
      self.isScheduled = isScheduled
      self.isCurrency = isCurrency
      self.price = price
      self.instrumentType = instrumentType
      self.isOpen = isOpen
      self.message = message
      self.shouldShowWarningIcon = shouldShowWarningIcon
    }
    
  }
  
  public struct OrderPrice {
    
    let stopLoss: Double?
    let squareOff: Double?
    
    public init(stopLoss: Double?,
                squareOff: Double?) {
      self.stopLoss = stopLoss
      self.squareOff = squareOff
    }
    
  }
  
  public struct OrderLeg {
    
    public let title: String
    public let subtitle: String
    public let status: OrderStatus
    public let completionHandler: (() -> Void)?
    
    public init(title: String,
                subtitle: String,
                status: OrderStatus,
                completionHandler: (() -> Void)?) {
      self.title = title
      self.status = status
      self.completionHandler = completionHandler
      self.subtitle = subtitle
    }
    
  }
  
  public enum OrderPriceBand {
    
    case above
    case below
    
    var title: String {
      switch self {
      case .above: return L10n.Other.above
      case .below: return L10n.Other.below
      }
    }
    
  }
  
  public enum OrderFilling {
    
    case limit(Double)
    case market
    case smartOrder
    
  }
  
  public enum OrderQuantityAdditionalInformation {
    
    case suspended
    case delisted
    case t1(Int)
    
  }
  
  public enum ProductType: Hashable {
    
    case intraday
    case delivery
    case mtf
    case mtfs
    case co
    case oco
    case strategy
    
    var title: String {
      switch self {
      case .intraday: return L10n.Lists.intraday
      case .delivery: return L10n.Lists.delivery
      case .mtf: return L10n.Lists.mtf
      case .mtfs: return L10n.Lists.mtfs
      case .co: return L10n.Lists.co
      case .oco: return L10n.Lists.oco
      case .strategy: return L10n.Lists.oco
      }
    }
    
  }
  
  public enum BasketOrderStatus {
    
    case completed
    case error
    case pending
    case inQueue
    case cancelled
    
  }
  
}

// MARK: Helper

private typealias Helper = ScripListPHOCell
private extension Helper {
  
  func makeCustomButtonView(image: UIImage, text: String, shouldAddVerticalSeparator: Bool = true) -> UIView {
    let label = Label(.metaBold)
    label.textAlignment = .center
    let attachment = NSTextAttachment()
    attachment.bounds = CGRect(x: .zero,
                               y: -.quarterOfDefaultMargin,
                               width: .defaultMargin,
                               height: .defaultMargin)
    attachment.image = image
    let space = NSTextAttachment()
    space.bounds.size.width = .halfOfDefaultMargin
    let attributedText = NSMutableAttributedString()
    attributedText.append(NSAttributedString(attachment: attachment))
    attributedText.append(NSAttributedString(attachment: space))
    attributedText.append(NSAttributedString(string: text))
    label.attributedText = attributedText
    
    let contentStackView = UIStackView()
    contentStackView.spacing = .halfOfDefaultMargin
    contentStackView.addArrangedSubview(label)
    
    let mainStackView = UIStackView()
    mainStackView.isLayoutMarginsRelativeArrangement = true
    mainStackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: .halfOfDefaultMargin,
                                                                     leading: .zero,
                                                                     bottom: .halfOfDefaultMargin,
                                                                     trailing: .zero)
    if shouldAddVerticalSeparator {
      let verticalDivider = UIView().withBackgroundColor(.appUi3)
      verticalDivider.widthAnchor.constraint(equalToConstant: 1).isActive = true
      mainStackView.addArrangedSubview(verticalDivider)
    }
    mainStackView.addArrangedSubview(contentStackView)
    mainStackView.isHidden = true
    return mainStackView
  }
  
}

// MARK: ActionsHelper

private typealias ActionsHelper = ScripListPHOCell
private extension ActionsHelper {
  
  @objc func didTapDetailsBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
    delegate?.didTapDetailsBottomView(at: tag)
  }
  
  @objc func didTapCancelBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
    delegate?.didTapCancelBottomView(at: tag)
  }
  
  @objc func didTapModifyBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
    delegate?.didTapModifyBottomView(at: tag)
  }
  
  @objc func didTapRetryBottomView(_ gestureRecognizer: UITapGestureRecognizer) {
    delegate?.didTapRetryBottomView(at: tag)
  }
  
}

// MARK: Constants

private typealias Constant = ScripListPHOCell
private extension Constant {
  
  enum Constants {
    
    static let conditionalSeparatorHeight: CGFloat = 4
    static let defaultSeparatorHeight: CGFloat = 1
    static let mainHorizontalStackViewDefaultMargins = NSDirectionalEdgeInsets(top: .threeQuartersOfDefaultMargin,
                                                                               leading: .defaultMargin,
                                                                               bottom: .threeQuartersOfDefaultMargin,
                                                                               trailing: .defaultMargin)
    
  }
  
}