import re
import difflib

def tokenize_text(text):
    '''
    Splits each punctuation character as an individual token.
    Example:
      "myself," -> ['myself', ',', '"']
      "now."    -> ['now', '.', '"']
    '''
    return re.findall(r'\w+|[^\w\s]', text)

def rebuild_text(token_list):
    '''
    Reconstructs a string with custom spacing rules:
      1) Single punctuation (',', '.', '?', '!') attaches to the previous token without space.
      2) If that punctuation is among [',', '.', '?', '!'] and the next token is a quote,
         we insert exactly one space before the quote, except if this is the end of the sentence
         (i.e., the quote is the last token).
      3) We never insert an extra space after a left quote => "I (not " I).
      4) We do not insert space between '.' and '"' if that quote is the last token (e.g. 'now."' not 'now. "').
    '''

    result = []
    i = 0
    skip_leading_space_on_next_word = False  # For the case after a left quote

    while i < len(token_list):
        token = token_list[i]

        # A) <span...> => treat as word (leading space if needed)
        if token.startswith('<span'):
            if result and not skip_leading_space_on_next_word:
                result.append(' ' + token)
            else:
                result.append(token)
            skip_leading_space_on_next_word = False
            i += 1
            continue

        # B) Single-character punctuation
        if re.match(r'^[^\w\s]$', token):
            # Attach it to the previous token without space
            if result:
                result[-1] = result[-1].rstrip()
            result.append(token)

            # If punctuation is one of [',', '.', '?', '!'] and next token is a quote
            if token in [',', '.', '?', '!'] and (i + 1) < len(token_list):
                nxt = token_list[i + 1]
                if nxt in ['"', "'"]:
                    # Check if that quote is the last token or not
                    # If it's the last token => "now." -> next token is the final quote => no space
                    # If it's not the last token => insert space
                    if (i + 1) == (len(token_list) - 1):
                        # This means next token is the final one => do not insert space
                        # e.g. "now." + final quote => "now.""
                        # We'll just append the quote with no space
                        result.append(nxt)
                        skip_leading_space_on_next_word = True
                        i += 2
                        continue
                    else:
                        # Insert exactly one space, then quote => e.g. my..., "
                        result.append(' ')
                        result.append(nxt)
                        # After left quote => skip leading space for next word
                        skip_leading_space_on_next_word = True
                        i += 2
                        continue

            skip_leading_space_on_next_word = False
            i += 1
            continue

        # C) Quote token (" or ')
        if token in ['"', "'"]:
            # Typically a left quote => no leading space
            if result:
                result[-1] = result[-1].rstrip()
            result.append(token)
            skip_leading_space_on_next_word = True
            i += 1
            continue

        # D) Normal word
        if not result:
            # first token
            result.append(token)
        else:
            if skip_leading_space_on_next_word:
                # no space => e.g. "I
                result.append(token)
            else:
                result.append(' ' + token)

        skip_leading_space_on_next_word = False
        i += 1

    return ''.join(result)

def tutor_html(original_text, modified_text):
    '''
    1) Uses difflib.Differ() to compare original_text vs. modified_text:
       - '- ' => token only in original => wrap in red
       - '+ ' => token only in modified => wrap in blue
       - '  ' => same => in both
       - '? ' => skip (alignment hints)
    2) Rebuild each version with rebuild_text to apply the custom spacing rules.
    '''

    html_output = ''

    original_tokens = tokenize_text(original_text)
    modified_tokens = tokenize_text(modified_text)

    differ = difflib.Differ()
    raw_diff = list(differ.compare(original_tokens, modified_tokens))

    orig_list = []
    mod_list = []

    for line in raw_diff:
        if line.startswith('? '):
            continue

        sign = line[:2]  # '- ', '+ ', '  '
        token = line[2:]

        if sign == '- ':
            red_token = f'<span style="color:red">{token}</span>'
            orig_list.append(red_token)
        elif sign == '+ ':
            blue_token = f'<span style="color:blue">{token}</span>'
            mod_list.append(blue_token)
        else:
            orig_list.append(token)
            mod_list.append(token)

    original_colored_text = rebuild_text(orig_list)
    modified_colored_text = rebuild_text(mod_list)

    html_output += (
        '<p><u>Original text</u> (deleted parts in red): '
        f'{original_colored_text}</p>\n'
    )
    html_output += (
        '<p><u>Modified text</u> (added parts in blue): '
        f'{modified_colored_text}</p>\n'
    )

    return html_output
