import SwiftUI
import WatchKit

/// One screen. Tap to record, tap to send, Cancel to throw it away.
///
/// Press-and-hold is deliberately NOT used: watchOS ends a touch when you lower
/// your wrist, which is exactly what truncated the Telegram recordings. A
/// toggle survives the gesture.
struct ContentView: View {
    @Environment(\.scenePhase) private var scenePhase
    @StateObject private var recorder = Recorder()
    @StateObject private var uploader = Uploader.shared
    /// Presses of the Action Button, arriving through RecordMemoIntent.
    @StateObject private var launch = LaunchRequest.shared
    @State private var permission: Bool?
    @State private var notice: String?
    /// Swallows the second half of a double-tap. Without it, two quick taps on
    /// stop would upload one recording and immediately begin another that
    /// nobody asked for — and on a wrist, that one runs until it hits the cap.
    @State private var cooling = false
    /// The gap between the tap and the microphone actually being live.
    /// Activating an audio session is not instant, and a button that looks
    /// dead is a button people tap again.
    @State private var starting = false

    var body: some View {
        VStack(spacing: 8) {
            Text(statusLine)
                .font(.caption2)
                .foregroundStyle(statusTint)
                .multilineTextAlignment(.center)
                .lineLimit(2)
                .animation(.default, value: statusLine)

            Button(action: toggle) {
                ZStack {
                    // Meter ring. Scales with input level so you can SEE the mic
                    // working — the silent-recording failure is invisible
                    // otherwise until whisper hands back a hallucinated word.
                    if recorder.isRecording {
                        Circle()
                            .stroke(Color.red.opacity(0.45), lineWidth: 4)
                            .frame(width: 92 + CGFloat(recorder.level) * 44,
                                   height: 92 + CGFloat(recorder.level) * 44)
                            .animation(.easeOut(duration: 0.12), value: recorder.level)
                    }
                    Circle()
                        .fill(recorder.isRecording ? Color.red : Color.accentColor)
                        .frame(width: 92, height: 92)
                    if starting {
                        ProgressView().tint(.white)
                    } else {
                        Image(systemName: recorder.isRecording ? "stop.fill" : "mic.fill")
                            .font(.system(size: 34, weight: .semibold))
                            .foregroundStyle(.white)
                    }
                }
            }
            // Shrinks the instant a finger lands, so the tap is acknowledged
            // before any of the work behind it has happened.
            .buttonStyle(PressFeedbackStyle())
            .disabled(permission == false || cooling || starting || uploader.state == .uploading)

            if recorder.isRecording {
                Text(timeString(recorder.elapsed))
                    .font(.system(.title3, design: .monospaced))
                    .monospacedDigit()

                Button("Cancel", role: .destructive) { cancel() }
                    .buttonStyle(.plain)
                    .font(.caption2)
                    .foregroundStyle(.secondary)
            }
        }
        .padding(.vertical, 4)
        .task {
            permission = await recorder.requestPermission()
            // A cold launch from the Action Button lands here with the press
            // already waiting: the intent runs before there is a view to tell.
            // Permission has to be settled first, or the recording starts
            // against a microphone we have not been allowed to open yet.
            if launch.consumePress() { toggle() }
        }
        .onChange(of: launch.pressCount) { _, _ in
            // The warm path: the app was already open, so there was no `task`
            // to catch it. Ignored until permission resolves — the `task`
            // above will pick the press up when it does.
            guard permission != nil, launch.consumePress() else { return }
            toggle()
        }
        .onChange(of: scenePhase) { _, phase in
            // Leaving the app discards whatever was being recorded.
            //
            // Not a policy choice so much as making the truth visible: without
            // the audio background mode, watchOS suspends us here and the
            // recorder stops getting time regardless. Doing nothing would leave
            // an orphaned file and a screen still claiming to be listening.
            //
            // Discarded rather than sent, because a recording cut off by
            // leaving is half a sentence, and half a sentence routed into a
            // reminder is worse than no reminder.
            //
            // Deliberately .background and NOT .inactive: a notification or a
            // brief wrist-drop makes an app inactive, and killing a recording
            // for those would make the thing unusable.
            if phase == .background, recorder.isRecording {
                recorder.cancel()
                notice = "Recording discarded — you left the app"
            }
        }
    }

    private var statusTint: Color {
        if recorder.isRecording && recorder.silentFor >= 3 { return .orange }
        if case .failed = uploader.state { return .orange }
        return .secondary
    }

    private var statusLine: String {
        if permission == false { return "Microphone access denied — enable it in Settings" }
        if starting { return "Starting…" }
        if let notice { return notice }
        if recorder.isRecording {
            // Three seconds of nothing is long enough to be real, short enough
            // to still be worth telling someone mid-recording.
            return recorder.silentFor >= 3 ? "⚠️ Not hearing anything" : "Listening…"
        }
        switch uploader.state {
        case .uploading: return "Sending…"
        case .queued:    return "Sent — Steward will reply"
        case .failed(let why): return why
        case .idle:      return "Tap to record"
        }
    }

    private func toggle() {
        // Re-entrancy guard first: everything below is cheap, so without this a
        // fast double-tap gets through both halves before the UI catches up.
        guard !cooling, !starting else { return }
        cooling = true
        Task {
            try? await Task.sleep(for: .seconds(Config.tapCooldownSeconds))
            cooling = false
        }

        notice = nil
        if recorder.isRecording {
            WKInterfaceDevice.current().play(.stop)
            switch recorder.stop() {
            case .ok(let finished):
                uploader.upload(finished.url, durationMs: finished.durationMs)
            case .tooShort:
                notice = "Too short — hold on a moment and speak"
            case .silent:
                // The watch mic failing silently is a real failure mode; say so
                // rather than uploading a file whisper will hallucinate over.
                notice = "Didn't hear anything — try again"
            }
        } else {
            WKInterfaceDevice.current().play(.start)
            // Yield before starting so "Starting…" and the spinner actually
            // paint: activating the audio session blocks, and doing it inline
            // would show nothing until it had already finished.
            starting = true
            Task {
                await Task.yield()
                do { try recorder.start() } catch { notice = error.localizedDescription }
                starting = false
            }
        }
    }

    private func cancel() {
        WKInterfaceDevice.current().play(.click)
        recorder.cancel()
        notice = "Discarded"
    }

    private func timeString(_ t: TimeInterval) -> String {
        String(format: "%d:%02d", Int(t) / 60, Int(t) % 60)
    }
}

/// Instant touch acknowledgement. A watch button with no press state reads as
/// unresponsive during the moment before recording begins, which is precisely
/// when someone taps it a second time.
private struct PressFeedbackStyle: ButtonStyle {
    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .scaleEffect(configuration.isPressed ? 0.90 : 1)
            .opacity(configuration.isPressed ? 0.75 : 1)
            .animation(.easeOut(duration: 0.08), value: configuration.isPressed)
    }
}
