// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';

import '../../data/gallery_options.dart';
import '../../gallery_localizations.dart';
import '../../layout/adaptive.dart';
import '../../layout/image_placeholder.dart';
import 'backlayer.dart';
import 'border_tab_indicator.dart';
import 'colors.dart';
import 'header_form.dart';
import 'item_cards.dart';
import 'model/data.dart';
import 'model/destination.dart';

class _FrontLayer extends StatefulWidget {
  const _FrontLayer({
    required this.title,
    required this.index,
    required this.mobileTopOffset,
    required this.restorationId,
  });

  final String title;
  final int index;
  final double mobileTopOffset;
  final String restorationId;

  @override
  _FrontLayerState createState() => _FrontLayerState();
}

class _FrontLayerState extends State<_FrontLayer> {
  List<Destination>? destinations;

  static const double frontLayerBorderRadius = 16.0;
  static const EdgeInsets bottomPadding = EdgeInsets.only(bottom: 120);

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // We use didChangeDependencies because the initialization involves an
    // InheritedWidget (for localization). However, we don't need to get
    // destinations again when, say, resizing the window.
    if (destinations == null) {
      if (widget.index == 0) {
        destinations = getFlyDestinations(context);
      }
      if (widget.index == 1) {
        destinations = getSleepDestinations(context);
      }
      if (widget.index == 2) {
        destinations = getEatDestinations(context);
      }
    }
  }

  Widget _header() {
    return Align(
      alignment: AlignmentDirectional.centerStart,
      child: Padding(
        padding: const EdgeInsets.only(
          top: 20,
          bottom: 22,
        ),
        child: SelectableText(
          widget.title,
          style: Theme.of(context).textTheme.titleSmall,
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final bool isDesktop = isDisplayDesktop(context);
    final bool isSmallDesktop = isDisplaySmallDesktop(context);
    final int crossAxisCount = isDesktop ? 4 : 1;

    return FocusTraversalGroup(
      policy: ReadingOrderTraversalPolicy(),
      child: Padding(
        padding: isDesktop
            ? EdgeInsets.zero
            : EdgeInsets.only(top: widget.mobileTopOffset),
        child: PhysicalShape(
          elevation: 16,
          color: cranePrimaryWhite,
          clipper: const ShapeBorderClipper(
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.only(
                topLeft: Radius.circular(frontLayerBorderRadius),
                topRight: Radius.circular(frontLayerBorderRadius),
              ),
            ),
          ),
          child: Padding(
            padding: isDesktop
                ? EdgeInsets.symmetric(
                        horizontal:
                            isSmallDesktop ? appPaddingSmall : appPaddingLarge)
                    .add(bottomPadding)
                : const EdgeInsets.symmetric(horizontal: 20).add(bottomPadding),
            child: Column(
              children: <Widget>[
                _header(),
                Expanded(
                  child: MasonryGridView.count(
                    key: ValueKey<String>('CraneListView-${widget.index}'),
                    restorationId: widget.restorationId,
                    crossAxisCount: crossAxisCount,
                    crossAxisSpacing: 16.0,
                    itemBuilder: (BuildContext context, int index) =>
                        DestinationCard(destination: destinations![index]),
                    itemCount: destinations!.length,
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

/// Builds a Backdrop.
///
/// A Backdrop widget has two layers, front and back. The front layer is shown
/// by default, and slides down to show the back layer, from which a user
/// can make a selection. The user can also configure the titles for when the
/// front or back layer is showing.
class Backdrop extends StatefulWidget {

  const Backdrop({
    super.key,
    required this.frontLayer,
    required this.backLayerItems,
    required this.frontTitle,
    required this.backTitle,
  });
  final Widget frontLayer;
  final List<BackLayerItem> backLayerItems;
  final Widget frontTitle;
  final Widget backTitle;

  @override
  State<Backdrop> createState() => _BackdropState();
}

class _BackdropState extends State<Backdrop>
    with TickerProviderStateMixin, RestorationMixin {
  final RestorableInt tabIndex = RestorableInt(0);
  late TabController _tabController;
  late Animation<Offset> _flyLayerHorizontalOffset;
  late Animation<Offset> _sleepLayerHorizontalOffset;
  late Animation<Offset> _eatLayerHorizontalOffset;

  // How much the 'sleep' front layer is vertically offset relative to other
  // front layers, in pixels, with the mobile layout.
  static const double _sleepLayerTopOffset = 60.0;

  @override
  String get restorationId => 'tab_non_scrollable_demo';

  @override
  void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
    registerForRestoration(tabIndex, 'tab_index');
    _tabController.index = tabIndex.value;
  }

  @override
  void initState() {
    super.initState();
    _tabController = TabController(length: 3, vsync: this);
    _tabController.addListener(() {
      // When the tab controller's value is updated, make sure to update the
      // tab index value, which is state restorable.
      setState(() {
        tabIndex.value = _tabController.index;
      });
    });

    // Offsets to create a horizontal gap between front layers.
    final Animation<double> tabControllerAnimation = _tabController.animation!;

    _flyLayerHorizontalOffset = tabControllerAnimation.drive(
        Tween<Offset>(begin: Offset.zero, end: const Offset(-0.05, 0)));

    _sleepLayerHorizontalOffset = tabControllerAnimation.drive(
        Tween<Offset>(begin: const Offset(0.05, 0), end: Offset.zero));

    _eatLayerHorizontalOffset = tabControllerAnimation.drive(Tween<Offset>(
        begin: const Offset(0.10, 0), end: const Offset(0.05, 0)));
  }

  @override
  void dispose() {
    _tabController.dispose();
    tabIndex.dispose();
    super.dispose();
  }

  void _handleTabs(int tabIndex) {
    _tabController.animateTo(tabIndex,
        duration: const Duration(milliseconds: 300));
  }

  @override
  Widget build(BuildContext context) {
    final bool isDesktop = isDisplayDesktop(context);
    final double textScaleFactor = GalleryOptions.of(context).textScaleFactor(context);
    final GalleryLocalizations localizations = GalleryLocalizations.of(context)!;
    return Material(
      color: cranePurple800,
      child: Padding(
        padding: const EdgeInsets.only(top: 12),
        child: FocusTraversalGroup(
          policy: ReadingOrderTraversalPolicy(),
          child: Scaffold(
            backgroundColor: cranePurple800,
            appBar: AppBar(
              automaticallyImplyLeading: false,
              systemOverlayStyle: SystemUiOverlayStyle.light,
              elevation: 0,
              titleSpacing: 0,
              flexibleSpace: CraneAppBar(
                tabController: _tabController,
                tabHandler: _handleTabs,
              ),
            ),
            body: Stack(
              children: <Widget>[
                BackLayer(
                  tabController: _tabController,
                  backLayerItems: widget.backLayerItems,
                ),
                Container(
                  margin: EdgeInsets.only(
                    top: isDesktop
                        ? (isDisplaySmallDesktop(context)
                                ? textFieldHeight * 3
                                : textFieldHeight * 2) +
                            20 * textScaleFactor / 2
                        : 175 + 140 * textScaleFactor / 2,
                  ),
                  // To display the middle front layer higher than the others,
                  // we allow the TabBarView to overflow by an offset
                  // (doubled because it technically overflows top & bottom).
                  // The other front layers are top padded by this offset.
                  child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
                    return OverflowBox(
                      maxHeight:
                          constraints.maxHeight + _sleepLayerTopOffset * 2,
                      child: TabBarView(
                        physics: isDesktop
                            ? const NeverScrollableScrollPhysics()
                            : null, // use default TabBarView physics
                        controller: _tabController,
                        children: <Widget>[
                          SlideTransition(
                            position: _flyLayerHorizontalOffset,
                            child: _FrontLayer(
                              title: localizations.craneFlySubhead,
                              index: 0,
                              mobileTopOffset: _sleepLayerTopOffset,
                              restorationId: 'fly-subhead',
                            ),
                          ),
                          SlideTransition(
                            position: _sleepLayerHorizontalOffset,
                            child: _FrontLayer(
                              title: localizations.craneSleepSubhead,
                              index: 1,
                              mobileTopOffset: 0,
                              restorationId: 'sleep-subhead',
                            ),
                          ),
                          SlideTransition(
                            position: _eatLayerHorizontalOffset,
                            child: _FrontLayer(
                              title: localizations.craneEatSubhead,
                              index: 2,
                              mobileTopOffset: _sleepLayerTopOffset,
                              restorationId: 'eat-subhead',
                            ),
                          ),
                        ],
                      ),
                    );
                  }),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class CraneAppBar extends StatefulWidget {

  const CraneAppBar({
    super.key,
    this.tabHandler,
    required this.tabController,
  });
  final void Function(int)? tabHandler;
  final TabController tabController;

  @override
  State<CraneAppBar> createState() => _CraneAppBarState();
}

class _CraneAppBarState extends State<CraneAppBar> {
  @override
  Widget build(BuildContext context) {
    final bool isDesktop = isDisplayDesktop(context);
    final bool isSmallDesktop = isDisplaySmallDesktop(context);
    final double textScaleFactor = GalleryOptions.of(context).textScaleFactor(context);
    final GalleryLocalizations localizations = GalleryLocalizations.of(context)!;

    return SafeArea(
      child: Padding(
        padding: EdgeInsets.symmetric(
          horizontal:
              isDesktop && !isSmallDesktop ? appPaddingLarge : appPaddingSmall,
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: <Widget>[
            const ExcludeSemantics(
              child: FadeInImagePlaceholder(
                image: AssetImage(
                  'crane/logo/logo.png',
                  package: 'flutter_gallery_assets',
                ),
                placeholder: SizedBox(
                  width: 40,
                  height: 60,
                ),
                width: 40,
                height: 60,
              ),
            ),
            Expanded(
              child: Padding(
                padding: const EdgeInsetsDirectional.only(start: 24),
                child: Theme(
                  data: Theme.of(context).copyWith(
                    splashColor: Colors.transparent,
                  ),
                  child: TabBar(
                    indicator: BorderTabIndicator(
                      indicatorHeight: isDesktop ? 28 : 32,
                      textScaleFactor: textScaleFactor,
                    ),
                    controller: widget.tabController,
                    labelPadding: const EdgeInsets.symmetric(horizontal: 32),
                    isScrollable: true,
                    // left-align tabs on desktop
                    labelStyle: Theme.of(context).textTheme.labelLarge,
                    labelColor: cranePrimaryWhite,
                    physics: const BouncingScrollPhysics(),
                    unselectedLabelColor: cranePrimaryWhite.withOpacity(.6),
                    onTap: (int index) => widget.tabController.animateTo(
                      index,
                      duration: const Duration(milliseconds: 300),
                    ),
                    tabs: <Widget>[
                      Tab(text: localizations.craneFly),
                      Tab(text: localizations.craneSleep),
                      Tab(text: localizations.craneEat),
                    ],
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
