import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:kasy_kit/core/dev_inspector/dev_inspector_info.dart';

class DevInspectorService {
  DevInspectorService._();

  static const _screenRegex = r'(Page|Screen|Route|View)$';

  static const _skipSet = <String>{
    'RenderView',
    'RepaintBoundary',
    'PhysicalModel',
    'Semantics',
    'BlockSemantics',
    'ExcludeSemantics',
    'MergeSemantics',
    'Focus',
    'FocusScope',
    'FocusTraversalOrder',
    'FocusTraversalGroup',
    'Actions',
    'Shortcuts',
    'MouseRegion',
    'Listener',
    'AnnotatedRegion',
    'Localizations',
    'Directionality',
    'Title',
    'MediaQuery',
    'DefaultTextStyle',
    'IconTheme',
    'NotificationListener',
    'ScrollConfiguration',
    'PrimaryScrollController',
    'ScrollNotificationObserver',
    'HeroControllerScope',
    'WidgetInspector',
    'CheckedModeBanner',
    'Banner',
    'DevInspector',
  };

  // Flutter internal layout widgets — walk up to a semantic ancestor
  static const _preferParentSet = <String>{
    'RichText',
    'RawGestureDetector',
    'RawMaterialButton',
    'Center',
    'SizedBox',
  };

  static const _genericUiSet = <String>{
    'Padding',
    'Container',
    'Center',
    'Align',
    'GestureDetector',
    'RawGestureDetector',
    'InkWell',
    'Material',
    'DefaultSelectionStyle',
    'Column',
    'Row',
    'SizedBox',
    'Expanded',
    'Flexible',
    'Ink',
    'Builder',
    'ClipRect',
    'ClipRRect',
    'SliverToBoxAdapter',
    'SliverPadding',
    'Viewport',
    'CustomPaint',
    'ImageFiltered',
    'AnimatedBuilder',
    'IndexedSemantics',
    'AnimatedPhysicalModel',
    'AnimatedDefaultTextStyle',
  };

  static bool _isGenericUi(String typeName) {
    if (_genericUiSet.contains(typeName)) return true;
    return typeName.startsWith('NotificationListener<') ||
        typeName.startsWith('Listener<') ||
        typeName.startsWith('Builder<');
  }

  static bool _shouldSkip(String typeName) {
    if (typeName.startsWith('_')) return true;
    return _skipSet.contains(typeName);
  }

  static List<String> _extractProperties(Element element) {
    final props = <String>[];
    try {
      final node = element.widget.toDiagnosticsNode();
      for (final prop in node.getProperties()) {
        final name = prop.name;
        if (name == null || name.isEmpty) continue;
        final val = prop.toDescription();
        if (val.isEmpty || val == 'null' || val == 'false') continue;
        if (val.contains('Closure') && val.length > 40) continue;
        props.add('$name: $val');
        if (props.length >= 10) break;
      }
    } catch (_) {}
    return props;
  }

  static List<String> _extractAncestors(
    Element element, {
    bool includePrivate = false,
  }) {
    final ancestors = <String>[];
    try {
      element.visitAncestorElements((ancestor) {
        if (ancestors.length >= 30) return false;
        final name = ancestor.widget.runtimeType.toString();
        if (_shouldSkip(name)) return true;
        if (!includePrivate && name.startsWith('_')) return true;
        if (!_isGenericUi(name)) {
          ancestors.add(name);
        }
        return true;
      });
    } catch (_) {}
    return ancestors;
  }

  static String? _sanitizeType(String raw) {
    final type = raw.trim();
    if (type.isEmpty) return null;
    return type.startsWith('_') ? type.substring(1) : type;
  }

  static String? _extractScreenHint(Element element, List<String> ancestors) {
    final route = ModalRoute.of(element);
    final routeName = route?.settings.name;
    if (routeName != null && routeName.isNotEmpty && routeName != '/') {
      return routeName;
    }

    final privateAwareAncestors = _extractAncestors(element, includePrivate: true);
    final regex = RegExp('^_?[A-Za-z0-9]+$_screenRegex');
    for (final type in privateAwareAncestors.reversed) {
      if (regex.hasMatch(type)) return _sanitizeType(type);
    }
    for (final type in ancestors.reversed) {
      if (RegExp(_screenRegex).hasMatch(type)) return _sanitizeType(type);
    }
    return null;
  }

  static String? _extractSemanticWidget(String widgetType, List<String> ancestors) {
    final chain = <String>[widgetType, ...ancestors];
    final featureRegex = RegExp(
      r'(Page|Screen|Card|Tile|Item|Section|Panel|Dialog|Modal|Header|Footer|Widget|Component)$',
    );

    // 1) Prioritize feature-level widgets/classes first.
    for (final type in chain) {
      if (_shouldSkip(type)) continue;
      if (_preferParentSet.contains(type)) continue;
      if (_isGenericUi(type)) continue;
      if (featureRegex.hasMatch(type)) return type;
    }

    // 2) Fallback to first non-generic non-skipped widget.
    for (final type in chain) {
      if (_shouldSkip(type)) continue;
      if (_preferParentSet.contains(type)) continue;
      if (_isGenericUi(type)) continue;
      return type;
    }
    return null;
  }

  static List<String> _extractSearchHints({
    required String widgetType,
    required List<String> properties,
    required List<String> ancestors,
    required String? screenHint,
    required String? semanticWidget,
  }) {
    final hints = <String>{};
    if (screenHint != null) hints.add(screenHint);
    if (semanticWidget != null) hints.add(semanticWidget);
    for (final type in ancestors) {
      if (type.endsWith('Card') ||
          type.endsWith('Tile') ||
          type.endsWith('Item') ||
          type.endsWith('Component') ||
          type.endsWith('Widget')) {
        hints.add(type);
      }
      if (hints.length >= 5) break;
    }
    for (final prop in properties) {
      if (prop.startsWith('data:') ||
          prop.startsWith('label:') ||
          prop.startsWith('title:') ||
          prop.startsWith('hint:') ||
          prop.startsWith('value:')) {
        hints.add(prop);
      }
      if (hints.length >= 8) break;
    }
    hints.add(widgetType);
    return hints.take(8).toList();
  }

  /// Global bounds for inspector selection (boxes, slivers, and other targets).
  static Rect _globalBoundingRect(RenderObject target) {
    if (target is RenderBox) {
      final Offset globalOffset = target.localToGlobal(Offset.zero);
      return globalOffset & target.size;
    }
    try {
      final Matrix4 transform = target.getTransformTo(null);
      return MatrixUtils.transformRect(transform, target.paintBounds);
    } catch (_) {
      return Rect.zero;
    }
  }

  static DevInspectorInfo? _extractFromRenderObject(RenderObject target) {
    final creator = target.debugCreator;
    if (creator == null) return null;

    Element? element;
    try {
      // ignore: avoid_dynamic_calls
      element = (creator as dynamic).element as Element?;
    } catch (_) {
      return null;
    }
    if (element == null) return null;

    var typeName = element.widget.runtimeType.toString();
    var activeElement = element;

    if (_preferParentSet.contains(typeName)) {
      element.visitAncestorElements((ancestor) {
        final name = ancestor.widget.runtimeType.toString();
        if (!_shouldSkip(name) && !_preferParentSet.contains(name)) {
          typeName = name;
          activeElement = ancestor;
          return false;
        }
        return true;
      });
    }

    if (_shouldSkip(typeName)) return null;

    final Rect rect = _globalBoundingRect(target);
    final ancestors = _extractAncestors(activeElement);
    final properties = _extractProperties(activeElement);
    final screenHint = _extractScreenHint(activeElement, ancestors);
    final semanticWidget = _extractSemanticWidget(typeName, ancestors);
    final searchHints = _extractSearchHints(
      widgetType: typeName,
      properties: properties,
      ancestors: ancestors,
      screenHint: screenHint,
      semanticWidget: semanticWidget,
    );

    return DevInspectorInfo(
      widgetType: typeName,
      boundingBox: rect,
      properties: properties,
      ancestors: ancestors,
      screenHint: screenHint,
      semanticWidget: semanticWidget,
      searchHints: searchHints,
    );
  }

  /// Reads the widget currently selected in Flutter's native inspector.
  static DevInspectorInfo? inspectSelected() {
    if (!kDebugMode) return null;
    try {
      // ignore: invalid_use_of_visible_for_testing_member
      final RenderObject? renderObject =
          WidgetInspectorService.instance.selection.current;
      if (renderObject == null) return null;
      return _extractFromRenderObject(renderObject);
    } catch (_) {
      return null;
    }
  }

  /// Inspects the widget at a global position, syncs the native inspector
  /// selection (tooltip + highlight), and return the info.
  static DevInspectorInfo? inspectAndSelect(Offset globalPosition) {
    if (!kDebugMode) return null;
    try {
      final result = BoxHitTestResult();
      final renderView = WidgetsBinding.instance.renderViews.firstOrNull;
      if (renderView == null) return null;
      renderView.hitTest(result, position: globalPosition);
      for (final entry in result.path) {
        if (entry.target is! RenderObject) continue;
        final RenderObject candidate = entry.target as RenderObject;
        final DevInspectorInfo? info = _extractFromRenderObject(candidate);
        if (info != null) {
          // ignore: invalid_use_of_protected_member
          WidgetInspectorService.instance.setSelection(candidate, 'kit_inspector');
          return info;
        }
      }
      return null;
    } catch (_) {
      return null;
    }
  }
}
