import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flut/flut/object.dart';
import 'package:flut/flut/runtime.dart';
import 'package:flut/flut/error.dart';

class FlutColor extends FlutImmutableObject {
  final Color color;

  const FlutColor(this.color) : super('Color');

  @override
  Map<String, dynamic> flutEncode() {
    final result = flutBaseProps();
    result['value'] = color.toARGB32();
    return result;
  }

  static Color? flutDecode(FlutRuntime runtime, Map<String, dynamic> data) {
    return Color(runtime.unpackRequiredField<int>(data, 'value'));
  }
}

class FlutPaintingStyle extends FlutEnumObject<PaintingStyle> {
  const FlutPaintingStyle()
    : super('PaintingStyle', const {
        'fill': PaintingStyle.fill,
        'stroke': PaintingStyle.stroke,
      });
}

class FlutPlaceholderAlignment extends FlutEnumObject<PlaceholderAlignment> {
  const FlutPlaceholderAlignment()
    : super('PlaceholderAlignment', const {
        'baseline': PlaceholderAlignment.baseline,
        'aboveBaseline': PlaceholderAlignment.aboveBaseline,
        'belowBaseline': PlaceholderAlignment.belowBaseline,
        'top': PlaceholderAlignment.top,
        'bottom': PlaceholderAlignment.bottom,
        'middle': PlaceholderAlignment.middle,
      });
}

class FlutTextBaseline extends FlutEnumObject<TextBaseline> {
  const FlutTextBaseline()
    : super('TextBaseline', const {
        'alphabetic': TextBaseline.alphabetic,
        'ideographic': TextBaseline.ideographic,
      });
}

class FlutTextAlign extends FlutEnumObject<TextAlign> {
  const FlutTextAlign()
    : super('TextAlign', const {
        'left': TextAlign.left,
        'right': TextAlign.right,
        'center': TextAlign.center,
        'justify': TextAlign.justify,
        'start': TextAlign.start,
        'end': TextAlign.end,
      });
}

class FlutFontWeight extends FlutImmutableObject {
  final FontWeight fontWeight;

  const FlutFontWeight(this.fontWeight) : super('FontWeight');

  @override
  Map<String, dynamic> flutEncode() {
    final result = flutBaseProps();
    result['index'] = FontWeight.values.indexOf(fontWeight);
    return result;
  }

  static FontWeight? flutDecode(
    FlutRuntime runtime,
    Map<String, dynamic> data,
  ) {
    return FontWeight.values[runtime.unpackRequiredField<int>(data, 'index')];
  }
}

class FlutAxis extends FlutEnumObject<Axis> {
  const FlutAxis()
    : super('Axis', const {
        'horizontal': Axis.horizontal,
        'vertical': Axis.vertical,
      });
}

class FlutSize extends FlutImmutableObject {
  final Size size;

  const FlutSize(this.size) : super('Size');

  @override
  Map<String, dynamic> flutEncode() {
    final result = flutBaseProps();
    result['width'] = size.width;
    result['height'] = size.height;
    return result;
  }

  static Size? flutDecode(FlutRuntime runtime, Map<String, dynamic> data) {
    return Size(
      runtime.unpackRequiredField<double>(data, 'width'),
      runtime.unpackRequiredField<double>(data, 'height'),
    );
  }
}

class FlutOffset extends FlutImmutableObject {
  final Offset offset;

  const FlutOffset(this.offset) : super('Offset');

  @override
  Map<String, dynamic> flutEncode() {
    final result = flutBaseProps();
    result['dx'] = offset.dx;
    result['dy'] = offset.dy;
    return result;
  }

  static Offset? flutDecode(FlutRuntime runtime, Map<String, dynamic> data) {
    return Offset(
      runtime.unpackRequiredField<double>(data, 'dx'),
      runtime.unpackRequiredField<double>(data, 'dy'),
    );
  }
}

class FlutRect extends FlutImmutableObject {
  final Rect rect;

  const FlutRect(this.rect) : super('Rect');

  @override
  Map<String, dynamic> flutEncode() {
    final result = flutBaseProps();
    result['left'] = rect.left;
    result['top'] = rect.top;
    result['right'] = rect.right;
    result['bottom'] = rect.bottom;
    return result;
  }

  static Rect? flutDecode(FlutRuntime runtime, Map<String, dynamic> data) {
    return Rect.fromLTRB(
      runtime.unpackRequiredField<double>(data, 'left'),
      runtime.unpackRequiredField<double>(data, 'top'),
      runtime.unpackRequiredField<double>(data, 'right'),
      runtime.unpackRequiredField<double>(data, 'bottom'),
    );
  }
}

class FlutPaint extends FlutImmutableObject {
  final Paint paint;

  const FlutPaint(this.paint) : super('Paint');

  @override
  Map<String, dynamic> flutEncode() => flutBaseProps();

  static Paint? flutDecode(FlutRuntime runtime, Map<String, dynamic> data) {
    final paint = Paint();
    final color = runtime.unpackOptionalField<Color>(data, 'color');
    if (color != null) paint.color = color;
    final strokeWidth = runtime.unpackOptionalField<double>(
      data,
      'strokeWidth',
    );
    if (strokeWidth != null) paint.strokeWidth = strokeWidth;
    final style = runtime.unpackOptionalField<PaintingStyle>(data, 'style');
    if (style != null) paint.style = style;
    return paint;
  }
}

class FlutCanvas extends FlutRealtimeObject<Canvas> {
  FlutCanvas.wrap({
    required FlutRuntime runtime,
    required int oid,
    required Canvas target,
  }) : super.wrap(runtime: runtime, oid: oid, type: 'Canvas', target: target);

  @override
  dynamic getRawProperty(String property) {
    throw FlutUnknownPropertyException('Canvas', property);
  }

  @override
  bool setProperty(String property, dynamic value) {
    throw FlutUnknownPropertyException('Canvas', property);
  }

  @override
  dynamic callMethod(String method, Map<String, dynamic> args) {
    switch (method) {
      case 'drawLine':
        flutTarget.drawLine(
          flutRuntime.decodeObject<Offset>(args['p1'] as Map<String, dynamic>)!,
          flutRuntime.decodeObject<Offset>(args['p2'] as Map<String, dynamic>)!,
          flutRuntime.decodeObject<Paint>(
            args['paint'] as Map<String, dynamic>,
          )!,
        );
        return null;
      case 'drawRect':
        flutTarget.drawRect(
          flutRuntime.decodeObject<Rect>(args['rect'] as Map<String, dynamic>)!,
          flutRuntime.decodeObject<Paint>(
            args['paint'] as Map<String, dynamic>,
          )!,
        );
        return null;
      case 'drawCircle':
        flutTarget.drawCircle(
          flutRuntime.decodeObject<Offset>(args['c'] as Map<String, dynamic>)!,
          (args['radius'] as num).toDouble(),
          flutRuntime.decodeObject<Paint>(
            args['paint'] as Map<String, dynamic>,
          )!,
        );
        return null;
      case 'drawOval':
        flutTarget.drawOval(
          flutRuntime.decodeObject<Rect>(args['rect'] as Map<String, dynamic>)!,
          flutRuntime.decodeObject<Paint>(
            args['paint'] as Map<String, dynamic>,
          )!,
        );
        return null;
      case 'drawArc':
        flutTarget.drawArc(
          flutRuntime.decodeObject<Rect>(args['rect'] as Map<String, dynamic>)!,
          (args['startAngle'] as num).toDouble(),
          (args['sweepAngle'] as num).toDouble(),
          args['useCenter'] as bool,
          flutRuntime.decodeObject<Paint>(
            args['paint'] as Map<String, dynamic>,
          )!,
        );
        return null;
      case 'clipRect':
        flutTarget.clipRect(
          flutRuntime.decodeObject<Rect>(args['rect'] as Map<String, dynamic>)!,
        );
        return null;
      case 'save':
        flutTarget.save();
        return null;
      case 'restore':
        flutTarget.restore();
        return null;
      case 'translate':
        flutTarget.translate(
          (args['dx'] as num).toDouble(),
          (args['dy'] as num).toDouble(),
        );
        return null;
      case 'scale':
        final sy = args['sy'];
        flutTarget.scale(
          (args['sx'] as num).toDouble(),
          sy != null ? (sy as num).toDouble() : null,
        );
        return null;
      case 'rotate':
        flutTarget.rotate((args['radians'] as num).toDouble());
        return null;
    }
    throw FlutUnknownMethodException(method);
  }
}
