//
//  VoiceRecorder.swift
//  ylgamehall
//
//  录音业务对象。管理 AVAudioRecorder + AudioSession + 实时音量/时长状态。
//
//  - 不负责 UI（RecordingOverlay 通过 @ObservedObject 订阅 @Published 状态）
//  - 不负责触摸捕获（RecordingAwareWindow.touchObserver 实现，由
//    RecordingPresenter 串接）
//
//  契约：docs/H5-Native-Contract.md §3.1 ［4］prepareaudio 的录音子链路。
//

import AVFoundation
import Combine    // ObservableObject / @Published：SwiftUI 数据绑定标准用法。
                  // 项目最低 iOS 15.6，无 iOS 17+ `@Observable` 宏可替代。
                  // CLAUDE.md「避免 Combine」指禁止用 Publisher 链替代 async/await，
                  // 不禁止 ObservableObject。
import os.log

/// 录音状态机 + AVAudioRecorder 包装。
public final class VoiceRecorder: ObservableObject {

    private static let log = Logger(subsystem: "ylgamehall", category: "VoiceRecorder")

    /// 最大录音时长 (秒)，对齐 daoqi VoiceRecorderBaseVC.h:15 `kDefaultMaxRecordTime`。
    public static let maxDuration: TimeInterval = 60

    /// 录音参数（8kHz / 16bit / mono PCM）。对齐 daoqi
    /// VoiceRecorderBaseVC.m:116-128 `getAudioRecorderSettingDict`。
    /// AMR-NB 编码硬要求 8kHz mono；改动会让 amrFileCodec.mm `ReadPCMFrame`
    /// 拿不到完整 160 sample 帧，转码失败。
    private static let recorderSettings: [String: Any] = [
        AVSampleRateKey: 8000.0 as Float,
        AVFormatIDKey: UInt32(kAudioFormatLinearPCM),
        AVLinearPCMBitDepthKey: 16,
        AVNumberOfChannelsKey: 1,
    ]

    private var recorder: AVAudioRecorder?
    private var timer: Timer?
    private var startTime: Date?
    private var onFinishCallback: ((URL, TimeInterval) -> Void)?
    private var recordingFileURL: URL?

    /// 实时音量峰值 (~-160 ~ 0 dB)；UI 用作波形动画。
    @Published public private(set) var averagePower: Float = -160

    /// 已录制秒数（0 ~ maxDuration）。
    @Published public private(set) var elapsed: TimeInterval = 0

    public init() {}

    public enum RecorderError: Error, Sendable {
        case sessionFailed(String)
        case recorderCreateFailed(String)
        case startFailed
    }

    /// 启动录音。调用前应已获得麦克风权限（由 prepareaudio handler 保证）。
    ///
    /// - Parameters:
    ///   - fileName: wav 文件名（不含 .wav 扩展），对齐 daoqi `getCurrentTimeString` 产物
    ///   - onFinish: 完成回调 (wavURL, durationSec)。durationSec 是 `Date.now -
    ///     startTime`，**不**用作 `getaudiourl.time` 字段；Phase 3.E
    ///     prepareaudio 实现里会用 AVAudioPlayer 重读 wav duration 再 round
    ///     取整秒，对齐 daoqi NewRootVC.m:1892-1893 行为
    public func start(
        fileName: String,
        onFinish: @escaping (URL, TimeInterval) -> Void
    ) throws {
        // AudioSession 配置（对齐 daoqi ChatVoiceRecorderVC.m:122-126）
        let session = AVAudioSession.sharedInstance()
        do {
            try session.setCategory(.playAndRecord, options: [.defaultToSpeaker])
            try session.setActive(true)
        } catch {
            throw RecorderError.sessionFailed(error.localizedDescription)
        }

        let wavURL = Self.wavFileURL(named: fileName)
        try? FileManager.default.removeItem(at: wavURL)

        let r: AVAudioRecorder
        do {
            r = try AVAudioRecorder(url: wavURL, settings: Self.recorderSettings)
        } catch {
            throw RecorderError.recorderCreateFailed(error.localizedDescription)
        }
        r.isMeteringEnabled = true
        guard r.prepareToRecord(), r.record() else {
            throw RecorderError.startFailed
        }

        self.recorder = r
        self.recordingFileURL = wavURL
        self.onFinishCallback = onFinish
        self.startTime = Date()
        self.elapsed = 0
        self.averagePower = -160

        startTimer()
        Self.log.debug("start file=\(fileName, privacy: .public)")
    }

    /// 停止录音并触发 onFinish。已停止状态下重复调用是 no-op。
    public func stop() {
        guard let r = recorder, r.isRecording else { return }
        let actualDuration = elapsed
        r.stop()
        let wavURL = recordingFileURL
        let cb = onFinishCallback

        cleanup()

        if let wavURL, let cb {
            Self.log.debug("stop duration=\(actualDuration, privacy: .public)s")
            cb(wavURL, actualDuration)
        }
    }

    /// 取消录音并删除文件，**不**触发 onFinish。
    public func cancel() {
        guard let r = recorder else { return }
        if r.isRecording { r.stop() }
        if let url = recordingFileURL {
            try? FileManager.default.removeItem(at: url)
        }
        cleanup()
        Self.log.debug("cancel")
    }

    private func cleanup() {
        stopTimer()
        try? AVAudioSession.sharedInstance().setActive(false)
        recorder = nil
        recordingFileURL = nil
        onFinishCallback = nil
        startTime = nil
    }

    // MARK: - 计时器（0.1s tick，更新音量峰值 + 检测 60s 上限）

    private func startTimer() {
        let t = Timer(timeInterval: 0.1, repeats: true) { [weak self] _ in
            MainActor.assumeIsolated { self?.tick() }
        }
        RunLoop.main.add(t, forMode: .common)
        self.timer = t
    }

    private func stopTimer() {
        timer?.invalidate()
        timer = nil
    }

    private func tick() {
        guard let r = recorder, r.isRecording, let start = startTime else { return }
        r.updateMeters()
        averagePower = r.averagePower(forChannel: 0)
        elapsed = Date().timeIntervalSince(start)

        if elapsed >= Self.maxDuration {
            stop()
        }
    }

    // MARK: - 沙盒文件路径

    /// 录音 wav 写入路径。对齐 daoqi VoiceRecorderBaseVC.m:62-64
    /// `getCacheDirectory`（实际返回 NSDocumentDirectory[0]，不是 Caches/）。
    private static func wavFileURL(named name: String) -> URL {
        FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
            .appendingPathComponent(name)
            .appendingPathExtension("wav")
    }

    // MARK: - 公用工具

    /// 生成 `yyyyMMddHHmmss` 时间戳字符串。对齐 daoqi
    /// VoiceRecorderBaseVC.m:50-54 `getCurrentTimeString`。
    public static func currentTimeString() -> String {
        let f = DateFormatter()
        f.locale = Locale(identifier: "en_US_POSIX")
        f.dateFormat = "yyyyMMddHHmmss"
        return f.string(from: Date())
    }
}
