import SwiftUI
import GoogleMobileAds

// MARK: - HistoryView

struct HistoryView: View {

    let transactions: [RiwayatCard]
    var onBack: (() -> Void)?
    var onExportPDF: (([RiwayatCard]) -> Void)?

    @State private var selectedFilter: FilterOption = .allTime
    @State private var adLoaded: Bool = false

    // TODO: Replace with real ad unit ID before release
    private let adUnitID = "ca-app-pub-3389368171983845/5404549734"

    // Pre-compute banner size so the UIView always gets a valid height
    private let bannerAdSize: GADAdSize = GADCurrentOrientationAnchoredAdaptiveBannerAdSizeWithWidth(
        UIScreen.main.bounds.width - 48
    )
    private var bannerAdHeight: CGFloat { CGFloat(bannerAdSize.size.height) }

    enum FilterOption: CaseIterable {
        case allTime, today, thisWeek, thisMonth
        var label: String {
            switch self {
            case .allTime:   return L10n.filterAllTime
            case .today:     return L10n.filterToday
            case .thisWeek:  return L10n.filterThisWeek
            case .thisMonth: return L10n.filterThisMonth
            }
        }
    }

    private var filtered: [RiwayatCard] {
        guard selectedFilter != .allTime else { return transactions }
        var calendar = Calendar.current
        calendar.firstWeekday = 2  // Senin sebagai awal minggu
        let now = Date()
        return transactions.filter { rw in
            guard let date = rw.getTransationTime() else { return false }
            switch selectedFilter {
            case .today:
                return calendar.isDateInToday(date)
            case .thisWeek:
                guard let interval = calendar.dateInterval(of: .weekOfYear, for: now) else { return false }
                return interval.contains(date)
            case .thisMonth:
                guard let interval = calendar.dateInterval(of: .month, for: now) else { return false }
                return interval.contains(date)
            case .allTime:
                return true
            }
        }
    }

    var body: some View {
        ZStack(alignment: .bottom) {
            Color(Theme.Color.background).ignoresSafeArea()

            ScrollView(showsIndicators: false) {
                LazyVStack(spacing: 0) {
                    headerView
                        .padding(.horizontal, 24)
                        .padding(.top, 16)

                    // Ad banner — inner frame always has proper height so Google can load it;
                    // outer frame collapses to 0 + clipped to hide it until loaded.
                    BannerAdView(
                        adUnitID: adUnitID,
                        adSize: bannerAdSize,
                        onAdLoaded: { adLoaded = true },
                        onAdFailed: { adLoaded = false }
                    )
                    .frame(height: bannerAdHeight)          // proper height — always valid for GAD
                    .clipped()
                    .frame(height: adLoaded ? bannerAdHeight : 0)  // collapse space when hidden
                    .padding(.horizontal, 24)
                    .padding(.top, adLoaded ? 16 : 0)
                    .opacity(adLoaded ? 1 : 0)
                    .animation(.easeInOut(duration: 0.3), value: adLoaded)

                    sectionHeader
                        .padding(.horizontal, 24)
                        .padding(.top, adLoaded ? 20 : 28)
                        .padding(.bottom, 12)
                        .animation(.easeInOut(duration: 0.3), value: adLoaded)

                    transactionList

                    Spacer().frame(height: 100)
                }
            }

            exportButton
                .padding(.horizontal, 24)
                .padding(.bottom, 90)
        }
    }

    // MARK: - Header

    private var headerView: some View {
        HStack {
            Button(action: { onBack?() }) {
                Image(systemName: "arrow.left")
                    .font(.system(size: 18, weight: .semibold))
                    .foregroundColor(Color(Theme.Color.textPrimary))
                    .frame(width: 36, height: 36)
                    .background(Color(Theme.Color.card))
                    .clipShape(Circle())
                    .shadow(color: .black.opacity(0.06), radius: 6, x: 0, y: 2)
            }

            Spacer()

            Text(L10n.historyTitle)
                .font(.system(size: Theme.FontSize.subtitle, weight: .bold))
                .foregroundColor(Color(Theme.Color.textPrimary))

            Spacer()

            // Placeholder to balance the HStack (same width as back button)
            Color.clear.frame(width: 36, height: 36)
        }
    }

    // MARK: - Section Header

    private var sectionHeader: some View {
        HStack {
            Text(L10n.recentActivity)
                .font(.system(size: Theme.FontSize.caption, weight: .semibold))
                .foregroundColor(Color(Theme.Color.textSecondary))
                .kerning(1.2)

            Spacer()

            Menu {
                ForEach(FilterOption.allCases, id: \.self) { option in
                    Button(option.label) { selectedFilter = option }
                }
            } label: {
                HStack(spacing: 4) {
                    Text(selectedFilter.label)
                        .font(.system(size: Theme.FontSize.caption, weight: .semibold))
                    Image(systemName: "chevron.down")
                        .font(.system(size: 10, weight: .semibold))
                }
                .foregroundColor(Color(Theme.Color.secondary))
                .padding(.horizontal, 12)
                .padding(.vertical, 6)
                .background(Color(Theme.Color.primary).opacity(0.15))
                .clipShape(Capsule())
            }
        }
    }

    // MARK: - Transaction List

    private var transactionList: some View {
        LazyVStack(spacing: 10) {
            if filtered.isEmpty {
                emptyState
            } else {
                ForEach(Array(filtered.enumerated()), id: \.offset) { _, rw in
                    TransactionRow(riwayat: rw)
                        .padding(.horizontal, 24)
                }
            }
        }
    }

    private var emptyState: some View {
        VStack(spacing: 12) {
            Image(systemName: "tray")
                .font(.system(size: 40))
                .foregroundColor(Color(Theme.Color.textSecondary))
            Text(L10n.noTransactionsFound)
                .font(.system(size: Theme.FontSize.body, weight: .medium))
                .foregroundColor(Color(Theme.Color.textSecondary))
        }
        .frame(maxWidth: .infinity)
        .padding(.top, 48)
    }

    // MARK: - Export Button

    private var exportButton: some View {
        let hasData = !filtered.isEmpty
        return Button(action: { onExportPDF?(filtered) }) {
            HStack(spacing: 8) {
                Image(systemName: "arrow.down.doc.fill")
                    .font(.system(size: 15, weight: .semibold))
                Text(L10n.exportPDF)
                    .font(.system(size: Theme.FontSize.body, weight: .semibold))
            }
            .foregroundColor(hasData ? Color(Theme.Color.secondary) : Color(Theme.Color.textSecondary))
            .frame(maxWidth: .infinity)
            .frame(height: 52)
            .background(Color(Theme.Color.card))
            .overlay(
                RoundedRectangle(cornerRadius: 14)
                    .stroke(hasData ? Color(Theme.Color.primary) : Color(Theme.Color.textSecondary).opacity(0.3), lineWidth: 1.5)
            )
            .clipShape(RoundedRectangle(cornerRadius: 14))
            .shadow(color: .black.opacity(hasData ? 0.06 : 0), radius: 8, x: 0, y: 2)
        }
        .disabled(!hasData)
        .animation(.easeInOut(duration: 0.2), value: hasData)
    }
}

// MARK: - BannerAdView (UIViewRepresentable)

private struct BannerAdView: UIViewRepresentable {

    let adUnitID: String
    let adSize: GADAdSize
    var onAdLoaded: (() -> Void)?
    var onAdFailed: (() -> Void)?

    func makeCoordinator() -> Coordinator { Coordinator(self) }

    func makeUIView(context: Context) -> HistoryBannerView {
        let banner = HistoryBannerView(adSize: adSize)
        banner.adUnitID = adUnitID
        banner.delegate = context.coordinator
        banner.onDidMoveToWindow = { [weak coordinator = context.coordinator] bannerView in
            coordinator?.handleBannerDidMoveToWindow(bannerView)
        }
        return banner
    }

    func updateUIView(_ uiView: HistoryBannerView, context: Context) {
        context.coordinator.attachRootViewControllerIfNeeded(to: uiView)
        context.coordinator.loadBannerIfNeeded(uiView, reason: "updateUIView")
    }

    // MARK: Coordinator

    final class Coordinator: NSObject, GADBannerViewDelegate {
        let parent: BannerAdView
        private var retryWorkItem: DispatchWorkItem?
        private var isAdLoaded = false
        private var isLoadInFlight = false
        private let retryDelay: TimeInterval = 20

        init(_ parent: BannerAdView) { self.parent = parent }

        deinit {
            retryWorkItem?.cancel()
        }

        func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
            retryWorkItem?.cancel()
            isLoadInFlight = false
            isAdLoaded = true
            log("banner loaded", bannerView: bannerView)
            DispatchQueue.main.async { self.parent.onAdLoaded?() }
        }

        func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {
            isLoadInFlight = false
            isAdLoaded = false
            log("banner failed", bannerView: bannerView, error: error)
            scheduleRetry(for: bannerView)
            DispatchQueue.main.async { self.parent.onAdFailed?() }
        }

        func handleBannerDidMoveToWindow(_ bannerView: GADBannerView) {
            attachRootViewControllerIfNeeded(to: bannerView)
            loadBannerIfNeeded(bannerView, reason: "didMoveToWindow")
        }

        func attachRootViewControllerIfNeeded(to bannerView: GADBannerView) {
            guard let rootViewController = findNearestViewController(from: bannerView) else {
                log("root view controller not ready", bannerView: bannerView)
                return
            }

            if bannerView.rootViewController !== rootViewController {
                bannerView.rootViewController = rootViewController
                log("attached root view controller", bannerView: bannerView)
            }
        }

        func loadBannerIfNeeded(_ bannerView: GADBannerView, reason: String) {
            guard !isAdLoaded, !isLoadInFlight else { return }
            guard bannerView.window != nil else {
                log("skip load: banner is not in window", bannerView: bannerView, reason: reason)
                return
            }
            guard bannerView.rootViewController != nil else {
                log("skip load: root view controller missing", bannerView: bannerView, reason: reason)
                return
            }

            retryWorkItem?.cancel()
            isLoadInFlight = true
            log("loading banner", bannerView: bannerView, reason: reason)
            bannerView.load(GADRequest())
        }

        private func scheduleRetry(for bannerView: GADBannerView) {
            retryWorkItem?.cancel()

            let workItem = DispatchWorkItem { [weak self, weak bannerView] in
                guard let self, let bannerView else { return }
                self.attachRootViewControllerIfNeeded(to: bannerView)
                self.loadBannerIfNeeded(bannerView, reason: "retry")
            }
            retryWorkItem = workItem

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

        private func log(_ message: String, bannerView: GADBannerView, reason: String? = nil, error: Error? = nil) {
            var parts = ["[AdMob][History]", message]
            if let reason {
                parts.append("reason=\(reason)")
            }
            parts.append("adUnitID=\(bannerView.adUnitID ?? "-")")
            parts.append("visible=\(bannerView.window != nil)")
            parts.append("loaded=\(isAdLoaded)")
            parts.append("inFlight=\(isLoadInFlight)")
            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 findNearestViewController(from view: UIView) -> UIViewController? {
            sequence(first: view.next, next: { $0?.next })
                .first { $0 is UIViewController } as? UIViewController
        }
    }
}

private final class HistoryBannerView: GADBannerView {
    var onDidMoveToWindow: ((HistoryBannerView) -> Void)?

    override func didMoveToWindow() {
        super.didMoveToWindow()
        onDidMoveToWindow?(self)
    }
}

// MARK: - TransactionRow

private struct TransactionRow: View {

    let riwayat: RiwayatCard

    private var isCredit: Bool { riwayat.getProsesTipe() == 0 }

    private var place: String {
        riwayat.getLocationName()?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
    }

    private var hasPlace: Bool { !place.isEmpty }

    private var title: String {
        hasPlace ? place : (isCredit ? L10n.topup : L10n.payment)
    }

    private var amount: String {
        let formatter = NumberFormatter()
        formatter.locale = Locale(identifier: "id_ID")
        formatter.numberStyle = .currency
        formatter.currencyCode = "IDR"
        formatter.currencySymbol = "Rp "
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 0
        let value = riwayat.getAmount()
        let formatted = formatter.string(for: value) ?? "Rp \(value)"
        return isCredit ? "+\(formatted)" : "-\(formatted)"
    }

    private var dateText: String {
        if let date = riwayat.getTransationTime() {
            let fmt = DateFormatter()
            fmt.dateFormat = "MMM d, yyyy · HH:mm"
            return fmt.string(from: date)
        }
        if let raw = riwayat.getDesk(), !raw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
            return raw
        }
        return "–"
    }

    private var statusText: String { isCredit ? L10n.topup : L10n.payment }

    private var iconName: String {
        let t = title.lowercased()
        if t.contains("top") || t.contains("deposit") || t.contains("bank") { return "arrow.down.circle.fill" }
        if t.contains("coffee") || t.contains("cafe")                        { return "cup.and.saucer.fill" }
        if t.contains("transit") || t.contains("metro") || t.contains("mrt") || t.contains("krl") { return "tram.fill" }
        if t.contains("cloud") || t.contains("storage")                      { return "cloud.fill" }
        if t.contains("bistro") || t.contains("resto") || t.contains("food") { return "fork.knife" }
        if t.contains("market") || t.contains("grocery")                     { return "cart.fill" }
        return isCredit ? "arrow.down.circle.fill" : "creditcard.fill"
    }

    private var amountColor: Color {
        isCredit ? Color(Theme.Color.success) : Color(UIColor.systemRed)
    }

    var body: some View {
        HStack(spacing: 14) {
            // Icon — grey background, green icon
            ZStack {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color(Theme.Color.background))
                    .frame(width: 44, height: 44)
                Image(systemName: iconName)
                    .font(.system(size: 18, weight: .medium))
                    .foregroundColor(Color(Theme.Color.secondary))
            }

            // Title + Date
            VStack(alignment: .leading, spacing: 3) {
                Text(title)
                    .font(.system(size: Theme.FontSize.body, weight: .semibold))
                    .foregroundColor(Color(Theme.Color.textPrimary))
                    .lineLimit(2)
                    .fixedSize(horizontal: false, vertical: true)
                Text(dateText)
                    .font(.system(size: Theme.FontSize.caption))
                    .foregroundColor(Color(Theme.Color.textSecondary))
            }

            Spacer()

            // Amount + Status badge (only when place is available)
            VStack(alignment: .trailing, spacing: 4) {
                Text(amount)
                    .font(.system(size: Theme.FontSize.body, weight: .bold))
                    .foregroundColor(amountColor)
                    .lineLimit(1)

                if hasPlace {
                    let statusColor = isCredit ? Color(Theme.Color.success) : Color(UIColor.systemOrange)
                    Text(isCredit ? L10n.topup : L10n.payment)
                        .font(.system(size: 9, weight: .bold))
                        .foregroundColor(statusColor)
                        .padding(.horizontal, 8)
                        .padding(.vertical, 3)
                        .background(statusColor.opacity(0.12))
                        .clipShape(Capsule())
                }
            }
        }
        .padding(16)
        .background(Color(Theme.Color.card))
        .clipShape(RoundedRectangle(cornerRadius: 16))
        .shadow(color: .black.opacity(0.05), radius: 6, x: 0, y: 2)
    }
}

// MARK: - UIKit Bridge

import UIKit

final class HistoryHostingController: UIViewController {

    var riwayatList: [RiwayatCard] = []
    var cardLabel:   String = ""
    var balanceText: String = ""
    var cardNumber:  String = ""

    // MARK: - Interstitial Ad

    private let interstitialAdUnitID = "ca-app-pub-3389368171983845/1759422963"
    private var interstitial: GADInterstitialAd?
    private var pendingExportList: [RiwayatCard] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        var historyView = HistoryView(transactions: riwayatList)
        historyView.onBack = { [weak self] in self?.dismiss(animated: true) }
        historyView.onExportPDF = { [weak self] filteredList in self?.handleExportPDFTapped(filteredList) }

        let hosting = UIHostingController(rootView: historyView)
        addChild(hosting)
        hosting.view.frame = view.bounds
        hosting.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(hosting.view)
        hosting.didMove(toParent: self)

        loadInterstitial()
    }

    private func loadInterstitial() {
        debugLog("[AdMob][HistoryInterstitial] loading interstitial | adUnitID=\(interstitialAdUnitID)")
        GADInterstitialAd.load(
            withAdUnitID: interstitialAdUnitID,
            request: GADRequest()
        ) { [weak self] ad, error in
            if let error = error as NSError? {
                debugLog(
                    "[AdMob][HistoryInterstitial] failed | adUnitID=\(self?.interstitialAdUnitID ?? "-") | errorDomain=\(error.domain) | errorCode=\(error.code) | error=\(error.localizedDescription)"
                )
            } else {
                debugLog("[AdMob][HistoryInterstitial] loaded | adUnitID=\(self?.interstitialAdUnitID ?? "-")")
            }

            self?.interstitial = ad
            self?.interstitial?.fullScreenContentDelegate = self
        }
    }

    // MARK: - Export trigger

    private func handleExportPDFTapped(_ filteredList: [RiwayatCard]) {
        pendingExportList = filteredList
        if let interstitial {
            interstitial.present(fromRootViewController: self)
        } else {
            exportPDF()
        }
    }

    // MARK: - PDF Export

    private func exportPDF() {
        let data = buildPDF(from: pendingExportList)
        let fileName = "emoney_history_\(Date().timeIntervalSince1970).pdf"
        let url = FileManager.default.temporaryDirectory.appendingPathComponent(fileName)
        do {
            try data.write(to: url)
        } catch {
            return
        }
        let activityVC = UIActivityViewController(activityItems: [url], applicationActivities: nil)
        activityVC.popoverPresentationController?.sourceView = view
        present(activityVC, animated: true)
    }

    private func buildPDF(from list: [RiwayatCard]) -> Data {
        let pageW: CGFloat = 595.2
        let pageH: CGFloat = 841.8
        let margin: CGFloat = 40
        let colW = pageW - margin * 2

        let renderer = UIGraphicsPDFRenderer(bounds: CGRect(x: 0, y: 0, width: pageW, height: pageH))

        return renderer.pdfData { ctx in
            ctx.beginPage()
            var y: CGFloat = margin

            // ── Formatters ────────────────────────────────────────────
            let dateFmt = DateFormatter()
            dateFmt.locale = Locale(identifier: "id_ID")
            dateFmt.dateFormat = "dd MMMM yyyy, HH:mm"

            let numFmt = NumberFormatter()
            numFmt.locale = Locale(identifier: "id_ID")
            numFmt.numberStyle = .currency
            numFmt.currencySymbol = "Rp "
            numFmt.groupingSeparator = "."
            numFmt.decimalSeparator = ","
            numFmt.maximumFractionDigits = 0

            // ── Styles ────────────────────────────────────────────────
            let bodyFont    = UIFont.systemFont(ofSize: 10)
            let boldFont    = UIFont.boldSystemFont(ofSize: 10)
            let titleFont   = UIFont.boldSystemFont(ofSize: 13)
            let headerFont  = UIFont.boldSystemFont(ofSize: 10)
            let smallFont   = UIFont.systemFont(ofSize: 9)
            let green       = UIColor(red: 0.36, green: 0.49, blue: 0.48, alpha: 1)

            func attrs(_ font: UIFont,
                       _ color: UIColor = .black,
                       align: NSTextAlignment = .left) -> [NSAttributedString.Key: Any] {
                let para = NSMutableParagraphStyle()
                para.alignment = align
                return [.font: font, .foregroundColor: color, .paragraphStyle: para]
            }

            func drawText(_ text: String, x: CGFloat, y: CGFloat,
                          width: CGFloat, font: UIFont,
                          color: UIColor = .black,
                          align: NSTextAlignment = .left) {
                let rect = CGRect(x: x, y: y, width: width, height: 200)
                (text as NSString).draw(in: rect, withAttributes: attrs(font, color, align: align))
            }

            func textHeight(_ text: String, font: UIFont, width: CGFloat) -> CGFloat {
                let rect = CGRect(x: 0, y: 0, width: width, height: .greatestFiniteMagnitude)
                let bounding = (text as NSString).boundingRect(
                    with: rect.size,
                    options: [.usesLineFragmentOrigin, .usesFontLeading],
                    attributes: [.font: font], context: nil)
                return ceil(bounding.height)
            }

            // ── Header image ───────────────────────────────────────────
            if let headerImg = UIImage(named: "header") {
                let imgH: CGFloat = 30
                let imgW = headerImg.size.width * (imgH / headerImg.size.height)
                let imgRect = CGRect(x: margin, y: y, width: imgW, height: imgH)
                headerImg.draw(in: imgRect)
                y += imgH + 12
            } else {
                // Fallback: draw green square with "E"
                let iconSize: CGFloat = 48
                green.setFill()
                UIBezierPath(roundedRect: CGRect(x: margin, y: y,
                                                  width: iconSize, height: iconSize),
                             cornerRadius: 10).fill()
                drawText("E", x: margin, y: y + 14, width: iconSize,
                         font: titleFont, color: .white, align: .center)
                y += iconSize + 12
            }

            // ── Subtitle ──────────────────────────────────────────────
            let subtitle = "Dibuat oleh aplikasi emoney Info: cek saldo dan riwayat uang elektronik."
            drawText(subtitle, x: margin, y: y, width: colW, font: bodyFont)
            y += textHeight(subtitle, font: bodyFont, width: colW) + 16

            // ── Separator line ────────────────────────────────────────
            UIColor.lightGray.setStroke()
            let linePath = UIBezierPath()
            linePath.move(to: CGPoint(x: margin, y: y))
            linePath.addLine(to: CGPoint(x: pageW - margin, y: y))
            linePath.lineWidth = 0.5
            linePath.stroke()
            y += 10

            // ── Card Info (left-aligned, 3 fixed columns) ─────────────
            // Label | : | Value
            let labelW: CGFloat = 90
            let colonX: CGFloat = margin + labelW
            let valueX: CGFloat = colonX + 14

            func drawInfoRow(label: String, value: String) {
                drawText(label,  x: margin, y: y, width: labelW, font: boldFont)
                drawText(":",    x: colonX, y: y, width: 14,     font: bodyFont)
                drawText(value,  x: valueX, y: y, width: colW - labelW - 14, font: bodyFont)
            }

            drawInfoRow(label: "Kartu",      value: cardLabel)
            y += 16
            drawInfoRow(label: "Saldo",      value: balanceText)
            y += 16
            drawInfoRow(label: "Nomor Kartu", value: cardNumber.formatCardNumber())
            y += 24

            // ── Separator ─────────────────────────────────────────────
            UIColor.lightGray.setStroke()
            let line2 = UIBezierPath()
            line2.move(to: CGPoint(x: margin, y: y))
            line2.addLine(to: CGPoint(x: pageW - margin, y: y))
            line2.lineWidth = 0.5
            line2.stroke()
            y += 10

            // ── Table Header ──────────────────────────────────────────
            let normalizedCardLabel = cardLabel.lowercased()
            let hideLocationForPdf = normalizedCardLabel.contains("flazz")
                || normalizedCardLabel.contains("mandiri e-money")
            let hasLocation = !hideLocationForPdf && list.contains {
                let loc = $0.getLocationName()?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
                return !loc.isEmpty
            }

            let dateColW: CGFloat = 130
            let typeColW: CGFloat = 70
            let locColW:  CGFloat = hasLocation ? 120 : 0
            let amtColX           = margin + dateColW + typeColW + locColW

            drawText("Tanggal",   x: margin,                        y: y, width: dateColW, font: headerFont, color: green)
            drawText("Transaksi", x: margin + dateColW,             y: y, width: typeColW, font: headerFont, color: green)
            if hasLocation {
                drawText("Lokasi", x: margin + dateColW + typeColW, y: y, width: locColW,  font: headerFont, color: green)
            }
            drawText("Jumlah",    x: amtColX, y: y,
                     width: colW - dateColW - typeColW - locColW, font: headerFont, color: green, align: .right)
            y += 14

            // Header underline
            green.withAlphaComponent(0.4).setStroke()
            let headerLine = UIBezierPath()
            headerLine.move(to: CGPoint(x: margin, y: y))
            headerLine.addLine(to: CGPoint(x: pageW - margin, y: y))
            headerLine.lineWidth = 0.5
            headerLine.stroke()
            y += 6

            // ── Table Rows ────────────────────────────────────────────
            for (i, rw) in list.enumerated() {
                let location = rw.getLocationName()?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
                let rowHeight: CGFloat = 16

                // New page if near bottom
                if y > pageH - margin - rowHeight {
                    ctx.beginPage()
                    y = margin
                }

                // Alternating row background
                if i % 2 == 0 {
                    UIColor(white: 0.97, alpha: 1).setFill()
                    UIBezierPath(rect: CGRect(x: margin - 4, y: y - 2,
                                             width: colW + 8, height: rowHeight)).fill()
                }

                let dateStr = rw.getTransationTime().map { dateFmt.string(from: $0) }
                    ?? rw.getDesk()
                    ?? "–"
                let typeStr = rw.getProsesTipe() == 0 ? "Top up" : "Payment"
                let amtStr  = numFmt.string(for: rw.getAmount()) ?? "Rp 0"
                let amtColor: UIColor = rw.getProsesTipe() == 0
                    ? UIColor(red: 0.13, green: 0.55, blue: 0.13, alpha: 1)
                    : .black

                drawText(dateStr, x: margin,                        y: y, width: dateColW, font: smallFont)
                drawText(typeStr, x: margin + dateColW,             y: y, width: typeColW, font: smallFont)
                if hasLocation {
                    drawText(location.isEmpty ? "–" : location,
                             x: margin + dateColW + typeColW,       y: y, width: locColW,  font: smallFont)
                }
                drawText(amtStr,  x: amtColX, y: y,
                         width: colW - dateColW - typeColW - locColW, font: smallFont,
                         color: amtColor, align: .right)
                y += rowHeight
            }

            // ── Footer ────────────────────────────────────────────────
            y += 10
            UIColor.lightGray.setStroke()
            let footerLine = UIBezierPath()
            footerLine.move(to: CGPoint(x: margin, y: y))
            footerLine.addLine(to: CGPoint(x: pageW - margin, y: y))
            footerLine.lineWidth = 0.5
            footerLine.stroke()
            y += 6
            drawText("emoneyInfo © \(Calendar.current.component(.year, from: Date()))",
                     x: margin, y: y, width: colW, font: smallFont,
                     color: .lightGray, align: .center)
        }
    }
}

// MARK: - GADFullScreenContentDelegate

extension HistoryHostingController: GADFullScreenContentDelegate {

    // Called when the interstitial is dismissed — proceed with export
    func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
        debugLog("[AdMob][HistoryInterstitial] dismissed")
        interstitial = nil
        loadInterstitial()  // pre-load for the next export attempt
        exportPDF()
    }

    // Called when the interstitial fails to present — fall back to direct export
    func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
        let nsError = error as NSError
        debugLog(
            "[AdMob][HistoryInterstitial] present failed | errorDomain=\(nsError.domain) | errorCode=\(nsError.code) | error=\(nsError.localizedDescription)"
        )
        interstitial = nil
        exportPDF()
    }
}

// MARK: - Preview

#if DEBUG
struct HistoryView_Previews: PreviewProvider {
    static var previews: some View {
        let items: [RiwayatCard] = {
            let data: [(String, Int, Int, String)] = [
                ("Whole Foods Market",  142500, 1, "2023-10-19 08:43:00"),
                ("Blue Bottle Coffee",    6500, 1, "2023-10-19 09:15:00"),
                ("Metropolitan Transit",  2750, 1, "2023-10-20 07:12:00"),
                ("Deposit from Bank",   500000, 0, "2023-10-21 11:30:00"),
                ("Cloud Storage Pro",    9990, 1, "2023-10-20 09:00:00"),
                ("The GreenBistro",     34200, 1, "2023-10-19 20:15:00"),
            ]
            let fmt = DateFormatter()
            fmt.dateFormat = "yyyy-MM-dd HH:mm:ss"
            return data.map { (title, amount, tipe, dateStr) in
                let rw = RiwayatCard()
                rw.setTitle(title)
                rw.setAmount(amount)
                rw.setProsesTipe(tipe)
                if let date = fmt.date(from: dateStr) { rw.setTransactionTime(date) }
                return rw
            }
        }()
        HistoryView(transactions: items)
    }
}
#endif
