import string
import re
from typing import Counter


def f1_score(gold_answer: str, pred_answer: str) -> float:
    """
    Calculate token-level F1 score
        See also https://github.com/W4ngatang/qags/blob/master/qa_utils.py#L43
    Args:
        gold_answer (str): answer selected based on source document
        pred_answer (str): answer selected based on generated summary
    """

    def _normalize_answer(text: str):

        def _remove_punc(text: str):
            exclude = set(string.punctuation)
            return "".join(ch for ch in text if ch not in exclude)

        def _remove_articles(text: str):
            return re.sub(r"\b(a|an|the)\b", " ", text)

        def _white_space_fix(text: str):
            return " ".join(text.split())

        return _white_space_fix(_remove_articles(_remove_punc(text.lower())))

    gold_toks = _normalize_answer(gold_answer).split()
    pred_toks = _normalize_answer(pred_answer).split()

    common_toks = Counter(gold_toks) & Counter(pred_toks)

    num_same_toks = sum(common_toks.values())

    # If either is <unanswerable>, then F1 is 1 if they agree, 0 otherwise
    if gold_answer == "<unanswerable>" or pred_answer == "<unanswerable>":
        return int(gold_answer == pred_answer)

    if num_same_toks == 0:
        return 0.0

    precision = 1.0 * num_same_toks / len(pred_toks)
    recall = 1.0 * num_same_toks / len(gold_toks)
    f1 = (2 * precision * recall) / (precision + recall)
    return f1