import 'dart:ui';

import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc/search.dart';
import 'package:nc_photos/compute_queue.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/download_handler.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
import 'package:nc_photos/entity/search.dart';
import 'package:nc_photos/exception_util.dart' as exception_util;
import 'package:nc_photos/k.dart' as k;
import 'package:nc_photos/language_util.dart' as language_util;
import 'package:nc_photos/object_extension.dart';
import 'package:nc_photos/pref.dart';
import 'package:nc_photos/share_handler.dart';
import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart';
import 'package:nc_photos/throttler.dart';
import 'package:nc_photos/widget/animated_visibility.dart';
import 'package:nc_photos/widget/builder/photo_list_item_builder.dart';
import 'package:nc_photos/widget/handler/add_selection_to_album_handler.dart';
import 'package:nc_photos/widget/handler/archive_selection_handler.dart';
import 'package:nc_photos/widget/handler/remove_selection_handler.dart';
import 'package:nc_photos/widget/home_search_suggestion.dart';
import 'package:nc_photos/widget/page_visibility_mixin.dart';
import 'package:nc_photos/widget/photo_list_item.dart';
import 'package:nc_photos/widget/photo_list_util.dart' as photo_list_util;
import 'package:nc_photos/widget/search_landing.dart';
import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart';
import 'package:nc_photos/widget/selection_app_bar.dart';
import 'package:nc_photos/widget/viewer.dart';

class HomeSearch extends StatefulWidget {
  const HomeSearch({
    Key? key,
    required this.account,
  }) : super(key: key);

  @override
  createState() => _HomeSearchState();

  final Account account;
}

class _HomeSearchState extends State<HomeSearch>
    with
        SelectableItemStreamListMixin<HomeSearch>,
        RouteAware,
        PageVisibilityMixin {
  @override
  initState() {
    super.initState();
    _initBloc();
  }

  @override
  dispose() {
    _inputFocus.dispose();
    _inputController.dispose();
    super.dispose();
  }

  @override
  build(BuildContext context) {
    return BlocListener<SearchBloc, SearchBlocState>(
      bloc: _bloc,
      listener: (context, state) => _onStateChange(context, state),
      child: BlocBuilder<SearchBloc, SearchBlocState>(
        bloc: _bloc,
        builder: (context, state) => _buildContent(context, state),
      ),
    );
  }

  @override
  onItemTap(SelectableItem item, int index) {
    item.as<PhotoListFileItem>()?.run((fileItem) {
      Navigator.pushNamed(
        context,
        Viewer.routeName,
        arguments:
            ViewerArguments(widget.account, _backingFiles, fileItem.fileIndex),
      );
    });
  }

  void _initBloc() {
    if (_bloc.state is! SearchBlocInit) {
      // process the current state
      WidgetsBinding.instance.addPostFrameCallback((_) {
        setState(() {
          _onStateChange(context, _bloc.state);
        });
      });
    }
  }

  Widget _buildContent(BuildContext context, SearchBlocState state) {
    return WillPopScope(
      onWillPop: _onBackButtonPressed,
      child: Focus(
        focusNode: _stealFocus,
        child: Form(
          key: _formKey,
          child: Stack(
            children: [
              buildItemStreamListOuter(
                context,
                child: Stack(
                  children: [
                    CustomScrollView(
                      physics: _isSearchMode
                          ? const NeverScrollableScrollPhysics()
                          : null,
                      slivers: [
                        _buildAppBar(context, state),
                        if (_isShowLanding(state))
                          SliverToBoxAdapter(
                            child: SearchLanding(
                              account: widget.account,
                              onFavoritePressed: _onLandingFavoritePressed,
                              onVideoPressed: _onLandingVideoPressed,
                            ),
                          )
                        else if (state is SearchBlocSuccess &&
                            !_buildItemQueue.isProcessing &&
                            itemStreamListItems.isEmpty)
                          SliverToBoxAdapter(
                            child: Center(
                              child: Padding(
                                padding: const EdgeInsets.only(top: 24),
                                child: Text(L10n.global().listNoResultsText),
                              ),
                            ),
                          )
                        else
                          buildItemStreamList(
                            maxCrossAxisExtent: _thumbSize,
                          ),
                        SliverToBoxAdapter(
                          child: SizedBox(
                            height: _calcBottomAppBarExtent(context),
                          ),
                        ),
                      ],
                    ),
                    AnimatedVisibility(
                      opacity: _isSearchMode ? 1 : 0,
                      duration: k.animationDurationShort,
                      child: SafeArea(
                        left: false,
                        right: false,
                        bottom: false,
                        child: Padding(
                          padding: const EdgeInsets.only(top: kToolbarHeight),
                          child: Stack(
                            children: [
                              GestureDetector(
                                onTap: () {
                                  if (_isSearchMode) {
                                    setState(() {
                                      _setSearchMode(false);
                                    });
                                  }
                                },
                                child: Container(color: Colors.black54),
                              ),
                              _buildSearchPane(context, state),
                            ],
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
              Align(
                alignment: Alignment.bottomCenter,
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    if (state is SearchBlocLoading ||
                        _buildItemQueue.isProcessing)
                      const LinearProgressIndicator(),
                    SizedBox(
                      width: double.infinity,
                      height: _calcBottomAppBarExtent(context),
                      child: ClipRect(
                        child: BackdropFilter(
                          filter: Theme.of(context).appBarBlurFilter,
                          child: const ColoredBox(
                            color: Colors.transparent,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildAppBar(BuildContext context, SearchBlocState state) {
    if (isSelectionMode) {
      return _buildSelectionAppBar(context);
    } else {
      return _buildNormalAppBar(context, state);
    }
  }

  Widget _buildSelectionAppBar(BuildContext conetxt) {
    return SelectionAppBar(
      count: selectedListItems.length,
      onClosePressed: () {
        setState(() {
          clearSelectedItems();
        });
      },
      actions: [
        IconButton(
          icon: const Icon(Icons.share),
          tooltip: L10n.global().shareTooltip,
          onPressed: () => _onSelectionSharePressed(context),
        ),
        IconButton(
          icon: const Icon(Icons.add),
          tooltip: L10n.global().addToAlbumTooltip,
          onPressed: () => _onSelectionAddToAlbumPressed(context),
        ),
        PopupMenuButton<_SelectionMenuOption>(
          tooltip: MaterialLocalizations.of(context).moreButtonTooltip,
          itemBuilder: (context) => [
            PopupMenuItem(
              value: _SelectionMenuOption.download,
              child: Text(L10n.global().downloadTooltip),
            ),
            PopupMenuItem(
              value: _SelectionMenuOption.archive,
              child: Text(L10n.global().archiveTooltip),
            ),
            PopupMenuItem(
              value: _SelectionMenuOption.delete,
              child: Text(L10n.global().deleteTooltip),
            ),
          ],
          onSelected: (option) => _onSelectionMenuSelected(context, option),
        ),
      ],
    );
  }

  Widget _buildNormalAppBar(BuildContext context, SearchBlocState state) {
    return SliverAppBar(
      automaticallyImplyLeading: false,
      floating: true,
      snap: true,
      title: Focus(
        onFocusChange: (hasFocus) {
          if (hasFocus && !_isSearchMode) {
            setState(() {
              _setSearchMode(true);
            });
          }
        },
        child: TextFormField(
          focusNode: _inputFocus,
          controller: _inputController,
          decoration: InputDecoration(
            hintText: L10n.global().searchTooltip,
          ),
          onFieldSubmitted: (_) {
            _onSearchPressed();
          },
          onSaved: (value) {
            _formValue?.input = value ?? "";
          },
          onChanged: (value) {
            _searchSuggestionThrottler.trigger(
              maxResponceTime: const Duration(milliseconds: 500),
              maxPendingCount: 8,
              data: value,
            );
          },
        ),
      ),
      actions: [
        IconButton(
          onPressed: _onSearchPressed,
          tooltip: L10n.global().searchTooltip,
          icon: const Icon(Icons.search),
        ),
      ],
      bottom: _isShowLanding(state)
          ? null
          : PreferredSize(
              preferredSize: const Size.fromHeight(40),
              child: SizedBox(
                height: 40,
                child: Align(
                  alignment: AlignmentDirectional.centerStart,
                  child: _FilterBubbleList(
                    filters: state.criteria.filters,
                    onEditPressed: () => _onEditFilterPressed(state),
                  ),
                ),
              ),
            ),
    );
  }

  Widget _buildSearchPane(BuildContext context, SearchBlocState state) {
    return Align(
      alignment: Alignment.topCenter,
      child: ColoredBox(
        color: Theme.of(context).colorScheme.background,
        child: SingleChildScrollView(
          child: HomeSearchSuggestion(
            account: widget.account,
            controller: _searchSuggestionController,
          ),
        ),
      ),
    );
  }

  void _onStateChange(BuildContext context, SearchBlocState state) {
    if (state is SearchBlocInit) {
      itemStreamListItems = [];
    } else if (state is SearchBlocSuccess || state is SearchBlocLoading) {
      _transformItems(state.items);
    } else if (state is SearchBlocFailure) {
      _transformItems(state.items);
      if (isPageVisible()) {
        SnackBarManager().showSnackBar(SnackBar(
          content: Text(exception_util.toUserString(state.exception)),
          duration: k.snackBarDurationNormal,
        ));
      }
    } else if (state is SearchBlocInconsistent) {
      _reqQuery(_activeInput, _activeFilters);
    }
  }

  Future<bool> _onBackButtonPressed() async {
    if (_isSearchMode) {
      setState(() {
        _setSearchMode(false);
      });
      return false;
    } else if (_bloc.state is! SearchBlocInit) {
      // back to landing
      _reqResetLanding();
      setState(() {
        _activeInput = "";
        _activeFilters = [];
        _inputController.text = "";
        _searchSuggestionController.search("");
      });
      return false;
    } else {
      return true;
    }
  }

  void _onSearchPressed() {
    if (_formKey.currentState?.validate() == true) {
      _formValue = _FormValue();
      _formKey.currentState!.save();
      _activeInput = _formValue!.input;

      setState(() {
        _setSearchMode(false);
      });
      _reqQuery(_activeInput, _activeFilters);
    }
  }

  void _onLandingFavoritePressed() {
    _activeFilters = [
      const SearchFavoriteFilter(true),
    ];
    _reqQuery(_activeInput, _activeFilters);
  }

  void _onLandingVideoPressed() {
    _activeFilters = [
      const SearchFileTypeFilter(SearchFileType.video),
    ];
    _reqQuery(_activeInput, _activeFilters);
  }

  Future<void> _onEditFilterPressed(SearchBlocState state) async {
    final result = await showDialog<List<SearchFilter>>(
      context: context,
      builder: (context) => _FilterEditDialog(searchState: state),
    );
    if (result == null) {
      return;
    }
    _activeFilters = result;

    _reqQuery(_activeInput, _activeFilters);
  }

  void _onSelectionMenuSelected(
      BuildContext context, _SelectionMenuOption option) {
    switch (option) {
      case _SelectionMenuOption.archive:
        _onSelectionArchivePressed(context);
        break;
      case _SelectionMenuOption.delete:
        _onSelectionDeletePressed(context);
        break;
      case _SelectionMenuOption.download:
        _onSelectionDownloadPressed();
        break;
      default:
        _log.shout("[_onSelectionMenuSelected] Unknown option: $option");
        break;
    }
  }

  void _onSelectionSharePressed(BuildContext context) {
    final c = KiwiContainer().resolve<DiContainer>();
    final selected = selectedListItems
        .whereType<PhotoListFileItem>()
        .map((e) => e.file)
        .toList();
    ShareHandler(
      c,
      context: context,
      clearSelection: () {
        setState(() {
          clearSelectedItems();
        });
      },
    ).shareFiles(widget.account, selected);
  }

  Future<void> _onSelectionAddToAlbumPressed(BuildContext context) {
    final c = KiwiContainer().resolve<DiContainer>();
    return AddSelectionToAlbumHandler(c)(
      context: context,
      account: widget.account,
      selection: selectedListItems
          .whereType<PhotoListFileItem>()
          .map((e) => e.file)
          .toList(),
      clearSelection: () {
        if (mounted) {
          setState(() {
            clearSelectedItems();
          });
        }
      },
    );
  }

  void _onSelectionDownloadPressed() {
    final c = KiwiContainer().resolve<DiContainer>();
    final selected = selectedListItems
        .whereType<PhotoListFileItem>()
        .map((e) => e.file)
        .toList();
    DownloadHandler(c).downloadFiles(widget.account, selected);
    setState(() {
      clearSelectedItems();
    });
  }

  Future<void> _onSelectionArchivePressed(BuildContext context) async {
    final c = KiwiContainer().resolve<DiContainer>();
    final selectedFiles = selectedListItems
        .whereType<PhotoListFileItem>()
        .map((e) => e.file)
        .toList();
    setState(() {
      clearSelectedItems();
    });
    await ArchiveSelectionHandler(c)(
      account: widget.account,
      selection: selectedFiles,
    );
  }

  Future<void> _onSelectionDeletePressed(BuildContext context) async {
    final c = KiwiContainer().resolve<DiContainer>();
    final selectedFiles = selectedListItems
        .whereType<PhotoListFileItem>()
        .map((e) => e.file)
        .toList();
    setState(() {
      clearSelectedItems();
    });
    await RemoveSelectionHandler(c)(
      account: widget.account,
      selection: selectedFiles,
      isMoveToTrash: true,
    );
  }

  void _transformItems(List<File> files) {
    _buildItemQueue.addJob(
      PhotoListItemBuilderArguments(
        widget.account,
        files,
        sorter: photoListFileDateTimeSorter,
        grouper: PhotoListFileDateGrouper(isMonthOnly: _thumbZoomLevel < 0),
        shouldShowFavoriteBadge: true,
        locale: language_util.getSelectedLocale() ??
            PlatformDispatcher.instance.locale,
      ),
      buildPhotoListItem,
      (result) {
        if (mounted) {
          setState(() {
            _backingFiles = result.backingFiles;
            itemStreamListItems = result.listItems;
          });
        }
      },
    );
  }

  void _reqQuery(String input, List<SearchFilter> filters) {
    _bloc.add(SearchBlocQuery(widget.account, SearchCriteria(input, filters)));
  }

  void _reqResetLanding() {
    _bloc.add(SearchBlocResetLanding(widget.account));
  }

  void _setSearchMode(bool value) {
    _isSearchMode = value;
    if (value) {
      _inputFocus.requestFocus();
    } else {
      _inputController.text = _activeInput;
      _searchSuggestionController.search(_activeInput);
      _stealFocus.requestFocus();
    }
  }

  double _calcBottomAppBarExtent(BuildContext context) =>
      NavigationBarTheme.of(context).height!;

  bool _isShowLanding(SearchBlocState state) => state is SearchBlocInit;

  late final _bloc = SearchBloc(KiwiContainer().resolve<DiContainer>());

  final _formKey = GlobalKey<FormState>();
  _FormValue? _formValue;
  final _inputController = TextEditingController();
  final _inputFocus = FocusNode();
  // used to steal focus from input field
  final _stealFocus = FocusNode();
  var _isSearchMode = false;

  var _activeInput = "";
  var _activeFilters = <SearchFilter>[];

  final _searchSuggestionController = HomeSearchSuggestionController();
  late final _searchSuggestionThrottler = Throttler<String>(
    onTriggered: (data) {
      _searchSuggestionController.search(data.last);
    },
  );

  final _buildItemQueue =
      ComputeQueue<PhotoListItemBuilderArguments, PhotoListItemBuilderResult>();

  late final _thumbZoomLevel = Pref().getHomePhotosZoomLevelOr(0);
  late final _thumbSize =
      photo_list_util.getThumbSize(_thumbZoomLevel).toDouble();

  var _backingFiles = <FileDescriptor>[];

  static final _log = Logger("widget.home_search._HomeSearchState");
}

class _FormValue {
  String input = "";
}

extension on SearchFileType {
  String toUserString() {
    switch (this) {
      case SearchFileType.image:
        return L10n.global().searchFilterTypeOptionImageLabel;

      case SearchFileType.video:
        return L10n.global().searchFilterTypeOptionVideoLabel;
    }
  }
}

class _FilterBubbleList extends StatelessWidget {
  const _FilterBubbleList({
    Key? key,
    required this.filters,
    this.onEditPressed,
  }) : super(key: key);

  @override
  build(BuildContext context) {
    return Align(
      alignment: AlignmentDirectional.centerStart,
      child: Row(
        children: [
          Expanded(
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              child: Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: [
                  const SizedBox(width: 16),
                  ...filters
                      .map((f) => _buildBubble(context, _toUserString(f))),
                  const SizedBox(width: 8),
                ],
              ),
            ),
          ),
          TextButton(
            onPressed: onEditPressed,
            child: Text(L10n.global().searchFilterButtonLabel),
          ),
        ],
      ),
    );
  }

  Widget _buildBubble(BuildContext context, String label) {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 4),
      child: FilterChip(
        elevation: 1,
        pressElevation: 1,
        showCheckmark: false,
        visualDensity: VisualDensity.compact,
        selected: true,
        selectedColor:
            Theme.of(context).elevate(Theme.of(context).colorScheme.surface, 5),
        label: Text(label),
        onSelected: (_) {},
      ),
    );
  }

  String _toUserString(SearchFilter filter) {
    if (filter is SearchFileTypeFilter) {
      switch (filter.type) {
        case SearchFileType.image:
          return L10n.global().searchFilterBubbleTypeImageText;

        case SearchFileType.video:
          return L10n.global().searchFilterBubbleTypeVideoText;
      }
    } else if (filter is SearchFavoriteFilter) {
      return filter.value
          ? L10n.global().searchFilterBubbleFavoriteTrueText
          : L10n.global().searchFilterBubbleFavoriteFalseText;
    }
    throw ArgumentError.value(filter, "filter");
  }

  final List<SearchFilter> filters;
  final VoidCallback? onEditPressed;
}

class _FilterEditDialog extends StatefulWidget {
  const _FilterEditDialog({
    Key? key,
    required this.searchState,
  }) : super(key: key);

  @override
  createState() => _FilterEditDialogState();

  final SearchBlocState searchState;
}

class _FilterEditDialogState extends State<_FilterEditDialog> {
  @override
  build(BuildContext context) {
    return Form(
      key: _formKey,
      child: AlertDialog(
        title: Text(L10n.global().searchFilterDialogTitle),
        content: SingleChildScrollView(
          child: Column(
            children: [
              _FilterDropdown<SearchFileType>(
                label: L10n.global().searchFilterTypeLabel,
                items: SearchFileType.values,
                itemStringifier: (item) => item.toUserString(),
                initialValue: widget.searchState.criteria.filters
                    .whereType<SearchFileTypeFilter>()
                    .firstOrNull
                    ?.type,
                onSaved: (value) {
                  if (value != null) {
                    _formValue?.filters.add(SearchFileTypeFilter(value));
                  }
                },
              ),
              const SizedBox(height: 8),
              _FilterDropdown<bool>(
                label: L10n.global().searchFilterFavoriteLabel,
                items: const [true, false],
                itemStringifier: (item) => item
                    ? L10n.global().searchFilterOptionTrueLabel
                    : L10n.global().searchFilterOptionFalseLabel,
                initialValue: widget.searchState.criteria.filters
                    .whereType<SearchFavoriteFilter>()
                    .firstOrNull
                    ?.value,
                onSaved: (value) {
                  if (value != null) {
                    _formValue?.filters.add(SearchFavoriteFilter(value));
                  }
                },
              ),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: _onApplyPressed,
            child: Text(L10n.global().applyButtonLabel),
          ),
        ],
      ),
    );
  }

  void _onApplyPressed() {
    if (_formKey.currentState?.validate() == true) {
      _formValue = _FilterEditFormValue();
      _formKey.currentState!.save();
      Navigator.of(context).pop(_formValue!.filters);
    }
  }

  final _formKey = GlobalKey<FormState>();
  _FilterEditFormValue? _formValue;
}

class _FilterEditFormValue {
  final filters = <SearchFilter>[];
}

class _FilterDropdown<T> extends StatefulWidget {
  const _FilterDropdown({
    Key? key,
    required this.label,
    required this.items,
    required this.itemStringifier,
    this.initialValue,
    this.onValueChanged,
    this.onSaved,
  }) : super(key: key);

  @override
  createState() => _FilterDropdownState<T>();

  final String label;
  final List<T> items;
  final String Function(T item) itemStringifier;
  final T? initialValue;
  final ValueChanged<T?>? onValueChanged;
  final FormFieldSetter<T>? onSaved;
}

class _FilterDropdownState<T> extends State<_FilterDropdown<T>> {
  @override
  initState() {
    super.initState();
    _value = widget.initialValue;
  }

  @override
  build(BuildContext context) {
    return Row(
      children: [
        Expanded(
          child: Text(
            widget.label,
            style: TextStyle(
              color: Theme.of(context).colorScheme.onSurfaceVariant,
            ),
          ),
        ),
        Expanded(
          child: DropdownButtonFormField<T>(
            value: _value,
            items: [
              DropdownMenuItem(
                value: null,
                child: Text(L10n.global().searchFilterOptionAnyLabel),
              ),
              ...widget.items.map((e) => DropdownMenuItem(
                    value: e,
                    child: Text(widget.itemStringifier(e)),
                  )),
            ],
            onChanged: (value) {
              setState(() {
                _value = value;
              });
              widget.onValueChanged?.call(_value);
            },
            onSaved: widget.onSaved,
          ),
        ),
      ],
    );
  }

  T? _value;
}

enum _SelectionMenuOption {
  archive,
  delete,
  download,
}