import 'package:flutter/material.dart';
import 'package:string_scanner/string_scanner.dart';

import '../../../example5_themes_playground/theme/code_theme.dart';

/// This syntax highlighter is adopted and converted from the Google Flutter
/// Gallery application.
///
/// Copyright 2016 The Chromium Authors. All rights reserved.
/// Use of this source code is governed by a BSD-style license that can be
/// found in the LICENSE file.
class SyntaxHighlighterStyle {
  SyntaxHighlighterStyle(
      {this.baseStyle,
      this.numberStyle,
      this.commentStyle,
      this.keywordStyle,
      this.stringStyle,
      this.punctuationStyle,
      this.classStyle,
      this.constantStyle});

  /// A fixed example light code view style.
  ///
  /// This constructor does not create a ThemeData extensions based
  /// SyntaxHighlighterStyle.
  /// It uses the same colors as the colors used in the [CodeTheme] theme
  /// extension, but it could be any fixed none theme related colors.
  static SyntaxHighlighterStyle lightThemeStyle() => SyntaxHighlighterStyle(
        baseStyle: const TextStyle(color: Color(0xFF092249)),
        numberStyle: const TextStyle(color: Color(0xFF1565C0)),
        commentStyle: const TextStyle(color: Color(0xFF446736)),
        keywordStyle: const TextStyle(color: Color(0xFF9C27B0)),
        stringStyle: const TextStyle(color: Color(0xFF43A047)),
        punctuationStyle: const TextStyle(color: Color(0xFF081936)),
        classStyle: const TextStyle(color: Color(0xFF6607C5)),
        constantStyle: const TextStyle(color: Color(0xFF795548)),
      );

  /// A fixed example dark code view style.
  ///
  /// This constructor does not create a ThemeData extensions based
  /// SyntaxHighlighterStyle.
  /// It uses the same colors as the colors used in the [CodeTheme] theme
  /// extension, but it could be any fixed none theme related colors.
  static SyntaxHighlighterStyle darkThemeStyle() => SyntaxHighlighterStyle(
        baseStyle: const TextStyle(color: Color(0xFFEEEED6)),
        numberStyle: const TextStyle(color: Color(0xFFB4CDA8)),
        commentStyle: const TextStyle(color: Color(0xFF90C07A)),
        keywordStyle: const TextStyle(color: Color(0xFF5BAAE8)),
        stringStyle: const TextStyle(color: Color(0xFFD3A384)),
        punctuationStyle: const TextStyle(color: Color(0xFFEAE9D4)),
        classStyle: const TextStyle(color: Color(0xFF39C8B0)),
        constantStyle: const TextStyle(color: Color(0xFFB9A1DC)),
      );

  /// A theme harmonized code view syntax syntax highlighter style.
  ///
  /// This style gets its colors from the theme extension [CodeTheme] baked
  /// into [ThemeData].
  static SyntaxHighlighterStyle harmonized(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    final bool isLight = theme.brightness == Brightness.light;
    // Get our custom code view theme from the CodeTheme extension, with a
    // fallback to brightness dependent CodeTheme.light or CodeTheme.dark.
    // We use the static const light and dark versions as fallback highlight
    // colors in case the theme extension CodeTheme has not been added to
    // ThemeData, making this harmonized constructor more robust.
    //
    // The theme extension CodeTheme in this example is based on same
    // colors as its static light and dark mode, but the harmonized one from
    // ThemeData is M3 color harmonized to source color. In this case active
    // theme primary color. Another benefit from using the colors via the
    // ThemeData extension, is that its harmonization function result is color
    // lerp animated, in sync with the rest of the ThemeData color changes
    // automatically as you change theme in the app, as it should be.
    // Fallback is only used if CodeTheme is not setup in app's ThemeData.
    // This will not happen in this app. This kind of fallback would be to
    // defaults you would use if you had a package that provided a theme
    // extension, and it would fallback to defaults like this internally if
    // it had not been added by user to ThemeData as an extension.
    final CodeTheme colors = theme.extension<CodeTheme>() ??
        (isLight ? CodeTheme.light : CodeTheme.dark);

    return SyntaxHighlighterStyle(
      baseStyle: TextStyle(color: colors.baseColor),
      numberStyle: TextStyle(color: colors.numberColor),
      commentStyle: TextStyle(color: colors.commentColor),
      keywordStyle: TextStyle(color: colors.keywordColor),
      stringStyle: TextStyle(color: colors.stringColor),
      punctuationStyle: TextStyle(color: colors.punctuationColor),
      classStyle: TextStyle(color: colors.classColor),
      constantStyle: TextStyle(color: colors.constantColor),
    );
  }

  SyntaxHighlighterStyle copyWith({
    TextStyle? baseStyle,
    TextStyle? numberStyle,
    TextStyle? commentStyle,
    TextStyle? keywordStyle,
    TextStyle? stringStyle,
    TextStyle? punctuationStyle,
    TextStyle? classStyle,
    TextStyle? constantStyle,
  }) =>
      SyntaxHighlighterStyle(
        baseStyle: baseStyle ?? this.baseStyle,
        numberStyle: numberStyle ?? this.numberStyle,
        commentStyle: commentStyle ?? this.commentStyle,
        keywordStyle: keywordStyle ?? this.keywordStyle,
        stringStyle: stringStyle ?? this.stringStyle,
        punctuationStyle: punctuationStyle ?? this.punctuationStyle,
        classStyle: classStyle ?? this.classStyle,
        constantStyle: constantStyle ?? this.constantStyle,
      );

  final TextStyle? baseStyle;
  final TextStyle? numberStyle;
  final TextStyle? commentStyle;
  final TextStyle? keywordStyle;
  final TextStyle? stringStyle;
  final TextStyle? punctuationStyle;
  final TextStyle? classStyle;
  final TextStyle? constantStyle;
}

// ignore: one_member_abstracts, we inherited this code this way :)
abstract class SyntaxHighlighter {
  TextSpan format(String src);
}

class DartSyntaxHighlighter extends SyntaxHighlighter {
  DartSyntaxHighlighter([this.style]) {
    _spans = <_HighlightSpan>[];
    style ??= SyntaxHighlighterStyle.darkThemeStyle();
  }

  SyntaxHighlighterStyle? style;

  static const List<String> _keywords = <String>[
    'abstract',
    'as',
    'assert',
    'async',
    'await',
    'break',
    'case',
    'catch',
    'class',
    'const',
    'continue',
    'default',
    'deferred',
    'do',
    'dynamic',
    'else',
    'enum',
    'export',
    'external',
    'extends',
    'factory',
    'false',
    'final',
    'finally',
    'for',
    'get',
    'if',
    'implements',
    'import',
    'in',
    'is',
    'library',
    'new',
    'null',
    'operator',
    'part',
    'rethrow',
    'return',
    'sealed',
    'set',
    'static',
    'super',
    'switch',
    'sync',
    'this',
    'throw',
    'true',
    'try',
    'typedef',
    'var',
    'void',
    'while',
    'with',
    'yield'
  ];

  static const List<String> _builtInTypes = <String>[
    'int',
    'double',
    'num',
    'bool'
  ];

  late String _src;
  late StringScanner _scanner;

  late List<_HighlightSpan> _spans;

  @override
  TextSpan format(String src) {
    _src = src;
    _scanner = StringScanner(_src);

    if (_generateSpans()) {
      // Successfully parsed the code
      final List<TextSpan> formattedText = <TextSpan>[];
      int currentPosition = 0;

      for (final _HighlightSpan span in _spans) {
        if (currentPosition != span.start) {
          formattedText
              .add(TextSpan(text: _src.substring(currentPosition, span.start)));
        }

        formattedText.add(TextSpan(
            style: span.textStyle(style), text: span.textForSpan(_src)));

        currentPosition = span.end;
      }

      if (currentPosition != _src.length) {
        formattedText
            .add(TextSpan(text: _src.substring(currentPosition, _src.length)));
      }

      return TextSpan(style: style!.baseStyle, children: formattedText);
    } else {
      // Parsing failed, return with only basic formatting
      return TextSpan(style: style!.baseStyle, text: src);
    }
  }

  bool _generateSpans() {
    int lastLoopPosition = _scanner.position;

    while (!_scanner.isDone) {
      // Skip White space
      _scanner.scan(RegExp(r'\s+'));

      // Block comments
      if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) {
        _spans.add(_HighlightSpan(_HighlightType.comment,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Line comments
      if (_scanner.scan('//')) {
        final int startComment = _scanner.lastMatch!.start;

        bool eof = false;
        int endComment;
        if (_scanner.scan(RegExp(r'.*\n'))) {
          endComment = _scanner.lastMatch!.end - 1;
        } else {
          eof = true;
          endComment = _src.length;
        }

        _spans.add(
            _HighlightSpan(_HighlightType.comment, startComment, endComment));

        if (eof) break;

        continue;
      }

      // Raw r"String"
      // ignore: unnecessary_raw_strings
      if (_scanner.scan(RegExp(r'r".*"'))) {
        _spans.add(_HighlightSpan(_HighlightType.string,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Raw r'String'
      // ignore: unnecessary_raw_strings
      if (_scanner.scan(RegExp(r"r'.*'"))) {
        _spans.add(_HighlightSpan(_HighlightType.string,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Multiline """String"""
      if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) {
        _spans.add(_HighlightSpan(_HighlightType.string,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Multiline '''String'''
      if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) {
        _spans.add(_HighlightSpan(_HighlightType.string,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // "String"
      if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) {
        _spans.add(_HighlightSpan(_HighlightType.string,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // 'String'
      if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) {
        _spans.add(_HighlightSpan(_HighlightType.string,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Double
      if (_scanner.scan(RegExp(r'\d+\.\d+'))) {
        _spans.add(_HighlightSpan(_HighlightType.number,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Integer
      if (_scanner.scan(RegExp(r'\d+'))) {
        _spans.add(_HighlightSpan(_HighlightType.number,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Punctuation
      if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) {
        _spans.add(_HighlightSpan(_HighlightType.punctuation,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Meta data
      if (_scanner.scan(RegExp(r'@\w+'))) {
        _spans.add(_HighlightSpan(_HighlightType.keyword,
            _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        continue;
      }

      // Words
      if (_scanner.scan(RegExp(r'\w+'))) {
        _HighlightType? type;

        String word = _scanner.lastMatch![0]!;
        if (word.startsWith('_')) word = word.substring(1);

        if (_keywords.contains(word)) {
          type = _HighlightType.keyword;
        } else if (_builtInTypes.contains(word)) {
          type = _HighlightType.keyword;
        } else if (_firstLetterIsUpperCase(word)) {
          type = _HighlightType.klass;
        } else if (word.length >= 2 &&
            word.startsWith('k') &&
            _firstLetterIsUpperCase(word.substring(1))) {
          type = _HighlightType.constant;
        }

        if (type != null) {
          _spans.add(_HighlightSpan(
              type, _scanner.lastMatch!.start, _scanner.lastMatch!.end));
        }
      }

      // Check if this loop did anything
      if (lastLoopPosition == _scanner.position) {
        // Failed to parse this file, abort gracefully
        return false;
      }
      lastLoopPosition = _scanner.position;
    }

    _simplify();
    return true;
  }

  void _simplify() {
    for (int i = _spans.length - 2; i >= 0; i -= 1) {
      if (_spans[i].type == _spans[i + 1].type &&
          _spans[i].end == _spans[i + 1].start) {
        _spans[i] =
            _HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end);
        _spans.removeAt(i + 1);
      }
    }
  }

  bool _firstLetterIsUpperCase(String str) {
    if (str.isNotEmpty) {
      final String first = str.substring(0, 1);
      return first == first.toUpperCase();
    }
    return false;
  }
}

enum _HighlightType {
  number,
  comment,
  keyword,
  string,
  punctuation,
  klass,
  constant
}

class _HighlightSpan {
  _HighlightSpan(this.type, this.start, this.end);
  final _HighlightType type;
  final int start;
  final int end;

  String textForSpan(String src) {
    return src.substring(start, end);
  }

  TextStyle? textStyle(SyntaxHighlighterStyle? style) {
    if (type == _HighlightType.number) {
      return style!.numberStyle;
    } else if (type == _HighlightType.comment) {
      return style!.commentStyle;
    } else if (type == _HighlightType.keyword) {
      return style!.keywordStyle;
    } else if (type == _HighlightType.string) {
      return style!.stringStyle;
    } else if (type == _HighlightType.punctuation) {
      return style!.punctuationStyle;
    } else if (type == _HighlightType.klass) {
      return style!.classStyle;
    } else if (type == _HighlightType.constant) {
      return style!.constantStyle;
    } else {
      return style!.baseStyle;
    }
  }
}
