import AppIntents
import Combine
import Foundation

/// The control's intent and the press signal it delivers.
///
/// Compiled into BOTH the watch app and the JarvisControls extension:
/// opening the app means the system performs the intent in the APP's process,
/// so the type has to exist there or the press fails in an app that has never
/// heard of it.
///
/// WHY `OpenIntent` RATHER THAN A PLAIN `AppIntent`. WidgetKit ships two
/// initialisers for ControlWidgetButton — one `where Action : AppIntent`, for
/// actions that run in place, and one `where Action : OpenIntent`, for actions
/// that open the app. A plain AppIntent silently matched the first, so the
/// button was a run-in-place action being asked to open an app, and the watch
/// reported "Record a Memo failed". `openAppWhenRun` / `supportedModes` do not
/// change which initialiser the type matches.

/// `OpenIntent` requires a target describing WHAT to open. There is only one
/// thing to open here, but the protocol needs a value, so this is it.
enum MemoTarget: String, AppEnum {
    case newMemo

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Memo"
    static var caseDisplayRepresentations: [MemoTarget: DisplayRepresentation] = [
        .newMemo: "New Memo"
    ]
}

/// Carries a press from an intent into the running UI.
///
/// The intent and the view are in the same process but not in a fixed order:
/// on a cold launch `perform()` can run before `ContentView` appears, and on a
/// warm one long after. So this holds the press until the view takes it rather
/// than firing into whatever happens to be listening.
@MainActor
final class LaunchRequest: ObservableObject {
    static let shared = LaunchRequest()
    private init() {}

    /// Bumped per press. A counter rather than a Bool so two presses in a row
    /// are two events — a Bool that is already true changes nothing and the
    /// second press would be swallowed.
    @Published private(set) var pressCount = 0
    private var handled = 0

    func press() { pressCount += 1 }

    /// True at most once per press. The view calls this from both its initial
    /// `task` and its `onChange`, since either may see it first and only one
    /// of them should act.
    func consumePress() -> Bool {
        guard pressCount > handled else { return false }
        handled = pressCount
        return true
    }
}

struct ControlRecordMemoIntent: OpenIntent {
    static var title: LocalizedStringResource = "Record a Memo"
    static var description = IntentDescription(
        "Opens Jarvis and starts recording. Press again to stop and send."
    )

    @Parameter(title: "Target")
    var target: MemoTarget

    init() { target = .newMemo }
    init(target: MemoTarget) { self.target = target }

    @MainActor
    func perform() async throws -> some IntentResult {
        LaunchRequest.shared.press()
        return .result()
    }
}
