import 'package:flut/dart/ui.dart';
import 'package:flut/dart/core.dart';
import 'package:flutter/material.dart';
import 'package:flut/flut/object.dart';
import 'package:flut/flut/native.dart';
import 'package:flut/flut/error.dart';
import 'package:flut/flut/widget.dart';
import 'package:flut/flutter/widgets/focus_manager.dart';
import 'package:flut/flutter/material/app_bar.dart';
import 'package:flut/flutter/widgets/basic.dart';
import 'package:flut/flutter/widgets/container.dart';
import 'package:flut/flutter/widgets/editable_text.dart';
import 'package:flut/flutter/widgets/framework.dart';
import 'package:flut/flutter/widgets/gesture_detector.dart';
import 'package:flut/flutter/widgets/icon.dart';
import 'package:flut/flutter/widgets/implicit_animations.dart';
import 'package:flut/flutter/widgets/scroll_controller.dart';
import 'package:flut/flutter/widgets/scroll_position.dart';
import 'package:flut/flutter/widgets/scroll_view.dart';
import 'package:flut/flutter/material/selectable_text.dart';
import 'package:flut/flutter/widgets/single_child_scroll_view.dart';
import 'package:flut/flutter/widgets/text.dart';
import 'package:flut/flutter/widgets/widget_span.dart';
import 'package:flut/flutter/widgets/visibility.dart';
import 'package:flut/flutter/material/app.dart';
import 'package:flut/flutter/material/card.dart';
import 'package:flut/flutter/material/divider.dart';
import 'package:flut/flutter/material/elevated_button.dart';
import 'package:flut/flutter/material/floating_action_button.dart';
import 'package:flut/flutter/material/icon_button.dart';
import 'package:flut/flutter/material/ink_well.dart';
import 'package:flut/flutter/material/progress_indicator.dart';
import 'package:flut/flutter/material/scaffold.dart';
import 'package:flut/flutter/material/text_field.dart';
import 'package:flut/flutter/material/theme_data.dart';
import 'package:flut/flutter/material/text_theme.dart';
import 'package:flut/flutter/painting/alignment.dart';
import 'package:flut/flutter/painting/border_radius.dart';
import 'package:flut/flutter/painting/borders.dart';
import 'package:flut/flutter/painting/box_decoration.dart';
import 'package:flut/flutter/painting/edge_insets.dart';
import 'package:flut/flutter/painting/text_painter.dart';
import 'package:flut/flutter/painting/text_span.dart';
import 'package:flut/flutter/painting/text_style.dart';
import 'package:flut/flutter/animation/curves.dart';
import 'package:flut/flutter/widgets/media_query.dart';
import 'package:flut/flutter/material/theme.dart';
import 'package:flut/flutter/material/color_scheme.dart';
import 'package:flut/flutter/widgets/icon_data.dart';
import 'package:flut/flutter/material/input_decorator.dart';
import 'package:flut/flutter/scheduler/binding.dart';
import 'package:flut/flutter/material/input_border.dart';
import 'package:flut/flutter/rendering/flex.dart';
import 'package:flut/flutter/rendering/custom_paint.dart';
import 'package:flut/flutter/rendering/wrap.dart';
import 'package:flut/flutter/services/mouse_cursor.dart';
import 'package:flut/flutter/services/hardware_keyboard.dart';
import 'package:flut/flutter/services/keyboard_key.dart';
import 'package:flut/flutter/gestures/drag_details.dart';
import 'package:flut/flutter/material/text_button.dart';
import 'package:flut/flutter/material/outlined_button.dart';
import 'package:flut/flutter/material/list_tile.dart';
import 'package:flut/flutter/material/switch.dart';
import 'package:flut/flutter/material/checkbox.dart';
import 'package:flut/flutter/material/tooltip.dart';
import 'package:flut/flutter/material/circle_avatar.dart';

class _Sentinel {
  const _Sentinel();
}

const _sentinel = _Sentinel();

class FlutRuntime {
  final FlutNative flutNative;

  final Map<int, FlutRealtimeObject> objectRegistry = {};
  final Map<int, int> _hashToOid = {};
  final Map<String, Function> _staticRegistry = {};

  int _nextOid = 1;

  FlutRuntime(this.flutNative) {
    _registerStaticHandlers();
  }

  int generateOid() => _nextOid++;

  int createObject(Map<String, dynamic> data) {
    final oid = generateOid();
    data['_flut_oid'] = oid;

    final type = data['_flut_type'] as String?;
    if (type == null) {
      throw FlutRuntimeException('Missing _flut_type in createObject');
    }

    final result = _decodeByType(type, data);
    if (result is! FlutRealtimeObject) {
      throw FlutRuntimeException(
        'createObject requires a FlutRealtimeObject type, got $type',
      );
    }

    objectRegistry[oid] = result;
    _hashToOid[identityHashCode(result.flutTarget)] = oid;

    return oid;
  }

  /// Decode a key from JSON data. Returns ValueKey if key is present.
  Key? decodeKey(Map<String, dynamic> data) {
    final key = data['key'];
    if (key == null) return null;
    return ValueKey(key);
  }

  void registerStatic(String name, Function handler) {
    _staticRegistry[name] = handler;
  }

  void _registerStaticHandlers() {
    FlutTheme.registerStatics(this);
    FlutMediaQuery.registerStatics(this);
    FlutHardwareKeyboard.registerStatics(this);
    FlutSchedulerBinding.registerStatics(this);
  }

  void triggerAction(String actionId, {Map<String, dynamic>? payload}) {
    if (actionId.isEmpty) return;
    final data = <String, dynamic>{'id': actionId, ...?payload};
    flutNative.invokeNativeNoWaitSync('action', data);
  }

  dynamic decodeAction(String? actionId) {
    if (actionId == null) return null;
    return ([
      dynamic a0 = _sentinel,
      dynamic a1 = _sentinel,
      dynamic a2 = _sentinel,
      dynamic a3 = _sentinel,
    ]) {
      final args = <dynamic>[];
      for (final a in [a0, a1, a2, a3]) {
        if (identical(a, _sentinel)) break;
        try {
          args.add(encodeValue(a));
        } catch (_) {
          args.add(null);
        }
      }
      triggerAction(actionId, payload: args.isEmpty ? null : {'args': args});
    };
  }

  dynamic unpackNullableRequiredCallback(
    Map<String, dynamic> data,
    String fieldName,
  ) {
    return decodeAction(unpackNullableRequiredField<String>(data, fieldName));
  }

  dynamic unpackRequiredCallback(Map<String, dynamic> data, String fieldName) {
    return decodeAction(unpackRequiredField<String>(data, fieldName));
  }

  dynamic unpackOptionalCallback(Map<String, dynamic> data, String fieldName) {
    return decodeAction(unpackOptionalField<String>(data, fieldName));
  }

  Map<String, dynamic>? invokeAction(
    String actionId, {
    Map<String, dynamic>? payload,
  }) {
    if (actionId.isEmpty) return null;
    final data = <String, dynamic>{'id': actionId, ...?payload};
    return flutNative.invokeNativeSync('action', data);
  }

  T? callAction<T>(
    String actionId, {
    List<dynamic>? args,
    Map<String, dynamic>? kwargs,
  }) {
    final payload = <String, dynamic>{};
    if (args != null && args.isNotEmpty) {
      payload['args'] = args.map((a) => encodeValue(a)).toList();
    }
    if (kwargs != null && kwargs.isNotEmpty) {
      payload['kwargs'] = kwargs.map((k, v) => MapEntry(k, encodeValue(v)));
    }
    final result = invokeAction(
      actionId,
      payload: payload.isEmpty ? null : payload,
    );
    final innerResult = result?['result'];
    if (innerResult == null) return null;
    if (innerResult is T) return innerResult;
    if (innerResult is Map<String, dynamic>) {
      return decodeObject<T>(innerResult);
    }
    return null;
  }

  Map<String, dynamic> handleStaticCall(
    String name,
    List<dynamic> args,
    Map<String, dynamic>? kwargs,
  ) {
    final handler = _staticRegistry[name];
    if (handler == null) {
      return {'error': 'Unknown static function', 'name': name};
    }

    final resolvedArgs = args.map(_resolveArg).toList();
    final resolvedKwargs = kwargs?.map((k, v) => MapEntry(k, _resolveArg(v)));

    try {
      Map<Symbol, dynamic>? namedArgs;
      if (resolvedKwargs != null && resolvedKwargs.isNotEmpty) {
        namedArgs = resolvedKwargs.map((k, v) => MapEntry(Symbol(k), v));
      }
      final result = Function.apply(handler, [
        this,
        ...resolvedArgs,
      ], namedArgs);
      return {'value': encodeValue(result)};
    } catch (e) {
      return {'error': e.toString()};
    }
  }

  dynamic _resolveArg(dynamic arg) {
    if (arg is Map<String, dynamic> && arg.containsKey('oid')) {
      final oid = arg['oid'] as int;
      final obj = objectRegistry[oid];
      if (obj is FlutBuildContext) {
        return obj.context;
      }
      return obj;
    }
    return arg;
  }

  Widget? decodeWidget(dynamic data) {
    return decodeObject<Widget>(data);
  }

  Widget buildWidgetFromJson(Map<String, dynamic> data) {
    final widget = decodeWidget(data);
    if (widget == null) {
      throw FlutRuntimeException('Failed to decode widget');
    }
    return widget;
  }

  T? unpackOptionalField<T>(Map<String, dynamic> data, String fieldName) {
    final value = data[fieldName];
    if (value == null) return null;

    if (T == double) return (value as num).toDouble() as T;
    if (T == int) return value as T;
    if (T == bool) return value as T;
    if (T == String) return value as T;

    if (value is List) {
      if (T == List<Widget>) {
        return value.map((c) => decodeWidget(c)).nonNulls.toList() as T;
      }

      if (T == List<InlineSpan>) {
        return value.map((c) => decodeObject<InlineSpan>(c)).nonNulls.toList()
            as T;
      }
    }

    return decodeObject<T>(value);
  }

  T unpackRequiredField<T>(Map<String, dynamic> data, String fieldName) {
    if (!data.containsKey(fieldName)) {
      final parentType = data['_flut_type'] as String? ?? '';
      throw FlutMissingRequiredParameterException(parentType, fieldName);
    }
    return unpackOptionalField<T>(data, fieldName) as T;
  }

  T? unpackNullableRequiredField<T>(
    Map<String, dynamic> data,
    String fieldName,
  ) {
    if (!data.containsKey(fieldName)) {
      final parentType = data['_flut_type'] as String? ?? '';
      throw FlutMissingRequiredParameterException(parentType, fieldName);
    }
    return unpackOptionalField<T>(data, fieldName);
  }

  // ===========================================================================
  // decodeObject<T> - Generic decoder using '_flut_type' field
  // ===========================================================================

  T? decodeObject<T>(dynamic data) {
    if (data == null) return null;

    if (data is! Map<String, dynamic>) {
      throw FlutInvalidValueException(T.toString(), data);
    }

    final type = data['_flut_type'] as String?;
    if (type == null) {
      throw FlutRuntimeException('Missing _flut_type field');
    }

    final oid = data['_flut_oid'] as int?;
    if (oid != null) {
      final existing = objectRegistry[oid];
      if (existing != null) {
        if (existing is T) return existing as T;
        if (existing.flutTarget is T) return existing.flutTarget as T;
      }
    }

    final result = _decodeByType(type, data);

    if (result is FlutRealtimeObject) {
      if (oid != null) {
        objectRegistry[oid] = result;
        _hashToOid[identityHashCode(result.flutTarget)] = oid;
      }
      if (result is T) return result as T;
      if (result.flutTarget is T) return result.flutTarget as T;
      throw FlutInvalidValueException(T.toString(), result);
    }

    if (result is! T) {
      throw FlutInvalidValueException(T.toString(), result);
    }

    return result;
  }

  dynamic _decodeByType(String type, Map<String, dynamic> data) {
    switch (type) {
      case 'TextEditingController':
        return FlutTextEditingController.flutDecode(this, data);
      case 'ScrollController':
        return FlutScrollController.flutDecode(this, data);
      case 'FocusNode':
        return FlutFocusNode.flutDecode(this, data);
      case 'EdgeInsets':
        return FlutEdgeInsets.flutDecode(this, data);
      case 'Alignment':
        return FlutAlignment.flutDecode(this, data);
      case 'BorderRadius':
        return FlutBorderRadius.flutDecode(this, data);
      case 'BorderStyle':
        return const FlutBorderStyle().flutDecode(data);
      case 'FontWeight':
        return FlutFontWeight.flutDecode(this, data);
      case 'BorderSide':
        return FlutBorderSide.flutDecode(this, data);
      case 'Border':
        return FlutBorder.flutDecode(this, data);
      case 'BoxDecoration':
        return FlutBoxDecoration.flutDecode(this, data);
      case 'TextSpan':
        return FlutTextSpan.flutDecode(this, data);
      case 'WidgetSpan':
        return FlutWidgetSpan.flutDecode(this, data);
      case 'TextStyle':
        return FlutTextStyle.flutDecode(this, data);
      case 'TextOverflow':
        return const FlutTextOverflow().flutDecode(data);
      case 'IconData':
        return FlutIconData.flutDecode(this, data);
      case 'TextTheme':
        return FlutTextTheme.flutDecode(this, data);
      case 'ColorScheme':
        return FlutColorScheme.flutDecode(this, data);
      case 'ThemeData':
        return FlutThemeData.flutDecode(this, data);
      case 'InputDecoration':
        return FlutInputDecoration.flutDecode(this, data);
      case 'InputBorder':
        return FlutInputBorder.flutDecode(this, data);
      case 'Duration':
        return FlutDuration.flutDecode(this, data);
      case 'Curve':
        return FlutCurve.flutDecode(this, data);
      case 'LogicalKeyboardKey':
        return FlutLogicalKeyboardKey.flutDecode(this, data);
      case 'PhysicalKeyboardKey':
        return FlutPhysicalKeyboardKey.flutDecode(this, data);
      case 'Color':
        return FlutColor.flutDecode(this, data);
      case 'MainAxisAlignment':
        return const FlutMainAxisAlignment().flutDecode(data);
      case 'MainAxisSize':
        return const FlutMainAxisSize().flutDecode(data);
      case 'CrossAxisAlignment':
        return const FlutCrossAxisAlignment().flutDecode(data);
      case 'MouseCursor':
        return FlutMouseCursor.flutDecode(this, data);
      case 'KeyEventResult':
        return const FlutKeyEventResult().flutDecode(data);
      case 'PaintingStyle':
        return const FlutPaintingStyle().flutDecode(data);
      case 'PlaceholderAlignment':
        return const FlutPlaceholderAlignment().flutDecode(data);
      case 'TextBaseline':
        return const FlutTextBaseline().flutDecode(data);
      case 'FlexFit':
        return const FlutFlexFit().flutDecode(data);
      case 'Axis':
        return const FlutAxis().flutDecode(data);
      case 'TextAlign':
        return const FlutTextAlign().flutDecode(data);
      case 'Size':
        return FlutSize.flutDecode(this, data);
      case 'Offset':
        return FlutOffset.flutDecode(this, data);
      case 'Rect':
        return FlutRect.flutDecode(this, data);
      case 'Paint':
        return FlutPaint.flutDecode(this, data);
      case 'CustomPainter':
        return FlutCustomPainter.flutDecode(this, data);
      case 'StatefulWidget':
        return FlutStatefulWidget(
          key: decodeKey(data),
          flutId: unpackRequiredField<String>(data, '_flut_id'),
          className: unpackRequiredField<String>(data, 'className'),
          native: flutNative,
          runtime: this,
        );
      case 'StatelessWidget':
        return FlutStatelessWidget(
          key: decodeKey(data),
          flutId: unpackRequiredField<String>(data, '_flut_id'),
          className: unpackRequiredField<String>(data, 'className'),
          native: flutNative,
          runtime: this,
        );
      case 'AppBar':
        return FlutAppBar.flutDecode(this, data);
      case 'Text':
        return FlutText.flutDecode(this, data);
      case 'SelectableText':
        return FlutSelectableText.flutDecode(this, data);
      case 'Center':
        return FlutCenter.flutDecode(this, data);
      case 'Padding':
        return FlutPadding.flutDecode(this, data);
      case 'Align':
        return FlutAlign.flutDecode(this, data);
      case 'Opacity':
        return FlutOpacity.flutDecode(this, data);
      case 'ClipRRect':
        return FlutClipRRect.flutDecode(this, data);
      case 'SizedBox':
        return FlutSizedBox.flutDecode(this, data);
      case 'Expanded':
        return FlutExpanded.flutDecode(this, data);
      case 'Flexible':
        return FlutFlexible.flutDecode(this, data);
      case 'Spacer':
        return FlutSpacer.flutDecode(this, data);
      case 'Column':
        return FlutColumn.flutDecode(this, data);
      case 'Row':
        return FlutRow.flutDecode(this, data);
      case 'Stack':
        return FlutStack.flutDecode(this, data);
      case 'Positioned':
        return FlutPositioned.flutDecode(this, data);
      case 'Container':
        return FlutContainer.flutDecode(this, data);
      case 'Icon':
        return FlutIcon.flutDecode(this, data);
      case 'Visibility':
        return FlutVisibility.flutDecode(this, data);
      case 'ListView':
        return FlutListView.flutDecode(this, data);
      case 'SingleChildScrollView':
        return FlutSingleChildScrollView.flutDecode(this, data);
      case 'AnimatedContainer':
        return FlutAnimatedContainer.flutDecode(this, data);
      case 'AnimatedOpacity':
        return FlutAnimatedOpacity.flutDecode(this, data);
      case 'MouseRegion':
        return FlutMouseRegion.flutDecode(this, data);
      case 'GestureDetector':
        return FlutGestureDetector.flutDecode(this, data);
      case 'MaterialApp':
        return FlutMaterialApp.flutDecode(this, data);
      case 'Scaffold':
        return FlutScaffold.flutDecode(this, data);
      case 'Card':
        return FlutCard.flutDecode(this, data);
      case 'Divider':
        return FlutDivider.flutDecode(this, data);
      case 'CircularProgressIndicator':
        return FlutCircularProgressIndicator.flutDecode(this, data);
      case 'FloatingActionButton':
        return FlutFloatingActionButton.flutDecode(this, data);
      case 'ElevatedButton':
        return FlutElevatedButton.flutDecode(this, data);
      case 'IconButton':
        return FlutIconButton.flutDecode(this, data);
      case 'InkWell':
        return FlutInkWell.flutDecode(this, data);
      case 'TextField':
        return FlutTextField.flutDecode(this, data);
      case 'CustomPaint':
        return FlutCustomPaint.flutDecode(this, data);
      case 'Wrap':
        return FlutWrap.flutDecode(this, data);
      case 'WrapAlignment':
        return const FlutWrapAlignment().flutDecode(data);
      case 'WrapCrossAlignment':
        return const FlutWrapCrossAlignment().flutDecode(data);
      case 'Transform.rotate':
        return FlutTransformRotate.flutDecode(this, data);
      case 'Transform.scale':
        return FlutTransformScale.flutDecode(this, data);
      case 'Transform.translate':
        return FlutTransformTranslate.flutDecode(this, data);
      case 'TextButton':
        return FlutTextButton.flutDecode(this, data);
      case 'OutlinedButton':
        return FlutOutlinedButton.flutDecode(this, data);
      case 'ListTile':
        return FlutListTile.flutDecode(this, data);
      case 'Switch':
        return FlutSwitch.flutDecode(this, data);
      case 'Checkbox':
        return FlutCheckbox.flutDecode(this, data);
      case 'LinearProgressIndicator':
        return FlutLinearProgressIndicator.flutDecode(this, data);
      case 'Tooltip':
        return FlutTooltip.flutDecode(this, data);
      case 'CircleAvatar':
        return FlutCircleAvatar.flutDecode(this, data);
      default:
        throw FlutUnknownTypeException(type);
    }
  }

  // ===========================================================================
  // encodeValue - Encode Dart values for Python
  // ===========================================================================

  dynamic encodeValue(dynamic value) {
    if (value == null) return null;
    if (value is num || value is String || value is bool) return value;
    if (value is List) return value.map(encodeValue).toList();
    if (value is Map) {
      return value.map((k, v) => MapEntry(k, encodeValue(v)));
    }
    if (value is FlutImmutableObject) return value.flutEncode();
    if (value is FlutRealtimeObject) return value.flutEncode();

    final hash = identityHashCode(value);
    final existingOid = _hashToOid[hash];
    if (existingOid != null) {
      final existing = objectRegistry[existingOid];
      if (existing != null) return existing.flutEncode();
    }

    if (value is Color) return FlutColor(value).flutEncode();
    if (value is Size) return FlutSize(value).flutEncode();
    if (value is Offset) return FlutOffset(value).flutEncode();
    if (value is Rect) return FlutRect(value).flutEncode();
    if (value is EdgeInsets) return FlutEdgeInsets(value).flutEncode();
    if (value is ColorScheme) return FlutColorScheme(value).flutEncode();
    if (value is ThemeData) return FlutThemeData(value).flutEncode();
    if (value is TextTheme) return FlutTextTheme(value).flutEncode();
    if (value is TextStyle) return FlutTextStyle(value).flutEncode();
    if (value is MediaQueryData) return FlutMediaQueryData(value).flutEncode();
    if (value is DragStartDetails) {
      return FlutDragStartDetails(value).flutEncode();
    }
    if (value is DragUpdateDetails) {
      return FlutDragUpdateDetails(value).flutEncode();
    }
    if (value is DragEndDetails) return FlutDragEndDetails(value).flutEncode();
    if (value is Velocity) return FlutVelocity(value).flutEncode();
    if (value is KeyEvent) return FlutKeyEvent(value).flutEncode();

    if (value is ScrollPosition) {
      final wrapper = wrapObject<FlutScrollPosition>(
        value,
        (oid) =>
            FlutScrollPosition.wrap(runtime: this, oid: oid, target: value),
      );
      return wrapper.flutEncode();
    }

    throw FlutEncodeException(value.runtimeType);
  }

  // ===========================================================================
  // wrapObject - Wrap Dart objects in FlutRealtimeObject
  // ===========================================================================

  T wrapObject<T extends FlutRealtimeObject>(
    Object dartObj,
    T Function(int oid) factory, {
    int? requestedOid,
  }) {
    final hash = identityHashCode(dartObj);

    final existingOid = _hashToOid[hash];
    if (existingOid != null) {
      final existing = objectRegistry[existingOid];
      if (existing != null) {
        if (requestedOid != null && existingOid != requestedOid) {
          throw FlutOidConflictException(existingOid, requestedOid);
        }
        return existing as T;
      }
    }

    final oid = requestedOid ?? generateOid();

    if (objectRegistry.containsKey(oid)) {
      throw FlutOidInUseException(oid);
    }

    final wrapper = factory(oid);
    objectRegistry[oid] = wrapper;
    _hashToOid[hash] = oid;

    return wrapper;
  }

  void releaseObject(int oid) {
    final wrapper = objectRegistry.remove(oid);
    if (wrapper != null) {
      _hashToOid.removeWhere((_, v) => v == oid);
    }
  }

  /// Release objects piggybacked in a widget_build response.
  ///
  /// Python collects garbage-collected object IDs and includes them in
  /// the response JSON as '_flut_release_oids'. This avoids sync calls
  /// during builds that could cause reentrancy.
  void releaseOidsFromResponse(Map<String, dynamic> response) {
    final oids = response['_flut_release_oids'] as List<dynamic>?;
    if (oids == null || oids.isEmpty) return;

    for (final oid in oids) {
      if (oid is int) {
        releaseObject(oid);
      }
    }
  }
}
