/// SDD-028 `BackIntentService` — Dart integration layer.
///
/// Trace: SDD-028 (Android back-intent normalization) / SAD-018.
///
/// Responsibilities:
///  * Listen on `MethodChannel("app.chanora/back_intent")` for
///    `backIntent` calls forwarded by `BackIntentBridge` (Kotlin).
///  * Read the current shell state via **injected** getter callbacks
///    (`pttActiveProbe`, `modalOpenProbe`, `atRootProbe`) so the class
///    is testable without a `MaterialApp` / global Navigator. No
///    global statics are introduced.
///  * Apply `decideBackIntent(...)` from `back_intent_policy.dart` to
///    produce a deterministic `BackIntentDecision`.
///  * Dispatch the decision to injected effect callbacks
///    (`closeTopModal`, `popRoute`) and, for `ExitApp`, ask the
///    platform to finish via `channel.invokeMethod('popToSystem')`.
///
/// Non-Android platforms: use [BackIntentService.noOp] which never
/// registers a channel handler. The rest of the app can construct a
/// `BackIntentService` unconditionally.
library;

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

import 'back_intent_policy.dart';

/// Probe returning the current value of a shell-state flag.
typedef BackIntentBoolProbe = bool Function();

/// Effect callback invoked by [BackIntentService] when the policy
/// resolves a non-platform decision.
typedef BackIntentEffect = void Function();

/// MethodChannel contract shared with `BackIntentBridge.kt`.
@visibleForTesting
const String backIntentChannelName = 'app.chanora/back_intent';

@visibleForTesting
const String backIntentMethodFromPlatform = 'backIntent';

@visibleForTesting
const String backIntentMethodPopToSystem = 'popToSystem';

@visibleForTesting
const String backIntentPayloadKindKey = 'kind';

@visibleForTesting
const String backIntentPayloadKindSystemBack = 'system_back';

/// SDD-028 service. Construct once per app shell.
class BackIntentService {
  BackIntentService({
    required BackIntentBoolProbe pttActiveProbe,
    required BackIntentBoolProbe modalOpenProbe,
    required BackIntentBoolProbe atRootProbe,
    required BackIntentEffect closeTopModal,
    required BackIntentEffect popRoute,
    MethodChannel? channel,
  })  : _pttActiveProbe = pttActiveProbe,
        _modalOpenProbe = modalOpenProbe,
        _atRootProbe = atRootProbe,
        _closeTopModal = closeTopModal,
        _popRoute = popRoute,
        _channel = channel ?? const MethodChannel(backIntentChannelName),
        _enabled = true;

  /// No-op factory for non-Android targets (SDD-028 §6).
  ///
  /// Constructs a service whose probes always return false and whose
  /// effect callbacks do nothing. It never installs a channel handler.
  factory BackIntentService.noOp() {
    return BackIntentService._noOp();
  }

  BackIntentService._noOp()
      : _pttActiveProbe = _alwaysFalse,
        _modalOpenProbe = _alwaysFalse,
        _atRootProbe = _alwaysFalse,
        _closeTopModal = _noEffect,
        _popRoute = _noEffect,
        _channel = const MethodChannel(backIntentChannelName),
        _enabled = false;

  final BackIntentBoolProbe _pttActiveProbe;
  final BackIntentBoolProbe _modalOpenProbe;
  final BackIntentBoolProbe _atRootProbe;
  final BackIntentEffect _closeTopModal;
  final BackIntentEffect _popRoute;
  final MethodChannel _channel;
  final bool _enabled;

  bool _started = false;

  /// Install the MethodChannel handler. Safe to call once per instance.
  /// On the no-op variant this returns without registering anything.
  void start() {
    if (!_enabled || _started) {
      return;
    }
    _channel.setMethodCallHandler(_handleMethodCall);
    _started = true;
  }

  /// Remove the MethodChannel handler. Safe to call repeatedly.
  void stop() {
    if (!_started) {
      return;
    }
    _channel.setMethodCallHandler(null);
    _started = false;
  }

  /// Resolve and dispatch a back intent. Visible for tests so the
  /// decision pipeline can be driven without a real platform channel.
  @visibleForTesting
  BackIntentDecision dispatch() {
    final decision = decideBackIntent(
      pttActive: _pttActiveProbe(),
      modalOpen: _modalOpenProbe(),
      atRoot: _atRootProbe(),
    );
    _apply(decision);
    return decision;
  }

  Future<dynamic> _handleMethodCall(MethodCall call) async {
    if (call.method != backIntentMethodFromPlatform) {
      return null;
    }
    final args = call.arguments;
    if (args is Map &&
        args[backIntentPayloadKindKey] != backIntentPayloadKindSystemBack) {
      // Unknown kind: ignore defensively per SDD-028 §5 sanitiser stance.
      return null;
    }
    dispatch();
    return null;
  }

  void _apply(BackIntentDecision decision) {
    switch (decision) {
      case IgnoreDueToPtt():
        // SDD-028 §2.a — consume without UI effect.
        return;
      case CloseModal():
        _closeTopModal();
        return;
      case PopRoute():
        _popRoute();
        return;
      case ExitApp():
        // SDD-028 §2.d — defer to platform fallback.
        // ignore: discarded_futures
        _channel.invokeMethod<void>(backIntentMethodPopToSystem);
        return;
    }
  }

  static bool _alwaysFalse() => false;
  static void _noEffect() {}
}
