import 'dart:async';
import 'dart:io';

import 'package:camera/camera.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'package:qlyra/backend/modules/contacts.dart';
import 'package:qlyra/core/config/app_frost.dart';
import 'package:qlyra/core/config/app_nav_pill_style.dart';
import 'package:qlyra/core/config/app_visual_style.dart';
import 'package:qlyra/core/media/gallery_source.dart';
import 'package:qlyra/core/media/video_transcoder.dart';
import 'package:qlyra/core/utils/format.dart';
import 'package:qlyra/frontend/widgets/attachment/contact_picker_page.dart';
import 'package:qlyra/frontend/widgets/attachment/media_preview_screen.dart';
import 'package:qlyra/frontend/widgets/attachment/photo_editor.dart';
import 'package:qlyra/frontend/widgets/attachment/photo_hero.dart';
import 'package:qlyra/frontend/widgets/attachment/video_edit.dart';
import 'package:qlyra/frontend/widgets/attachment/video_preview_screen.dart';
import 'package:qlyra/frontend/widgets/custom_notification.dart';
import 'package:qlyra/frontend/widgets/sheet_helpers.dart';
import 'package:qlyra/frontend/widgets/sliding_pill_nav.dart';
import 'package:qlyra/l10n/app_localizations.dart';

import '../small_spinner.dart';

const int _navItemCount = 5;

List<PillNavItem> _buildNavItems(AppLocalizations l10n) => [
  PillNavItem(icon: Symbols.image, label: l10n.attachSheetGallery),
  PillNavItem(icon: Symbols.description, label: l10n.scheduledAttachFile),
  PillNavItem(icon: Symbols.location_on, label: l10n.scheduledAttachLocation),
  PillNavItem(icon: Symbols.bar_chart, label: l10n.attachSheetPoll),
  PillNavItem(icon: Symbols.person, label: l10n.attachSheetContact),
];

Future<void> showAttachmentSheet(
  BuildContext context, {
  String? title,
  void Function(List<PickedPhoto> photos, String caption)? onSend,
  VoidCallback? onPickFile,
  VoidCallback? onShareLocation,
  VoidCallback? onCreatePoll,
  ValueChanged<CachedContact>? onSendContact,
}) {
  return showModalBottomSheet<void>(
    context: context,
    isScrollControlled: true,
    requestFocus: false,
    backgroundColor: Colors.transparent,
    barrierColor: AppFrost.scrim(),
    builder: (_) => AttachmentSheet(
      title: title,
      onSend: onSend,
      onPickFile: onPickFile,
      onShareLocation: onShareLocation,
      onCreatePoll: onCreatePoll,
      onSendContact: onSendContact,
    ),
  );
}

class AttachmentSheet extends StatefulWidget {
  final String? title;
  final void Function(List<PickedPhoto> photos, String caption)? onSend;
  final VoidCallback? onPickFile;
  final VoidCallback? onShareLocation;
  final VoidCallback? onCreatePoll;
  final ValueChanged<CachedContact>? onSendContact;

  const AttachmentSheet({
    super.key,
    this.title,
    this.onSend,
    this.onPickFile,
    this.onShareLocation,
    this.onCreatePoll,
    this.onSendContact,
  });

  @override
  State<AttachmentSheet> createState() => _AttachmentSheetState();
}

class _AttachmentSheetState extends State<AttachmentSheet> {
  static const int _loadAhead = 24;

  static List<GalleryItem>? _cachedItems;
  static GalleryPermission _cachedPermission = GalleryPermission.granted;
  static bool _cachedHasMore = false;

  final GallerySource _source = GallerySource.create();
  final ValueNotifier<Set<String>> _selected = ValueNotifier(<String>{});
  final Map<String, GlobalKey<_ThumbnailState>> _thumbKeys = {};
  final Map<String, PhotoEditState> _edits = {};
  final Map<String, VideoEditState> _videoEdits = {};
  bool _videoEditorReady = false;
  bool _exporting = false;
  final Set<String> _tempFiles = {};
  final Set<String> _sentFiles = {};
  final TextEditingController _captionCtrl = TextEditingController();
  final PageController _pageController = PageController();

  bool _navDragging = false;
  double _navDragBasePageT = 0;
  double _navDragAccumDx = 0;

  bool _loading = true;
  bool _loadingMore = false;
  bool _hasMore = false;
  int _loadToken = 0;
  GalleryPermission _permission = GalleryPermission.granted;
  List<GalleryItem> _items = const [];

  @override
  void initState() {
    super.initState();
    final cached = _cachedItems;
    if (cached != null) {
      _items = cached;
      _permission = _cachedPermission;
      _hasMore = _cachedHasMore;
      _loading = false;
      _loadGallery(silent: true);
    } else {
      _loadGallery();
    }
    VideoTranscoder.ensureAvailable().then((ready) {
      if (mounted && ready) setState(() => _videoEditorReady = true);
    });
  }

  @override
  void dispose() {
    _pageController.dispose();
    _selected.dispose();
    _captionCtrl.dispose();
    for (final path in _tempFiles) {
      if (_sentFiles.contains(path)) continue;
      File(path).delete().then((_) {}, onError: (_) {});
    }
    super.dispose();
  }

  Future<void> _loadGallery({bool silent = false}) async {
    final token = ++_loadToken;
    if (!silent) setState(() => _loading = true);
    final permission = await _source.ensurePermission();
    if (!mounted || token != _loadToken) return;
    if (permission == GalleryPermission.denied) {
      _cachedItems = null;
      _cachedHasMore = false;
      setState(() {
        _permission = permission;
        _items = const [];
        _hasMore = false;
        _loading = false;
      });
      return;
    }
    final loaded = _items.length;
    final page = await _source.load(
      offset: 0,
      limit: loaded > GallerySource.pageSize ? loaded : GallerySource.pageSize,
    );
    if (!mounted || token != _loadToken) return;
    _permission = permission;
    _loading = false;
    _publishItems(page.items, page.hasMore);
  }

  Future<void> _loadMore() async {
    if (_loading || _loadingMore || !_hasMore) return;
    final token = _loadToken;
    final offset = _items.length;
    _loadingMore = true;
    final page = await _source.load(offset: offset);
    _loadingMore = false;
    if (!mounted || token != _loadToken || offset != _items.length) return;
    if (page.items.isEmpty) {
      _cachedHasMore = false;
      setState(() => _hasMore = false);
      return;
    }
    _publishItems([..._items, ...page.items], page.hasMore);
  }

  void _publishItems(List<GalleryItem> items, bool hasMore) {
    final seen = <String>{};
    final unique = [
      for (final item in items)
        if (seen.add(item.id)) item,
    ];
    _cachedItems = unique;
    _cachedPermission = _permission;
    _cachedHasMore = hasMore;
    setState(() {
      _items = unique;
      _hasMore = hasMore;
    });
  }

  void _toggleSelection(GalleryItem item) {
    final next = Set<String>.from(_selected.value);
    if (!next.remove(item.id)) next.add(item.id);
    _selected.value = next;
  }

  GlobalKey<_ThumbnailState> _thumbKey(String id) =>
      _thumbKeys.putIfAbsent(id, () => GlobalKey<_ThumbnailState>());

  void _openPreview(GalleryItem item) {
    final thumbKey = _thumbKey(item.id);
    final hero = PhotoHeroController(
      origin: () => photoHeroRect(thumbKey),
      image: thumbKey.currentState?.provider,
    );
    if (item.isVideo) {
      final edit = _videoEdits.putIfAbsent(item.id, VideoEditState.new);
      Navigator.of(context).push(
        PhotoHeroRoute<void>(
          hero: hero,
          builder: (_) => VideoPreviewScreen(
            item: item,
            hero: hero,
            title: widget.title,
            selectedIds: _selected,
            editable: _videoEditorReady,
            edit: edit,
            onToggleSelection: () => _toggleSelection(item),
            onSend: () => _sendSelection(fallback: item),
            onEditChanged: () {
              if (mounted) setState(() {});
            },
            initialCaption: _captionCtrl.text,
            onCaptionChanged: (text) => _captionCtrl.text = text,
          ),
        ),
      );
      return;
    }
    Navigator.of(context).push(
      PhotoHeroRoute<void>(
        hero: hero,
        builder: (_) => MediaPreviewScreen(
          item: item,
          hero: hero,
          title: widget.title,
          selectedIds: _selected,
          onToggleSelection: () => _toggleSelection(item),
          onSend: () => _sendSelection(fallback: item),
          editState: _edits[item.id],
          onEditChanged: (state) {
            if (mounted) setState(() => _edits[item.id] = state);
          },
          initialCaption: _captionCtrl.text,
          onCaptionChanged: (text) => _captionCtrl.text = text,
          tempFiles: _tempFiles,
        ),
      ),
    );
  }

  void _onSectionTap(int index) {
    _pageController.animateToPage(
      index,
      duration: _navAnim,
      curve: Curves.easeOutCubic,
    );
  }

  Future<void> _onCameraTap() async {
    final l10n = AppLocalizations.of(context)!;
    XFile? shot;
    try {
      shot = await ImagePicker().pickImage(source: ImageSource.camera);
    } catch (_) {
      if (mounted) showCustomNotification(context, l10n.attachSheetCameraError);
      return;
    }
    if (shot == null || !mounted) return;
    _openPreview(GalleryItem.fromFile(File(shot.path)));
  }

  Future<bool> _exportVideos(List<GalleryItem> chosen) async {
    final jobs = <(GalleryItem, VideoEditState)>[];
    for (final item in chosen) {
      if (!item.isVideo) continue;
      final edit = _videoEdits[item.id];
      if (edit != null && edit.hasEdits) jobs.add((item, edit));
    }
    if (jobs.isEmpty) return true;

    final progress = ValueNotifier<double>(0);
    final navigator = Navigator.of(context, rootNavigator: true);
    var cancelled = false;
    setState(() => _exporting = true);
    unawaited(
      showGeneralDialog<void>(
        context: context,
        barrierDismissible: false,
        barrierColor: Colors.black54,
        pageBuilder: (_, _, _) => _ExportProgress(
          progress: progress,
          onCancel: () {
            cancelled = true;
            VideoTranscoder.cancel();
          },
        ),
      ),
    );

    var ok = true;
    for (final (item, edit) in jobs) {
      final file = item.localFile ?? await item.originFile();
      if (file == null) {
        ok = false;
        break;
      }
      final info = await VideoTranscoder.probe(file.path);
      var source = info != null && info.width > 0 && info.height > 0
          ? Size(info.width.toDouble(), info.height.toDouble())
          : Size.zero;
      if (source.isEmpty) {
        final dims = await item.dimensions();
        if (dims == null) {
          ok = false;
          break;
        }
        source = Size(dims.$1.toDouble(), dims.$2.toDouble());
      }
      final signature = edit.signature(source);
      if (edit.exported != null && edit.exportedSignature == signature) {
        continue;
      }
      progress.value = 0;
      final spec = await buildVideoExportSpec(
        edit,
        file.path,
        source,
        info?.fps ?? 30,
      );
      if (spec == null) {
        ok = false;
        break;
      }
      final done = await VideoTranscoder.export(
        spec,
        onProgress: (value) => progress.value = value,
      );
      final overlay = spec.overlayPath;
      if (overlay != null) {
        File(overlay).delete().then((_) {}, onError: (_) {});
      }
      if (!done) {
        ok = false;
        break;
      }
      edit.exported = File(spec.output);
      edit.exportedSignature = signature;
      _tempFiles.add(spec.output);
    }

    progress.dispose();
    navigator.pop();
    if (!mounted) return false;
    setState(() => _exporting = false);
    if (!ok && !cancelled) {
      showCustomNotification(
        context,
        AppLocalizations.of(context)!.videoEditorExportFailed,
      );
    }
    return ok;
  }

  Future<void> _sendSelection({GalleryItem? fallback}) async {
    if (_exporting) return;
    final ids = _selected.value;
    var chosen = _items.where((it) => ids.contains(it.id)).toList();
    if (chosen.isEmpty && fallback != null) chosen = [fallback];
    if (chosen.isEmpty) return;
    if (!await _exportVideos(chosen) || !mounted) return;
    final picked = chosen
        .map(
          (it) => PickedPhoto(
            item: it,
            editedFile: it.isVideo
                ? _videoEdits[it.id]?.exported
                : _edits[it.id]?.working,
          ),
        )
        .toList();
    final callback = widget.onSend;
    if (callback != null) {
      for (final photo in picked) {
        final path = photo.editedFile?.path;
        if (path != null) _sentFiles.add(path);
      }
    }
    final caption = _captionCtrl.text.trim();
    Navigator.of(context).pop();
    callback?.call(picked, caption);
  }

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    return DraggableScrollableSheet(
      initialChildSize: 0.62,
      minChildSize: 0.4,
      maxChildSize: 0.94,
      expand: false,
      snap: true,
      snapSizes: const [0.62, 0.94],
      builder: (context, scrollController) {
        final bottomInset = MediaQuery.viewPaddingOf(context).bottom;
        final barReserve = _barHeight + bottomInset;
        return Container(
          decoration: BoxDecoration(
            color: cs.surfaceContainerLow,
            borderRadius: const BorderRadius.vertical(top: Radius.circular(24)),
          ),
          clipBehavior: Clip.antiAlias,
          child: Column(
            children: [
              const SheetGrabber(),
              Expanded(
                child: Stack(
                  children: [
                    _buildPages(scrollController, cs, barReserve),
                    Positioned(
                      left: 0,
                      right: 0,
                      bottom: 0,
                      child: _buildBottomBar(),
                    ),
                    Positioned(
                      right: 16,
                      bottom:
                          barReserve +
                          8 +
                          MediaQuery.viewInsetsOf(context).bottom,
                      child: AnimatedBuilder(
                        animation: Listenable.merge([
                          _selected,
                          _pageController,
                        ]),
                        builder: (context, _) {
                          final count = _selected.value.length;
                          final galleryT = (1 - _currentPageT()).clamp(
                            0.0,
                            1.0,
                          );
                          if (count == 0 || galleryT == 0) {
                            return const SizedBox.shrink();
                          }
                          return Opacity(
                            opacity: galleryT,
                            child: IgnorePointer(
                              ignoring: galleryT < 0.5,
                              child: _buildSendButton(cs),
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  static const double _pillMargin = 10;
  static const double _barHeight = SlidingPillNav.height + _pillMargin;
  static const Duration _navAnim = Duration(milliseconds: 300);

  Color _composerColor(ColorScheme cs) => Color.alphaBlend(
    cs.surfaceContainerHighest.withValues(alpha: 0.92),
    cs.surface,
  );

  Color _composerBorderColor(ColorScheme cs) =>
      cs.outlineVariant.withValues(alpha: 0.5);

  Widget _buildPages(
    ScrollController scrollController,
    ColorScheme cs,
    double bottomReserve,
  ) {
    final l10n = AppLocalizations.of(context)!;
    return PageView(
      controller: _pageController,
      children: [
        _KeepAlivePage(
          child: _buildGalleryPage(scrollController, cs, bottomReserve),
        ),
        _buildActionPage(
          cs,
          bottomReserve,
          icon: Symbols.description,
          title: l10n.attachSheetSendFileTitle,
          subtitle: l10n.attachSheetSendFileSubtitle,
          buttonLabel: l10n.attachSheetChooseFileButton,
          onTap: widget.onPickFile,
        ),
        _buildActionPage(
          cs,
          bottomReserve,
          icon: Symbols.location_on,
          title: l10n.attachSheetShareLocationTitle,
          subtitle: l10n.attachSheetShareLocationSubtitle,
          buttonLabel: l10n.attachSheetSendLocationButton,
          onTap: widget.onShareLocation,
        ),
        _buildActionPage(
          cs,
          bottomReserve,
          icon: Symbols.bar_chart,
          title: l10n.attachSheetCreatePoll,
          subtitle: l10n.attachSheetCreatePollSubtitle,
          buttonLabel: l10n.attachSheetCreatePoll,
          onTap: widget.onCreatePoll,
        ),
        _buildContactPage(cs, bottomReserve),
      ],
    );
  }

  Widget _buildContactPage(ColorScheme cs, double bottomReserve) {
    final onSendContact = widget.onSendContact;
    if (onSendContact == null) return _buildPlaceholderPage(cs, bottomReserve);
    return ContactPickerPage(
      bottomReserve: bottomReserve,
      onPick: onSendContact,
    );
  }

  Widget _buildActionPage(
    ColorScheme cs,
    double bottomReserve, {
    required IconData icon,
    required String title,
    required String subtitle,
    required String buttonLabel,
    required VoidCallback? onTap,
  }) {
    return Padding(
      padding: EdgeInsets.only(bottom: bottomReserve),
      child: Center(
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 32),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 72,
                height: 72,
                decoration: BoxDecoration(
                  color: cs.primaryContainer,
                  shape: BoxShape.circle,
                ),
                child: Icon(icon, size: 34, color: cs.onPrimaryContainer),
              ),
              const SizedBox(height: 16),
              Text(
                title,
                textAlign: TextAlign.center,
                style: TextStyle(
                  color: cs.onSurface,
                  fontSize: 17,
                  fontWeight: FontWeight.w600,
                ),
              ),
              const SizedBox(height: 6),
              Text(
                subtitle,
                textAlign: TextAlign.center,
                style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
              ),
              const SizedBox(height: 20),
              FilledButton(
                onPressed: onTap == null
                    ? null
                    : () {
                        Navigator.of(context).pop();
                        onTap();
                      },
                child: Text(buttonLabel),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildGalleryPage(
    ScrollController scrollController,
    ColorScheme cs,
    double bottomReserve,
  ) {
    if (_loading) {
      return Center(child: SmallSpinner(size: 36, color: cs.primary));
    }
    if (_permission == GalleryPermission.denied) {
      return _buildDenied(scrollController, cs, bottomReserve);
    }
    if (_items.isEmpty) {
      return _buildMessage(
        scrollController,
        cs,
        AppLocalizations.of(context)!.attachSheetNoImagesFound,
        bottomReserve,
      );
    }

    return LayoutBuilder(
      builder: (context, constraints) {
        const spacing = 2.0;
        const hpad = 2.0;
        final cell = (constraints.maxWidth - hpad * 2 - spacing * 2) / 3;
        final headerHeight = cell * 2 + spacing;
        final headerPhotos = _items.take(4).toList();
        final gridPhotos = _items.length > 4
            ? _items.sublist(4)
            : const <GalleryItem>[];

        return CustomScrollView(
          controller: scrollController,
          slivers: [
            if (_permission == GalleryPermission.limited)
              SliverToBoxAdapter(child: _buildLimitedBanner(cs)),
            SliverPadding(
              padding: const EdgeInsets.fromLTRB(hpad, hpad, hpad, 0),
              sliver: SliverToBoxAdapter(
                child: SizedBox(
                  height: headerHeight,
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      SizedBox(
                        width: cell,
                        child: _CameraTile(onTap: _onCameraTap, cs: cs),
                      ),
                      const SizedBox(width: spacing),
                      Expanded(child: _buildHeaderPhotos(headerPhotos, cs)),
                    ],
                  ),
                ),
              ),
            ),
            SliverPadding(
              padding: const EdgeInsets.fromLTRB(hpad, spacing, hpad, 0),
              sliver: SliverGrid(
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3,
                  mainAxisSpacing: spacing,
                  crossAxisSpacing: spacing,
                ),
                delegate: SliverChildBuilderDelegate((context, index) {
                  if (index >= gridPhotos.length - _loadAhead) {
                    unawaited(_loadMore());
                  }
                  final item = gridPhotos[index];
                  return _GalleryTile(
                    key: ValueKey(item.id),
                    thumbKey: _thumbKey(item.id),
                    item: item,
                    selectedIds: _selected,
                    onOpen: () => _openPreview(item),
                    onToggle: () => _toggleSelection(item),
                    editedFile: _edits[item.id]?.working,
                    cs: cs,
                  );
                }, childCount: gridPhotos.length),
              ),
            ),
            SliverToBoxAdapter(
              child: Column(
                children: [
                  if (_hasMore)
                    Padding(
                      padding: const EdgeInsets.symmetric(vertical: 14),
                      child: SmallSpinner(size: 22, color: cs.primary),
                    ),
                  SizedBox(height: bottomReserve + 6),
                ],
              ),
            ),
          ],
        );
      },
    );
  }

  Widget _buildHeaderPhotos(List<GalleryItem> photos, ColorScheme cs) {
    const spacing = 2.0;
    Widget tile(int i) {
      if (i >= photos.length) return const SizedBox.expand();
      final item = photos[i];
      return _GalleryTile(
        key: ValueKey(item.id),
        thumbKey: _thumbKey(item.id),
        item: item,
        selectedIds: _selected,
        onOpen: () => _openPreview(item),
        onToggle: () => _toggleSelection(item),
        editedFile: _edits[item.id]?.working,
        cs: cs,
      );
    }

    return Column(
      children: [
        Expanded(
          child: Row(
            children: [
              Expanded(child: tile(0)),
              const SizedBox(width: spacing),
              Expanded(child: tile(1)),
            ],
          ),
        ),
        const SizedBox(height: spacing),
        Expanded(
          child: Row(
            children: [
              Expanded(child: tile(2)),
              const SizedBox(width: spacing),
              Expanded(child: tile(3)),
            ],
          ),
        ),
      ],
    );
  }

  Widget _buildLimitedBanner(ColorScheme cs) {
    final l10n = AppLocalizations.of(context)!;
    return InkWell(
      onTap: () => _source.manageAccess().then((_) => _loadGallery()),
      child: Container(
        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
        color: cs.surfaceContainerHighest,
        child: Row(
          children: [
            Icon(Symbols.info, size: 18, color: cs.onSurfaceVariant),
            const SizedBox(width: 10),
            Expanded(
              child: Text(
                l10n.attachSheetLimitedAccessInfo,
                style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
              ),
            ),
            Text(
              l10n.loginEdit,
              style: TextStyle(
                color: cs.primary,
                fontSize: 13,
                fontWeight: FontWeight.w600,
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildPlaceholderPage(ColorScheme cs, double bottomReserve) {
    return Padding(
      padding: EdgeInsets.only(bottom: bottomReserve),
      child: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Symbols.construction, size: 48, color: cs.onSurfaceVariant),
            const SizedBox(height: 12),
            Text(
              AppLocalizations.of(context)!.attachSheetSectionInProgress,
              style: TextStyle(color: cs.onSurfaceVariant, fontSize: 15),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildDenied(
    ScrollController scrollController,
    ColorScheme cs,
    double bottomReserve,
  ) {
    final l10n = AppLocalizations.of(context)!;
    return _scrollableCenter(
      scrollController,
      bottomReserve,
      Padding(
        padding: const EdgeInsets.symmetric(horizontal: 32),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Symbols.no_photography, size: 48, color: cs.onSurfaceVariant),
            const SizedBox(height: 12),
            Text(
              l10n.attachSheetNoGalleryAccessTitle,
              textAlign: TextAlign.center,
              style: TextStyle(color: cs.onSurface, fontSize: 16),
            ),
            const SizedBox(height: 4),
            Text(
              l10n.attachSheetNoGalleryAccessSubtitle,
              textAlign: TextAlign.center,
              style: TextStyle(color: cs.onSurfaceVariant, fontSize: 13),
            ),
            const SizedBox(height: 16),
            Row(
              mainAxisSize: MainAxisSize.min,
              children: [
                TextButton(
                  onPressed: _loadGallery,
                  child: Text(l10n.attachSheetAllow),
                ),
                const SizedBox(width: 8),
                TextButton(
                  onPressed: () => _source.openSettings(),
                  child: Text(l10n.attachSheetSettings),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildMessage(
    ScrollController scrollController,
    ColorScheme cs,
    String text,
    double bottomReserve,
  ) {
    return _scrollableCenter(
      scrollController,
      bottomReserve,
      Text(text, style: TextStyle(color: cs.onSurfaceVariant, fontSize: 15)),
    );
  }

  Widget _scrollableCenter(
    ScrollController scrollController,
    double bottomReserve,
    Widget child,
  ) {
    return CustomScrollView(
      controller: scrollController,
      slivers: [
        SliverFillRemaining(
          hasScrollBody: false,
          child: Padding(
            padding: EdgeInsets.only(bottom: bottomReserve),
            child: Center(child: child),
          ),
        ),
      ],
    );
  }

  Widget _buildSendButton(ColorScheme cs) {
    return Material(
      color: cs.primary,
      shape: const StadiumBorder(),
      elevation: 3,
      child: InkWell(
        customBorder: const StadiumBorder(),
        onTap: () => _sendSelection(),
        child: Padding(
          padding: const EdgeInsets.all(14),
          child: Icon(Symbols.send, color: cs.onPrimary, size: 24, weight: 500),
        ),
      ),
    );
  }

  double _currentPageT() {
    if (!_pageController.hasClients) return 0;
    return _pageController.page ?? 0;
  }

  void _onPillDragStart() {
    _navDragging = true;
    _navDragBasePageT = _currentPageT();
    _navDragAccumDx = 0;
  }

  void _onPillDragUpdate(double dx, double inactiveWidth) {
    if (!_navDragging || !_pageController.hasClients) return;
    _navDragAccumDx += dx;
    final pageT = (_navDragBasePageT + _navDragAccumDx / inactiveWidth).clamp(
      0.0,
      (_navItemCount - 1).toDouble(),
    );
    _pageController.jumpTo(pageT * _pageController.position.viewportDimension);
  }

  void _onPillDragEnd() {
    if (!_navDragging) return;
    _navDragging = false;
    final target = _currentPageT().round().clamp(0, _navItemCount - 1);
    _pageController.animateToPage(
      target,
      duration: _navAnim,
      curve: Curves.easeOutCubic,
    );
  }

  Widget _buildBottomBar() {
    final inset = MediaQuery.viewInsetsOf(context).bottom;
    return Padding(
      padding: EdgeInsets.only(bottom: inset),
      child: SafeArea(
        top: false,
        child: Padding(
          padding: const EdgeInsets.fromLTRB(8, 0, 8, _pillMargin),
          child: ValueListenableBuilder<Set<String>>(
            valueListenable: _selected,
            builder: (context, selected, _) {
              return AnimatedSwitcher(
                duration: const Duration(milliseconds: 200),
                switchInCurve: Curves.easeOut,
                switchOutCurve: Curves.easeIn,
                child: selected.isEmpty
                    ? _buildPillNav()
                    : _buildCaptionBar(Theme.of(context).colorScheme),
              );
            },
          ),
        ),
      ),
    );
  }

  Widget _buildPillNav() {
    final navItems = _buildNavItems(AppLocalizations.of(context)!);
    return LayoutBuilder(
      key: const ValueKey('nav'),
      builder: (context, constraints) {
        final geometry = PillNavGeometry.fromInnerWidth(
          constraints.maxWidth - 4,
          navItems.length,
        );
        return GestureDetector(
          behavior: HitTestBehavior.opaque,
          onHorizontalDragStart: (_) => _onPillDragStart(),
          onHorizontalDragUpdate: (d) =>
              _onPillDragUpdate(d.delta.dx, geometry.inactiveWidth),
          onHorizontalDragEnd: (_) => _onPillDragEnd(),
          onHorizontalDragCancel: _onPillDragEnd,
          child: AnimatedBuilder(
            animation: _pageController,
            builder: (context, _) {
              final cs = Theme.of(context).colorScheme;
              return ValueListenableBuilder<VisualStyle>(
                valueListenable: AppVisualStyle.current,
                builder: (context, style, _) =>
                    ValueListenableBuilder<NavPillStyle>(
                      valueListenable: AppNavPillStyle.current,
                      builder: (context, navStyle, _) {
                        final frost =
                            style.glossyChrome &&
                            NavPillMaterial.isFrost(navStyle);
                        final liquid =
                            style.glossyChrome &&
                            NavPillMaterial.isLiquid(navStyle);
                        return SlidingPillNav(
                          items: navItems,
                          position: _currentPageT(),
                          geometry: geometry,
                          onTap: _onSectionTap,
                          backgroundColor: liquid
                              ? null
                              : (frost
                                    ? AppFrost.glassTint(cs)
                                    : _composerColor(cs)),
                          borderColor: _composerBorderColor(cs),
                        );
                      },
                    ),
              );
            },
          ),
        );
      },
    );
  }

  Widget _buildCaptionBar(ColorScheme cs) {
    final l10n = AppLocalizations.of(context)!;
    return SizedBox(
      key: const ValueKey('caption'),
      height: SlidingPillNav.height,
      child: Center(
        child: Container(
          height: 52,
          padding: const EdgeInsets.symmetric(horizontal: 20),
          decoration: BoxDecoration(
            color: _composerColor(cs),
            borderRadius: BorderRadius.circular(26),
            border: Border.all(color: _composerBorderColor(cs), width: 0.5),
          ),
          child: Row(
            children: [
              Expanded(
                child: TextField(
                  controller: _captionCtrl,
                  style: TextStyle(color: cs.onSurface, fontSize: 15),
                  cursorColor: cs.primary,
                  textCapitalization: TextCapitalization.sentences,
                  decoration: InputDecoration(
                    isCollapsed: true,
                    border: InputBorder.none,
                    hintText: l10n.attachSheetAddCaptionHint,
                    hintStyle: TextStyle(
                      color: cs.onSurfaceVariant,
                      fontSize: 15,
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _KeepAlivePage extends StatefulWidget {
  final Widget child;

  const _KeepAlivePage({required this.child});

  @override
  State<_KeepAlivePage> createState() => _KeepAlivePageState();
}

class _KeepAlivePageState extends State<_KeepAlivePage>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return widget.child;
  }
}

enum _CameraAccess { unknown, granted, denied }

class _CameraTile extends StatefulWidget {
  final VoidCallback onTap;
  final ColorScheme cs;

  const _CameraTile({required this.onTap, required this.cs});

  @override
  State<_CameraTile> createState() => _CameraTileState();
}

class _CameraTileState extends State<_CameraTile> with WidgetsBindingObserver {
  static const String _askedKey = 'qlyra_camera_permission_asked';

  CameraController? _controller;
  bool _starting = false;
  _CameraAccess _access = _CameraAccess.unknown;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _resolveAccess();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _resolveAccess();
    } else if (state == AppLifecycleState.inactive ||
        state == AppLifecycleState.paused) {
      _stopPreview();
    }
  }

  Future<void> _resolveAccess() async {
    if (!(Platform.isAndroid || Platform.isIOS)) return;

    var status = await Permission.camera.status;
    if (status.isDenied) {
      final prefs = await SharedPreferences.getInstance();
      if (!(prefs.getBool(_askedKey) ?? false)) {
        await prefs.setBool(_askedKey, true);
        status = await Permission.camera.request();
      }
    }
    if (!mounted) return;

    final granted = status.isGranted;
    setState(
      () => _access = granted ? _CameraAccess.granted : _CameraAccess.denied,
    );
    if (granted) _startPreview();
  }

  void _onTap() {
    if (_access == _CameraAccess.denied) {
      openAppSettings();
      return;
    }
    widget.onTap();
  }

  Future<void> _startPreview() async {
    if (_starting || _controller != null) return;
    _starting = true;
    try {
      final cameras = await availableCameras();
      if (cameras.isEmpty || !mounted) return;
      final back = cameras.firstWhere(
        (c) => c.lensDirection == CameraLensDirection.back,
        orElse: () => cameras.first,
      );
      final controller = CameraController(
        back,
        ResolutionPreset.low,
        enableAudio: false,
      );
      await controller.initialize();
      if (!mounted) {
        await controller.dispose();
        return;
      }
      setState(() => _controller = controller);
    } catch (_) {
    } finally {
      _starting = false;
    }
  }

  void _stopPreview() {
    final controller = _controller;
    if (controller == null) return;
    _controller = null;
    controller.dispose();
    if (mounted) setState(() {});
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _controller?.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final cs = widget.cs;
    final controller = _controller;
    final hasPreview = controller != null && controller.value.isInitialized;
    final denied = _access == _CameraAccess.denied;
    final l10n = AppLocalizations.of(context)!;

    return GestureDetector(
      onTap: _onTap,
      child: Stack(
        fit: StackFit.expand,
        children: [
          if (hasPreview)
            _buildPreview(controller)
          else
            Container(color: cs.surfaceContainerHighest),
          if (hasPreview)
            const DecoratedBox(
              decoration: BoxDecoration(
                gradient: LinearGradient(
                  begin: Alignment.topCenter,
                  end: Alignment.bottomCenter,
                  colors: [Color(0x00000000), Color(0x66000000)],
                ),
              ),
            ),
          Align(
            alignment: hasPreview ? Alignment.bottomLeft : Alignment.center,
            child: Padding(
              padding: const EdgeInsets.all(8),
              child: hasPreview
                  ? const Icon(
                      Symbols.photo_camera,
                      size: 22,
                      color: Colors.white,
                      weight: 500,
                    )
                  : Column(
                      mainAxisSize: MainAxisSize.min,
                      children: [
                        Icon(
                          denied
                              ? Symbols.no_photography
                              : Symbols.photo_camera,
                          size: 34,
                          color: cs.onSurface,
                          weight: 400,
                        ),
                        const SizedBox(height: 6),
                        Text(
                          denied
                              ? l10n.attachSheetCameraAllow
                              : l10n.attachSheetCamera,
                          textAlign: TextAlign.center,
                          style: TextStyle(
                            color: cs.onSurfaceVariant,
                            fontSize: 12,
                          ),
                        ),
                      ],
                    ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildPreview(CameraController controller) {
    final size = controller.value.previewSize;
    if (size == null) {
      return Container(color: widget.cs.surfaceContainerHighest);
    }
    return ClipRect(
      child: FittedBox(
        fit: BoxFit.cover,
        child: SizedBox(
          width: size.height,
          height: size.width,
          child: CameraPreview(controller),
        ),
      ),
    );
  }
}

class _GalleryTile extends StatefulWidget {
  final GlobalKey<_ThumbnailState> thumbKey;
  final GalleryItem item;
  final ValueListenable<Set<String>> selectedIds;
  final VoidCallback onOpen;
  final VoidCallback onToggle;
  final File? editedFile;
  final ColorScheme cs;

  const _GalleryTile({
    super.key,
    required this.thumbKey,
    required this.item,
    required this.selectedIds,
    required this.onOpen,
    required this.onToggle,
    this.editedFile,
    required this.cs,
  });

  @override
  State<_GalleryTile> createState() => _GalleryTileState();
}

class _GalleryTileState extends State<_GalleryTile> {
  late bool _selected;

  @override
  void initState() {
    super.initState();
    _selected = widget.selectedIds.value.contains(widget.item.id);
    widget.selectedIds.addListener(_onSelectionChanged);
  }

  @override
  void dispose() {
    widget.selectedIds.removeListener(_onSelectionChanged);
    super.dispose();
  }

  void _onSelectionChanged() {
    final selected = widget.selectedIds.value.contains(widget.item.id);
    if (selected != _selected) setState(() => _selected = selected);
  }

  @override
  Widget build(BuildContext context) {
    final item = widget.item;
    return GestureDetector(
      onTap: widget.onOpen,
      child: Stack(
        fit: StackFit.expand,
        children: [
          AnimatedScale(
            scale: _selected ? 0.86 : 1.0,
            duration: const Duration(milliseconds: 150),
            curve: Curves.easeOut,
            child: _Thumbnail(
              key: widget.thumbKey,
              item: item,
              editedFile: widget.editedFile,
              cs: widget.cs,
            ),
          ),
          if (item.isVideo)
            Positioned(
              left: 6,
              bottom: 6,
              child: Row(
                children: [
                  Icon(
                    Symbols.play_arrow,
                    size: 16,
                    color: Colors.white,
                    fill: 1,
                  ),
                  if (item.duration != null)
                    Text(
                      formatDurationMmSs(item.duration!),
                      style: const TextStyle(
                        color: Colors.white,
                        fontSize: 11,
                        fontWeight: FontWeight.w600,
                        shadows: [Shadow(blurRadius: 3, color: Colors.black54)],
                      ),
                    ),
                ],
              ),
            ),
          Positioned(
            top: 0,
            right: 0,
            child: GestureDetector(
              onTap: widget.onToggle,
              behavior: HitTestBehavior.opaque,
              child: Padding(
                padding: const EdgeInsets.all(6),
                child: ValueListenableBuilder<Set<String>>(
                  valueListenable: widget.selectedIds,
                  builder: (context, ids, _) {
                    final index = ids.toList().indexOf(widget.item.id);
                    return _SelectionCheck(
                      number: index >= 0 ? index + 1 : null,
                      cs: widget.cs,
                    );
                  },
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class _SelectionCheck extends StatelessWidget {
  final int? number;
  final ColorScheme cs;

  const _SelectionCheck({required this.number, required this.cs});

  @override
  Widget build(BuildContext context) {
    final selected = number != null;
    return Container(
      width: 24,
      height: 24,
      alignment: Alignment.center,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: selected ? cs.primary : Colors.black.withValues(alpha: 0.25),
        border: Border.all(color: Colors.white, width: 2),
      ),
      child: selected
          ? Text(
              '$number',
              style: TextStyle(
                color: cs.onPrimary,
                fontSize: 12,
                fontWeight: FontWeight.w700,
                height: 1.0,
              ),
            )
          : null,
    );
  }
}

class _Thumbnail extends StatefulWidget {
  final GalleryItem item;
  final File? editedFile;
  final ColorScheme cs;

  const _Thumbnail({
    super.key,
    required this.item,
    this.editedFile,
    required this.cs,
  });

  @override
  State<_Thumbnail> createState() => _ThumbnailState();
}

class _ThumbnailState extends State<_Thumbnail> {
  static const int _pixelSize = 320;
  ImageProvider? _provider;

  ImageProvider? get provider => _provider;

  @override
  void initState() {
    super.initState();
    _resolveProvider();
  }

  @override
  void didUpdateWidget(covariant _Thumbnail oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.editedFile?.path != oldWidget.editedFile?.path ||
        widget.item.id != oldWidget.item.id) {
      setState(_resolveProvider);
    }
  }

  void _resolveProvider() {
    final file =
        widget.editedFile ??
        (widget.item.isVideo ? null : widget.item.localFile);
    if (file != null) {
      _provider = ResizeImage(
        FileImage(file),
        width: _pixelSize,
        allowUpscaling: false,
      );
      return;
    }
    _provider = null;
    final id = widget.item.id;
    widget.item.thumbnail(_pixelSize).then((data) {
      if (!mounted || data == null || widget.item.id != id) return;
      if (widget.editedFile != null) return;
      setState(() => _provider = MemoryImage(data));
    });
  }

  @override
  Widget build(BuildContext context) {
    final provider = _provider;
    if (provider == null) return _placeholder();
    return Image(
      image: provider,
      fit: BoxFit.cover,
      gaplessPlayback: true,
      errorBuilder: (_, _, _) => _placeholder(),
    );
  }

  Widget _placeholder() => ColoredBox(
    color: widget.cs.surfaceContainerHighest,
    child: widget.item.isVideo
        ? Center(
            child: Icon(
              Symbols.movie,
              size: 28,
              color: widget.cs.onSurfaceVariant,
            ),
          )
        : null,
  );
}

class _ExportProgress extends StatelessWidget {
  final ValueListenable<double> progress;
  final VoidCallback onCancel;

  const _ExportProgress({required this.progress, required this.onCancel});

  @override
  Widget build(BuildContext context) {
    final cs = Theme.of(context).colorScheme;
    final l10n = AppLocalizations.of(context)!;
    return Center(
      child: Material(
        color: cs.surfaceContainerHigh,
        borderRadius: BorderRadius.circular(20),
        child: Padding(
          padding: const EdgeInsets.fromLTRB(24, 22, 24, 10),
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              ValueListenableBuilder<double>(
                valueListenable: progress,
                builder: (context, value, _) => SizedBox(
                  width: 46,
                  height: 46,
                  child: CircularProgressIndicator(
                    value: value <= 0 ? null : value,
                    strokeWidth: 3,
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Text(
                l10n.videoEditorProcessing,
                style: TextStyle(color: cs.onSurface, fontSize: 15),
              ),
              const SizedBox(height: 6),
              TextButton(
                onPressed: onCancel,
                child: Text(l10n.photoEditorCancel),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
