import Capacitor
import Foundation
import MushiMushi
import UIKit

/// Capacitor iOS plugin. Delegates to the native `MushiMushi` SDK so behaviour
/// is identical to the standalone iOS package — single code path = single
/// audit surface.
@objc(MushiMushiPlugin)
public class MushiMushiPlugin: CAPPlugin {

    private var configured = false
    private var triggerMode: MushiConfig.TriggerMode = .shake
    private var theme: MushiConfig.Theme = .init(accentColor: "#FF2D78")

    @objc func configure(_ call: CAPPluginCall) {
        guard let projectId = call.getString("projectId"),
              let apiKey = call.getString("apiKey") else {
            call.reject("projectId and apiKey are required")
            return
        }

        let endpoint = call.getString("endpoint") ?? "https://api.mushimushi.dev"
        let captureScreenshot = call.getBool("captureScreenshot") ?? true
        let minDescriptionLength = call.getInt("minDescriptionLength") ?? 20
        let triggerStr = call.getString("triggerMode") ?? "shake"

        // Cross-platform parity: unknown strings fall back to `.shake` (the
        // documented default and the native SDK default), matching the Android
        // Capacitor plugin. A typo must never silently disable all triggers.
        triggerMode = {
            switch triggerStr {
            case "shake": return .shake
            case "button": return .button
            case "both": return .both
            case "none": return .none
            default: return .shake
            }
        }()

        // Reuse the native SDK so the offline queue / shake / API client
        // are exactly one implementation across native + Capacitor apps.
        if let themeObj = call.getObject("theme") {
            theme = .init(
                accentColor: themeObj["accentColor"] as? String ?? "#FF2D78",
                dark: themeObj["dark"] as? Bool ?? false
            )
        }
        let triggerInsetObj = call.getObject("triggerInset")
        let triggerInsetPreset = call.getString("triggerInsetPreset")
        let presetBottom: Double = triggerInsetPreset == "tabBarSafe" ? 72 : 96
        let triggerInset = MushiConfig.TriggerInset(
            bottom: CGFloat(triggerInsetObj?["bottom"] as? Double ?? presetBottom),
            leading: (triggerInsetObj?["leading"] as? Double).map(CGFloat.init),
            trailing: (triggerInsetObj?["trailing"] as? Double).map(CGFloat.init) ?? 20
        )

        let config = MushiConfig(
            projectId: projectId,
            apiKey: apiKey,
            endpoint: endpoint,
            triggerMode: triggerMode,
            captureScreenshot: captureScreenshot,
            minDescriptionLength: minDescriptionLength,
            theme: theme,
            triggerInset: triggerInset
        )
        Mushi.shared.configure(with: config)

        // Bridge native report-submitted callbacks to the JS layer.
        NotificationCenter.default.addObserver(
            forName: Notification.Name("dev.mushimushi.report.submitted"),
            object: nil,
            queue: .main
        ) { [weak self] note in
            guard let info = note.userInfo as? [String: Any] else { return }
            self?.notifyListeners("reportSubmitted", data: info)
        }

        configured = true
        call.resolve()
    }

    @objc func report(_ call: CAPPluginCall) {
        guard configured else { call.reject("Not configured"); return }
        guard let description = call.getString("description") else {
            call.reject("description is required"); return
        }
        let category = call.getString("category") ?? "bug"
        let metadata = call.getObject("metadata") as? [String: Any]
        Mushi.shared.report(description: description, category: category, metadata: metadata)
        call.resolve(["accepted": true])
    }

    @objc func captureScreenshot(_ call: CAPPluginCall) {
        DispatchQueue.main.async {
            let img = ScreenshotCapture.captureBase64()
            call.resolve(["image": img as Any])
        }
    }

    @objc func showWidget(_ call: CAPPluginCall) {
        let metadata = call.getObject("metadata") as? [String: Any]
        Mushi.shared.showWidget(category: call.getString("category"), metadata: metadata)
        call.resolve()
    }

    @objc func setUser(_ call: CAPPluginCall) {
        if let user = call.getObject("user") as? [String: Any] {
            Mushi.shared.setUser(user)
        } else {
            Mushi.shared.setUser(nil)
        }
        call.resolve()
    }

    @objc func setMetadata(_ call: CAPPluginCall) {
        guard let key = call.getString("key") else {
            call.reject("key is required")
            return
        }
        let value = call.options["value"] as Any?
        Mushi.shared.setMetadata(key, value: value is NSNull ? nil : value)
        call.resolve()
    }

    @objc func flushQueue(_ call: CAPPluginCall) {
        // The native SDK auto-flushes on a 30s timer; we expose a manual
        // trigger so JS callers can force a flush after coming online.
        Mushi.shared.flushOfflineQueueNow { delivered in
            call.resolve(["delivered": delivered])
        }
    }

    @objc func addBreadcrumb(_ call: CAPPluginCall) {
        guard let message = call.getString("message") else {
            call.reject("message is required"); return
        }
        let categoryStr = call.getString("category") ?? "custom"
        let category: MushiBreadcrumb.Category = {
            switch categoryStr {
            case "navigation": return .navigation
            case "ui.tap": return .uiTap
            case "console": return .console
            case "network": return .network
            case "lifecycle": return .lifecycle
            default: return .custom
            }
        }()
        let levelStr = call.getString("level") ?? "info"
        let level: MushiBreadcrumb.Level = {
            switch levelStr {
            case "debug": return .debug
            case "warning": return .warning
            case "error": return .error
            default: return .info
            }
        }()
        // Match Android's coercion: any JS value gets stringified so a
        // `{ count: 5, ok: true }` payload doesn't silently get dropped on
        // iOS (it would on a strict `[String: String]` cast since none of
        // the values are Swift Strings).
        let dataRaw = call.getObject("data") ?? [:]
        let data: [String: String]? = dataRaw.isEmpty
            ? nil
            : dataRaw.reduce(into: [String: String]()) { acc, pair in
                acc[pair.key] = String(describing: pair.value)
            }
        Mushi.shared.addBreadcrumb(category: category, level: level, message: message, data: data)
        call.resolve()
    }

    @objc func getBreadcrumbs(_ call: CAPPluginCall) {
        let crumbs: [[String: Any]] = Mushi.shared.getBreadcrumbs().map { crumb in
            var dict: [String: Any] = [
                "timestamp": crumb.timestamp,
                "category": crumb.category.rawValue,
                "level": crumb.level.rawValue,
                "message": crumb.message
            ]
            if let data = crumb.data { dict["data"] = data }
            return dict
        }
        call.resolve(["breadcrumbs": crumbs])
    }
}
