import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'app.dart';
import 'data/species_repository.dart';
import 'data/variety_repository.dart';
import 'di/injector.dart';
import 'i18n/strings.g.dart';
import 'services/auto_backup_service.dart';
import 'services/coarse_location.dart';
import 'services/inbox_service.dart';
import 'services/locale_store.dart';
import 'services/message_store.dart';
import 'services/notification_service.dart';
import 'services/offer_outbox.dart';
import 'services/onboarding_store.dart';
import 'services/profile_cache.dart';
import 'services/profile_store.dart';
import 'services/social_account_store.dart';
import 'services/social_connection.dart';
import 'services/social_service.dart';
import 'services/social_settings.dart';
import 'services/sync_service.dart';
import 'services/trust_referents.dart';
import 'services/wot_settings.dart';
import 'ui/theme.dart';

/// Boots the app WITHOUT blocking the first frame: paints a splash immediately,
/// then wires the encrypted DB + services and swaps in [TaneApp] once ready.
///
/// Before this, `main` awaited the whole of `configureDependencies()` (open the
/// encrypted DB, derive the social key, seed/scan the species catalog…) before
/// the first `runApp`, so nothing painted until init finished — the startup jank
/// visible as skipped frames. Now `main` runs `runApp` immediately with this
/// widget; the heavy work runs while the splash is already on screen.
class Bootstrap extends StatefulWidget {
  const Bootstrap({super.key});

  @override
  State<Bootstrap> createState() => _BootstrapState();
}

class _BootstrapState extends State<Bootstrap> {
  late Future<TaneApp> _app = _boot();

  Future<TaneApp> _boot() async {
    await configureDependencies();

    // The saved language lives in the keystore, so it can only be applied once
    // DI is up. Until now the splash showed in the device language (it has no
    // text), so there is no visible flip.
    final savedLocale = await getIt<LocaleStore>().saved();
    if (savedLocale != null) LocaleSettings.setLocaleSync(savedLocale);

    final onboarding = getIt<OnboardingStore>();
    final connection = getIt.isRegistered<SocialConnection>()
        ? getIt<SocialConnection>()
        : null;
    final inbox =
        getIt.isRegistered<InboxService>() ? getIt<InboxService>() : null;
    final notifications = getIt.isRegistered<NotificationService>()
        ? getIt<NotificationService>()
        : null;
    // Ask for notification permission and set up the OS channel (no-op on
    // unsupported platforms). Done before the inbox starts listening.
    if (notifications != null) await notifications.initialize();
    final sync =
        getIt.isRegistered<SyncService>() ? getIt<SyncService>() : null;
    // Subscribe the inbox + sync listeners BEFORE the shared connection starts
    // connecting, so the first session is caught; then bring the connection up.
    inbox?.start();
    sync?.start();
    connection?.start();

    return TaneApp(
      repository: getIt<VarietyRepository>(),
      species: getIt<SpeciesRepository>(),
      onboarding: onboarding,
      social:
          getIt.isRegistered<SocialService>() ? getIt<SocialService>() : null,
      socialSettings: getIt<SocialSettings>(),
      connection: connection,
      location: const GeolocatorCoarseLocation(),
      outbox: getIt<OfferOutbox>(),
      messageStore: getIt<MessageStore>(),
      profileStore: getIt<ProfileStore>(),
      profileCache: getIt<ProfileCache>(),
      socialAccounts: getIt<SocialAccountStore>(),
      trustReferents: getIt<TrustReferents>(),
      wotSettings: getIt<WotSettings>(),
      inbox: inbox,
      notifications: notifications,
      showIntro: !await onboarding.introSeen(),
      autoBackup: getIt.isRegistered<AutoBackupService>()
          ? getIt<AutoBackupService>()
          : null,
    );
  }

  // `configureDependencies` is idempotent (and self-heals a half-wired
  // container), so retrying after a failed boot is safe.
  void _retry() => setState(() => _app = _boot());

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<TaneApp>(
      future: _app,
      builder: (context, snapshot) {
        if (snapshot.hasData) return snapshot.data!;
        return BootstrapSplash(
          error: snapshot.hasError,
          onRetry: snapshot.hasError ? _retry : null,
        );
      },
    );
  }
}

/// The pre-init frame. A minimal [MaterialApp] so its text has a
/// `Directionality`, theme and localizations. Its background matches the native
/// splash colour ([seedGreen]) for a seamless handoff — one continuous green
/// screen from launch until the app is ready. Shows a spinner while booting and,
/// on [error], the failure message with a [onRetry] action.
class BootstrapSplash extends StatelessWidget {
  const BootstrapSplash({required this.error, this.onRetry, super.key});

  final bool error;
  final VoidCallback? onRetry;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: buildTaneTheme(),
      // Asturian (`ast`) has no bundled Material/Cupertino localizations, so map
      // it to Spanish for the framework chrome — same as the main app (app.dart).
      locale: materialLocaleFor(TranslationProvider.of(context).flutterLocale),
      supportedLocales: AppLocaleUtils.supportedLocales,
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      home: Scaffold(
        backgroundColor: seedGreen,
        body: Center(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Image.asset('assets/logo.png', width: 120, height: 120),
              const SizedBox(height: 32),
              if (error) ...[
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 32),
                  child: Text(
                    context.t.bootstrap.failed,
                    textAlign: TextAlign.center,
                    style: const TextStyle(color: Colors.white, fontSize: 16),
                  ),
                ),
                const SizedBox(height: 16),
                if (onRetry != null)
                  FilledButton(
                    onPressed: onRetry,
                    style: FilledButton.styleFrom(
                      backgroundColor: Colors.white,
                      foregroundColor: seedGreen,
                    ),
                    child: Text(context.t.bootstrap.retry),
                  ),
              ] else
                const SizedBox(
                  width: 28,
                  height: 28,
                  child: CircularProgressIndicator(
                    strokeWidth: 3,
                    valueColor: AlwaysStoppedAnimation(Colors.white),
                  ),
                ),
            ],
          ),
        ),
      ),
    );
  }
}
