import 'package:flutter/material.dart';

import '../../utils/color_utils.dart';
import '../wind_context.dart';
import '../wind_style.dart';
import 'wind_parser_interface.dart';

/// **Typography Parser**
///
/// Handles text styling including colors, sizes, weights, and alignment.
///
/// ### Supported Utility Classes:
/// - **Color:** `text-{color}-{shade}`, `text-[#hex]`, `text-{color}/{opacity}`
/// - **Alignment:** `text-left`, `text-center`, `text-right`, `text-justify`, `text-start`, `text-end`
/// - **Font Size:** `text-{preset}`, `text-[{size}]`, `text-{size}/{lineHeight}`
/// - **Font Weight:** `font-{preset}`, `font-[{100-900}]`
/// - **Font Style:** `italic`, `not-italic`
/// - **Font Family:** `font-{sans|serif|mono}`, `font-[{family}]`
/// - **Decoration:** `underline`, `overline`, `line-through`, `no-underline`
/// - **Decoration Style:** `decoration-{solid|double|dotted|dashed|wavy}`
/// - **Decoration Color:** `decoration-{color}-{shade}`, `decoration-[#hex]`
/// - **Decoration Thickness:** `decoration-{value}`, `decoration-[{value}]`
/// - **Transform:** `uppercase`, `lowercase`, `capitalize`, `normal-case`
/// - **Letter Spacing:** `tracking-{tighter|tight|normal|wide|wider|widest}`, `tracking-[{value}]`
/// - **Line Height:** `leading-{preset|value}`, `leading-[{value}]`
/// - **Text Overflow:** `truncate`, `text-ellipsis`, `text-clip`
/// - **Line Clamp:** `line-clamp-{number|none}`
/// - **Whitespace/Wrap:** `whitespace-{normal|nowrap}`, `text-{wrap|nowrap|balance}`
///
/// Returns a [WindStyle] with typography-related properties.
class TextParser implements WindParserInterface {
  const TextParser();

  /// Regex for text color: `text-red-500`, `text-[#123456]`
  static final RegExp _textColorRegex = RegExp(
    r'^text-(?:(?<color>[a-zA-Z]+)(?:-(?<shade>[0-9]{2,3}))?|\[(?<arbitrary>#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}))\])$',
  );

  /// Regex for text alignment: `text-center`
  static final RegExp _textAlignRegex = RegExp(
    r'^text-(?<align>left|right|center|justify|start|end)$',
  );

  /// Regex for font size: `text-xl`, `text-[20px]`
  static final RegExp _fontSizeRegex = RegExp(
    r'^text-(?:(?<size>[a-zA-Z0-9-]+)|\[(?<arbitrary>[0-9.]+(?:px|rem)?)\])(?:/(?:(?<lineHeight>[a-zA-Z0-9.]+)|\[(?<arbitraryLineHeight>[0-9.]+(?:px|rem)?)\]))?$',
  );

  /// Regex for font weight: `font-bold`, `font-[700]`
  static final RegExp _fontWeightRegex = RegExp(
    r'^font-(?:(?<weight>[a-zA-Z0-9-]+)|\[(?<arbitrary>[0-9]+)\])$',
  );

  /// Regex for font style: `italic`, `not-italic`
  static final RegExp _fontStyleRegex = RegExp(
    r'^(?<style>italic|not-italic)$',
  );

  /// Regex for font family: `font-sans`, `font-serif`, `font-mono`, `font-[CustomFont]`
  ///
  /// Notes about intentional overlap:
  /// - The `[a-zA-Z]+` part allows custom theme font families such as `font-display`.
  /// - This intentionally overlaps with named font-weight tokens like `font-bold`,
  ///   `font-semibold`, etc. As a result, typos in weight classes (e.g. `font-blod`)
  ///   would match this regex and be treated as a font family rather than being
  ///   rejected.
  /// - To keep this behavior predictable, the font-weight parser is expected to run
  ///   before the font-family parser in the main parsing logic so that valid
  ///   weight classes are consumed by the weight parser and only non-weights fall
  ///   through to this regex.
  /// If the order of font-related checks is changed, ensure that font-weight
  /// parsing still occurs before font-family parsing to preserve this behavior.
  static final RegExp _fontFamilyRegex = RegExp(
    r'^font-(?:(?<family>sans|serif|mono|[a-zA-Z]+)|\[(?<arbitrary>[^\]]+)\])$',
  );

  /// Regex for text decoration: `underline`, `line-through`
  static final RegExp _textDecorationRegex = RegExp(
    r'^(?<decoration>underline|overline|line-through|no-underline)$',
  );

  /// Regex for decoration color: `decoration-red-500`
  static final RegExp _textDecorationColorRegex = RegExp(
    r'^decoration-(?:(?<color>[a-zA-Z]+)(?:-(?<shade>[0-9]{2,3}))?|\[(?<arbitrary>#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}))\])$',
  );

  /// Regex for decoration style: `decoration-solid`
  static final RegExp _textDecorationStyleRegex = RegExp(
    r'^decoration-(?<style>solid|double|dotted|dashed|wavy)$',
  );

  /// Regex for decoration thickness: `decoration-2`, `decoration-[3px]`
  static final RegExp _textDecorationThicknessRegex = RegExp(
    r'^decoration-(?:(?<value>[0-9]+)|\[(?<arbitrary>[0-9.]+(?:px)?)\])$',
  );

  /// Regex for text transform: `uppercase`
  static final RegExp _textTransformRegex = RegExp(
    r'^(?<transform>uppercase|lowercase|capitalize|normal-case)$',
  );

  /// Regex for letter spacing: `tracking-wide`, `tracking-[0.1em]`
  static final RegExp _letterSpacingRegex = RegExp(
    r'^tracking-(?:(?<value>tighter|tight|normal|wide|wider|widest)|\[(?<arbitrary>[0-9.]+(?:px|em)?)\])$',
  );

  /// Regex for line height: `leading-loose`, `leading-[24px]`
  static final RegExp _lineHeightRegex = RegExp(
    r'^leading-(?:(?<value>none|tight|snug|normal|relaxed|loose|[0-9]+)|\[(?<arbitrary>[0-9.]+(?:px|rem)?)\])$',
  );

  /// Regex for text overflow: `truncate`, `text-ellipsis`
  static final RegExp _textOverflowRegex = RegExp(
    r'^(?:(?<truncate>truncate)|text-(?<overflow>ellipsis|clip))$',
  );

  /// Regex for line clamp: `line-clamp-2`
  static final RegExp _lineClampRegex = RegExp(
    r'^line-clamp-(?<lines>[0-9]+|none)$',
  );

  /// Regex for whitespace/wrap: `whitespace-nowrap`, `text-wrap`
  static final RegExp _textWrapRegex = RegExp(
    r'^(?:whitespace-(?<ws>normal|nowrap)|text-(?<tw>wrap|nowrap|balance))$',
  );

  // --- Static Lookup Maps ---

  static const _textAlignMap = {
    'left': TextAlign.left,
    'right': TextAlign.right,
    'center': TextAlign.center,
    'justify': TextAlign.justify,
    'start': TextAlign.start,
    'end': TextAlign.end,
  };

  static const _textDecorationMap = {
    'underline': TextDecoration.underline,
    'overline': TextDecoration.overline,
    'line-through': TextDecoration.lineThrough,
    'no-underline': TextDecoration.none,
  };

  static const _decorationStyleMap = {
    'solid': TextDecorationStyle.solid,
    'double': TextDecorationStyle.double,
    'dotted': TextDecorationStyle.dotted,
    'dashed': TextDecorationStyle.dashed,
    'wavy': TextDecorationStyle.wavy,
  };

  // Note: WindTextTransform is likely defined in your wind_style.dart
  static const _textTransformMap = {
    'uppercase': WindTextTransform.uppercase,
    'lowercase': WindTextTransform.lowercase,
    'capitalize': WindTextTransform.capitalize,
    'normal-case': WindTextTransform.none,
  };

  @override
  WindStyle parse(
    WindStyle styles,
    List<String>? classes,
    WindContext context,
  ) {
    if (classes == null || classes.isEmpty) return styles;

    final theme = context.theme;

    // Temporary variables to hold parsed values.
    // We iterate in reverse to respect the "Last Class Wins" rule.
    Color? color;
    double? fontSize;
    FontWeight? fontWeight;
    FontStyle? fontStyle;
    double? letterSpacing;
    double? heightLine;
    double? heightLineFactor;
    TextDecoration? textDecoration;
    Color? textDecorationColor;
    TextDecorationStyle? textDecorationStyle;
    double? textDecorationThickness;
    TextAlign? textAlign;
    TextOverflow? textOverflow;
    int? maxLines;
    bool? softWrap;
    WindTextTransform? textTransform;
    String? fontFamily;

    for (var i = classes.length - 1; i >= 0; i--) {
      final className = classes[i];

      // 1. Text Color
      if (color == null) {
        final opacityData = parseColorOpacity(className);
        final match = _textColorRegex.firstMatch(opacityData.colorPart);

        if (match != null) {
          Color? parsedColor;

          if (match.namedGroup('arbitrary') != null) {
            parsedColor = hexToColor(match.namedGroup('arbitrary')!);
          } else {
            final colorName = match.namedGroup('color')!;
            final shade =
                int.tryParse(match.namedGroup('shade') ?? '500') ?? 500;
            parsedColor = theme.getColor(colorName, shade);
          }

          if (parsedColor != null) {
            if (opacityData.opacity != null) {
              parsedColor = applyOpacity(parsedColor, opacityData.opacity!);
            }
            color = parsedColor;
            continue;
          }
        }
      }

      // 2. Text Align
      if (textAlign == null) {
        final match = _textAlignRegex.firstMatch(className);
        if (match != null) {
          textAlign = _textAlignMap[match.namedGroup('align')];
          continue;
        }
      }

      // 3. Font Size
      if (fontSize == null || heightLine == null) {
        final match = _fontSizeRegex.firstMatch(className);
        if (match != null) {
          if (fontSize == null) {
            if (match.namedGroup('arbitrary') != null) {
              final val = match.namedGroup('arbitrary')!;
              fontSize = double.tryParse(val.replaceAll(RegExp(r'px|rem'), ''));
            } else {
              final sizeKey = match.namedGroup('size')!;
              fontSize = theme.fontSizes[sizeKey];
            }

            if (heightLine == null) {
              double? lhValue;

              if (match.namedGroup('arbitraryLineHeight') != null) {
                final val = match.namedGroup('arbitraryLineHeight')!;
                lhValue = double.tryParse(
                  val.replaceAll(RegExp(r'px|rem'), ''),
                );
              } else if (match.namedGroup('lineHeight') != null) {
                final lhKey = match.namedGroup('lineHeight')!;
                // Check if it's a numeric value like "9" in text-2xl/9
                // Tailwind's numeric line-height values are based on rem units
                // /9 = 9 * 0.25rem = 2.25rem = 36px (but this is absolute height)
                final numericValue = double.tryParse(lhKey);
                if (numericValue != null) {
                  // Convert Tailwind's numeric scale to pixels (N * 4px)
                  // This gives actual line-height in pixels
                  lhValue = numericValue * 4;
                } else {
                  // Named values like 'loose', 'tight' etc.
                  lhValue = theme.leading[lhKey];
                }
              }

              if (lhValue != null && fontSize != null && fontSize > 0) {
                heightLine = lhValue;
                heightLineFactor = null;
              }
            }

            if (fontSize != null) {
              continue;
            }
          }
        }
      }

      // 4. Font Weight
      if (fontWeight == null) {
        final match = _fontWeightRegex.firstMatch(className);
        if (match != null) {
          if (match.namedGroup('arbitrary') != null) {
            final val = match.namedGroup('arbitrary')!;
            int? weightValue = int.tryParse(val);
            if (weightValue != null) {
              final index = (weightValue ~/ 100) - 1;
              if (index >= 0 && index <= 8) {
                fontWeight = FontWeight.values[index];
                continue;
              }
            }
          } else {
            // Important: Only continue if a valid weight is found.
            // Classes like 'font-display' match this regex but are not
            // valid weights, so they should fall through to the font-family
            // parser. See _fontFamilyRegex documentation for details.
            final parsedWeight = theme.fontWeights[match.namedGroup('weight')];
            if (parsedWeight != null) {
              fontWeight = parsedWeight;
              continue;
            }
          }
        }
      }

      // 5. Font Style
      if (fontStyle == null) {
        final match = _fontStyleRegex.firstMatch(className);
        if (match != null) {
          fontStyle = match.namedGroup('style') == 'italic'
              ? FontStyle.italic
              : FontStyle.normal;
          continue;
        }
      }

      // 6. Text Decoration
      if (textDecoration == null) {
        final match = _textDecorationRegex.firstMatch(className);
        if (match != null) {
          textDecoration = _textDecorationMap[match.namedGroup('decoration')];
          continue;
        }
      }

      // 7. Decoration Color
      if (textDecorationColor == null) {
        final match = _textDecorationColorRegex.firstMatch(className);
        if (match != null) {
          Color? parsedColor;

          if (match.namedGroup('arbitrary') != null) {
            parsedColor = hexToColor(match.namedGroup('arbitrary')!);
          } else {
            final colorName = match.namedGroup('color')!;
            final shade =
                int.tryParse(match.namedGroup('shade') ?? '500') ?? 500;
            parsedColor = theme.getColor(colorName, shade);
          }

          if (parsedColor != null) {
            textDecorationColor = parsedColor;
            continue;
          }
        }
      }

      // 8. Decoration Style
      if (textDecorationStyle == null) {
        final match = _textDecorationStyleRegex.firstMatch(className);
        if (match != null) {
          textDecorationStyle = _decorationStyleMap[match.namedGroup('style')];
          continue;
        }
      }

      // 9. Decoration Thickness
      if (textDecorationThickness == null) {
        final match = _textDecorationThicknessRegex.firstMatch(className);
        if (match != null) {
          if (match.namedGroup('arbitrary') != null) {
            final val = match.namedGroup('arbitrary')!;
            textDecorationThickness = double.tryParse(val.replaceAll('px', ''));
          } else {
            final val = match.namedGroup('value');
            if (val != null) {
              textDecorationThickness = double.tryParse(val);
            }
          }

          continue;
        }
      }

      // 10. Text Transform
      if (textTransform == null) {
        final match = _textTransformRegex.firstMatch(className);
        if (match != null) {
          textTransform = _textTransformMap[match.namedGroup('transform')];
          continue;
        }
      }

      // 11. Letter Spacing (Tracking)
      if (letterSpacing == null) {
        final match = _letterSpacingRegex.firstMatch(className);
        if (match != null) {
          double? parsedValue;

          if (match.namedGroup('arbitrary') != null) {
            final val = match.namedGroup('arbitrary')!;
            parsedValue = double.tryParse(val.replaceAll(RegExp(r'px|em'), ''));
          } else {
            final key = match.namedGroup('value')!;
            parsedValue = theme.tracking[key]?.toDouble();
          }

          if (parsedValue != null) {
            letterSpacing = parsedValue;
            continue;
          }
        }
      }

      // 12. Line Height (Leading)
      if (heightLine == null && heightLineFactor == null) {
        final match = _lineHeightRegex.firstMatch(className);
        if (match != null) {
          double? parsedValue;
          double? parsedFactorValue;

          // If it's px arbitrary, you can use `parsedValue`
          // If it's a number or predefined, use `parsedFactorValue`
          if (match.namedGroup('arbitrary') != null) {
            final val = match.namedGroup('arbitrary')!;
            parsedValue = double.tryParse(
              val.replaceAll(RegExp(r'px|rem'), ''),
            );
          } else {
            final val = match.namedGroup('value')!;

            // If it's a number, parse as value by using getSpacing
            final factor = double.tryParse(val);
            if (factor != null) {
              parsedValue = theme.getSpacing(val);
            } else {
              parsedFactorValue = theme.leading[val];
            }
          }

          if (parsedValue != null) {
            heightLine = parsedValue;
            heightLineFactor = null;
            continue;
          } else if (parsedFactorValue != null) {
            heightLineFactor = parsedFactorValue;
            heightLine = null;
            continue;
          }
        }
      }

      // 13. Text Overflow / Truncate
      if (textOverflow == null) {
        final match = _textOverflowRegex.firstMatch(className);
        if (match != null) {
          if (match.namedGroup('truncate') != null) {
            textOverflow = TextOverflow.ellipsis;
            maxLines ??= 1;
            softWrap ??= false;
          } else {
            final overflow = match.namedGroup('overflow');
            textOverflow = overflow == 'ellipsis'
                ? TextOverflow.ellipsis
                : TextOverflow.clip; // 'clip' is default usually
          }
          continue;
        }
      }

      // 14. Whitespace / Wrap
      if (softWrap == null) {
        final match = _textWrapRegex.firstMatch(className);
        if (match != null) {
          final val = match.namedGroup('ws') ?? match.namedGroup('tw');
          if (val == 'nowrap') {
            softWrap = false;
          } else {
            softWrap = true; // normal, wrap, balance
          }
          continue;
        }
      }

      // 15. Line Clamp
      if (maxLines == null) {
        final match = _lineClampRegex.firstMatch(className);
        if (match != null) {
          final val = match.namedGroup('lines')!;
          if (val == 'none') {
            maxLines = null;
          } else {
            maxLines = int.tryParse(val);
            if (maxLines != null) {
              textOverflow ??= TextOverflow.ellipsis;
            }
          }
          continue;
        }
      }

      // 16. Font Family
      if (fontFamily == null) {
        final match = _fontFamilyRegex.firstMatch(className);
        if (match != null) {
          if (match.namedGroup('arbitrary') != null) {
            fontFamily = match.namedGroup('arbitrary')!;
          } else {
            final familyName = match.namedGroup('family')!;
            fontFamily = theme.fontFamilies[familyName] ?? familyName;
          }
          continue;
        }
      }
    }

    return styles.copyWith(
      color: color,
      fontSize: fontSize,
      fontWeight: fontWeight,
      fontStyle: fontStyle,
      letterSpacing: letterSpacing,
      heightLine: heightLine,
      heightLineFactor: heightLineFactor,
      textDecoration: textDecoration,
      textDecorationColor: textDecorationColor,
      textDecorationStyle: textDecorationStyle,
      textDecorationThickness: textDecorationThickness,
      textAlign: textAlign,
      textOverflow: textOverflow,
      maxLines: maxLines,
      softWrap: softWrap,
      textTransform: textTransform,
      fontFamily: fontFamily,
    );
  }

  @override
  bool canParse(String className) {
    return className.startsWith('text-') ||
        className.startsWith('font-') ||
        className == 'italic' ||
        className == 'not-italic' ||
        className == 'underline' ||
        className == 'overline' ||
        className == 'line-through' ||
        className == 'no-underline' ||
        className.startsWith('decoration-') ||
        className.startsWith('tracking-') ||
        className.startsWith('leading-') ||
        className == 'uppercase' ||
        className == 'lowercase' ||
        className == 'capitalize' ||
        className == 'normal-case' ||
        className == 'truncate' ||
        className.startsWith('line-clamp-') ||
        className.startsWith('whitespace-');
  }
}
