// 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 'dart:math' as math;

import 'package:flutter/material.dart';

class ExampleDragTarget extends StatefulWidget {
  const ExampleDragTarget({super.key});

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

class ExampleDragTargetState extends State<ExampleDragTarget> {
  Color _color = Colors.grey;

  void _handleAccept(Color data) {
    setState(() {
      _color = data;
    });
  }

  @override
  Widget build(BuildContext context) {
    return DragTarget<Color>(
      onAccept: _handleAccept,
      builder: (BuildContext context, List<Color?> data, List<dynamic> rejectedData) {
        return Container(
          height: 100.0,
          margin: const EdgeInsets.all(10.0),
          decoration: BoxDecoration(
            color: data.isEmpty ? _color : Colors.grey.shade200,
            border: Border.all(
              width: 3.0,
              color: data.isEmpty ? Colors.white : Colors.blue,
            ),
          ),
        );
      },
    );
  }
}

class Dot extends StatefulWidget {
  const Dot({ super.key, this.color, this.size, this.child, this.tappable = false });

  final Color? color;
  final double? size;
  final Widget? child;
  final bool tappable;

  @override
  DotState createState() => DotState();
}
class DotState extends State<Dot> {
  int taps = 0;

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: widget.tappable ? () { setState(() { taps += 1; }); } : null,
      child: Container(
        width: widget.size,
        height: widget.size,
        decoration: BoxDecoration(
          color: widget.color,
          border: Border.all(width: taps.toDouble()),
          shape: BoxShape.circle,
        ),
        child: widget.child,
      ),
    );
  }
}

class ExampleDragSource extends StatelessWidget {
  const ExampleDragSource({
    super.key,
    this.color,
    this.heavy = false,
    this.under = true,
    this.child,
  });

  final Color? color;
  final bool heavy;
  final bool under;
  final Widget? child;

  static const double kDotSize = 50.0;
  static const double kHeavyMultiplier = 1.5;
  static const double kFingerSize = 50.0;

  @override
  Widget build(BuildContext context) {
    double size = kDotSize;
    if (heavy) {
      size *= kHeavyMultiplier;
    }

    final Widget contents = DefaultTextStyle(
      style: Theme.of(context).textTheme.bodyMedium!,
      textAlign: TextAlign.center,
      child: Dot(
        color: color,
        size: size,
        child: Center(child: child),
      ),
    );

    Widget feedback = Opacity(
      opacity: 0.75,
      child: contents,
    );

    Offset feedbackOffset;
    DragAnchorStrategy dragAnchorStrategy;
    if (!under) {
      feedback = Transform(
        transform: Matrix4.identity()
                     ..translate(-size / 2.0, -(size / 2.0 + kFingerSize)),
        child: feedback,
      );
      feedbackOffset = const Offset(0.0, -kFingerSize);
      dragAnchorStrategy = pointerDragAnchorStrategy;
    } else {
      feedbackOffset = Offset.zero;
      dragAnchorStrategy = childDragAnchorStrategy;
    }

    if (heavy) {
      return LongPressDraggable<Color>(
        data: color,
        feedback: feedback,
        feedbackOffset: feedbackOffset,
        dragAnchorStrategy: dragAnchorStrategy,
        child: contents,
      );
    } else {
      return Draggable<Color>(
        data: color,
        feedback: feedback,
        feedbackOffset: feedbackOffset,
        dragAnchorStrategy: dragAnchorStrategy,
        child: contents,
      );
    }
  }
}

class DashOutlineCirclePainter extends CustomPainter {
  const DashOutlineCirclePainter();

  static const int segments = 17;
  static const double deltaTheta = math.pi * 2 / segments; // radians
  static const double segmentArc = deltaTheta / 2.0; // radians
  static const double startOffset = 1.0; // radians

  @override
  void paint(Canvas canvas, Size size) {
    final double radius = size.shortestSide / 2.0;
    final Paint paint = Paint()
      ..color = const Color(0xFF000000)
      ..style = PaintingStyle.stroke
      ..strokeWidth = radius / 10.0;
    final Path path = Path();
    final Rect box = Offset.zero & size;
    for (double theta = 0.0; theta < math.pi * 2.0; theta += deltaTheta) {
      path.addArc(box, theta + startOffset, segmentArc);
    }
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(DashOutlineCirclePainter oldDelegate) => false;
}

class MovableBall extends StatelessWidget {
  const MovableBall(this.position, this.ballPosition, this.callback, {super.key});

  final int position;
  final int ballPosition;
  final ValueChanged<int> callback;

  static final GlobalKey kBallKey = GlobalKey();
  static const double kBallSize = 50.0;

  @override
  Widget build(BuildContext context) {
    final Widget ball = DefaultTextStyle(
      style: Theme.of(context).primaryTextTheme.bodyMedium!,
      textAlign: TextAlign.center,
      child: Dot(
        key: kBallKey,
        color: Colors.blue.shade700,
        size: kBallSize,
        tappable: true,
        child: const Center(child: Text('BALL')),
      ),
    );
    const Widget dashedBall = SizedBox(
      width: kBallSize,
      height: kBallSize,
      child: CustomPaint(
        painter: DashOutlineCirclePainter()
      ),
    );
    if (position == ballPosition) {
      return Draggable<bool>(
        data: true,
        childWhenDragging: dashedBall,
        feedback: ball,
        maxSimultaneousDrags: 1,
        child: ball,
      );
    } else {
      return DragTarget<bool>(
        onAccept: (bool data) { callback(position); },
        builder: (BuildContext context, List<bool?> accepted, List<dynamic> rejected) {
          return dashedBall;
        },
      );
    }
  }
}

class DragAndDropApp extends StatefulWidget {
  const DragAndDropApp({super.key});

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

class DragAndDropAppState extends State<DragAndDropApp> {
  int position = 1;

  void moveBall(int newPosition) {
    setState(() { position = newPosition; });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Drag and Drop Flutter Demo'),
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                ExampleDragSource(
                  color: Colors.yellow.shade300,
                  child: const Text('under'),
                ),
                ExampleDragSource(
                  color: Colors.green.shade300,
                  under: false,
                  heavy: true,
                  child: const Text('long-press above'),
                ),
                ExampleDragSource(
                  color: Colors.indigo.shade300,
                  under: false,
                  child: const Text('above'),
                ),
              ],
            ),
          ),
          const Expanded(
            child: Row(
              children: <Widget>[
                Expanded(child: ExampleDragTarget()),
                Expanded(child: ExampleDragTarget()),
                Expanded(child: ExampleDragTarget()),
                Expanded(child: ExampleDragTarget()),
              ],
            ),
          ),
          Expanded(
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: <Widget>[
                MovableBall(1, position, moveBall),
                MovableBall(2, position, moveBall),
                MovableBall(3, position, moveBall),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

void main() {
  runApp(const MaterialApp(
    title: 'Drag and Drop Flutter Demo',
    home: DragAndDropApp(),
  ));
}