// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:async';

import 'package:analysis_server/src/analysis_server.dart';
import 'package:analyzer/instrumentation/service.dart';
import 'package:analyzer/src/utilities/cancellation.dart';
import 'package:unified_analytics/unified_analytics.dart';

/// An interface for interacting with surveys via the unified_analytics package.
class SurveyManager {
  /// The period to wait after startup before first checking for surveys.
  ///
  /// This delay avoids showing too many prompts at startup (the IDE may prompt
  /// to fetch packages and the server may prompt for analytics or "dart fix").
  final Duration _initialDelay;

  /// The period to wait between checks for surveys.
  ///
  /// We check periodically (instead of only once) so that users who keep their
  /// IDEs open for long periods will still have survey checks periodically.
  final Duration _checkFrequency;

  /// The analytics object that provides access to surveys.
  final Analytics _analytics;

  final InstrumentationService _instrumentationService;

  /// The timer for triggering the next survey check.
  Timer? _timer;

  /// The analysis server that can show prompts to the user.
  final AnalysisServer _server;

  /// Tracks whether we've been asked to shutdown.
  ///
  /// This is used to prevent an in-progress async check from starting another
  /// timer if cancellation occurred while it was running.
  bool _isShutdown = false;

  new(
    this._server,
    this._instrumentationService,
    this._analytics, {
    // Delay the first check slightly because there are other prompts that
    // may appear at startup (fetching packages, analytics, "dart fix") that
    // we aren't coordinated with.
    this._initialDelay = const Duration(minutes: 5),
    this._checkFrequency = const Duration(hours: 24),
  }) {
    _timer = Timer(_initialDelay, checkForSurveys);
  }

  Future<void> checkForSurveys() async {
    try {
      // Ensure we can prompt the user and open web pages.
      var prompt = _server.userPromptSender;
      var uriOpener = _server.openUriNotificationSender;
      if (prompt == null || uriOpener == null) return;

      // Find the first survey to show.
      var surveys = await _analytics.fetchAvailableSurveys();
      var survey = surveys.firstOrNull;
      if (survey == null) return;

      // If we were shutdown during the above async request, skip any further
      // processing. We don't want to mark a survey as shown if we're shutting
      // down.
      if (_isShutdown) return;

      // Create a map of buttons by text because we only get the button text
      // back and we need the button to read the URL and record the interaction.
      var buttonMap = {
        for (var button in survey.buttonList) button.buttonText: button,
      };

      _analytics.surveyShown(survey);
      var clickedButtonText = await prompt(
        MessageType.info,
        survey.description,
        buttonMap.keys.toList(),
        NotCancelableToken(), // Not user-cancellable
      );
      var clickedButton = buttonMap[clickedButtonText];
      if (clickedButton == null) return;

      // Record that ths survey was interacted with so it's not shown again.
      _analytics.surveyInteracted(survey: survey, surveyButton: clickedButton);

      // If this button had a URL, open it. If not, it was probably a dismiss
      // or snooze button.
      var url = clickedButton.url;
      if (url != null) {
        await uriOpener(Uri.parse(url));
      }
    } catch (e) {
      _instrumentationService.logError('Failed to perform survey checks: $e');
    } finally {
      // Wait for the usual check period before checking again.
      if (!_isShutdown) {
        _timer = Timer(_checkFrequency, checkForSurveys);
      }
    }
  }

  void shutdown() {
    _isShutdown = true;
    _timer?.cancel();
  }
}
