import UIKit
import GoogleMobileAds
import Toast_Swift

final class HomeViewController: UIViewController {

    // MARK: - Public state

    var balanceText: String = "Rp 0" {
        didSet { balanceLabel.text = balanceText }
    }
    var cardTypeText: String = "E-Money Card" {
        didSet { cardTypeLabel.text = cardTypeText }
    }
    var lastTransaction: LastTransactionItem? {
        didSet { configureLastTransaction() }
    }

    // MARK: - Callbacks

    var onScanTapped: (() -> Void)?
    var onViewHistoryTapped: (() -> Void)?
    var onSettingsTapped: (() -> Void)?

    // Diisi setelah scan berhasil, dipakai oleh SceneDelegate untuk history
    var latestRiwayatList: [RiwayatCard] = [] {
        didSet { updateViewHistoryButtonState() }
    }
    var latestCardNumber: String = ""   // raw card number for PDF export

    // MARK: - UI Elements

    private let scrollView  = UIScrollView()
    private let contentView = UIView()

    // Header
    private let appNameLabel   = UILabel()
    private let settingsButton = UIButton(type: .system)

    // Balance
    private let availableLabel = UILabel()
    private let balanceLabel   = UILabel()

    // Card
    private let cardView        = UIView()
    private let cardGradient    = CAGradientLayer()
    private let nfcIconView     = UIImageView()
    private let tapCardLabel    = UILabel()
    private let cardNumberLabel = UILabel()
    private let copyButton      = UIButton(type: .system)
    private let cardTypeLabel   = UILabel()

    // Raw card number stored after scan for re-formatting on setting change
    private var rawCardNumber: String = ""

    // Instruction
    private let tapHereLabel   = UILabel()
    private let tapHintLabel   = UILabel()

    // Scan button
    private let scanButton     = UIButton(type: .system)

    // Ads
    private let promoCard      = UIView()
    private var bannerView     = GADBannerView()
    private var bannerRetryWorkItem: DispatchWorkItem?
    private var isBannerAdLoaded = false
    private var isBannerLoadInFlight = false
    private let bannerRetryDelay: TimeInterval = 20

    // Dynamic layout: toggle based on ad load state
    private var lastTxTopNoAd:   NSLayoutConstraint!
    private var lastTxTopWithAd: NSLayoutConstraint!

    // Last transaction
    private let lastTxHeader   = UILabel()
    private let lastTxCard     = UIView()
    private let txIconView     = UIView()
    private let txIconImage    = UIImageView()
    private let txTitleLabel   = UILabel()
    private let txDateLabel    = UILabel()
    private let txAmountLabel  = UILabel()
    private let txStatusLabel  = UILabel()

    // Footer link
    private let viewHistoryButton = UIButton(type: .system)

    // MARK: - Lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = Theme.Color.background
        setupScrollView()
        setupHeader()
        setupBalanceSection()
        setupCard()
        setupInstruction()
        setupScanButton()
        setupPromo()
        setupLastTransaction()
        setupViewHistoryButton()
        setupConstraints()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(onSettingChanged),
            name: Notification.Name("refreshScreen"),
            object: nil
        )
        loadBannerAdIfNeeded(reason: "viewDidAppear")
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: Notification.Name("refreshScreen"), object: nil)
    }

    deinit {
        bannerRetryWorkItem?.cancel()
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        cardGradient.frame = cardView.bounds
    }

    // MARK: - Setup

    private func setupScrollView() {
        scrollView.showsVerticalScrollIndicator = false
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        contentView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
    }

    private func setupHeader() {
        appNameLabel.text = "Emoney Info"
        appNameLabel.font = Theme.Font.title(weight: .bold)
        appNameLabel.textColor = Theme.Color.textPrimary

        let gearImage = UIImage(systemName: "gearshape", withConfiguration:
            UIImage.SymbolConfiguration(pointSize: 20, weight: .medium))
        settingsButton.setImage(gearImage, for: .normal)
        settingsButton.tintColor = Theme.Color.textPrimary
        settingsButton.addTarget(self, action: #selector(settingsTapped), for: .touchUpInside)

        [appNameLabel, settingsButton].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview($0)
        }
    }

    private func setupBalanceSection() {
        availableLabel.text = L10n.availableBalance
        availableLabel.font = Theme.Font.caption(weight: .semibold)
        availableLabel.textColor = Theme.Color.textSecondary
        availableLabel.letterSpacing(1.5)

        balanceLabel.text = "Rp 0"
        balanceLabel.font = .systemFont(ofSize: 36, weight: .bold)
        balanceLabel.textColor = Theme.Color.textPrimary

        [availableLabel, balanceLabel].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview($0)
        }
    }

    private func setupCard() {
        cardView.layer.cornerRadius = 24
        cardView.clipsToBounds = true

        cardGradient.colors = [
            UIColor(red: 0.48, green: 0.83, blue: 0.82, alpha: 1).cgColor,
            UIColor(red: 0.36, green: 0.49, blue: 0.48, alpha: 1).cgColor
        ]
        cardGradient.startPoint = CGPoint(x: 0, y: 0)
        cardGradient.endPoint   = CGPoint(x: 1, y: 1)
        cardView.layer.insertSublayer(cardGradient, at: 0)

        let nfcConfig = UIImage.SymbolConfiguration(pointSize: 48, weight: .medium)
        nfcIconView.image = UIImage(systemName: "wave.3.right.circle.fill", withConfiguration: nfcConfig)
        nfcIconView.tintColor = .white.withAlphaComponent(0.9)
        nfcIconView.contentMode = .scaleAspectFit

        tapCardLabel.text = L10n.cardTapInstruction
        tapCardLabel.font = Theme.Font.caption(weight: .bold)
        tapCardLabel.textColor = .white.withAlphaComponent(0.85)
        tapCardLabel.letterSpacing(2)

        cardTypeLabel.text = cardTypeText
        cardTypeLabel.font = Theme.Font.caption(weight: .regular)
        cardTypeLabel.textColor = .white.withAlphaComponent(0.7)

        cardNumberLabel.font = .systemFont(ofSize: 11, weight: .medium)
        cardNumberLabel.textColor = .white.withAlphaComponent(0.85)
        cardNumberLabel.isHidden = true

        let copyConfig = UIImage.SymbolConfiguration(pointSize: 11, weight: .medium)
        copyButton.setImage(UIImage(systemName: "doc.on.doc", withConfiguration: copyConfig), for: .normal)
        copyButton.tintColor = .white.withAlphaComponent(0.75)
        copyButton.isHidden = true
        copyButton.addTarget(self, action: #selector(copyCardNumber), for: .touchUpInside)

        [cardView, nfcIconView, tapCardLabel, cardNumberLabel, copyButton, cardTypeLabel].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        contentView.addSubview(cardView)
        cardView.addSubview(nfcIconView)
        cardView.addSubview(tapCardLabel)
        cardView.addSubview(cardNumberLabel)
        cardView.addSubview(copyButton)
        cardView.addSubview(cardTypeLabel)
    }

    private func setupInstruction() {
        tapHereLabel.isHidden = true

        tapHintLabel.text = L10n.tapCardHint
        tapHintLabel.font = Theme.Font.body(weight: .regular)
        tapHintLabel.textColor = Theme.Color.textSecondary
        tapHintLabel.textAlignment = .center
        tapHintLabel.numberOfLines = 2

        [tapHereLabel, tapHintLabel].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
            contentView.addSubview($0)
        }
    }

    private func setupScanButton() {
        scanButton.setTitle(L10n.checkBalance, for: .normal)
        scanButton.titleLabel?.font = Theme.Font.body(weight: .semibold)
        scanButton.setTitleColor(.white, for: .normal)
        scanButton.backgroundColor = Theme.Color.secondary
        scanButton.layer.cornerRadius = 14
        scanButton.addTarget(self, action: #selector(scanTapped), for: .touchUpInside)
        scanButton.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(scanButton)
    }

    private func setupPromo() {
        // TODO: Replace ad unit ID before release
        let adUnitID = "ca-app-pub-3389368171983845/5892672309"

        let adSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(
            UIScreen.main.bounds.width - 48   // matches horizontal padding 24pt each side
        )
        bannerView = GADBannerView(adSize: adSize)
        bannerView.adUnitID = adUnitID
        bannerView.rootViewController = self
        bannerView.delegate = self
        bannerView.translatesAutoresizingMaskIntoConstraints = false

        promoCard.backgroundColor = .clear
        promoCard.translatesAutoresizingMaskIntoConstraints = false
        promoCard.clipsToBounds = true
        promoCard.isHidden = true   // shown only when ad loads successfully
        promoCard.addSubview(bannerView)
        contentView.addSubview(promoCard)

        NSLayoutConstraint.activate([
            bannerView.topAnchor.constraint(equalTo: promoCard.topAnchor),
            bannerView.leadingAnchor.constraint(equalTo: promoCard.leadingAnchor),
            bannerView.trailingAnchor.constraint(equalTo: promoCard.trailingAnchor),
            bannerView.bottomAnchor.constraint(equalTo: promoCard.bottomAnchor),
            bannerView.heightAnchor.constraint(equalToConstant: CGFloat(adSize.size.height))
        ])

        // Ad is loaded externally via loadBannerAd() after GADMobileAds SDK has started.
    }

    func loadBannerAd() {
        loadBannerAdIfNeeded(reason: "external")
    }

    private func loadBannerAdIfNeeded(reason: String) {
        guard !isBannerAdLoaded, !isBannerLoadInFlight else { return }
        guard isViewLoaded, view.window != nil else {
            logAdMob("skip load: view is not visible", reason: reason)
            return
        }

        bannerRetryWorkItem?.cancel()
        bannerView.rootViewController = self
        isBannerLoadInFlight = true
        logAdMob("loading banner", reason: reason)
        bannerView.load(GADRequest())
    }

    private func scheduleBannerRetry(after delay: TimeInterval = 20) {
        bannerRetryWorkItem?.cancel()

        let workItem = DispatchWorkItem { [weak self] in
            self?.loadBannerAdIfNeeded(reason: "retry")
        }
        bannerRetryWorkItem = workItem

        logAdMob("scheduling retry in \(Int(delay))s")
        DispatchQueue.main.asyncAfter(deadline: .now() + delay, execute: workItem)
    }

    private func logAdMob(_ message: String, reason: String? = nil, error: Error? = nil) {
        var parts = ["[AdMob][Home]", message]
        if let reason {
            parts.append("reason=\(reason)")
        }
        parts.append("adUnitID=\(bannerView.adUnitID ?? "-")")
        parts.append("visible=\(viewIfLoaded?.window != nil)")
        parts.append("loaded=\(isBannerAdLoaded)")
        parts.append("inFlight=\(isBannerLoadInFlight)")
        parts.append("rootVC=\(String(describing: type(of: bannerView.rootViewController)))")

        if let nsError = error as NSError? {
            parts.append("errorDomain=\(nsError.domain)")
            parts.append("errorCode=\(nsError.code)")
            parts.append("error=\(nsError.localizedDescription)")
        }

        debugLog(parts.joined(separator: " | "))
    }

    private func setupLastTransaction() {
        lastTxHeader.text = L10n.lastTransaction
        lastTxHeader.font = Theme.Font.subtitle(weight: .bold)
        lastTxHeader.textColor = Theme.Color.textPrimary
        lastTxHeader.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(lastTxHeader)

        lastTxCard.backgroundColor = Theme.Color.card
        lastTxCard.layer.cornerRadius = 16
        lastTxCard.layer.shadowColor   = UIColor.black.cgColor
        lastTxCard.layer.shadowOpacity = 0.06
        lastTxCard.layer.shadowOffset  = CGSize(width: 0, height: 2)
        lastTxCard.layer.shadowRadius  = 8
        lastTxCard.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(lastTxCard)

        // Transaction icon
        txIconView.backgroundColor = Theme.Color.background
        txIconView.layer.cornerRadius = 12

        txIconImage.tintColor = Theme.Color.secondary
        txIconImage.contentMode = .scaleAspectFit
        txIconImage.translatesAutoresizingMaskIntoConstraints = false
        txIconView.addSubview(txIconImage)

        txTitleLabel.font = Theme.Font.body(weight: .semibold)
        txTitleLabel.textColor = Theme.Color.textPrimary

        txDateLabel.font = Theme.Font.caption(weight: .regular)
        txDateLabel.textColor = Theme.Color.textSecondary

        txAmountLabel.font = Theme.Font.body(weight: .bold)
        txAmountLabel.textColor = UIColor.systemRed
        txAmountLabel.textAlignment = .right

        txStatusLabel.font = Theme.Font.caption(weight: .semibold)
        txStatusLabel.textColor = Theme.Color.success
        txStatusLabel.textAlignment = .right

        let leftStack = UIStackView(arrangedSubviews: [txTitleLabel, txDateLabel])
        leftStack.axis    = .vertical
        leftStack.spacing = 2

        let rightStack = UIStackView(arrangedSubviews: [txAmountLabel, txStatusLabel])
        rightStack.axis      = .vertical
        rightStack.spacing   = 2
        rightStack.alignment = .trailing

        let hStack = UIStackView(arrangedSubviews: [txIconView, leftStack, rightStack])
        hStack.axis      = .horizontal
        hStack.spacing   = 12
        hStack.alignment = .center

        [txIconView, txIconImage, hStack, leftStack, rightStack].forEach {
            $0.translatesAutoresizingMaskIntoConstraints = false
        }
        lastTxCard.addSubview(hStack)

        NSLayoutConstraint.activate([
            txIconImage.centerXAnchor.constraint(equalTo: txIconView.centerXAnchor),
            txIconImage.centerYAnchor.constraint(equalTo: txIconView.centerYAnchor),
            txIconImage.widthAnchor.constraint(equalToConstant: 20),
            txIconImage.heightAnchor.constraint(equalToConstant: 20),
            txIconView.widthAnchor.constraint(equalToConstant: 44),
            txIconView.heightAnchor.constraint(equalToConstant: 44),

            hStack.topAnchor.constraint(equalTo: lastTxCard.topAnchor, constant: 16),
            hStack.leadingAnchor.constraint(equalTo: lastTxCard.leadingAnchor, constant: 16),
            hStack.trailingAnchor.constraint(equalTo: lastTxCard.trailingAnchor, constant: -16),
            hStack.bottomAnchor.constraint(equalTo: lastTxCard.bottomAnchor, constant: -16),

            rightStack.widthAnchor.constraint(greaterThanOrEqualToConstant: 80)
        ])

        configureLastTransaction()
    }

    private func setupViewHistoryButton() {
        viewHistoryButton.setTitle("\(L10n.viewFullHistory)  →", for: .normal)
        viewHistoryButton.titleLabel?.font = Theme.Font.body(weight: .semibold)
        viewHistoryButton.addTarget(self, action: #selector(viewHistoryTapped), for: .touchUpInside)
        viewHistoryButton.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(viewHistoryButton)
        updateViewHistoryButtonState()
    }

    private func updateViewHistoryButtonState() {
        let hasData = !latestRiwayatList.isEmpty
        viewHistoryButton.isEnabled = hasData
        viewHistoryButton.setTitleColor(hasData ? Theme.Color.secondary : Theme.Color.textSecondary, for: .normal)
        viewHistoryButton.alpha = hasData ? 1.0 : 0.4
    }

    // MARK: - Constraints

    private func setupConstraints() {
        NSLayoutConstraint.activate([

            // ScrollView
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            // ContentView
            contentView.topAnchor.constraint(equalTo: scrollView.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor),
            contentView.widthAnchor.constraint(equalTo: scrollView.widthAnchor),

            // Header
            appNameLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            appNameLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),

            settingsButton.centerYAnchor.constraint(equalTo: appNameLabel.centerYAnchor),
            settingsButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
            settingsButton.widthAnchor.constraint(equalToConstant: 32),
            settingsButton.heightAnchor.constraint(equalToConstant: 32),

            // Balance
            availableLabel.topAnchor.constraint(equalTo: appNameLabel.bottomAnchor, constant: 28),
            availableLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),

            balanceLabel.topAnchor.constraint(equalTo: availableLabel.bottomAnchor, constant: 4),
            balanceLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),

            // Card
            cardView.topAnchor.constraint(equalTo: balanceLabel.bottomAnchor, constant: 20),
            cardView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
            cardView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
            cardView.heightAnchor.constraint(equalToConstant: 160),

            nfcIconView.centerXAnchor.constraint(equalTo: cardView.centerXAnchor),
            nfcIconView.centerYAnchor.constraint(equalTo: cardView.centerYAnchor, constant: -10),
            nfcIconView.widthAnchor.constraint(equalToConstant: 60),
            nfcIconView.heightAnchor.constraint(equalToConstant: 60),

            tapCardLabel.topAnchor.constraint(equalTo: nfcIconView.bottomAnchor, constant: 8),
            tapCardLabel.centerXAnchor.constraint(equalTo: cardView.centerXAnchor),

            // card number + copy — bottom left
            cardNumberLabel.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -14),
            cardNumberLabel.leadingAnchor.constraint(equalTo: cardView.leadingAnchor, constant: 16),

            copyButton.centerYAnchor.constraint(equalTo: cardNumberLabel.centerYAnchor),
            copyButton.leadingAnchor.constraint(equalTo: cardNumberLabel.trailingAnchor, constant: 6),
            copyButton.widthAnchor.constraint(equalToConstant: 20),
            copyButton.heightAnchor.constraint(equalToConstant: 20),

            // card type — bottom right
            cardTypeLabel.bottomAnchor.constraint(equalTo: cardView.bottomAnchor, constant: -14),
            cardTypeLabel.trailingAnchor.constraint(equalTo: cardView.trailingAnchor, constant: -16),

            // Instruction
            tapHereLabel.topAnchor.constraint(equalTo: cardView.bottomAnchor, constant: 20),
            tapHereLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),

            tapHintLabel.topAnchor.constraint(equalTo: tapHereLabel.bottomAnchor, constant: 6),
            tapHintLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            tapHintLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 40),
            tapHintLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -40),

            // Scan Button
            scanButton.topAnchor.constraint(equalTo: tapHintLabel.bottomAnchor, constant: 20),
            scanButton.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
            scanButton.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
            scanButton.heightAnchor.constraint(equalToConstant: 52),

            // Promo
            promoCard.topAnchor.constraint(equalTo: scanButton.bottomAnchor, constant: 28),
            promoCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
            promoCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),

            // Last Transaction leading (top is toggled dynamically)
            lastTxHeader.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),

            lastTxCard.topAnchor.constraint(equalTo: lastTxHeader.bottomAnchor, constant: 12),
            lastTxCard.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
            lastTxCard.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),

            // View History
            viewHistoryButton.topAnchor.constraint(equalTo: lastTxCard.bottomAnchor, constant: 12),
            viewHistoryButton.centerXAnchor.constraint(equalTo: contentView.centerXAnchor),
            viewHistoryButton.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -140)
        ])

        // Dynamic top constraints for Last Transaction — toggled by ad load state
        lastTxTopNoAd   = lastTxHeader.topAnchor.constraint(equalTo: scanButton.bottomAnchor, constant: 28)
        lastTxTopWithAd = lastTxHeader.topAnchor.constraint(equalTo: promoCard.bottomAnchor, constant: 28)
        lastTxTopNoAd.isActive = true   // default: no ad
    }

    // MARK: - Actions

    @objc private func scanTapped() {
        latestRiwayatList = []
        latestCardNumber  = ""
        lastTransaction   = nil
        onScanTapped?()
    }

    @objc private func viewHistoryTapped() {
        onViewHistoryTapped?()
    }

    @objc private func settingsTapped() {
        onSettingsTapped?()
    }

    @objc private func copyCardNumber() {
        guard !rawCardNumber.isEmpty else { return }
        UIPasteboard.general.string = rawCardNumber

        var style = ToastStyle()
        style.backgroundColor = Theme.Color.primary
        style.messageColor    = .white
        style.messageFont     = Theme.Font.body(weight: .semibold)
        style.cornerRadius    = 12

        view.makeToast(L10n.copiedToClipboard, duration: 2.0, position: .top, style: style)
    }

    @objc private func onSettingChanged() {
        guard !rawCardNumber.isEmpty else { return }
        updateCardNumberDisplay()
    }

    // MARK: - Card number display

    private func updateCardNumberDisplay() {
        // "Show Card Number on Home" toggle maps to key "masked"
        // isOn = true  → show full number
        // isOn = false → mask first 12 digits
        let showFull = UserDefaults.standard.bool(forKey: "masked")
        cardNumberLabel.text    = showFull ? rawCardNumber.formatCardNumber() : rawCardNumber.maskFirst12()
        cardNumberLabel.isHidden = false
        copyButton.isHidden      = false
    }

    // MARK: - Data

    private func configureLastTransaction() {
        guard let tx = lastTransaction else {
            txTitleLabel.text  = "–"
            txDateLabel.text   = "–"
            txAmountLabel.text = "–"
            txStatusLabel.text = "–"
            txIconImage.image  = UIImage(systemName: "creditcard.fill")
            return
        }

        let hasPlace = !tx.place.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty

        // Title: gunakan place jika ada, fallback ke status topup/payment
        txTitleLabel.text = hasPlace ? tx.place : (tx.isCredit ? L10n.topup : L10n.payment)
        txTitleLabel.numberOfLines = 2

        // Icon: tram hanya untuk KMT, selain itu pakai arrow.down/creditcard
        let iconName: String
        if tx.isKMT {
            iconName = "tram.fill"
        } else {
            iconName = tx.isCredit ? "arrow.down.circle.fill" : "creditcard.fill"
        }
        txIconImage.image = UIImage(systemName: iconName)

        txDateLabel.text   = tx.date
        txAmountLabel.text = tx.amount
        txStatusLabel.text = tx.status.uppercased()
        txStatusLabel.textColor = tx.isCredit ? Theme.Color.success : UIColor.systemRed
        txAmountLabel.textColor = tx.isCredit ? Theme.Color.success : UIColor.systemRed
        if (!hasPlace){
            txStatusLabel.isHidden = true
        }
    }
}

// MARK: - Data Model

struct LastTransactionItem {
    let place:    String   // locationName, bisa kosong
    let date:     String
    let amount:   String
    let status:   String
    let isCredit: Bool
    let isKMT:    Bool
}

// MARK: - GADBannerViewDelegate

extension HomeViewController: GADBannerViewDelegate {
    func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
        bannerRetryWorkItem?.cancel()
        isBannerLoadInFlight = false
        isBannerAdLoaded = true
        logAdMob("banner loaded")
        lastTxTopNoAd.isActive   = false
        lastTxTopWithAd.isActive = true
        UIView.animate(withDuration: 0.3) {
            self.promoCard.isHidden = false
            self.view.layoutIfNeeded()
        }
    }

    func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {
        isBannerLoadInFlight = false
        isBannerAdLoaded = false
        logAdMob("banner failed", error: error)
        lastTxTopWithAd.isActive = false
        lastTxTopNoAd.isActive   = true
        UIView.animate(withDuration: 0.3) {
            self.promoCard.isHidden = true
            self.view.layoutIfNeeded()
        }
        scheduleBannerRetry(after: bannerRetryDelay)
    }
}

// MARK: - ApduCallback

import CoreNFC

extension HomeViewController: ApduCallback {
    func felicaConnected(unifiedNfcApi: UnifiedNfcApi, tag:NFCFeliCaTag) {
        debugLog("felica nih.")
        unifiedNfcApi.checkFelicaCard(tag: tag)
    }
    
    func connected(unifiedNfcApi: UnifiedNfcApi) {
        debugLog("normal nih.")
        unifiedNfcApi.checkCard()
    }

    func complete(emoney: Emoney) {
        DispatchQueue.main.async {
            self.balanceText  = Self.idrCurrencyFormatter.string(for: emoney.getBalance()) ?? "Rp 0"
            self.cardTypeText = emoney.getCardLabel()

            self.rawCardNumber    = emoney.getCardNumber()
            self.latestCardNumber = emoney.getCardNumber()
            self.updateCardNumberDisplay()

            if (emoney.isTampilRiwayat()){
                let riwayat = self.deduplicateHistory(emoney.getRiwayatList())
                if let first = riwayat.first {
                    let dateFmt = DateFormatter()
                    dateFmt.dateFormat = "dd MMM yyyy, HH:mm"
                    let dateStr = first.getTransationTime().map { dateFmt.string(from: $0) }
                        ?? first.getDesk()
                        ?? "–"
                    let isCredit = first.getProsesTipe() == 0
                    let sign = isCredit ? "+" : "-"
                    let place = first.getLocationName()?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
                    let isKMT = emoney.getCardLabel().uppercased().contains("KMT")
                    self.lastTransaction = LastTransactionItem(
                        place:    place,
                        date:     dateStr,
                        amount:   "\(sign)\(Self.idrCurrencyFormatter.string(for: first.getAmount()) ?? "Rp 0")",
                        status:   isCredit ? L10n.topup : L10n.payment,
                        isCredit: isCredit,
                        isKMT:    isKMT
                    )
                }

                // Simpan list untuk history
                self.latestRiwayatList = riwayat
            }
        }
    }

    func failed(error: NSError) {}
}

private extension HomeViewController {
    static let idrCurrencyFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.locale = Locale(identifier: "id_ID")
        formatter.numberStyle = .currency
        formatter.currencyCode = "IDR"
        formatter.currencySymbol = "Rp "
        formatter.maximumFractionDigits = 0
        formatter.minimumFractionDigits = 0
        return formatter
    }()

    func deduplicateHistory(_ items: [RiwayatCard]) -> [RiwayatCard] {
        var seen = Set<String>()
        return items.filter { item in
            let key = historyKey(for: item)
            return seen.insert(key).inserted
        }
    }

    func historyKey(for item: RiwayatCard) -> String {
        let timestamp = item.getTransationTime()?.timeIntervalSince1970 ?? 0
        let locationId = item.getLocationId() ?? ""
        let locationName = item.getLocationName() ?? ""
        let title = item.getTitle() ?? ""
        return "\(timestamp)|\(item.getAmount())|\(item.getProsesTipe())|\(locationId)|\(locationName)|\(title)"
    }
}

// MARK: - UILabel letter spacing helper

private extension UILabel {
    func letterSpacing(_ spacing: CGFloat) {
        guard let text = text else { return }
        let attributed = NSMutableAttributedString(string: text)
        attributed.addAttribute(.kern, value: spacing, range: NSRange(location: 0, length: text.count))
        attributedText = attributed
    }
}

// MARK: - String card masking

private extension String {
    /// Replaces the first 12 digits with * and shows the last 4, formatted as **** **** **** XXXX
    func maskFirst12() -> String {
        let digits = self.filter { $0.isNumber }
        guard digits.count == 16 else { return self }
        let last4 = String(digits.suffix(4))
        return "**** **** **** \(last4)"
    }
}
