import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'package:vector_graphics/vector_graphics_compat.dart';

import 'src/cache.dart';
import 'src/loaders.dart';
import 'src/utilities/file.dart';

export 'package:vector_graphics/vector_graphics.dart'
    show BytesLoader, PictureInfo, VectorGraphicUtilities, vg;

export 'src/cache.dart';
export 'src/default_theme.dart';
export 'src/loaders.dart';

/// Builder function to create an error widget. This builder is called when
/// the image failed loading.
typedef SvgErrorWidgetBuilder = Widget Function(
  BuildContext context,
  Object error,
  StackTrace stackTrace,
);

/// Instance for [Svg]'s utility methods, which can produce a [DrawableRoot]
/// or [PictureInfo] from [String] or [Uint8List].
final Svg svg = Svg._();

/// A utility class for decoding SVG data to a [DrawableRoot] or a [PictureInfo].
///
/// These methods are used by [SvgPicture], but can also be directly used e.g.
/// to create a [DrawableRoot] you manipulate or render to your own [Canvas].
/// Access to this class is provided by the exported [svg] member.
class Svg {
  Svg._();

  /// A global override flag for [SvgPicture.cacheColorFilter].
  ///
  /// If this is null, the value in [SvgPicture.cacheColorFilter] is used. If it
  /// is not null, it will override that value.
  @Deprecated('This no longer does anything.')
  bool? cacheColorFilterOverride;

  /// The cache instance for decoded SVGs.
  final Cache cache = Cache();
}

// ignore: avoid_classes_with_only_static_members
/// Deprecated class, will be removed, does not do anything.
@Deprecated('This feature does not do anything anymore.')
class PictureProvider {
  /// Deprecated, use [svg.cache] instead.
  @Deprecated('Use svg.cache instead.')
  static Cache get cache => svg.cache;
}

/// A widget that will parse SVG data for rendering on screen.
class SvgPicture extends StatelessWidget {
  /// Instantiates a widget that renders an SVG picture using the `pictureProvider`.
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
  /// If `matchTextDirection` is set to true, the picture will be flipped
  /// horizontally in [TextDirection.rtl] contexts.
  ///
  /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  /// if set to true, it will not clip the canvas used internally to the view box,
  /// meaning the picture may draw beyond the intended area and lead to undefined
  /// behavior or additional memory overhead.
  ///
  /// A custom `placeholderBuilder` can be specified for cases where decoding or
  /// acquiring data may take a noticeably long time, e.g. for a network picture.
  ///
  /// The `semanticsLabel` can be used to identify the purpose of this picture for
  /// screen reading software.
  ///
  /// If [excludeFromSemantics] is true, then [semanticsLabel] will be ignored.
  const SvgPicture(
    this.bytesLoader, {
    super.key,
    this.width,
    this.height,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
    this.matchTextDirection = false,
    this.allowDrawingOutsideViewBox = false,
    this.placeholderBuilder,
    this.colorFilter,
    this.semanticsLabel,
    this.excludeFromSemantics = false,
    this.clipBehavior = Clip.hardEdge,
    this.errorBuilder,
    @Deprecated(
        'No code should use this parameter. It never was implemented properly. '
        'The SVG theme must be set on the bytesLoader.')
    SvgTheme? theme,
    @Deprecated('This no longer does anything.') bool cacheColorFilter = false,
    this.renderingStrategy = RenderingStrategy.picture,
  });

  /// Instantiates a widget that renders an SVG picture from an [AssetBundle].
  ///
  /// The key will be derived from the `assetName`, `package`, and `bundle`
  /// arguments. The `package` argument must be non-null when displaying an SVG
  /// from a package and null otherwise. See the `Assets in packages` section for
  /// details.
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
  /// If `matchTextDirection` is set to true, the picture will be flipped
  /// horizontally in [TextDirection.rtl] contexts.
  ///
  /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  /// if set to true, it will not clip the canvas used internally to the view box,
  /// meaning the picture may draw beyond the intended area and lead to undefined
  /// behavior or additional memory overhead.
  ///
  /// A custom `placeholderBuilder` can be specified for cases where decoding or
  /// acquiring data may take a noticeably long time.
  ///
  /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  /// [ColorFilter] on any [Paint]s created for this drawing.
  ///
  /// The `theme` argument, if provided, will override the default theme
  /// used when parsing SVG elements.
  ///
  /// ## Assets in packages
  ///
  /// To create the widget with an asset from a package, the [package] argument
  /// must be provided. For instance, suppose a package called `my_icons` has
  /// `icons/heart.svg` .
  ///
  /// Then to display the image, use:
  ///
  /// ```dart
  /// SvgPicture.asset('icons/heart.svg', package: 'my_icons')
  /// ```
  ///
  /// Assets used by the package itself should also be displayed using the
  /// [package] argument as above.
  ///
  /// If the desired asset is specified in the `pubspec.yaml` of the package, it
  /// is bundled automatically with the app. In particular, assets used by the
  /// package itself must be specified in its `pubspec.yaml`.
  ///
  /// A package can also choose to have assets in its 'lib/' folder that are not
  /// specified in its `pubspec.yaml`. In this case for those images to be
  /// bundled, the app has to specify which ones to include. For instance a
  /// package named `fancy_backgrounds` could have:
  ///
  /// ```none
  /// lib/backgrounds/background1.svg
  /// lib/backgrounds/background2.svg
  /// lib/backgrounds/background3.svg
  ///```
  ///
  /// To include, say the first image, the `pubspec.yaml` of the app should
  /// specify it in the assets section:
  ///
  /// ```yaml
  ///  assets:
  ///    - packages/fancy_backgrounds/backgrounds/background1.svg
  /// ```
  ///
  /// The `lib/` is implied, so it should not be included in the asset path.
  ///
  ///
  /// See also:
  ///
  ///  * <https://flutter.io/assets-and-images/>, an introduction to assets in
  ///    Flutter.
  ///
  /// If [excludeFromSemantics] is true, then [semanticsLabel] will be ignored.
  SvgPicture.asset(
    String assetName, {
    super.key,
    this.matchTextDirection = false,
    AssetBundle? bundle,
    String? package,
    this.width,
    this.height,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
    this.allowDrawingOutsideViewBox = false,
    this.placeholderBuilder,
    this.semanticsLabel,
    this.excludeFromSemantics = false,
    this.clipBehavior = Clip.hardEdge,
    this.errorBuilder,
    SvgTheme? theme,
    ColorMapper? colorMapper,
    ui.ColorFilter? colorFilter,
    @Deprecated('Use colorFilter instead.') ui.Color? color,
    @Deprecated('Use colorFilter instead.')
    ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
    @Deprecated('This no longer does anything.') bool cacheColorFilter = false,
    this.renderingStrategy = RenderingStrategy.picture,
  })  : bytesLoader = SvgAssetLoader(
          assetName,
          packageName: package,
          assetBundle: bundle,
          theme: theme,
          colorMapper: colorMapper,
        ),
        colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);

  /// Creates a widget that displays an SVG obtained from the network.
  ///
  /// The [url] argument must not be null.
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
  /// If `matchTextDirection` is set to true, the picture will be flipped
  /// horizontally in [TextDirection.rtl] contexts.
  ///
  /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  /// if set to true, it will not clip the canvas used internally to the view box,
  /// meaning the picture may draw beyond the intended area and lead to undefined
  /// behavior or additional memory overhead.
  ///
  /// A custom `placeholderBuilder` can be specified for cases where decoding or
  /// acquiring data may take a noticeably long time, such as high latency scenarios.
  ///
  /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  /// [ColorFilter] on any [Paint]s created for this drawing.
  ///
  /// The `theme` argument, if provided, will override the default theme
  /// used when parsing SVG elements.
  ///
  /// All network images are cached regardless of HTTP headers.
  ///
  /// An optional `headers` argument can be used to send custom HTTP headers
  /// with the image request.
  ///
  /// If [excludeFromSemantics] is true, then [semanticsLabel] will be ignored.
  SvgPicture.network(
    String url, {
    super.key,
    Map<String, String>? headers,
    this.width,
    this.height,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
    this.matchTextDirection = false,
    this.allowDrawingOutsideViewBox = false,
    this.placeholderBuilder,
    ui.ColorFilter? colorFilter,
    @Deprecated('Use colorFilter instead.') ui.Color? color,
    @Deprecated('Use colorFilter instead.')
    ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
    this.semanticsLabel,
    this.excludeFromSemantics = false,
    this.clipBehavior = Clip.hardEdge,
    this.errorBuilder,
    @Deprecated('This no longer does anything.') bool cacheColorFilter = false,
    SvgTheme? theme,
    ColorMapper? colorMapper,
    http.Client? httpClient,
    this.renderingStrategy = RenderingStrategy.picture,
  })  : bytesLoader = SvgNetworkLoader(
          url,
          headers: headers,
          theme: theme,
          colorMapper: colorMapper,
          httpClient: httpClient,
        ),
        colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);

  /// Creates a widget that displays an SVG obtained from a [File].
  ///
  /// The [file] argument must not be null.
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
  /// If `matchTextDirection` is set to true, the picture will be flipped
  /// horizontally in [TextDirection.rtl] contexts.
  ///
  /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  /// if set to true, it will not clip the canvas used internally to the view box,
  /// meaning the picture may draw beyond the intended area and lead to undefined
  /// behavior or additional memory overhead.
  ///
  /// A custom `placeholderBuilder` can be specified for cases where decoding or
  /// acquiring data may take a noticeably long time.
  ///
  /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  /// [ColorFilter] on any [Paint]s created for this drawing.
  ///
  /// The `theme` argument, if provided, will override the default theme
  /// used when parsing SVG elements.
  ///
  /// On Android, this may require the
  /// `android.permission.READ_EXTERNAL_STORAGE` permission.
  ///
  /// If [excludeFromSemantics] is true, then [semanticsLabel] will be ignored.
  SvgPicture.file(
    File file, {
    super.key,
    this.width,
    this.height,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
    this.matchTextDirection = false,
    this.allowDrawingOutsideViewBox = false,
    this.placeholderBuilder,
    ui.ColorFilter? colorFilter,
    @Deprecated('Use colorFilter instead.') ui.Color? color,
    @Deprecated('Use colorFilter instead.')
    ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
    this.semanticsLabel,
    this.excludeFromSemantics = false,
    this.clipBehavior = Clip.hardEdge,
    this.errorBuilder,
    SvgTheme? theme,
    ColorMapper? colorMapper,
    @Deprecated('This no longer does anything.') bool cacheColorFilter = false,
    this.renderingStrategy = RenderingStrategy.picture,
  })  : bytesLoader = SvgFileLoader(
          file,
          theme: theme,
          colorMapper: colorMapper,
        ),
        colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);

  /// Creates a widget that displays an SVG obtained from a [Uint8List].
  ///
  /// The [bytes] argument must not be null.
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
  /// If `matchTextDirection` is set to true, the picture will be flipped
  /// horizontally in [TextDirection.rtl] contexts.
  ///
  /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  /// if set to true, it will not clip the canvas used internally to the view box,
  /// meaning the picture may draw beyond the intended area and lead to undefined
  /// behavior or additional memory overhead.
  ///
  /// A custom `placeholderBuilder` can be specified for cases where decoding or
  /// acquiring data may take a noticeably long time.
  ///
  /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  /// [ColorFilter] on any [Paint]s created for this drawing.
  ///
  /// The `theme` argument, if provided, will override the default theme
  /// used when parsing SVG elements.
  ///
  /// If [excludeFromSemantics] is true, then [semanticsLabel] will be ignored.
  SvgPicture.memory(
    Uint8List bytes, {
    super.key,
    this.width,
    this.height,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
    this.matchTextDirection = false,
    this.allowDrawingOutsideViewBox = false,
    this.placeholderBuilder,
    ui.ColorFilter? colorFilter,
    @Deprecated('Use colorFilter instead.') ui.Color? color,
    @Deprecated('Use colorFilter instead.')
    ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
    this.semanticsLabel,
    this.excludeFromSemantics = false,
    this.clipBehavior = Clip.hardEdge,
    this.errorBuilder,
    SvgTheme? theme,
    ColorMapper? colorMapper,
    @Deprecated('This no longer does anything.') bool cacheColorFilter = false,
    this.renderingStrategy = RenderingStrategy.picture,
  })  : bytesLoader = SvgBytesLoader(
          bytes,
          theme: theme,
          colorMapper: colorMapper,
        ),
        colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);

  /// Creates a widget that displays an SVG obtained from a [String].
  ///
  /// The [string] argument must not be null.
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
  /// If `matchTextDirection` is set to true, the picture will be flipped
  /// horizontally in [TextDirection.rtl] contexts.
  ///
  /// The `allowDrawingOutsideOfViewBox` parameter should be used with caution -
  /// if set to true, it will not clip the canvas used internally to the view box,
  /// meaning the picture may draw beyond the intended area and lead to undefined
  /// behavior or additional memory overhead.
  ///
  /// A custom `placeholderBuilder` can be specified for cases where decoding or
  /// acquiring data may take a noticeably long time.
  ///
  /// The `color` and `colorBlendMode` arguments, if specified, will be used to set a
  /// [ColorFilter] on any [Paint]s created for this drawing.
  ///
  /// The `theme` argument, if provided, will override the default theme
  /// used when parsing SVG elements.
  ///
  /// If [excludeFromSemantics] is true, then [semanticsLabel] will be ignored.
  SvgPicture.string(
    String string, {
    super.key,
    this.width,
    this.height,
    this.fit = BoxFit.contain,
    this.alignment = Alignment.center,
    this.matchTextDirection = false,
    this.allowDrawingOutsideViewBox = false,
    this.placeholderBuilder,
    ui.ColorFilter? colorFilter,
    @Deprecated('Use colorFilter instead.') ui.Color? color,
    @Deprecated('Use colorFilter instead.')
    ui.BlendMode colorBlendMode = ui.BlendMode.srcIn,
    this.semanticsLabel,
    this.excludeFromSemantics = false,
    this.clipBehavior = Clip.hardEdge,
    this.errorBuilder,
    SvgTheme? theme,
    ColorMapper? colorMapper,
    @Deprecated('This no longer does anything.') bool cacheColorFilter = false,
    this.renderingStrategy = RenderingStrategy.picture,
  })  : bytesLoader = SvgStringLoader(
          string,
          theme: theme,
          colorMapper: colorMapper,
        ),
        colorFilter = colorFilter ?? _getColorFilter(color, colorBlendMode);

  static ColorFilter? _getColorFilter(
          ui.Color? color, ui.BlendMode colorBlendMode) =>
      color == null ? null : ui.ColorFilter.mode(color, colorBlendMode);

  /// The default placeholder for a SVG that may take time to parse or
  /// retrieve, e.g. from a network location.
  static WidgetBuilder defaultPlaceholderBuilder =
      (BuildContext ctx) => const LimitedBox();

  /// If specified, the width to use for the SVG.  If unspecified, the SVG
  /// will take the width of its parent.
  final double? width;

  /// If specified, the height to use for the SVG.  If unspecified, the SVG
  /// will take the height of its parent.
  final double? height;

  /// How to inscribe the picture into the space allocated during layout.
  /// The default is [BoxFit.contain].
  final BoxFit fit;

  /// How to align the picture within its parent widget.
  ///
  /// The alignment aligns the given position in the picture to the given position
  /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
  /// -1.0) aligns the image to the top-left corner of its layout bounds, while a
  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
  /// picture with the bottom right corner of its layout bounds. Similarly, an
  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
  /// middle of the bottom edge of its layout bounds.
  ///
  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
  /// [AlignmentDirectional]), then a [TextDirection] must be available
  /// when the picture is painted.
  ///
  /// Defaults to [Alignment.center].
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
  final AlignmentGeometry alignment;

  /// The [BytesLoader] used to resolve the SVG.
  final BytesLoader bytesLoader;

  /// The placeholder to use while fetching, decoding, and parsing the SVG data.
  final WidgetBuilder? placeholderBuilder;

  /// If true, will horizontally flip the picture in [TextDirection.rtl] contexts.
  final bool matchTextDirection;

  /// If true, will allow the SVG to be drawn outside of the clip boundary of its
  /// viewBox.
  final bool allowDrawingOutsideViewBox;

  /// The [Semantics.label] for this picture.
  ///
  /// The value indicates the purpose of the picture, and will be
  /// read out by screen readers.
  final String? semanticsLabel;

  /// Whether to exclude this picture from semantics.
  ///
  /// Useful for pictures which do not contribute meaningful information to an
  /// application.
  final bool excludeFromSemantics;

  /// The content will be clipped (or not) according to this option.
  ///
  /// See the enum [Clip] for details of all possible options and their common
  /// use cases.
  ///
  /// Defaults to [Clip.hardEdge], and must not be null.
  final Clip clipBehavior;

  /// Widget displayed while the target image failed loading.
  final SvgErrorWidgetBuilder? errorBuilder;

  /// The color filter, if any, to apply to this widget.
  final ColorFilter? colorFilter;

  /// Widget rendering strategy used to balance flexibility and performance.
  ///
  /// See the enum [RenderingStrategy] for details of all possible options and their common
  /// use cases.
  ///
  /// Defaults to [RenderingStrategy.picture].
  final RenderingStrategy renderingStrategy;

  @override
  Widget build(BuildContext context) {
    return createCompatVectorGraphic(
      loader: bytesLoader,
      width: width,
      height: height,
      fit: fit,
      alignment: alignment,
      semanticsLabel: semanticsLabel,
      excludeFromSemantics: excludeFromSemantics,
      clipBehavior: clipBehavior,
      errorBuilder: errorBuilder,
      colorFilter: colorFilter,
      placeholderBuilder: placeholderBuilder,
      strategy: renderingStrategy,
      clipViewbox: !allowDrawingOutsideViewBox,
      matchTextDirection: matchTextDirection,
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);

    properties
      ..add(StringProperty(
        'bytesLoader',
        bytesLoader.toString(),
        showName: false,
      ))
      ..add(DoubleProperty('width', width, defaultValue: null))
      ..add(DoubleProperty('height', height, defaultValue: null))
      ..add(DiagnosticsProperty<AlignmentGeometry>(
        'alignment',
        alignment,
        defaultValue: Alignment.center,
      ))
      ..add(DiagnosticsProperty<bool>(
        'allowDrawingOutsideViewBox',
        allowDrawingOutsideViewBox,
        defaultValue: false,
      ))
      ..add(EnumProperty<Clip>(
        'clipBehavior',
        clipBehavior,
        defaultValue: BoxFit.contain,
      ))
      ..add(StringProperty(
        'colorFilter',
        colorFilter.toString(),
        defaultValue: null,
      ))
      ..add(EnumProperty<BoxFit>(
        'fit',
        fit,
        defaultValue: BoxFit.contain,
      ))
      ..add(DiagnosticsProperty<Function>(
        'placeholderBuilder',
        placeholderBuilder,
        defaultValue: null,
      ))
      ..add(DiagnosticsProperty<bool>(
        'matchTextDirection',
        matchTextDirection,
        defaultValue: false,
      ))
      ..add(DiagnosticsProperty<bool>(
        'excludeFromSemantics',
        excludeFromSemantics,
        defaultValue: false,
      ))
      ..add(StringProperty(
        'semanticsLabel',
        semanticsLabel,
        defaultValue: null,
      ));
  }
}