/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#import "iOSTxtInputManager.h"
#import "KeyboardTypeMapper.h"
#import "base/log/log.h"

#include <Foundation/Foundation.h>
#include <UIKit/UIKit.h>

static const char _kTextAffinityDownstream[] = "TextAffinity.downstream";
static const char _kTextAffinityUpstream[] = "TextAffinity.upstream";

#pragma mark - iOSTextPosition
@implementation iOSTextPosition

+ (instancetype)positionWithIndex:(NSUInteger)index {
    return [[[iOSTextPosition alloc] initWithIndex:index] autorelease];
}

- (instancetype)initWithIndex:(NSUInteger)index {
    self = [super init];
    if (self) {
        _index = index;
    }
    return self;
}

@end

#pragma mark - iOSTextRange

@implementation iOSTextRange

+ (instancetype)rangeWithNSRange:(NSRange)range {
    return [[[iOSTextRange alloc] initWithNSRange:range] autorelease];
}

- (instancetype)initWithNSRange:(NSRange)range {
    self = [super init];
    if (self) {
        _range = range;
    }
    return self;
}

- (UITextPosition*)start {
    return [iOSTextPosition positionWithIndex:self.range.location];
}

- (UITextPosition*)end {
    return [iOSTextPosition positionWithIndex:self.range.location + self.range.length];
}

- (BOOL)isEmpty {
    return self.range.length == 0;
}

- (id)copyWithZone:(NSZone*)zone {
    return [[iOSTextRange allocWithZone:zone] initWithNSRange:self.range];
}

@end

@interface iOSTextInputView : UIView <UITextInput>

// UITextInput
@property(nonatomic, readonly) NSMutableString* text;
@property(nonatomic, readonly) NSMutableString* markedText;
@property(nonatomic, readonly) NSMutableString* appendText;
@property(readwrite, copy) UITextRange* selectedTextRange;
@property(nonatomic, strong) UITextRange* markedTextRange;
@property(nonatomic, copy) NSDictionary* markedTextStyle;
@property(nonatomic, weak) id<UITextInputDelegate> inputDelegate;
@property(nonatomic, copy) NSString* inputFilter;
@property(nonatomic) NSUInteger maxLength;
@property(nonatomic) NSUInteger markedTextLocation;
@property(nonatomic) NSUInteger markedTextLength;
@property(nonatomic) NSUInteger textLength;

// UITextInputTraits
@property(nonatomic) UITextAutocapitalizationType autocapitalizationType;
@property(nonatomic) UITextAutocorrectionType autocorrectionType;
@property(nonatomic) UITextSpellCheckingType spellCheckingType;
@property(nonatomic) BOOL enablesReturnKeyAutomatically;
@property(nonatomic) UIKeyboardAppearance keyboardAppearance;
@property(nonatomic) UIKeyboardType keyboardType;
@property(nonatomic) UIReturnKeyType returnKeyType;
@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry;

@property (nonatomic, copy) updateEditingClientBlock textInputBlock;
@property (nonatomic, copy) updateErrorTextBlock errorTextBlock;
@property (nonatomic, copy) performActionBlock textPerformBlock;

@end

@implementation iOSTextInputView {
    int _textInputClient;
    const char* _selectionAffinity;
    iOSTextRange* _selectedTextRange;
    BOOL _isDelete;
    BOOL _unmarkText;
    BOOL _discardedMarkedText;
    BOOL _isSetSelectedTextRange;
}

@synthesize tokenizer = _tokenizer;

- (instancetype)init {
    self = [super init];
    if (self) {
        _textInputClient = 0;
        _selectionAffinity = _kTextAffinityUpstream;
        // UITextInput
        _text = [[NSMutableString alloc] init];
        _markedText = [[NSMutableString alloc] init];
        _appendText = [[NSMutableString alloc] init];
        _selectedTextRange = [[iOSTextRange alloc] initWithNSRange:NSMakeRange(0, 0)];
        _markedTextLocation = 0;
        _markedTextLength = 0;
        _isDelete = NO;
        _unmarkText = NO;
        _discardedMarkedText = NO;
        _isSetSelectedTextRange = NO;
        // UITextInputTraits
        _autocapitalizationType = UITextAutocapitalizationTypeSentences;
        _autocorrectionType = UITextAutocorrectionTypeDefault;
        _spellCheckingType = UITextSpellCheckingTypeDefault;
        _enablesReturnKeyAutomatically = NO;
        _keyboardAppearance = UIKeyboardAppearanceDefault;
        _keyboardType = UIKeyboardTypeDefault;
        _returnKeyType = UIReturnKeyDone;
        _secureTextEntry = NO;
        _inputFilter = @"";
    }
    return self;
}

- (void)dealloc {
    [_text release];
    [_markedText release];
    [_appendText release];
    [_markedTextRange release];
    [_selectedTextRange release];
    [_tokenizer release];
    [super dealloc];
}

- (void)setTextInputClient:(int)client {
    _textInputClient = client;
}

- (void)setTextInputState:(NSDictionary*)state {
    if (self.markedTextRange != nil) {
        return;
    }
    NSString *newText = state[@"text"];
    BOOL textChanged = ![self.text isEqualToString:newText];
    if (textChanged) {
        [self.inputDelegate textWillChange:self];
        [self.text setString:newText];
    }
    NSInteger selectionBase = [state[@"selectionBase"] intValue];
    NSInteger selectionExtent = [state[@"selectionExtent"] intValue];
    NSRange selectedRange = [self clampSelection:NSMakeRange(MIN(selectionBase, selectionExtent), ABS(selectionBase - selectionExtent)) forText:self.text];
    NSRange oldSelectedRange = [(iOSTextRange*)self.selectedTextRange range];
    if (selectedRange.location != oldSelectedRange.location || selectedRange.length != oldSelectedRange.length) {
        [self.inputDelegate selectionWillChange:self];
        [self setSelectedTextRange:[iOSTextRange rangeWithNSRange:selectedRange] updateEditingState:NO];
        _selectionAffinity = _kTextAffinityDownstream;
        if ([state[@"selectionAffinity"] isEqualToString:@(_kTextAffinityUpstream)]){
           _selectionAffinity = _kTextAffinityUpstream;
        }
        [self.inputDelegate selectionDidChange:self];
    }
    if (textChanged) {
        [self.inputDelegate textDidChange:self];
    }
}

- (NSRange)clampSelection:(NSRange)range forText:(NSString*)text {
    int start = MIN(MAX(range.location, 0), text.length);
    int length = MIN(range.length, text.length - start);
    return NSMakeRange(start, length);
}

#pragma mark - UIResponder Overrides

- (BOOL)canBecomeFirstResponder {
    return YES;
}

#pragma mark - UITextInput Overrides

- (id<UITextInputTokenizer>)tokenizer {
    if (_tokenizer == nil) {
        _tokenizer = [[UITextInputStringTokenizer alloc] initWithTextInput:self];
    }
    return _tokenizer;
}

- (UITextRange*)selectedTextRange {
    return [_selectedTextRange copy];
}

- (void)setSelectedTextRange:(UITextRange*)selectedTextRange {
    _isSetSelectedTextRange = YES;
    [self setSelectedTextRange:selectedTextRange updateEditingState:YES];
}

- (void)setSelectedTextRange:(UITextRange*)selectedTextRange updateEditingState:(BOOL)update {
    if (_selectedTextRange != selectedTextRange) {
        if (self.hasText && !_isSetSelectedTextRange) {
            iOSTextRange* iosTextRange = (iOSTextRange*)selectedTextRange;
            _selectedTextRange = [[iOSTextRange
                                   rangeWithNSRange:[self RangeForCharactersInRange:self.text range:iosTextRange.range]] copy];
        } else {
            _selectedTextRange = [selectedTextRange copy];
        }
        if (update) {
            [self updateEditingState];
        }
    }
    _isSetSelectedTextRange = NO;
}

- (NSRange)RangeForCharactersInRange:(NSString* )text range:(NSRange)range {
    if (text == nil || range.location + range.length > text.length) {
        return NSMakeRange(NSNotFound, 0);
    }
    NSRange sanitizedRange = [text rangeOfComposedCharacterSequencesForRange:range];
    return NSMakeRange(sanitizedRange.location, range.length);
}

- (id)insertDictationResultPlaceholder {
    return @"";
}

- (void)removeDictationResultPlaceholder:(id)placeholder willInsertResult:(BOOL)willInsertResult {
}

- (NSString*)textInRange:(UITextRange*)range {
    if (range == nil || ![range isKindOfClass:[iOSTextRange class]] || self.text == nil) {
        LOGE("[iOSTxtInputManager] textInRange: invalid input (class=%{public}s, textIsNil=%{public}s)",
              range ? NSStringFromClass([range class]).UTF8String : "(null)",
              self.text ? "NO" : "YES");
        return @"";
    }
    NSRange textRange = ((iOSTextRange*)range).range;
    if (textRange.location == NSNotFound) {
        LOGE("[iOSTxtInputManager] textInRange: invalid textRange (location=NSNotFound)");
        return @"";
    }
    NSUInteger availableLength = self.text.length;
    if (self.maxLength > 0) {
        availableLength = MIN(availableLength, self.maxLength);
    }
    NSString* availableText = self.text;
    if (availableLength < self.text.length) {
        availableText = [self.text substringWithRange:NSMakeRange(0, availableLength)];
    }
    NSRange clampedRange = [self clampSelection:textRange forText:availableText];
    if (clampedRange.location == NSNotFound || clampedRange.location > availableText.length) {
        LOGE("[iOSTxtInputManager] textInRange: invalid clampedRange (loc=%{public}lu len=%{public}lu, availableLen=%{public}lu)",
              (unsigned long)clampedRange.location,
              (unsigned long)clampedRange.length,
              (unsigned long)availableText.length);
        return @"";
    }
    NSRange composedRange = [availableText rangeOfComposedCharacterSequencesForRange:clampedRange];
    if (composedRange.location == NSNotFound || composedRange.location + composedRange.length > availableText.length) {
        LOGE("[iOSTxtInputManager] textInRange: invalid composedRange (loc=%{public}lu len=%{public}lu, availableLen=%{public}lu)",
              (unsigned long)composedRange.location,
              (unsigned long)composedRange.length,
              (unsigned long)availableText.length);
        return @"";
    }
    return [availableText substringWithRange:composedRange];
}

- (void)replaceRange:(UITextRange*)range withText:(NSString*)text {
    if (self.returnKeyType == UIReturnKeyDefault && [text isEqualToString:@"\n"]) {
        return;
    }
    NSRange replaceRange = ((iOSTextRange*)range).range;
    NSRange selectedRange = _selectedTextRange.range;
    // Adjust the text selection:
    // * reduce the length by the intersection length
    // * adjust the location by newLength - oldLength + intersectionLength
    NSRange intersectionRange = NSIntersectionRange(replaceRange, selectedRange);
    if (replaceRange.location <= selectedRange.location) {
        selectedRange.location += text.length - replaceRange.length;
    }
    if (intersectionRange.location != NSNotFound) {
        selectedRange.location += intersectionRange.length;
        selectedRange.length -= intersectionRange.length;
    }
    [self.text replaceCharactersInRange:[self clampSelection:replaceRange forText:self.text] withString:text];
    [self setSelectedTextRange:
        [iOSTextRange rangeWithNSRange:
        [self clampSelection:selectedRange forText:self.text]] updateEditingState:NO];
    if (text.length == 0 && replaceRange.length != 0) {
        _unmarkText = YES;
        self.markedTextRange = [iOSTextRange rangeWithNSRange:replaceRange];
    } else {
        [self.appendText setString:text];
        self.markedTextRange = replaceRange.length > 0 ? [iOSTextRange rangeWithNSRange:replaceRange] : nil;
        if ((self.markedText.length == 0) && (self.text.length >= self.maxLength)) {
            NSRange range =  NSMakeRange(self.markedTextLocation, self.maxLength);
            if (range.location + range.length < self.text.length) {
                [self.appendText setString:[self.text substringWithRange:range]];
            }
        }
    }
    [self updateEditingState];
}

- (BOOL)shouldChangeTextInRange:(UITextRange*)range replacementText:(NSString*)text {
    if (self.returnKeyType != UIReturnKeyDefault && text.length > 0 && ![text isEqualToString:@"\n"]) {
        NSRange markedTextRange = ((iOSTextRange*)self.markedTextRange).range;
        NSRange selectedRange = _selectedTextRange.range;
        if (markedTextRange.length == 0 && selectedRange.length == 0) {
            if (self.text.length >= self.maxLength) {
                return NO;
            }
        }
    }
    if ((self.returnKeyType != UIReturnKeyDefault && ![text isEqualToString:@"\n"]) || self.returnKeyType == UIReturnKeyDefault) {
        if ([self.inputFilter length] > 0) {
            NSString *filteredText = @"";
            NSString *errorText = @"";
            NSRegularExpression *regex =
                [NSRegularExpression regularExpressionWithPattern:self.inputFilter options:NSRegularExpressionUseUnixLineSeparators error:nil];

            NSString *temp = nil;
            for (int i = 0; i < [text length]; i++) {
                temp = [text substringWithRange:NSMakeRange(i, 1)];
                auto hits = [regex matchesInString:temp options:0 range:NSMakeRange(0, [temp length])];                
                if ([hits count] > 0) {
                    filteredText = [filteredText stringByAppendingString: temp];
                } else {
                    errorText = [errorText stringByAppendingString: temp];
                }
            }
            if (![filteredText isEqualToString:text]) {
                [self updateInputFilterErrorText:errorText];
                return NO;
            }
        }
    }

    if (self.returnKeyType == UIReturnKeyDefault && [text isEqualToString:@"\n"]) {
        if (self.textPerformBlock) {
            self.textPerformBlock(iOSTextInputActionNewline, _textInputClient);
        }
        return YES;
    }

    if ([text isEqualToString:@"\n"]) {
        iOSTextInputAction action;
        switch (self.returnKeyType) {
            case UIReturnKeyDefault:
                action = iOSTextInputActionUnspecified;
                break;
            case UIReturnKeyDone:
                action = iOSTextInputActionDone;
                break;
            case UIReturnKeyGo:
                action = iOSTextInputActionGo;
                break;
            case UIReturnKeySend:
                action = iOSTextInputActionSend;
                break;
            case UIReturnKeySearch:
            case UIReturnKeyGoogle:
            case UIReturnKeyYahoo:
                action = iOSTextInputActionSearch;
                break;
            case UIReturnKeyNext:
                action = iOSTextInputActionNext;
                break;
            case UIReturnKeyContinue:
                action = iOSTextInputActionContinue;
                break;
            case UIReturnKeyJoin:
                action = iOSTextInputActionJoin;
                break;
            case UIReturnKeyRoute:
                action = iOSTextInputActionRoute;
                break;
            case UIReturnKeyEmergencyCall:
                action = iOSTextInputActionEmergencyCall;
                break;
        }
        if (self.textPerformBlock) {
            self.textPerformBlock(action, _textInputClient);
        }
        return NO;
    }
    
    return YES;
}

- (void)setMarkedText:(NSString*)markedText selectedRange:(NSRange)markedSelectedRange {
    NSRange selectedRange = _selectedTextRange.range;
    NSRange markedTextRange = ((iOSTextRange*)self.markedTextRange).range;
    if (markedText == nil) {
        markedText = @"";
    }
    [self.markedText setString:markedText];
    if (markedTextRange.length > 0) {
        // Replace text in the marked range with the new text.
        [self replaceRange:self.markedTextRange withText:markedText];
        markedTextRange.length = markedText.length;
    } else {
        // Replace text in the selected range with the new text.
        [self replaceRange:_selectedTextRange withText:markedText];
        markedTextRange = NSMakeRange(selectedRange.location, markedText.length);
    }
    self.markedTextRange =
        markedTextRange.length > 0 ? [iOSTextRange rangeWithNSRange:markedTextRange] : nil;
    NSUInteger selectionLocation = markedSelectedRange.location + markedTextRange.location;
    selectedRange = NSMakeRange(selectionLocation, markedSelectedRange.length);
    [self setSelectedTextRange:
        [iOSTextRange rangeWithNSRange:[self clampSelection:selectedRange forText:self.text]] updateEditingState:YES];
}

- (void)unmarkText {
    _unmarkText = YES;
    if (self.text.length >= self.maxLength) {
        NSInteger length = self.maxLength - self.textLength;
        NSRange range =  NSMakeRange(self.markedTextLocation, length > 0 ? length : 0);
        if (range.location + range.length > self.text.length) {
            return;
        }
        [self.appendText setString:[self.text substringWithRange:range]];
    } else {
        self.markedTextRange = nil;
    }
    [self updateEditingState];
}

- (UITextRange*)textRangeFromPosition:(UITextPosition*)fromPosition
                           toPosition:(UITextPosition*)toPosition {
    NSUInteger fromIndex = ((iOSTextPosition*)fromPosition).index;
    NSUInteger toIndex = ((iOSTextPosition*)toPosition).index;
    return [iOSTextRange rangeWithNSRange:NSMakeRange(fromIndex, toIndex - fromIndex)];
}

- (NSRange)RangeForCharacterAtIndex:(NSString* )text index:(NSUInteger)index {
    if (text == nil || index >= text.length) {
        return NSMakeRange(NSNotFound, 0);
    }
    if (index < text.length) {
        return [text rangeOfComposedCharacterSequenceAtIndex:index];
    }
    return NSMakeRange(index, 0);
}

- (NSUInteger)decrementOffsetPosition:(NSUInteger)position {
    return [self RangeForCharacterAtIndex:self.text index:MAX(0, position - 1)].location;
}

- (NSUInteger)incrementOffsetPosition:(NSUInteger)position {
    NSRange charRange = [self RangeForCharacterAtIndex:self.text index:position];
    return MIN(position + charRange.length, self.text.length);
}

- (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset {
    NSUInteger offsetPosition = ((iOSTextPosition*)position).index;
    NSInteger newLocation = (NSInteger)offsetPosition + offset;
    if (newLocation < 0 || newLocation > (NSInteger)self.text.length) {
        return nil;
    }
    if (offset >= 0) {
        for (NSInteger i = 0; i < offset && offsetPosition < self.text.length; ++i) {
            offsetPosition = [self incrementOffsetPosition:offsetPosition];
        }
    } else {
        for (NSInteger i = 0; i < ABS(offset) && offsetPosition > 0; ++i) {
            offsetPosition = [self decrementOffsetPosition:offsetPosition];
        }
    }
    return [iOSTextPosition positionWithIndex:offsetPosition];
}

- (UITextPosition*)positionFromPosition:(UITextPosition*)position
                            inDirection:(UITextLayoutDirection)direction
                                 offset:(NSInteger)offset {
    switch (direction) {
        case UITextLayoutDirectionLeft:
        case UITextLayoutDirectionUp:
            return [self positionFromPosition:position offset:offset * -1];
        case UITextLayoutDirectionRight:
        case UITextLayoutDirectionDown:
            return [self positionFromPosition:position offset:1];
    }
}

- (UITextPosition*)beginningOfDocument {
    return [iOSTextPosition positionWithIndex:0];
}

- (UITextPosition*)endOfDocument {
    return [iOSTextPosition positionWithIndex:self.text.length];
}

- (NSComparisonResult)comparePosition:(UITextPosition*)position toPosition:(UITextPosition*)other {
    NSUInteger positionIndex = ((iOSTextPosition*)position).index;
    NSUInteger otherIndex = ((iOSTextPosition*)other).index;
    if (positionIndex < otherIndex) {
        return NSOrderedAscending;
    }
    if (positionIndex > otherIndex) {
        return NSOrderedDescending;
    }
    return NSOrderedSame;
}

- (NSInteger)offsetFromPosition:(UITextPosition*)from toPosition:(UITextPosition*)toPosition {
    return ((iOSTextPosition*)toPosition).index - ((iOSTextPosition*)from).index;
}

- (UITextPosition*)positionWithinRange:(UITextRange*)range
                   farthestInDirection:(UITextLayoutDirection)direction {
    NSUInteger index;
    switch (direction) {
        case UITextLayoutDirectionLeft:
        case UITextLayoutDirectionUp:
            index = ((iOSTextPosition*)range.start).index;
            break;
        case UITextLayoutDirectionRight:
        case UITextLayoutDirectionDown:
            index = ((iOSTextPosition*)range.end).index;
            break;
    }
    return [iOSTextPosition positionWithIndex:index];
}

- (UITextRange*)characterRangeByExtendingPosition:(UITextPosition*)position
                                      inDirection:(UITextLayoutDirection)direction {
    NSUInteger positionIndex = ((iOSTextPosition*)position).index;
    NSUInteger startIndex;
    NSUInteger endIndex;
    switch (direction) {
        case UITextLayoutDirectionLeft:
        case UITextLayoutDirectionUp:
            startIndex = [self decrementOffsetPosition:positionIndex];
            endIndex = positionIndex;
            break;
        case UITextLayoutDirectionRight:
        case UITextLayoutDirectionDown:
            startIndex = positionIndex;
            endIndex = [self incrementOffsetPosition:positionIndex];
            break;
    }
    return [iOSTextRange rangeWithNSRange:NSMakeRange(startIndex, endIndex - startIndex)];
}

#pragma mark - UITextInput text direction handling

- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition*)position
                                              inDirection:(UITextStorageDirection)direction {
    return UITextWritingDirectionNatural;
}

- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection
                       forRange:(UITextRange*)range {}

#pragma mark - UITextInput cursor, selection rect handling

// The following methods are required to support force-touch cursor positioning
// and to position the
// candidates view for multi-stage input methods (e.g., Japanese) when using a
// physical keyboard.

- (CGRect)firstRectForRange:(UITextRange*)range {
    return CGRectZero;
}

- (CGRect)caretRectForPosition:(UITextPosition*)position {
    return CGRectZero;
}

- (UITextPosition*)closestPositionToPoint:(CGPoint)point {
    NSUInteger currentIndex = ((iOSTextPosition*)_selectedTextRange.start).index;
    return [iOSTextPosition positionWithIndex:currentIndex];
}

- (NSArray*)selectionRectsForRange:(UITextRange*)range {
    return @[];
}

- (UITextPosition*)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange*)range {
    return range.start;
}

- (UITextRange*)characterRangeAtPoint:(CGPoint)point {
    NSUInteger currentIndex = ((iOSTextPosition*)_selectedTextRange.start).index;
    return [iOSTextRange rangeWithNSRange:[self RangeForCharacterAtIndex:self.text index:currentIndex]];
}

- (void)beginFloatingCursorAtPoint:(CGPoint)point {}

- (void)updateFloatingCursorAtPoint:(CGPoint)point {}

- (void)endFloatingCursor {}

#pragma mark - UIKeyInput Overrides

- (void)updateEditingState {
    NSUInteger selectionBase = ((iOSTextPosition*)_selectedTextRange.start).index;
    NSUInteger selectionExtent = ((iOSTextPosition*)_selectedTextRange.end).index;
    
    // Empty compositing range is represented by the framework's TextRange.empty.
    NSInteger composingBase = -1;
    NSInteger composingExtent = -1;
    if (self.markedTextRange != nil) {
        composingBase = ((iOSTextPosition*)self.markedTextRange.start).index;
        composingExtent = ((iOSTextPosition*)self.markedTextRange.end).index;
    }

    NSRange markedTextRange = ((iOSTextRange*)self.markedTextRange).range;          
    if (markedTextRange.length == 0) {
        if (self.text.length > self.maxLength) {
            if (self.markedTextLength > 0) {
                NSString *prefixText = @"";
                NSString *insertText = @"";
                NSString *suffixText = @"";
                if (self.text.length >= self.markedTextLocation) {
                    prefixText = [self.text substringWithRange:NSMakeRange(0, self.markedTextLocation)];
                }
                if (self.text.length >= self.markedTextLocation + self.markedTextLength) {
                    insertText = [self.text substringWithRange:NSMakeRange(self.markedTextLocation, self.markedTextLength)];
                    suffixText = [self.text substringWithRange:NSMakeRange(self.markedTextLocation + self.markedTextLength, self.text.length - (self.markedTextLocation + self.markedTextLength))];
                }
                NSUInteger insertLength = self.maxLength - (self.text.length - self.markedTextLength);
                if (insertLength < insertText.length) {
                    insertText = [insertText substringWithRange:NSMakeRange(0, insertLength)];
                }

                NSString *newText = [prefixText stringByAppendingString: insertText];
                selectionBase = newText.length;
                selectionExtent = newText.length;
                newText = [newText stringByAppendingString: suffixText];
                [self.text setString:newText];
            } else {
                // NOTE: When discarding marked text, we skip maxLength trimming to avoid unintended extra truncation.
                if (self.appendText.length == 0 || (_unmarkText && !_discardedMarkedText)) {
                    NSString *newText = [self.text substringWithRange:NSMakeRange(0, self.maxLength)];
                    [self.text setString:newText];
                }
                selectionBase = self.maxLength;
                selectionExtent = self.maxLength;
            }
        }
        self.markedTextLocation = 0;
        self.markedTextLength = 0;
    } else {
        self.markedTextLocation = markedTextRange.location;
        self.markedTextLength = markedTextRange.length;
    }
    NSDictionary *dict = @{
        @"selectionBase" : @(selectionBase),
        @"selectionExtent" : @(selectionExtent),
        @"selectionAffinity" : @(_selectionAffinity),
        @"selectionIsDirectional" : @(false),
        @"composingBase" : @(composingBase),
        @"composingExtent" : @(composingExtent),
        @"text" : [NSString stringWithString:self.text],
        @"appendText" : [NSString stringWithString:self.appendText],
        @"isDelete" : @(_isDelete),
        @"unmarkText" : @(_unmarkText),
        @"discardedMarkedText" : @(_discardedMarkedText),
    };
    if (_unmarkText || _discardedMarkedText) {
        _unmarkText = NO;
        self.markedTextRange = nil;
        self.markedTextLocation = 0;
        self.markedTextLength = 0;
        self.textLength = self.text.length > self.maxLength && !_discardedMarkedText ? self.maxLength : self.text.length;
    } else {
        self.textLength = self.text.length - self.markedTextLength;
    }
    if  (_isDelete) { 
        _isDelete = NO;
        self.textLength = self.text.length;
    }
    _discardedMarkedText = NO;
    [self.appendText setString:@""];
    [self.markedText setString:@""];
    if (self.textInputBlock) {
        self.textInputBlock(_textInputClient, dict);
    }
}

- (void)updateInputFilterErrorText:(NSString*)errorText {
    NSDictionary *dict = @{
        @"errorText" : errorText,
    };
    if (self.errorTextBlock) {
        self.errorTextBlock(_textInputClient, dict);
    }
}

- (BOOL)hasText {
    return self.text.length > 0;
}

- (void)insertText:(NSString*)text {
    if ([self.inputFilter length] > 0) {
        NSString *filteredText = @"";
        NSString *errorText = @"";
        NSRegularExpression *regex =
            [NSRegularExpression regularExpressionWithPattern:self.inputFilter options:NSRegularExpressionUseUnixLineSeparators error:nil];

        NSString *temp = nil;
        for (int i = 0; i < [text length]; i++) {
            temp = [text substringWithRange:NSMakeRange(i, 1)];
            auto hits = [regex matchesInString:temp options:0 range:NSMakeRange(0, [temp length])];                
            if ([hits count] > 0) {
                filteredText = [filteredText stringByAppendingString: temp];
            } else {
                errorText = [errorText stringByAppendingString: temp];
            }
        }
        if (![filteredText isEqualToString:text]) {
            [self updateInputFilterErrorText:errorText];
        }
        text = filteredText;
    }
    NSInteger length = self.maxLength - self.text.length;
    if (self.text.length + text.length > self.maxLength && length > 0) {
        text = [text substringWithRange:NSMakeRange(0, length)];
    }

    _selectionAffinity = _kTextAffinityDownstream;
    [self replaceRange:_selectedTextRange withText:text];
}

- (void)deleteBackward {
    _selectionAffinity = _kTextAffinityDownstream;
    if ([self hasText]) {
        _isDelete = YES;
    }
    if (_selectedTextRange.isEmpty && [self hasText]) {
        NSRange oldRange = ((iOSTextRange*)_selectedTextRange).range;
        if (oldRange.location > 0) {
            NSRange newRange = NSMakeRange(oldRange.location - 1, 1);
            [self setSelectedTextRange:[iOSTextRange rangeWithNSRange:newRange]
                    updateEditingState:false];
        }
    }
    if (!_selectedTextRange.isEmpty) {
        [self replaceRange:_selectedTextRange withText:@""];
    }
}

- (void)discardMarkedText {
    [self.inputDelegate textWillChange:self];
    [self.inputDelegate selectionWillChange:self];
    _discardedMarkedText = YES;
    _unmarkText = YES;
    [self setMarkedText:nil selectedRange:NSMakeRange(0, 0)];
    [self.inputDelegate selectionDidChange:self];
    [self.inputDelegate textDidChange:self];
}

@end

@interface TextInputHideView : UIView

@end

@implementation TextInputHideView

- (BOOL)accessibilityElementsHidden {
    return YES;
}

@end

@implementation iOSTxtInputManager {
    iOSTextInputView* _view;
    iOSTextInputView* _secureView;
    iOSTextInputView* _activeView;
    TextInputHideView* _inputHider;
}

@synthesize textInputBlock = _textInputBlock;
@synthesize errorTextBlock = _errorTextBlock;
@synthesize textPerformBlock = _textPerformBlock;

+ (instancetype)sharedInstance {
    static dispatch_once_t onceToken;
    static iOSTxtInputManager *instance = nil;
    dispatch_once(&onceToken, ^{
        instance = [iOSTxtInputManager new];
    });
    return instance;
}

- (instancetype)init {
    self = [super init];
    if (self) {
        _view = [[iOSTextInputView alloc] init];
        _view.secureTextEntry = NO;
        _secureView = [[iOSTextInputView alloc] init];
        _secureView.secureTextEntry = YES;
        _activeView = _view;
        _inputHider = [[TextInputHideView alloc] init];
    }
    return self;
}

- (void)dealloc {
    [self hideTextInput];
    [_view release];
    [_secureView release];
    [_inputHider release];
    [self clearTextInputClient];
    [super dealloc];
}

- (UIView<UITextInput>*)textInputView {
    return _activeView;
}

- (void)showTextInput {
    NSAssert([UIApplication sharedApplication].keyWindow != nullptr,
             @"The application must have a key window since the keyboard client "
             @"must be part of the responder chain to function");
    if (self.textInputBlock) {
        _activeView.textInputBlock = self.textInputBlock;
    }
    if (self.errorTextBlock) {
        _activeView.errorTextBlock = self.errorTextBlock;
    }
    if (self.textPerformBlock) {
        _activeView.textPerformBlock = self.textPerformBlock;
    }
    if (![_activeView isFirstResponder]) {
        [self addToInputParentViewIfNeeded:_activeView];
        [_activeView becomeFirstResponder];
    }
}

- (UIWindow*)keyWindow {
    UIApplication *sharedApplication = [UIApplication sharedApplication];
    UIWindow *keyWindow = nil;
    if (@available(iOS 13.0, *)) {
        for (UIWindowScene *windowScene in sharedApplication.connectedScenes) {
            if (windowScene.activationState == UISceneActivationStateForegroundActive
                && [windowScene isKindOfClass:UIWindowScene.class]) {
                for (UIWindow *window in windowScene.windows) {
                    if (!window.hidden && window.isKeyWindow) {
                        keyWindow = window;
                        break;
                    }
                }
                if (keyWindow) {
                    break;
                }
            }
        }
    } else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        keyWindow = sharedApplication.keyWindow;
#pragma clang diagnostic pop
    }
    return keyWindow;
}

- (void)addToInputParentViewIfNeeded:(iOSTextInputView*)inputView {
    if ([inputView isDescendantOfView:_inputHider]) {
        [inputView removeFromSuperview];
    }
    [_inputHider addSubview:inputView];
    UIView* parentView = self.keyWindow;
    if (parentView == nil) {
        return;
    }
    if ([_inputHider isDescendantOfView:parentView]) {
        [_inputHider removeFromSuperview];
    }
    [parentView addSubview:_inputHider];
}

- (void)hideTextInput {
    [_activeView resignFirstResponder];
    [_activeView removeFromSuperview];
    [_inputHider removeFromSuperview];
}

- (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configuration {
    NSDictionary* inputType = configuration[@"inputType"];
    NSString* keyboardAppearance = configuration[@"keyboardAppearance"];
    if ([configuration[@"obscureText"] boolValue]) {
        _activeView = _secureView;
    } else {
        _activeView = _view;
    }
    NSString* inputTypeName = inputType[@"name"];
    UIKeyboardType keyboardType = [KeyboardTypeMapper toUIKeyboardType:inputTypeName];
    if (keyboardType == UIKeyboardTypeNumberPad) {
        if ([inputType[@"signed"] boolValue]) {
            keyboardType = UIKeyboardTypeNumbersAndPunctuation;
        }
        if ([inputType[@"decimal"] boolValue]) {
            keyboardType = UIKeyboardTypeDecimalPad;
        }   
    }
    _activeView.keyboardType = keyboardType;
    NSString* inputActionName = configuration[@"inputAction"];
    _activeView.returnKeyType = [KeyboardTypeMapper toUIReturnKeyType:inputActionName];
    NSString* textCapitalizationName = configuration[@"textCapitalization"];
    _activeView.autocapitalizationType = [KeyboardTypeMapper toUITextAutoCapitalizationType:textCapitalizationName];
    if ([keyboardAppearance isEqualToString:@"Brightness.dark"]) {
        _activeView.keyboardAppearance = UIKeyboardAppearanceDark;
    } else if ([keyboardAppearance isEqualToString:@"Brightness.light"]) {
        _activeView.keyboardAppearance = UIKeyboardAppearanceLight;
    } else {
        _activeView.keyboardAppearance = UIKeyboardAppearanceDefault;
    }
    NSString* autocorrect = configuration[@"autocorrect"];
    _activeView.autocorrectionType = autocorrect && ![autocorrect boolValue]
    ? UITextAutocorrectionTypeNo
    : UITextAutocorrectionTypeDefault;
    [_activeView setTextInputClient:client];
    [_activeView reloadInputViews];
    _activeView.inputFilter = configuration[@"inputFilter"];
    _activeView.maxLength = [configuration[@"maxLength"] intValue];
}

- (void)setTextInputEditingState:(NSDictionary*)state {
    [_activeView setTextInputState:state];
}

- (void)clearTextInputClient {
    [_activeView setTextInputClient:0];
    _activeView.textInputBlock = NULL;
    _activeView.errorTextBlock = NULL;
    _activeView.textPerformBlock = NULL;
}

- (void)finishComposing {
    [_activeView discardMarkedText];
}

@end