import PDFKit
import SwiftUI
import UIKit

/// The receipt as a real document.
///
/// A receipt that only exists inside a sheet is not a receipt: the rider cannot forward it to an
/// employer, attach it to an expense claim, or keep it after they uninstall. This renders the
/// same numbers to a PDF the rider owns. Mirrors `Receipt.kt` on Android, down to the layout.
enum Receipt {

    /// A5-ish at 72dpi. A receipt is a slip, not a page, and this prints and reads fine either way.
    private static let pageSize = CGSize(width: 420, height: 620)
    private static let margin: CGFloat = 32

    private static let ink = UIColor(red: 0.078, green: 0.078, blue: 0.078, alpha: 1)
    private static let muted = UIColor(white: 0.42, alpha: 1)
    private static let brand = UIColor(red: 0.961, green: 0.761, blue: 0.294, alpha: 1)

    /// Everything the document says, resolved by the caller so this stays free of string lookups.
    struct Text {
        let title: String
        let rideLabel: String
        let vehicleLabel: String
        let startedLabel: String
        let endedLabel: String
        let durationLabel: String
        let unlockLabel: String
        let perMinuteLabel: String
        let totalLabel: String
        let footer: String
        let rtl: Bool
    }

    /// Render the receipt and return the file. Written to the caches directory: it is a derived
    /// document, regenerated on demand, and the rider's copy is whatever they shared or saved.
    static func render(ride: RideResponse, invoice: InvoiceResponse, text: Text) -> URL {
        let renderer = UIGraphicsPDFRenderer(
            bounds: CGRect(origin: .zero, size: pageSize)
        )
        let data = renderer.pdfData { context in
            context.beginPage()
            let canvas = context.cgContext
            canvas.setFillColor(UIColor.white.cgColor)
            canvas.fill(CGRect(origin: .zero, size: pageSize))

            // Brand rule, so a printed slip is recognisable at a glance.
            canvas.setFillColor(brand.cgColor)
            canvas.fill(CGRect(x: 0, y: 0, width: pageSize.width, height: 6))

            var y: CGFloat = margin + 10
            y = draw("BeebBeeb", at: y, size: 22, color: ink, bold: true, rtl: text.rtl)
            y = draw(text.title, at: y + 2, size: 13, color: muted, rtl: text.rtl)
            y += 18

            let currency = invoice.currency
            var rows: [(String, String)] = [
                (text.rideLabel, String(ride.id.prefix(8)).uppercased()),
                // Empty when the ride predates booking capturing the code; the row is dropped
                // rather than printed blank.
                (text.vehicleLabel, ride.qrCode ?? ""),
                (text.startedLabel, RideFormat.time(ride.startedAt)),
                // The completed state holds the ride as it was before the end call returned, so
                // endedAt is still nil. The invoice is written at end, so its timestamp is the
                // ride's end time, not an approximation of it.
                (text.endedLabel, RideFormat.time(ride.endedAt ?? invoice.createdAt)),
                (text.durationLabel, RideFormat.duration(Int(invoice.durationSeconds))),
                (text.unlockLabel, RideFormat.money(ride.unlockFee, currency)),
                (text.perMinuteLabel, RideFormat.money(ride.perMinuteRate, currency)),
            ]
            rows = rows.filter { !$0.1.isEmpty }
            for (label, value) in rows {
                y = row(label, value, at: y, rtl: text.rtl)
            }

            y += 10
            canvas.setStrokeColor(UIColor(white: 0.87, alpha: 1).cgColor)
            canvas.stroke(CGRect(x: margin, y: y, width: pageSize.width - margin * 2, height: 0))
            y += 16
            y = row(text.totalLabel, RideFormat.money(invoice.amount, currency),
                    at: y, rtl: text.rtl, size: 15, bold: true)

            y += 26
            _ = draw(text.footer, at: y, size: 10, color: muted, rtl: text.rtl)
        }

        let url = FileManager.default.temporaryDirectory
            .appendingPathComponent("beebbeeb-receipt-\(ride.id.prefix(8)).pdf")
        try? data.write(to: url)
        return url
    }

    // MARK: - Drawing

    private static func row(
        _ label: String,
        _ value: String,
        at y: CGFloat,
        rtl: Bool,
        size: CGFloat = 12,
        bold: Bool = false
    ) -> CGFloat {
        // Label leads, value trails: in RTL the two swap sides, which is what makes the slip read
        // correctly in Arabic rather than looking like a mirrored English one.
        let next = draw(label, at: y, size: size, color: muted, rtl: rtl, alignEnd: false)
        _ = draw(value, at: y, size: size, color: ink, bold: bold, rtl: rtl, alignEnd: true)
        return next + 6
    }

    @discardableResult
    private static func draw(
        _ string: String,
        at y: CGFloat,
        size: CGFloat,
        color: UIColor,
        bold: Bool = false,
        rtl: Bool,
        alignEnd: Bool = false
    ) -> CGFloat {
        let paragraph = NSMutableParagraphStyle()
        // Pinned to the locale, not guessed from the characters: a Latin amount inside an Arabic
        // receipt must still sit on the Arabic side of the slip.
        paragraph.baseWritingDirection = rtl ? .rightToLeft : .leftToRight
        paragraph.alignment = alignEnd ? (rtl ? .left : .right) : (rtl ? .right : .left)

        let attributes: [NSAttributedString.Key: Any] = [
            .font: bold ? UIFont.boldSystemFont(ofSize: size) : UIFont.systemFont(ofSize: size),
            .foregroundColor: color,
            .paragraphStyle: paragraph,
        ]
        let width = pageSize.width - margin * 2
        let rect = CGRect(x: margin, y: y, width: width, height: size * 1.6)
        string.draw(with: rect, options: [.usesLineFragmentOrigin], attributes: attributes,
                    context: nil)
        return y + size * 1.6
    }
}

/// The system share sheet, which is also how a rider saves the receipt to Files.
struct ShareSheet: UIViewControllerRepresentable {
    let items: [Any]

    func makeUIViewController(context: Context) -> UIActivityViewController {
        UIActivityViewController(activityItems: items, applicationActivities: nil)
    }

    func updateUIViewController(_ controller: UIActivityViewController, context: Context) {}
}
