// MIT License
//
// Copyright (c) 2024 Simon Lightfoot
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import 'dart:math' as math;
import 'package:flutter/material.dart';

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Home(),
    );
  }
}

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

  @override
  State<Home> createState() => _HomeState();
}

class _HomeState extends State<Home> {
  double _value = 50;

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            '$_value',
            style: TextStyle(fontSize: 32.0),
          ),
          const SizedBox(height: 32),
          SliderTheme(
            data: Theme.of(context).sliderTheme.copyWith(
                  trackHeight: 12.0,
                ),
            child: LabelledSlider(
              buildLabel: (int index) {
                if (index == 2) {
                  return 'This is second division';
                }
                return 'Division $index';
              },
              onChanged: (double value) {
                setState(() => _value = value);
              },
              divisions: 5,
              min: 0,
              max: 100,
              value: _value,
              padding: EdgeInsets.only(right: 25.0),
            ),
          ),
        ],
      ),
    );
  }
}

class LabelledSlider extends StatelessWidget {
  const LabelledSlider({
    super.key,
    required this.onChanged,
    required this.buildLabel,
    required this.divisions,
    required this.min,
    required this.max,
    required this.value,
    required this.padding,
  });

  final ValueChanged<double>? onChanged;
  final String Function(int index) buildLabel;
  final int divisions;
  final double min;
  final double max;
  final double value;
  final EdgeInsets padding;

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      painter: _SliderLabelPainter(
        SliderTheme.of(context),
        DefaultTextStyle.of(context),
        MediaQuery.textScalerOf(context),
        this,
      ),
      child: Slider(
        onChanged: onChanged,
        divisions: divisions,
        min: min,
        max: max,
        value: value,
      ),
    );
  }
}

class _SliderLabelPainter extends CustomPainter {
  _SliderLabelPainter(
    this.sliderTheme,
    this.defaultTextStyle,
    this.textScaler,
    this.widget,
  );

  final SliderThemeData sliderTheme;
  final DefaultTextStyle defaultTextStyle;
  final TextScaler textScaler;
  final LabelledSlider widget;

  @override
  void paint(Canvas canvas, Size size) {
    final trackRect =
        Offset(8.0, 8.0) & Size(size.width - 18.0, size.height - 8.0);
    final padding = trackRect.height;
    final adjustedTrackWidth = trackRect.width - padding;

    final labelPainter = TextPainter(
      maxLines: 1,
      textScaler: textScaler,
      textDirection: TextDirection.ltr,
      textAlign: defaultTextStyle.textAlign ?? TextAlign.left,
      textHeightBehavior: defaultTextStyle.textHeightBehavior,
      textWidthBasis: defaultTextStyle.textWidthBasis,
    );

    var largestLabel = Size.zero;
    for (int i = 0; i <= widget.divisions; i++) {
      labelPainter.text = TextSpan(
        text: widget.buildLabel(i),
        style: defaultTextStyle.style,
      );
      labelPainter.layout();
      final width = labelPainter.size.width + widget.padding.horizontal;
      final height = labelPainter.size.height + widget.padding.vertical;
      if (width > largestLabel.width) {
        largestLabel = Size(width, largestLabel.height);
      }
      if (height > largestLabel.height) {
        largestLabel = Size(largestLabel.width, height);
      }
    }

    final dy = trackRect.center.dy;
    for (int i = 0; i <= widget.divisions; i++) {
      final value = i / widget.divisions;
      // The ticks are mapped to be within the track, so the tick mark width
      // must be subtracted from the track width.
      final dx = trackRect.left + value * adjustedTrackWidth + padding / 2;
      final tickMarkOffset = Offset(dx, dy);
      canvas.drawLine(
        tickMarkOffset,
        tickMarkOffset + Offset(0, largestLabel.width),
        Paint() //
          ..color = (sliderTheme.inactiveTickMarkColor ?? Colors.grey)
          ..strokeWidth = 2.0,
      );
      labelPainter.text = TextSpan(
        text: widget.buildLabel(i),
        style: defaultTextStyle.style,
      );
      labelPainter.layout();
      canvas.save();
      canvas.translate(
        tickMarkOffset.dx - labelPainter.size.height + widget.padding.vertical,
        tickMarkOffset.dy + labelPainter.size.width + widget.padding.horizontal,
      );
      canvas.rotate(-math.pi / 2);
      labelPainter.paint(canvas, Offset.zero);
      canvas.restore();
    }
  }

  @override
  bool shouldRepaint(covariant _SliderLabelPainter oldDelegate) {
    return sliderTheme != oldDelegate.sliderTheme ||
        defaultTextStyle != oldDelegate.defaultTextStyle ||
        textScaler != oldDelegate.textScaler ||
        widget.buildLabel != oldDelegate.widget.buildLabel ||
        widget.divisions != oldDelegate.widget.divisions ||
        widget.min != oldDelegate.widget.min ||
        widget.max != oldDelegate.widget.max ||
        widget.value != oldDelegate.widget.value;
  }
}