"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.excludeUnbalancedTrailingBracesAndPunctuation = exports.parseMatches = void 0;
var regex_lib_1 = require("../regex-lib");
var url_match_1 = require("../match/url-match");
var utils_1 = require("../utils");
var uri_utils_1 = require("./uri-utils");
var email_utils_1 = require("./email-utils");
var email_match_1 = require("../match/email-match");
var hashtag_utils_1 = require("./hashtag-utils");
var hashtag_match_1 = require("../match/hashtag-match");
var mention_utils_1 = require("./mention-utils");
var mention_match_1 = require("../match/mention-match");
var phone_number_utils_1 = require("./phone-number-utils");
var phone_match_1 = require("../match/phone-match");
// For debugging: search for and uncomment other "For debugging" lines
// import CliTable from 'cli-table';
/**
 * Parses URL, email, twitter, mention, and hashtag matches from the given
 * `text`.
 */
function parseMatches(text, args) {
    var tagBuilder = args.tagBuilder;
    var stripPrefix = args.stripPrefix;
    var stripTrailingSlash = args.stripTrailingSlash;
    var decodePercentEncoding = args.decodePercentEncoding;
    var hashtagServiceName = args.hashtagServiceName;
    var mentionServiceName = args.mentionServiceName;
    var matches = [];
    var textLen = text.length;
    // An array of all active state machines. Empty array means we're in the
    // "no url" state
    var stateMachines = [];
    // For debugging: search for and uncomment other "For debugging" lines
    // const table = new CliTable({
    //     head: ['charIdx', 'char', 'states', 'charIdx', 'startIdx', 'reached accept state'],
    // });
    var charIdx = 0;
    for (; charIdx < textLen; charIdx++) {
        var char = text.charAt(charIdx);
        if (stateMachines.length === 0) {
            stateNoMatch(char);
        }
        else {
            // Must loop through the state machines backwards for when one
            // is removed
            for (var stateIdx = stateMachines.length - 1; stateIdx >= 0; stateIdx--) {
                var stateMachine = stateMachines[stateIdx];
                switch (stateMachine.state) {
                    // Protocol-relative URL states
                    case 11 /* ProtocolRelativeSlash1 */:
                        stateProtocolRelativeSlash1(stateMachine, char);
                        break;
                    case 12 /* ProtocolRelativeSlash2 */:
                        stateProtocolRelativeSlash2(stateMachine, char);
                        break;
                    case 0 /* SchemeChar */:
                        stateSchemeChar(stateMachine, char);
                        break;
                    case 1 /* SchemeHyphen */:
                        stateSchemeHyphen(stateMachine, char);
                        break;
                    case 2 /* SchemeColon */:
                        stateSchemeColon(stateMachine, char);
                        break;
                    case 3 /* SchemeSlash1 */:
                        stateSchemeSlash1(stateMachine, char);
                        break;
                    case 4 /* SchemeSlash2 */:
                        stateSchemeSlash2(stateMachine, char);
                        break;
                    case 5 /* DomainLabelChar */:
                        stateDomainLabelChar(stateMachine, char);
                        break;
                    case 6 /* DomainHyphen */:
                        stateDomainHyphen(stateMachine, char);
                        break;
                    case 7 /* DomainDot */:
                        stateDomainDot(stateMachine, char);
                        break;
                    case 13 /* IpV4Digit */:
                        stateIpV4Digit(stateMachine, char);
                        break;
                    case 14 /* IpV4Dot */:
                        stateIPv4Dot(stateMachine, char);
                        break;
                    case 8 /* PortColon */:
                        statePortColon(stateMachine, char);
                        break;
                    case 9 /* PortNumber */:
                        statePortNumber(stateMachine, char);
                        break;
                    case 10 /* Path */:
                        statePath(stateMachine, char);
                        break;
                    // Email States
                    case 15 /* EmailMailto_M */:
                        stateEmailMailto_M(stateMachine, char);
                        break;
                    case 16 /* EmailMailto_A */:
                        stateEmailMailto_A(stateMachine, char);
                        break;
                    case 17 /* EmailMailto_I */:
                        stateEmailMailto_I(stateMachine, char);
                        break;
                    case 18 /* EmailMailto_L */:
                        stateEmailMailto_L(stateMachine, char);
                        break;
                    case 19 /* EmailMailto_T */:
                        stateEmailMailto_T(stateMachine, char);
                        break;
                    case 20 /* EmailMailto_O */:
                        stateEmailMailto_O(stateMachine, char);
                        break;
                    case 21 /* EmailMailto_Colon */:
                        stateEmailMailtoColon(stateMachine, char);
                        break;
                    case 22 /* EmailLocalPart */:
                        stateEmailLocalPart(stateMachine, char);
                        break;
                    case 23 /* EmailLocalPartDot */:
                        stateEmailLocalPartDot(stateMachine, char);
                        break;
                    case 24 /* EmailAtSign */:
                        stateEmailAtSign(stateMachine, char);
                        break;
                    case 25 /* EmailDomainChar */:
                        stateEmailDomainChar(stateMachine, char);
                        break;
                    case 26 /* EmailDomainHyphen */:
                        stateEmailDomainHyphen(stateMachine, char);
                        break;
                    case 27 /* EmailDomainDot */:
                        stateEmailDomainDot(stateMachine, char);
                        break;
                    // Hashtag states
                    case 28 /* HashtagHashChar */:
                        stateHashtagHashChar(stateMachine, char);
                        break;
                    case 29 /* HashtagTextChar */:
                        stateHashtagTextChar(stateMachine, char);
                        break;
                    // Mention states
                    case 30 /* MentionAtChar */:
                        stateMentionAtChar(stateMachine, char);
                        break;
                    case 31 /* MentionTextChar */:
                        stateMentionTextChar(stateMachine, char);
                        break;
                    // Phone number states
                    case 32 /* PhoneNumberOpenParen */:
                        statePhoneNumberOpenParen(stateMachine, char);
                        break;
                    case 33 /* PhoneNumberAreaCodeDigit1 */:
                        statePhoneNumberAreaCodeDigit1(stateMachine, char);
                        break;
                    case 34 /* PhoneNumberAreaCodeDigit2 */:
                        statePhoneNumberAreaCodeDigit2(stateMachine, char);
                        break;
                    case 35 /* PhoneNumberAreaCodeDigit3 */:
                        statePhoneNumberAreaCodeDigit3(stateMachine, char);
                        break;
                    case 36 /* PhoneNumberCloseParen */:
                        statePhoneNumberCloseParen(stateMachine, char);
                        break;
                    case 37 /* PhoneNumberPlus */:
                        statePhoneNumberPlus(stateMachine, char);
                        break;
                    case 38 /* PhoneNumberDigit */:
                        statePhoneNumberDigit(stateMachine, char);
                        break;
                    case 39 /* PhoneNumberSeparator */:
                        statePhoneNumberSeparator(stateMachine, char);
                        break;
                    case 40 /* PhoneNumberControlChar */:
                        statePhoneNumberControlChar(stateMachine, char);
                        break;
                    case 41 /* PhoneNumberPoundChar */:
                        statePhoneNumberPoundChar(stateMachine, char);
                        break;
                    default:
                        (0, utils_1.assertNever)(stateMachine.state);
                }
            }
        }
        // For debugging: search for and uncomment other "For debugging" lines
        // table.push([
        //     charIdx,
        //     char,
        //     stateMachines.map(machine => State[machine.state]).join('\n') || '(none)',
        //     charIdx,
        //     stateMachines.map(m => m.startIdx).join('\n'),
        //     stateMachines.map(m => m.acceptStateReached).join('\n'),
        // ]);
    }
    // Capture any valid match at the end of the string
    // Note: this loop must happen in reverse because
    // captureMatchIfValidAndRemove() removes state machines from the array
    // and we'll end up skipping every other one if we remove while looping
    // forward
    for (var i = stateMachines.length - 1; i >= 0; i--) {
        stateMachines.forEach(function (stateMachine) { return captureMatchIfValidAndRemove(stateMachine); });
    }
    // For debugging: search for and uncomment other "For debugging" lines
    // console.log(`\nRead string:\n  ${text}`);
    // console.log(table.toString());
    return matches;
    // Handles the state when we're not in a URL/email/etc. (i.e. when no state machines exist)
    function stateNoMatch(char) {
        if (char === '#') {
            // Hash char, start a Hashtag match
            stateMachines.push(createHashtagStateMachine(charIdx, 28 /* HashtagHashChar */));
        }
        else if (char === '@') {
            // '@' char, start a Mention match
            stateMachines.push(createMentionStateMachine(charIdx, 30 /* MentionAtChar */));
        }
        else if (char === '/') {
            // A slash could begin a protocol-relative URL
            stateMachines.push(createTldUrlStateMachine(charIdx, 11 /* ProtocolRelativeSlash1 */));
        }
        else if (char === '+') {
            // A '+' char can start a Phone number
            stateMachines.push(createPhoneNumberStateMachine(charIdx, 37 /* PhoneNumberPlus */));
        }
        else if (char === '(') {
            stateMachines.push(createPhoneNumberStateMachine(charIdx, 32 /* PhoneNumberOpenParen */));
        }
        else {
            if (regex_lib_1.digitRe.test(char)) {
                // A digit could start a phone number
                stateMachines.push(createPhoneNumberStateMachine(charIdx, 38 /* PhoneNumberDigit */));
                // A digit could start an IP address
                stateMachines.push(createIpV4UrlStateMachine(charIdx, 13 /* IpV4Digit */));
            }
            if ((0, email_utils_1.isEmailLocalPartStartChar)(char)) {
                // Any email local part. An 'm' character in particular could
                // start a 'mailto:' match
                var startState = char.toLowerCase() === 'm' ? 15 /* EmailMailto_M */ : 22 /* EmailLocalPart */;
                stateMachines.push(createEmailStateMachine(charIdx, startState));
            }
            if ((0, uri_utils_1.isSchemeStartChar)(char)) {
                // An uppercase or lowercase letter may start a scheme match
                stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
            }
            if (regex_lib_1.alphaNumericAndMarksRe.test(char)) {
                // A unicode alpha character or digit could start a domain name
                // label for a TLD match
                stateMachines.push(createTldUrlStateMachine(charIdx, 5 /* DomainLabelChar */));
            }
        }
        // Anything else, remain in the "non-url" state by not creating any
        // state machines
    }
    // Implements ABNF: ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
    function stateSchemeChar(stateMachine, char) {
        if (char === ':') {
            stateMachine.state = 2 /* SchemeColon */;
        }
        else if (char === '-') {
            stateMachine.state = 1 /* SchemeHyphen */;
        }
        else if ((0, uri_utils_1.isSchemeChar)(char)) {
            // Stay in SchemeChar state
        }
        else {
            // Any other character, not a scheme
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function stateSchemeHyphen(stateMachine, char) {
        if (char === '-') {
            // Stay in SchemeHyphen state
            // TODO: Should a colon following a dash be counted as the end of the scheme?
            // } else if (char === ':') {
            //     stateMachine.state = State.SchemeColon;
        }
        else if (char === '/') {
            // Not a valid scheme match, but may be the start of a
            // protocol-relative match (such as //google.com)
            (0, utils_1.remove)(stateMachines, stateMachine);
            stateMachines.push(createTldUrlStateMachine(charIdx, 11 /* ProtocolRelativeSlash1 */));
        }
        else if ((0, uri_utils_1.isSchemeChar)(char)) {
            stateMachine.state = 0 /* SchemeChar */;
        }
        else {
            // Any other character, not a scheme
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function stateSchemeColon(stateMachine, char) {
        if (char === '/') {
            stateMachine.state = 3 /* SchemeSlash1 */;
        }
        else if (char === '.') {
            // We've read something like 'hello:.' - don't capture
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
        else if ((0, uri_utils_1.isDomainLabelStartChar)(char)) {
            stateMachine.state = 5 /* DomainLabelChar */;
            // It's possible that we read an "introduction" piece of text,
            // and the character after the current colon actually starts an
            // actual scheme. An example of this is:
            //     "The link:http://google.com"
            // Hence, start a new machine to capture this match if so
            if ((0, uri_utils_1.isSchemeStartChar)(char)) {
                stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
            }
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function stateSchemeSlash1(stateMachine, char) {
        if (char === '/') {
            stateMachine.state = 4 /* SchemeSlash2 */;
        }
        else if ((0, uri_utils_1.isPathChar)(char)) {
            stateMachine.state = 10 /* Path */;
            stateMachine.acceptStateReached = true;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function stateSchemeSlash2(stateMachine, char) {
        if (char === '/') {
            // 3rd slash, must be an absolute path (path-absolute in the
            // ABNF), such as in a file:///c:/windows/etc. See
            // https://tools.ietf.org/html/rfc3986#appendix-A
            stateMachine.state = 10 /* Path */;
        }
        else if ((0, uri_utils_1.isDomainLabelStartChar)(char)) {
            // start of "authority" section - see https://tools.ietf.org/html/rfc3986#appendix-A
            stateMachine.state = 5 /* DomainLabelChar */;
            stateMachine.acceptStateReached = true;
        }
        else {
            // not valid
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    // Handles reading a '/' from the NonUrl state
    function stateProtocolRelativeSlash1(stateMachine, char) {
        if (char === '/') {
            stateMachine.state = 12 /* ProtocolRelativeSlash2 */;
        }
        else {
            // Anything else, cannot be the start of a protocol-relative
            // URL.
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    // Handles reading a second '/', which could start a protocol-relative URL
    function stateProtocolRelativeSlash2(stateMachine, char) {
        if ((0, uri_utils_1.isDomainLabelStartChar)(char)) {
            stateMachine.state = 5 /* DomainLabelChar */;
        }
        else {
            // Anything else, not a URL
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    // Handles when we have read a domain label character
    function stateDomainLabelChar(stateMachine, char) {
        if (char === '.') {
            stateMachine.state = 7 /* DomainDot */;
        }
        else if (char === '-') {
            stateMachine.state = 6 /* DomainHyphen */;
        }
        else if (char === ':') {
            // Beginning of a port number, end the domain name
            stateMachine.state = 8 /* PortColon */;
        }
        else if ((0, uri_utils_1.isUrlSuffixStartChar)(char)) {
            // '/', '?', or '#'
            stateMachine.state = 10 /* Path */;
        }
        else if ((0, uri_utils_1.isDomainLabelChar)(char)) {
            // Stay in the DomainLabelChar state
        }
        else {
            // Anything else, end the domain name
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function stateDomainHyphen(stateMachine, char) {
        if (char === '-') {
            // Remain in the DomainHyphen state
        }
        else if (char === '.') {
            // Not valid to have a '-.' in a domain label
            captureMatchIfValidAndRemove(stateMachine);
        }
        else if ((0, uri_utils_1.isDomainLabelStartChar)(char)) {
            stateMachine.state = 5 /* DomainLabelChar */;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function stateDomainDot(stateMachine, char) {
        if (char === '.') {
            // domain names cannot have multiple '.'s next to each other.
            // It's possible we've already read a valid domain name though,
            // and that the '..' sequence just forms an ellipsis at the end
            // of a sentence
            captureMatchIfValidAndRemove(stateMachine);
        }
        else if ((0, uri_utils_1.isDomainLabelStartChar)(char)) {
            stateMachine.state = 5 /* DomainLabelChar */;
            stateMachine.acceptStateReached = true; // after hitting a dot, and then another domain label, we've reached an accept state
        }
        else {
            // Anything else, end the domain name
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function stateIpV4Digit(stateMachine, char) {
        if (char === '.') {
            stateMachine.state = 14 /* IpV4Dot */;
        }
        else if (char === ':') {
            // Beginning of a port number
            stateMachine.state = 8 /* PortColon */;
        }
        else if (regex_lib_1.digitRe.test(char)) {
            // stay in the IPv4 digit state
        }
        else if ((0, uri_utils_1.isUrlSuffixStartChar)(char)) {
            stateMachine.state = 10 /* Path */;
        }
        else if (regex_lib_1.alphaNumericAndMarksRe.test(char)) {
            // If we hit an alpha character, must not be an IPv4
            // Example of this: 1.2.3.4abc
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function stateIPv4Dot(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.octetsEncountered++;
            // Once we have encountered 4 octets, it's *potentially* a valid
            // IPv4 address. Our IPv4 regex will confirm the match later
            // though to make sure each octet is in the 0-255 range, and
            // there's exactly 4 octets (not 5 or more)
            if (stateMachine.octetsEncountered === 4) {
                stateMachine.acceptStateReached = true;
            }
            stateMachine.state = 13 /* IpV4Digit */;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function statePortColon(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 9 /* PortNumber */;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function statePortNumber(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            // Stay in port number state
        }
        else if ((0, uri_utils_1.isUrlSuffixStartChar)(char)) {
            // '/', '?', or '#'
            stateMachine.state = 10 /* Path */;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function statePath(stateMachine, char) {
        if ((0, uri_utils_1.isPathChar)(char)) {
            // Stay in the path state
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    // Handles if we're reading a 'mailto:' prefix on the string
    function stateEmailMailto_M(stateMachine, char) {
        if (char.toLowerCase() === 'a') {
            stateMachine.state = 16 /* EmailMailto_A */;
        }
        else {
            stateEmailLocalPart(stateMachine, char);
        }
    }
    function stateEmailMailto_A(stateMachine, char) {
        if (char.toLowerCase() === 'i') {
            stateMachine.state = 17 /* EmailMailto_I */;
        }
        else {
            stateEmailLocalPart(stateMachine, char);
        }
    }
    function stateEmailMailto_I(stateMachine, char) {
        if (char.toLowerCase() === 'l') {
            stateMachine.state = 18 /* EmailMailto_L */;
        }
        else {
            stateEmailLocalPart(stateMachine, char);
        }
    }
    function stateEmailMailto_L(stateMachine, char) {
        if (char.toLowerCase() === 't') {
            stateMachine.state = 19 /* EmailMailto_T */;
        }
        else {
            stateEmailLocalPart(stateMachine, char);
        }
    }
    function stateEmailMailto_T(stateMachine, char) {
        if (char.toLowerCase() === 'o') {
            stateMachine.state = 20 /* EmailMailto_O */;
        }
        else {
            stateEmailLocalPart(stateMachine, char);
        }
    }
    function stateEmailMailto_O(stateMachine, char) {
        if (char.toLowerCase() === ':') {
            stateMachine.state = 21 /* EmailMailto_Colon */;
        }
        else {
            stateEmailLocalPart(stateMachine, char);
        }
    }
    function stateEmailMailtoColon(stateMachine, char) {
        if ((0, email_utils_1.isEmailLocalPartChar)(char)) {
            stateMachine.state = 22 /* EmailLocalPart */;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    // Handles the state when we're currently in the "local part" of an
    // email address (as opposed to the "domain part")
    function stateEmailLocalPart(stateMachine, char) {
        if (char === '.') {
            stateMachine.state = 23 /* EmailLocalPartDot */;
        }
        else if (char === '@') {
            stateMachine.state = 24 /* EmailAtSign */;
        }
        else if ((0, email_utils_1.isEmailLocalPartChar)(char)) {
            // stay in the "local part" of the email address
            // Note: because stateEmailLocalPart() is called from the
            // 'mailto' states (when the 'mailto' prefix itself has been
            // broken), make sure to set the state to EmailLocalPart
            stateMachine.state = 22 /* EmailLocalPart */;
        }
        else {
            // not an email address character
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    // Handles the state where we've read
    function stateEmailLocalPartDot(stateMachine, char) {
        if (char === '.') {
            // We read a second '.' in a row, not a valid email address
            // local part
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
        else if (char === '@') {
            // We read the '@' character immediately after a dot ('.'), not
            // an email address
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
        else if ((0, email_utils_1.isEmailLocalPartChar)(char)) {
            stateMachine.state = 22 /* EmailLocalPart */;
        }
        else {
            // Anything else, not an email address
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function stateEmailAtSign(stateMachine, char) {
        if ((0, uri_utils_1.isDomainLabelStartChar)(char)) {
            stateMachine.state = 25 /* EmailDomainChar */;
        }
        else {
            // Anything else, not an email address
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function stateEmailDomainChar(stateMachine, char) {
        if (char === '.') {
            stateMachine.state = 27 /* EmailDomainDot */;
        }
        else if (char === '-') {
            stateMachine.state = 26 /* EmailDomainHyphen */;
        }
        else if ((0, uri_utils_1.isDomainLabelChar)(char)) {
            // Stay in the DomainChar state
        }
        else {
            // Anything else, we potentially matched if the criteria has
            // been met
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function stateEmailDomainHyphen(stateMachine, char) {
        if (char === '-' || char === '.') {
            // Not valid to have two hyphens ("--") or hypen+dot ("-.")
            captureMatchIfValidAndRemove(stateMachine);
        }
        else if ((0, uri_utils_1.isDomainLabelChar)(char)) {
            stateMachine.state = 25 /* EmailDomainChar */;
        }
        else {
            // Anything else
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function stateEmailDomainDot(stateMachine, char) {
        if (char === '.' || char === '-') {
            // not valid to have two dots ("..") or dot+hypen (".-")
            captureMatchIfValidAndRemove(stateMachine);
        }
        else if ((0, uri_utils_1.isDomainLabelStartChar)(char)) {
            stateMachine.state = 25 /* EmailDomainChar */;
            // After having read a '.' and then a valid domain character,
            // we now know that the domain part of the email is valid, and
            // we have found at least a partial EmailMatch (however, the
            // email address may have additional characters from this point)
            stateMachine.acceptStateReached = true;
        }
        else {
            // Anything else
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    // Handles the state when we've just encountered a '#' character
    function stateHashtagHashChar(stateMachine, char) {
        if ((0, hashtag_utils_1.isHashtagTextChar)(char)) {
            // '#' char with valid hash text char following
            stateMachine.state = 29 /* HashtagTextChar */;
            stateMachine.acceptStateReached = true;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    // Handles the state when we're currently in the hash tag's text chars
    function stateHashtagTextChar(stateMachine, char) {
        if ((0, hashtag_utils_1.isHashtagTextChar)(char)) {
            // Continue reading characters in the HashtagText state
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    // Handles the state when we've just encountered a '@' character
    function stateMentionAtChar(stateMachine, char) {
        if ((0, mention_utils_1.isMentionTextChar)(char)) {
            // '@' char with valid mention text char following
            stateMachine.state = 31 /* MentionTextChar */;
            stateMachine.acceptStateReached = true;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    // Handles the state when we're currently in the mention's text chars
    function stateMentionTextChar(stateMachine, char) {
        if ((0, mention_utils_1.isMentionTextChar)(char)) {
            // Continue reading characters in the HashtagText state
        }
        else if (regex_lib_1.alphaNumericAndMarksRe.test(char)) {
            // Char is invalid for a mention text char, not a valid match.
            // Note that ascii alphanumeric chars are okay (which are tested
            // in the previous 'if' statement, but others are not)
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    function statePhoneNumberPlus(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 38 /* PhoneNumberDigit */;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
            // This character may start a new match. Add states for it
            stateNoMatch(char);
        }
    }
    function statePhoneNumberOpenParen(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 33 /* PhoneNumberAreaCodeDigit1 */;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
        // It's also possible that the paren was just an open brace for
        // a piece of text. Start other machines
        stateNoMatch(char);
    }
    function statePhoneNumberAreaCodeDigit1(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 34 /* PhoneNumberAreaCodeDigit2 */;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function statePhoneNumberAreaCodeDigit2(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 35 /* PhoneNumberAreaCodeDigit3 */;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function statePhoneNumberAreaCodeDigit3(stateMachine, char) {
        if (char === ')') {
            stateMachine.state = 36 /* PhoneNumberCloseParen */;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function statePhoneNumberCloseParen(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 38 /* PhoneNumberDigit */;
        }
        else if ((0, phone_number_utils_1.isPhoneNumberSeparatorChar)(char)) {
            stateMachine.state = 39 /* PhoneNumberSeparator */;
        }
        else {
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
    }
    function statePhoneNumberDigit(stateMachine, char) {
        // For now, if we've reached any digits, we'll say that the machine
        // has reached its accept state. The phone regex will confirm the
        // match later.
        // Alternatively, we could count the number of digits to avoid
        // invoking the phone number regex
        stateMachine.acceptStateReached = true;
        if ((0, phone_number_utils_1.isPhoneNumberControlChar)(char)) {
            stateMachine.state = 40 /* PhoneNumberControlChar */;
        }
        else if (char === '#') {
            stateMachine.state = 41 /* PhoneNumberPoundChar */;
        }
        else if (regex_lib_1.digitRe.test(char)) {
            // Stay in the phone number digit state
        }
        else if (char === '(') {
            stateMachine.state = 32 /* PhoneNumberOpenParen */;
        }
        else if ((0, phone_number_utils_1.isPhoneNumberSeparatorChar)(char)) {
            stateMachine.state = 39 /* PhoneNumberSeparator */;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
            // The transition from a digit character to a letter can be the
            // start of a new scheme URL match
            if ((0, uri_utils_1.isSchemeStartChar)(char)) {
                stateMachines.push(createSchemeUrlStateMachine(charIdx, 0 /* SchemeChar */));
            }
        }
    }
    function statePhoneNumberSeparator(stateMachine, char) {
        if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 38 /* PhoneNumberDigit */;
        }
        else if (char === '(') {
            stateMachine.state = 32 /* PhoneNumberOpenParen */;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
            // This character may start a new match. Add states for it
            stateNoMatch(char);
        }
    }
    // The ";" characters is "wait" in a phone number
    // The "," characters is "pause" in a phone number
    function statePhoneNumberControlChar(stateMachine, char) {
        if ((0, phone_number_utils_1.isPhoneNumberControlChar)(char)) {
            // Stay in the "control char" state
        }
        else if (char === '#') {
            stateMachine.state = 41 /* PhoneNumberPoundChar */;
        }
        else if (regex_lib_1.digitRe.test(char)) {
            stateMachine.state = 38 /* PhoneNumberDigit */;
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    // The "#" characters is "pound" in a phone number
    function statePhoneNumberPoundChar(stateMachine, char) {
        if ((0, phone_number_utils_1.isPhoneNumberControlChar)(char)) {
            stateMachine.state = 40 /* PhoneNumberControlChar */;
        }
        else if (regex_lib_1.digitRe.test(char)) {
            // According to some of the older tests, if there's a digit
            // after a '#' sign, the match is invalid. TODO: Revisit if this is true
            (0, utils_1.remove)(stateMachines, stateMachine);
        }
        else {
            captureMatchIfValidAndRemove(stateMachine);
        }
    }
    /*
     * Captures a match if it is valid (i.e. has a full domain name for a
     * TLD match). If a match is not valid, it is possible that we want to
     * keep reading characters in order to make a full match.
     */
    function captureMatchIfValidAndRemove(stateMachine) {
        // Remove the state machine first. There are a number of code paths
        // which return out of this function early, so make sure we have
        // this done
        (0, utils_1.remove)(stateMachines, stateMachine);
        // Make sure the state machine being checked has actually reached an
        // "accept" state. If it hasn't reach one, it can't be a match
        if (!stateMachine.acceptStateReached) {
            return;
        }
        var startIdx = stateMachine.startIdx;
        var matchedText = text.slice(stateMachine.startIdx, charIdx);
        // Handle any unbalanced braces (parens, square brackets, or curly
        // brackets) inside the URL. This handles situations like:
        //     The link (google.com)
        // and
        //     Check out this link here (en.wikipedia.org/wiki/IANA_(disambiguation))
        //
        // And also remove any punctuation chars at the end such as:
        //     '?', ',', ':', '.', etc.
        matchedText = excludeUnbalancedTrailingBracesAndPunctuation(matchedText);
        if (stateMachine.type === 'url') {
            // We don't want to accidentally match a URL that is preceded by an
            // '@' character, which would be an email address
            var charBeforeUrlMatch = text.charAt(stateMachine.startIdx - 1);
            if (charBeforeUrlMatch === '@') {
                return;
            }
            // For the purpose of this parser, we've generalized 'www'
            // matches as part of 'tld' matches. However, for backward
            // compatibility, we distinguish beween TLD matches and matches
            // that begin with 'www.' so that users may turn off 'www'
            // matches. As such, we need to correct for that now if the
            // URL begins with 'www.'
            var urlMatchType = stateMachine.matchType;
            if (urlMatchType === 'scheme') {
                // Autolinker accepts many characters in a url's scheme (like `fake://test.com`).
                // However, in cases where a URL is missing whitespace before an obvious link,
                // (for example: `nowhitespacehttp://www.test.com`), we only want the match to start
                // at the http:// part. We will check if the match contains a common scheme and then
                // shift the match to start from there.
                var httpSchemeMatch = uri_utils_1.httpSchemeRe.exec(matchedText);
                if (httpSchemeMatch) {
                    // If we found an overmatched URL, we want to find the index
                    // of where the match should start and shift the match to
                    // start from the beginning of the common scheme
                    startIdx = startIdx + httpSchemeMatch.index;
                    matchedText = matchedText.slice(httpSchemeMatch.index);
                }
                if (!(0, uri_utils_1.isValidSchemeUrl)(matchedText)) {
                    return; // not a valid match
                }
            }
            else if (urlMatchType === 'tld') {
                if (!(0, uri_utils_1.isValidTldMatch)(matchedText)) {
                    return; // not a valid match
                }
            }
            else if (urlMatchType === 'ipV4') {
                if (!(0, uri_utils_1.isValidIpV4Address)(matchedText)) {
                    return; // not a valid match
                }
            }
            else {
                (0, utils_1.assertNever)(urlMatchType);
            }
            matches.push(new url_match_1.UrlMatch({
                tagBuilder: tagBuilder,
                matchedText: matchedText,
                offset: startIdx,
                urlMatchType: urlMatchType,
                url: matchedText,
                protocolRelativeMatch: matchedText.slice(0, 2) === '//',
                // TODO: Do these settings need to be passed to the match,
                // or should we handle them here in UrlMatcher?
                stripPrefix: stripPrefix,
                stripTrailingSlash: stripTrailingSlash,
                decodePercentEncoding: decodePercentEncoding,
            }));
        }
        else if (stateMachine.type === 'email') {
            // if the email address has a valid TLD, add it to the list of matches
            if ((0, email_utils_1.isValidEmail)(matchedText)) {
                matches.push(new email_match_1.EmailMatch({
                    tagBuilder: tagBuilder,
                    matchedText: matchedText,
                    offset: startIdx,
                    email: matchedText.replace(email_utils_1.mailtoSchemePrefixRe, ''),
                }));
            }
        }
        else if (stateMachine.type === 'hashtag') {
            if ((0, hashtag_utils_1.isValidHashtag)(matchedText)) {
                matches.push(new hashtag_match_1.HashtagMatch({
                    tagBuilder: tagBuilder,
                    matchedText: matchedText,
                    offset: startIdx,
                    serviceName: hashtagServiceName,
                    hashtag: matchedText.slice(1),
                }));
            }
        }
        else if (stateMachine.type === 'mention') {
            if ((0, mention_utils_1.isValidMention)(matchedText, mentionServiceName)) {
                matches.push(new mention_match_1.MentionMatch({
                    tagBuilder: tagBuilder,
                    matchedText: matchedText,
                    offset: startIdx,
                    serviceName: mentionServiceName,
                    mention: matchedText.slice(1), // strip off the '@' character at the beginning
                }));
            }
        }
        else if (stateMachine.type === 'phone') {
            // remove any trailing spaces that were considered as "separator"
            // chars by the state machine
            matchedText = matchedText.replace(/ +$/g, '');
            if ((0, phone_number_utils_1.isValidPhoneNumber)(matchedText)) {
                var cleanNumber = matchedText.replace(/[^0-9,;#]/g, ''); // strip out non-digit characters exclude comma semicolon and #
                matches.push(new phone_match_1.PhoneMatch({
                    tagBuilder: tagBuilder,
                    matchedText: matchedText,
                    offset: startIdx,
                    number: cleanNumber,
                    plusSign: matchedText.charAt(0) === '+',
                }));
            }
        }
        else {
            (0, utils_1.assertNever)(stateMachine);
        }
    }
}
exports.parseMatches = parseMatches;
var openBraceRe = /[\(\{\[]/;
var closeBraceRe = /[\)\}\]]/;
var oppositeBrace = {
    ')': '(',
    '}': '{',
    ']': '[',
};
/**
 * Determines if a match found has unmatched closing parenthesis,
 * square brackets or curly brackets. If so, these unbalanced symbol(s) will be
 * removed from the URL match itself.
 *
 * A match may have an extra closing parenthesis/square brackets/curly brackets
 * at the end of the match because these are valid URL path characters. For
 * example, "wikipedia.com/something_(disambiguation)" should be auto-linked.
 *
 * However, an extra parenthesis *will* be included when the URL itself is
 * wrapped in parenthesis, such as in the case of:
 *
 *     "(wikipedia.com/something_(disambiguation))"
 *
 * In this case, the last closing parenthesis should *not* be part of the
 * URL itself, and this method will exclude it from the returned URL.
 *
 * For square brackets in URLs such as in PHP arrays, the same behavior as
 * parenthesis discussed above should happen:
 *
 *     "[http://www.example.com/foo.php?bar[]=1&bar[]=2&bar[]=3]"
 *
 * The very last closing square bracket should not be part of the URL itself,
 * and therefore this method will remove it.
 *
 * @param matchedText The full matched URL/email/hashtag/etc. from the state
 *   machine parser.
 * @return The updated matched text with extraneous suffix characters removed.
 */
function excludeUnbalancedTrailingBracesAndPunctuation(matchedText) {
    var braceCounts = {
        '(': 0,
        '{': 0,
        '[': 0,
    };
    for (var i = 0; i < matchedText.length; i++) {
        var char_1 = matchedText.charAt(i);
        if (openBraceRe.test(char_1)) {
            braceCounts[char_1]++;
        }
        else if (closeBraceRe.test(char_1)) {
            braceCounts[oppositeBrace[char_1]]--;
        }
    }
    var endIdx = matchedText.length - 1;
    var char;
    while (endIdx >= 0) {
        char = matchedText.charAt(endIdx);
        if (closeBraceRe.test(char)) {
            var oppositeBraceChar = oppositeBrace[char];
            if (braceCounts[oppositeBraceChar] < 0) {
                braceCounts[oppositeBraceChar]++;
                endIdx--;
            }
            else {
                break;
            }
        }
        else if (uri_utils_1.urlSuffixedCharsNotAllowedAtEndRe.test(char)) {
            // Walk back a punctuation char like '?', ',', ':', '.', etc.
            endIdx--;
        }
        else {
            break;
        }
    }
    return matchedText.slice(0, endIdx + 1);
}
exports.excludeUnbalancedTrailingBracesAndPunctuation = excludeUnbalancedTrailingBracesAndPunctuation;
function createSchemeUrlStateMachine(startIdx, state) {
    return {
        type: 'url',
        startIdx: startIdx,
        state: state,
        acceptStateReached: false,
        matchType: 'scheme',
    };
}
function createTldUrlStateMachine(startIdx, state) {
    return {
        type: 'url',
        startIdx: startIdx,
        state: state,
        acceptStateReached: false,
        matchType: 'tld',
    };
}
function createIpV4UrlStateMachine(startIdx, state) {
    return {
        type: 'url',
        startIdx: startIdx,
        state: state,
        acceptStateReached: false,
        matchType: 'ipV4',
        octetsEncountered: 1, // starts at 1 because we create this machine when encountering the first octet
    };
}
function createEmailStateMachine(startIdx, state) {
    return {
        type: 'email',
        startIdx: startIdx,
        state: state,
        acceptStateReached: false,
    };
}
function createHashtagStateMachine(startIdx, state) {
    return {
        type: 'hashtag',
        startIdx: startIdx,
        state: state,
        acceptStateReached: false,
    };
}
function createMentionStateMachine(startIdx, state) {
    return {
        type: 'mention',
        startIdx: startIdx,
        state: state,
        acceptStateReached: false,
    };
}
function createPhoneNumberStateMachine(startIdx, state) {
    return {
        type: 'phone',
        startIdx: startIdx,
        state: state,
        acceptStateReached: false,
    };
}
//# sourceMappingURL=parse-matches.js.map