/// macOS permission integration for Input Monitoring, Local Network,
/// and Notifications.
///
/// Trace:
/// - SRS-198 (Push-to-talk system permission acquisition).
/// - SRS-297 / SRS-300 (Input Monitoring for global PTT on macOS).
/// - SysRS-166 (Desktop notifications).
/// - SDD-091 (PTT capability badge — live capability level).
///
/// Responsibilities:
///  * Subscribe to the Swift-side `MethodChannel`
///    `app.chanora/macos_permissions` for inbound state-change
///    invocations emitted by the native handler in
///    `MainFlutterWindow.swift` (Swift → Dart).
///  * Provide imperative Dart → Swift entry points for checking and
///    requesting Input Monitoring, triggering the Local Network
///    prompt, and requesting notification authorization.
///  * Expose the latest resolved states as [ValueListenable] so UI
///    surfaces (PTT capability badge, permission banners, connection
///    error messages) can react without polling.
///
/// ## Non-macOS short-circuit
///
/// On Android / iOS / Linux / Windows / web, none of these macOS-
/// specific permissions exist. The MethodChannel is therefore never
/// constructed off-macOS. Each state listenable stays at its default
/// "granted" / "not needed" value so all consumers become no-ops.
///
/// ## No global statics
///
/// Following the pattern established by `AndroidPermissionsService`
/// (SDD-106) and `BackIntentService` (SDD-028), this class is
/// constructor-injected. The host app instantiates one instance at
/// startup and passes it through the widget tree.
library;

import 'dart:async';
import 'dart:developer' as developer;
import 'dart:io' show Platform;

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

// ---------------------------------------------------------------------------
// Channel constants
// ---------------------------------------------------------------------------

/// MethodChannel name shared with Swift `MacOSPermissionsHandler`.
@visibleForTesting
const String macOSPermissionsChannelName =
    'app.chanora/macos_permissions';

// Outbound (Dart → Swift) method names.
@visibleForTesting
const String methodCheckInputMonitoring = 'checkInputMonitoring';
@visibleForTesting
const String methodRequestInputMonitoring = 'requestInputMonitoring';
@visibleForTesting
const String methodOpenInputMonitoringSettings =
    'openInputMonitoringSettings';
@visibleForTesting
const String methodTriggerLocalNetworkPrompt = 'triggerLocalNetworkPrompt';
@visibleForTesting
const String methodCheckLocalNetwork = 'checkLocalNetwork';
@visibleForTesting
const String methodRequestNotifications = 'requestNotifications';
@visibleForTesting
const String methodCheckNotifications = 'checkNotifications';

// Inbound (Swift → Dart) method names.
@visibleForTesting
const String methodInputMonitoringStateChanged =
    'inputMonitoringStateChanged';
@visibleForTesting
const String methodLocalNetworkStateChanged = 'localNetworkStateChanged';

// ---------------------------------------------------------------------------
// Enums
// ---------------------------------------------------------------------------

/// Discrete states for macOS-specific permissions.
enum MacOSPermissionState {
  /// Permission granted.
  granted,

  /// Permission denied by the user.
  denied,

  /// Permission has not yet been determined (first launch before
  /// any prompt, or the system returned an unexpected value).
  notDetermined,

  /// No resolved state yet (cold launch before the first emission,
  /// or non-macOS host before short-circuit). Consumers treat this
  /// as "not yet known".
  unknown,
}

/// Local Network permission states, extended to cover macOS 14 and
/// earlier where Local Network Privacy does not exist.
enum MacOSLocalNetworkState {
  /// Permission granted or the Local Network prompt was satisfied.
  granted,

  /// Permission explicitly denied by the user (macOS 15+ only).
  denied,

  /// No prompt shown yet.
  notDetermined,

  /// Running on macOS 14 or earlier where Local Network Privacy
  /// does not apply. Consumers treat this as "granted".
  unsupported,

  /// No resolved state yet.
  unknown,
}

// ---------------------------------------------------------------------------
// State parsing helpers
// ---------------------------------------------------------------------------

MacOSPermissionState _parsePermissionState(String? raw) {
  switch (raw) {
    case 'Granted':
      return MacOSPermissionState.granted;
    case 'Denied':
      return MacOSPermissionState.denied;
    case 'NotDetermined':
      return MacOSPermissionState.notDetermined;
    default:
      return MacOSPermissionState.unknown;
  }
}

MacOSLocalNetworkState _parseLocalNetworkState(String? raw) {
  switch (raw) {
    case 'Granted':
      return MacOSLocalNetworkState.granted;
    case 'Denied':
      return MacOSLocalNetworkState.denied;
    case 'NotDetermined':
      return MacOSLocalNetworkState.notDetermined;
    case 'Unsupported':
      return MacOSLocalNetworkState.unsupported;
    default:
      return MacOSLocalNetworkState.unknown;
  }
}

// ---------------------------------------------------------------------------
// PTT capability level mapping
// ---------------------------------------------------------------------------

/// Maps the Input Monitoring state to the PTT capability level string
/// consumed by [PttCapabilityBadge].
///
/// Trace: SDD-091 (capability badge); desktop-ptt-architecture.md
/// (macOS Event Tap strategy).
String _pttCapabilityLevel(MacOSPermissionState inputMonitoring) {
  switch (inputMonitoring) {
    case MacOSPermissionState.granted:
      return 'L1MacOSEventTap';
    case MacOSPermissionState.denied:
    case MacOSPermissionState.notDetermined:
    case MacOSPermissionState.unknown:
      return 'L0Focused';
  }
}

// ---------------------------------------------------------------------------
// MacOSPermissionsService
// ---------------------------------------------------------------------------

/// Dart-side integration for macOS permission state.
///
/// Trace: SRS-198, SRS-297, SRS-300, SysRS-166, SDD-091.
class MacOSPermissionsService {
  /// Construct a service bound to [channel]. Injected for testability;
  /// production code uses the default channel keyed on
  /// [macOSPermissionsChannelName].
  MacOSPermissionsService({MethodChannel? channel})
      : _channel = channel ??
            (_isMacOS
                ? const MethodChannel(macOSPermissionsChannelName)
                : null);

  /// Platform-detection seam. Web counts as non-macOS.
  static bool get _isMacOS {
    if (kIsWeb) return false;
    return Platform.isMacOS;
  }

  final MethodChannel? _channel;
  bool _started = false;

  // -- Input Monitoring -----------------------------------------------------

  final ValueNotifier<MacOSPermissionState> _inputMonitoringState =
      ValueNotifier<MacOSPermissionState>(
    // Non-macOS: granted so consumers are no-ops.
    _isMacOS
        ? MacOSPermissionState.unknown
        : MacOSPermissionState.granted,
  );

  /// Latest known Input Monitoring permission state.
  ///
  /// On macOS this drives the PTT capability level: `granted` →
  /// `L1MacOSEventTap` (global PTT via Event Tap); anything else →
  /// `L0Focused` (focused-only PTT).
  ValueListenable<MacOSPermissionState> get inputMonitoringState =>
      _inputMonitoringState;

  // -- Local Network --------------------------------------------------------

  final ValueNotifier<MacOSLocalNetworkState> _localNetworkState =
      ValueNotifier<MacOSLocalNetworkState>(
    _isMacOS
        ? MacOSLocalNetworkState.unknown
        : MacOSLocalNetworkState.unsupported,
  );

  /// Latest known Local Network permission state.
  ///
  /// On macOS 15+ (Sequoia) this reflects the Local Network Privacy
  /// TCC permission. On macOS 14 and earlier, the value is
  /// [MacOSLocalNetworkState.unsupported] (no prompt needed).
  ValueListenable<MacOSLocalNetworkState> get localNetworkState =>
      _localNetworkState;

  // -- Notifications --------------------------------------------------------

  final ValueNotifier<MacOSPermissionState> _notificationState =
      ValueNotifier<MacOSPermissionState>(
    _isMacOS
        ? MacOSPermissionState.unknown
        : MacOSPermissionState.granted,
  );

  /// Latest known notification authorization state.
  ValueListenable<MacOSPermissionState> get notificationState =>
      _notificationState;

  // -- PTT capability (derived) ---------------------------------------------

  final ValueNotifier<String> _pttCapabilityState =
      ValueNotifier<String>(
    _pttCapabilityLevel(
      _isMacOS ? MacOSPermissionState.unknown : MacOSPermissionState.granted,
    ),
  );

  /// Derived PTT capability level string, ready for consumption by
  /// [PttCapabilityBadge]. Updates automatically when Input Monitoring
  /// state changes.
  ///
  /// Returns `"L1MacOSEventTap"` when Input Monitoring is granted,
  /// `"L0Focused"` otherwise.
  ValueListenable<String> get pttCapabilityState => _pttCapabilityState;

  // -- Lifecycle ------------------------------------------------------------

  /// Start listening for state updates from Swift. Idempotent.
  ///
  /// On non-macOS this is a no-op.
  ///
  /// Note: this only registers the inbound handler. Call
  /// [checkInitialStates] afterward to eagerly query the current
  /// permission states from the native side.
  void start() {
    if (_started) return;
    _started = true;
    final ch = _channel;
    if (ch == null) return;
    ch.setMethodCallHandler(_handle);
  }

  /// Eagerly query the current permission states from the native
  /// side. Call this after [start] so the PTT capability badge
  /// shows the correct level on the first frame.
  ///
  /// On non-macOS this is a no-op.
  void checkInitialStates() {
    final ch = _channel;
    if (ch == null) return;
    unawaited(_checkInputMonitoring());
    unawaited(_checkLocalNetwork());
    unawaited(_checkNotifications());
  }

  /// Stop listening. Idempotent.
  void stop() {
    if (!_started) return;
    _started = false;
    final ch = _channel;
    if (ch == null) return;
    ch.setMethodCallHandler(null);
  }

  // -- Inbound handler (Swift → Dart) --------------------------------------

  Future<dynamic> _handle(MethodCall call) async {
    switch (call.method) {
      case methodInputMonitoringStateChanged:
        final args = call.arguments;
        if (args is Map) {
          final state = _parsePermissionState(args['state'] as String?);
          _inputMonitoringState.value = state;
          _pttCapabilityState.value = _pttCapabilityLevel(state);
        }
        break;
      case methodLocalNetworkStateChanged:
        final args = call.arguments;
        if (args is Map) {
          _localNetworkState.value =
              _parseLocalNetworkState(args['state'] as String?);
        }
        break;
      default:
        break;
    }
    return null;
  }

  // -- Outbound: Input Monitoring -------------------------------------------

  /// Check the current Input Monitoring permission state without
  /// triggering a system prompt.
  Future<MacOSPermissionState> _checkInputMonitoring() async {
    final ch = _channel;
    if (ch == null) return MacOSPermissionState.granted;
    try {
      final raw =
          await ch.invokeMethod<String>(methodCheckInputMonitoring);
      final state = _parsePermissionState(raw);
      _inputMonitoringState.value = state;
      _pttCapabilityState.value = _pttCapabilityLevel(state);
      return state;
    } catch (e, st) {
      developer.log(
        'checkInputMonitoring failed',
        name: 'MacOSPermissionsService',
        error: e,
        stackTrace: st,
      );
      return _inputMonitoringState.value;
    }
  }

  /// Request Input Monitoring permission. On macOS this calls
  /// `CGRequestListenEventAccess()` which shows a system dialog
  /// or opens System Settings (depending on macOS version).
  ///
  /// Returns the new state after the request.
  Future<MacOSPermissionState> requestInputMonitoring() async {
    final ch = _channel;
    if (ch == null) return MacOSPermissionState.granted;
    try {
      final raw = await ch.invokeMethod<String>(
        methodRequestInputMonitoring,
      );
      final state = _parsePermissionState(raw);
      _inputMonitoringState.value = state;
      _pttCapabilityState.value = _pttCapabilityLevel(state);
      return state;
    } catch (e, st) {
      developer.log(
        'requestInputMonitoring failed',
        name: 'MacOSPermissionsService',
        error: e,
        stackTrace: st,
      );
      return _inputMonitoringState.value;
    }
  }

  /// Open System Settings → Privacy & Security → Input Monitoring
  /// so the user can manually grant the permission for the
  /// permanently-denied case (TCC drag-based permission cannot be
  /// programmatically granted).
  Future<void> openInputMonitoringSettings() async {
    final ch = _channel;
    if (ch == null) return;
    try {
      await ch.invokeMethod<void>(methodOpenInputMonitoringSettings);
    } catch (e, st) {
      developer.log(
        'openInputMonitoringSettings failed',
        name: 'MacOSPermissionsService',
        error: e,
        stackTrace: st,
      );
    }
  }

  // -- Outbound: Local Network ----------------------------------------------

  Future<MacOSLocalNetworkState> _checkLocalNetwork() async {
    final ch = _channel;
    if (ch == null) return MacOSLocalNetworkState.unsupported;
    try {
      final raw = await ch.invokeMethod<String>(methodCheckLocalNetwork);
      final state = _parseLocalNetworkState(raw);
      _localNetworkState.value = state;
      return state;
    } catch (e, st) {
      developer.log(
        'checkLocalNetwork failed',
        name: 'MacOSPermissionsService',
        error: e,
        stackTrace: st,
      );
      return _localNetworkState.value;
    }
  }

  /// Trigger the Local Network permission prompt by starting a
  /// brief `NWBrowser` scan for `_ts3._tcp`. On macOS 15+ this
  /// shows the system Local Network Privacy dialog. On earlier
  /// versions, this is a no-op (returns `unsupported`).
  Future<MacOSLocalNetworkState> triggerLocalNetworkPrompt() async {
    final ch = _channel;
    if (ch == null) return MacOSLocalNetworkState.unsupported;
    try {
      final raw = await ch.invokeMethod<String>(
        methodTriggerLocalNetworkPrompt,
      );
      final state = _parseLocalNetworkState(raw);
      _localNetworkState.value = state;
      return state;
    } catch (e, st) {
      developer.log(
        'triggerLocalNetworkPrompt failed',
        name: 'MacOSPermissionsService',
        error: e,
        stackTrace: st,
      );
      return _localNetworkState.value;
    }
  }

  // -- Outbound: Notifications ----------------------------------------------

  Future<MacOSPermissionState> _checkNotifications() async {
    final ch = _channel;
    if (ch == null) return MacOSPermissionState.granted;
    try {
      final raw =
          await ch.invokeMethod<String>(methodCheckNotifications);
      final state = _parsePermissionState(raw);
      _notificationState.value = state;
      return state;
    } catch (e, st) {
      developer.log(
        'checkNotifications failed',
        name: 'MacOSPermissionsService',
        error: e,
        stackTrace: st,
      );
      return _notificationState.value;
    }
  }

  /// Request notification authorization via `UNUserNotificationCenter`.
  /// Returns the new state after the system dialog resolves.
  Future<MacOSPermissionState> requestNotifications() async {
    final ch = _channel;
    if (ch == null) return MacOSPermissionState.granted;
    try {
      final raw = await ch.invokeMethod<String>(
        methodRequestNotifications,
      );
      final state = _parsePermissionState(raw);
      _notificationState.value = state;
      return state;
    } catch (e, st) {
      developer.log(
        'requestNotifications failed',
        name: 'MacOSPermissionsService',
        error: e,
        stackTrace: st,
      );
      return _notificationState.value;
    }
  }

  // -- Cleanup --------------------------------------------------------------

  /// Release state notifiers. Test helper; production keeps the
  /// service alive for the lifetime of the app.
  @visibleForTesting
  void dispose() {
    stop();
    _inputMonitoringState.dispose();
    _localNetworkState.dispose();
    _notificationState.dispose();
    _pttCapabilityState.dispose();
  }
}
