import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:kasy_kit/core/theme/theme.dart';

enum KasyTextFieldVariant { primary, secondary, embedded }

enum KasyTextFieldContentType { text, email, password, phone }

/// Design-system single/multi-line input field.
///
/// Supports form validation via [validator], prefix/suffix icons, and multiline
/// via [maxLines] while keeping the visual field height stable.
///
/// Use [showRequiredIndicator] to append a red asterisk after [label] without
/// baking `*` into the copy (recommended for required fields).
class KasyTextField extends StatefulWidget {
  static const double adjacentFieldSpacing = KasySpacing.md;
  static const double iconSlotExtent = 38;
  static const double iconGlyphSize = 17;
  static const double webSingleLineVerticalPadding = 16;

  final TextEditingController? controller;
  final FocusNode? focusNode;
  final String? hint;
  final String? label;

  /// When true, appends a red `*` after [label] (label text stays separate).
  final bool showRequiredIndicator;
  final String? description;
  final String? errorText;
  final bool isInvalid;
  final KasyTextFieldVariant variant;
  final KasyTextFieldContentType contentType;
  final String? semanticLabel;
  final Iterable<String>? autofillHints;
  final bool enabled;
  final bool readOnly;
  final bool autofocus;
  final bool obscureText;
  final TextInputType? keyboardType;
  final TextInputAction? textInputAction;
  final TextCapitalization textCapitalization;
  final List<TextInputFormatter>? inputFormatters;
  final ValueChanged<String>? onChanged;
  final ValueChanged<String>? onSubmitted;
  final VoidCallback? onEditingComplete;
  final FormFieldValidator<String>? validator;
  final void Function(String?)? onSaved;
  final int? minLines;
  final int? maxLines;
  final int? maxLength;
  final Widget? prefix;
  final Widget? suffix;

  /// Optional widget rendered to the right of [label] (e.g. a "Forgot password?" link).
  final Widget? labelTrailing;

  const KasyTextField({
    super.key,
    this.controller,
    this.focusNode,
    this.hint,
    this.label,
    this.showRequiredIndicator = false,
    this.description,
    this.errorText,
    this.isInvalid = false,
    this.variant = KasyTextFieldVariant.primary,
    this.contentType = KasyTextFieldContentType.text,
    this.semanticLabel,
    this.autofillHints,
    this.enabled = true,
    this.readOnly = false,
    this.autofocus = false,
    this.obscureText = false,
    this.keyboardType,
    this.textInputAction,
    this.textCapitalization = TextCapitalization.none,
    this.inputFormatters,
    this.onChanged,
    this.onSubmitted,
    this.onEditingComplete,
    this.validator,
    this.onSaved,
    this.minLines,
    this.maxLines = 1,
    this.maxLength,
    this.prefix,
    this.suffix,
    this.labelTrailing,
  });

  @override
  State<KasyTextField> createState() => _KasyTextFieldState();
}

class _KasyTextFieldState extends State<KasyTextField> {
  TextEditingController? _internalController;
  FocusNode? _internalFocusNode;
  bool _passwordVisible = false;

  TextEditingController get _effectiveController =>
      widget.controller ?? _internalController!;

  FocusNode get _effectiveFocusNode => widget.focusNode ?? _internalFocusNode!;

  @override
  void initState() {
    super.initState();
    if (widget.controller == null) {
      _internalController = TextEditingController();
    }
    if (widget.focusNode == null) {
      _internalFocusNode = FocusNode();
    }
    _effectiveController.addListener(_handleTextChanged);
  }

  @override
  void didUpdateWidget(covariant KasyTextField oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.focusNode != widget.focusNode) {
      if (oldWidget.focusNode == null && widget.focusNode != null) {
        _internalFocusNode?.dispose();
        _internalFocusNode = null;
      } else if (widget.focusNode == null) {
        _internalFocusNode ??= FocusNode();
      }
    }
    if (oldWidget.controller == widget.controller) return;

    final TextEditingController oldController =
        oldWidget.controller ?? _internalController!;
    oldController.removeListener(_handleTextChanged);

    if (oldWidget.controller == null && widget.controller != null) {
      _internalController?.dispose();
      _internalController = null;
    } else if (widget.controller == null) {
      _internalController ??= TextEditingController();
    }

    _effectiveController.addListener(_handleTextChanged);
  }

  @override
  void dispose() {
    _effectiveController.removeListener(_handleTextChanged);
    _internalController?.dispose();
    _internalFocusNode?.dispose();
    super.dispose();
  }

  void _handleTextChanged() {
    if (widget.maxLength == null || !mounted) return;
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    final bool useForm = widget.validator != null || widget.onSaved != null;
    if (!useForm) {
      return _buildField(
        context,
        validationErrorText: null,
        onChanged: widget.onChanged,
      );
    }

    return FormField<String>(
      initialValue: _effectiveController.text,
      enabled: widget.enabled,
      validator: (_) => widget.validator?.call(_effectiveController.text),
      onSaved: (_) => widget.onSaved?.call(_effectiveController.text),
      builder: (FormFieldState<String> field) {
        return _buildField(
          context,
          validationErrorText: field.errorText,
          onChanged: (String value) {
            field.didChange(value);
            widget.onChanged?.call(value);
          },
        );
      },
    );
  }

  Widget _buildField(
    BuildContext context, {
    required String? validationErrorText,
    required ValueChanged<String>? onChanged,
  }) {
    final bool hasErrorText =
        widget.errorText != null && widget.errorText!.trim().isNotEmpty;
    final bool hasValidationError =
        validationErrorText != null && validationErrorText.trim().isNotEmpty;
    final bool hasInvalidState =
        widget.isInvalid || hasErrorText || hasValidationError;
    final String? helperText = hasErrorText
        ? widget.errorText
        : hasValidationError
        ? validationErrorText
        : widget.description;
    final bool hasHelperText =
        helperText != null && helperText.trim().isNotEmpty;
    final bool hasCounter = widget.maxLength != null;
    final bool isDisabled = !widget.enabled;
    final bool isSecondaryVariant =
        widget.variant == KasyTextFieldVariant.secondary;
    final bool isEmbeddedVariant =
        widget.variant == KasyTextFieldVariant.embedded;
    final bool useWebSingleLinePadding =
        !isSecondaryVariant &&
        !isEmbeddedVariant &&
        kIsWeb &&
        widget.minLines == null &&
        widget.maxLines == 1;
    const double disabledTextOpacity = 0.56;
    const double disabledLabelOpacity = 0.46;
    const double disabledDescriptionOpacity = 0.34;
    final bool isPassword =
        widget.contentType == KasyTextFieldContentType.password;
    final bool isEmail = widget.contentType == KasyTextFieldContentType.email;
    final bool isPhone =
        widget.contentType == KasyTextFieldContentType.phone ||
        widget.keyboardType == TextInputType.phone;
    final bool resolvedObscureText = isPassword
        ? !_passwordVisible
        : widget.obscureText;
    final Iterable<String>? resolvedAutofillHints =
        widget.autofillHints ?? _defaultAutofillHints(widget.contentType);
    final TextInputType? resolvedKeyboardType =
        widget.keyboardType ??
        (isEmail
            ? TextInputType.emailAddress
            : isPhone
            ? TextInputType.phone
            : null);
    final List<TextInputFormatter>? resolvedInputFormatters = isPhone
        ? <TextInputFormatter>[
            FilteringTextInputFormatter.digitsOnly,
            ...?widget.inputFormatters,
          ]
        : widget.inputFormatters;
    final InputDecorationThemeData inputTheme = Theme.of(
      context,
    ).inputDecorationTheme;
    final TextStyle labelBaseStyle =
        inputTheme.labelStyle ??
        context.textTheme.bodyMedium ??
        const TextStyle();
    final TextStyle helperBaseStyle =
        inputTheme.helperStyle ??
        context.textTheme.bodyMedium ??
        const TextStyle();
    final TextStyle errorBaseStyle =
        inputTheme.errorStyle ??
        helperBaseStyle.copyWith(color: context.colors.error);
    final TextStyle hintBaseStyle =
        inputTheme.hintStyle ??
        context.textTheme.bodyMedium ??
        const TextStyle();
    final TextStyle labelStyle = labelBaseStyle.copyWith(
      color: hasInvalidState
          ? context.colors.error
          : (labelBaseStyle.color ?? context.colors.fieldLabel).withValues(
              alpha: isDisabled ? disabledLabelOpacity : 1,
            ),
    );
    final TextStyle descriptionStyle =
        (hasInvalidState ? errorBaseStyle : helperBaseStyle).copyWith(
          color: hasInvalidState
              ? context.colors.error
              : (helperBaseStyle.color ?? context.colors.muted).withValues(
                  alpha: isDisabled ? disabledDescriptionOpacity : 1,
                ),
        );
    final BorderRadius fieldRadius = BorderRadius.circular(KasyRadius.md);
    final bool shouldShowShadow =
        !isSecondaryVariant && !isEmbeddedVariant && !kIsWeb;
    final BoxShadow resolvedShadow = KasyShadows.inputField(
      context,
      enabled: !isDisabled,
    );
    final TargetPlatform platform = Theme.of(context).platform;
    final bool isApplePlatform =
        platform == TargetPlatform.iOS || platform == TargetPlatform.macOS;
    final double focusedBorderWidth = isApplePlatform ? 1.7 : 1.4;
    final Color focusedBorderColor = isApplePlatform
        ? const Color(0xFF0A84FF)
        : context.colors.primary;
    final Color resolvedFocusedBorderColor = hasInvalidState
        ? context.colors.error
        : focusedBorderColor;
    final Color enabledBorderColor = KasyShadows.inputFieldRestingBorder(
      context,
    );
    final Color restingBorderColor = hasInvalidState
        ? context.colors.error
        : enabledBorderColor;
    final double restingBorderWidth = isEmbeddedVariant
        ? 0
        : hasInvalidState
        ? 1.3
        : 1;
    final InputBorder embeddedBorder = OutlineInputBorder(
      borderRadius: fieldRadius,
      borderSide: BorderSide.none,
    );
    final InputBorder resolvedEnabledBorder = isEmbeddedVariant
        ? embeddedBorder
        : OutlineInputBorder(
            borderRadius: fieldRadius,
            borderSide: BorderSide(
              color: restingBorderColor,
              width: restingBorderWidth,
            ),
          );
    final InputBorder resolvedFocusedBorder = isEmbeddedVariant
        ? embeddedBorder
        : OutlineInputBorder(
            borderRadius: fieldRadius,
            borderSide: BorderSide(
              color: resolvedFocusedBorderColor,
              width: focusedBorderWidth,
            ),
          );
    final Color surfaceColor = isEmbeddedVariant
        ? Colors.transparent
        : isSecondaryVariant
        ? (context.isDark ? const Color(0xFF272729) : const Color(0xFFF0F0F2))
        : inputTheme.fillColor ?? context.colors.surface;
    final Color fieldFillColor = isDisabled
        ? surfaceColor.withValues(alpha: context.isDark ? 0.9 : 0.94)
        : surfaceColor;
    final Widget? resolvedPrefix = widget.prefix == null
        ? null
        : Center(
            child: IconTheme.merge(
              data: IconThemeData(
                size: KasyTextField.iconGlyphSize,
                color: context.colors.onSurface.withValues(alpha: 0.62),
              ),
              child: widget.prefix!,
            ),
          );
    final Widget? customSuffix = widget.suffix == null
        ? null
        : Center(
            child: IconTheme.merge(
              data: IconThemeData(
                size: KasyTextField.iconGlyphSize,
                color: context.colors.onSurface.withValues(alpha: 0.62),
              ),
              child: widget.suffix!,
            ),
          );
    final Widget? resolvedSuffix =
        customSuffix ??
        (isPassword
            ? Semantics(
                button: true,
                label: _passwordVisible ? 'Hide password' : 'Show password',
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: widget.enabled
                      ? () =>
                            setState(() => _passwordVisible = !_passwordVisible)
                      : null,
                  child: SizedBox(
                    width: KasyTextField.iconSlotExtent,
                    height: KasyTextField.iconSlotExtent,
                    child: Center(
                      child: Icon(
                        _passwordVisible ? KasyIcons.eyeOff : KasyIcons.eye,
                        size: KasyTextField.iconGlyphSize,
                        color: context.colors.onSurface.withValues(
                          alpha: widget.enabled ? 0.62 : disabledTextOpacity,
                        ),
                      ),
                    ),
                  ),
                ),
              )
            : null);
    final TextStyle fieldTextStyle =
        context.textTheme.bodyLarge?.copyWith(
          color: context.colors.onSurface.withValues(
            alpha: isDisabled ? disabledTextOpacity : 1,
          ),
          fontWeight: FontWeight.w400,
          fontSize: 15,
        ) ??
        const TextStyle(fontSize: 15);
    final InputDecoration decoration = InputDecoration(
      isDense: true,
      hintText: widget.hint,
      prefixIcon: resolvedPrefix,
      suffixIcon: resolvedSuffix,
      prefixIconConstraints: resolvedPrefix == null
          ? null
          : const BoxConstraints.tightFor(
              width: KasyTextField.iconSlotExtent,
              height: KasyTextField.iconSlotExtent,
            ),
      suffixIconConstraints: resolvedSuffix == null
          ? null
          : const BoxConstraints.tightFor(
              width: KasyTextField.iconSlotExtent,
              height: KasyTextField.iconSlotExtent,
            ),
      contentPadding: EdgeInsets.symmetric(
        horizontal: isEmbeddedVariant ? 0 : KasySpacing.md,
        vertical: useWebSingleLinePadding
            ? KasyTextField.webSingleLineVerticalPadding
            : KasySpacing.smd,
      ),
      fillColor: fieldFillColor,
      filled: !isEmbeddedVariant,
      // On web: keep fill unchanged on hover (hoverColor replaces fillColor — don't let
      // Flutter's default semi-transparent hoverColor make the field look transparent).
      hoverColor: kIsWeb ? fieldFillColor : null,
      enabledBorder: resolvedEnabledBorder,
      border: resolvedEnabledBorder,
      disabledBorder: isEmbeddedVariant
          ? embeddedBorder
          : OutlineInputBorder(
              borderRadius: fieldRadius,
              borderSide: BorderSide(color: enabledBorderColor),
            ),
      focusedBorder: resolvedFocusedBorder,
      errorBorder: OutlineInputBorder(
        borderRadius: fieldRadius,
        borderSide: BorderSide(color: context.colors.error, width: 1.3),
      ),
      focusedErrorBorder: OutlineInputBorder(
        borderRadius: fieldRadius,
        borderSide: BorderSide(
          color: context.colors.error,
          width: focusedBorderWidth,
        ),
      ),
      hintStyle: hintBaseStyle.copyWith(
        color: (hintBaseStyle.color ?? context.colors.muted).withValues(
          alpha: isDisabled ? 0.42 : 1,
        ),
      ),
    );
    final Widget innerField = TextField(
      controller: _effectiveController,
      focusNode: _effectiveFocusNode,
      enabled: widget.enabled,
      readOnly: widget.readOnly,
      autofocus: widget.autofocus,
      obscureText: resolvedObscureText,
      keyboardType: resolvedKeyboardType,
      autofillHints: resolvedAutofillHints,
      enableSuggestions: !isPassword,
      autocorrect: !isPassword && !isEmail,
      textInputAction: widget.textInputAction,
      textCapitalization: widget.textCapitalization,
      inputFormatters: resolvedInputFormatters,
      onChanged: onChanged,
      onSubmitted: widget.onSubmitted,
      onEditingComplete: widget.onEditingComplete,
      onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(),
      minLines: resolvedObscureText ? null : widget.minLines,
      maxLines: resolvedObscureText ? 1 : widget.maxLines,
      maxLength: widget.maxLength,
      buildCounter: hasCounter ? _hideInputCounter : null,
      style: fieldTextStyle,
      decoration: decoration,
    );
    final Widget decoratedField = DecoratedBox(
      decoration: BoxDecoration(
        borderRadius: fieldRadius,
        boxShadow: shouldShowShadow ? <BoxShadow>[resolvedShadow] : null,
      ),
      child: Semantics(
        textField: true,
        enabled: widget.enabled,
        label: widget.semanticLabel ?? widget.label ?? widget.hint ?? 'Input',
        child: innerField,
      ),
    );
    final Widget? labelTrailing = widget.labelTrailing;
    final Widget fieldWidget = Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        if (widget.label != null && widget.label!.trim().isNotEmpty) ...[
          Padding(
            padding: const EdgeInsets.only(left: KasySpacing.sm - 2),
            child: Row(
              children: [
                Expanded(
                  child: GestureDetector(
                    behavior: HitTestBehavior.opaque,
                    onTap: () => _effectiveFocusNode.requestFocus(),
                    child: Text.rich(
                      TextSpan(
                        children: <InlineSpan>[
                          TextSpan(text: widget.label!.trim(), style: labelStyle),
                          if (widget.showRequiredIndicator)
                            TextSpan(
                              text: ' *',
                              style: labelStyle.copyWith(
                                color: context.colors.error,
                              ),
                            ),
                        ],
                      ),
                      overflow: TextOverflow.ellipsis,
                      textAlign: TextAlign.left,
                    ),
                  ),
                ),
                if (labelTrailing != null) ...[
                  const SizedBox(width: KasySpacing.sm),
                  Align(alignment: Alignment.centerRight, child: labelTrailing),
                ],
              ],
            ),
          ),
          const SizedBox(height: KasySpacing.xs),
        ],
        decoratedField,
        if (hasHelperText || hasCounter) ...[
          const SizedBox(height: KasySpacing.xs),
          Padding(
            padding: const EdgeInsets.only(
              left: KasySpacing.sm - 2,
              right: KasySpacing.sm - 2,
            ),
            child: Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                if (hasHelperText)
                  Expanded(
                    child: Text(
                      helperText,
                      style: descriptionStyle,
                      textAlign: TextAlign.left,
                    ),
                  )
                else
                  const Spacer(),
                if (hasCounter) ...[
                  if (hasHelperText) const SizedBox(width: KasySpacing.sm),
                  Text(
                    '${_effectiveController.text.length}/${widget.maxLength}',
                    style: context.textTheme.bodySmall?.copyWith(
                      color: context.colors.onSurface.withValues(
                        alpha: isDisabled ? disabledDescriptionOpacity : 0.54,
                      ),
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                ],
              ],
            ),
          ),
        ],
      ],
    );
    return TapRegion(groupId: EditableText, child: fieldWidget);
  }

  Iterable<String>? _defaultAutofillHints(KasyTextFieldContentType type) {
    return switch (type) {
      KasyTextFieldContentType.text => null,
      KasyTextFieldContentType.email => const <String>[AutofillHints.email],
      KasyTextFieldContentType.password => const <String>[
        AutofillHints.password,
      ],
      KasyTextFieldContentType.phone => const <String>[
        AutofillHints.telephoneNumber,
      ],
    };
  }

  Widget? _hideInputCounter(
    BuildContext context, {
    required int currentLength,
    required bool isFocused,
    required int? maxLength,
  }) {
    return null;
  }
}
