from statistics import median
from typing import Any, List, Optional

import config


PAIRS_START = {
    '(': ')',
    '[': ']',
    '{': '}',
    '<': '>',
    }

PAIRS_END = {
    ')': '(',
    ']': '[',
    '}': '{',
    '>': '<',
    }

SCORES = {
    ')': 1,
    ']': 2,
    '}': 3,
    '>': 4,
    }


def get_missing_chars(line: str) -> List[str]:
    """Get the missing characters of an otherwise syntactically valid line.

    Args:
        line (str): a line containing characters

    Returns:
        List[str]: a list of terminating characters to correct the line

    """
    stack: List[str] = []
    for char in line:
        if char in PAIRS_END:
            stack.pop()
        else:
            stack.append(char)

    return [PAIRS_START[char] for char in stack[::-1]]


def catch_first_syntax_error(line: str) -> Optional[str]:
    """Catch the first syntax error in a line, if error(s) exist.

    Args:
        line (str): a line containing characters

    Returns:
        str: if an error was found, the first error
        None: no error was found

    """
    stack: List[str] = []
    for char in line:
        if char in PAIRS_END:
            if not stack or PAIRS_END[char] != stack[-1]:
                return char
            else:
                stack.pop()
        else:
            stack.append(char)

    return None


def fix_incomplete_lines(contents: List[str]) -> int:
    """Fix incomplete lines by appending correct matching characters.

    Remove all invalid lines first.

    Args:
        contents (List[str]): the file contents

    Returns:
        int: score of invalid first characters per line

    """
    scores = []
    for line in contents:
        score = 0
        first = catch_first_syntax_error(line)
        if not first:
            missing = get_missing_chars(line)
            for char in missing:
                score *= 5
                score += SCORES[char]
            scores.append(score)

    return int(median(scores))


def main() -> None:
    """Run the main code."""
    test_answer = 288957
    test_file = config.TestFile(test_answer)
    test = fix_incomplete_lines(test_file.contents)
    test_file.test(test)

    file = config.File()
    result = fix_incomplete_lines(file.contents)
    config.LOGGER.info(result)


if __name__ == '__main__':
    main()

from statistics import median
from typing import Any, List, Optional

import config


PAIRS_START = {
    '(': ')',
    '[': ']',
    '{': '}',
    '<': '>',
    }

PAIRS_END = {
    ')': '(',
    ']': '[',
    '}': '{',
    '>': '<',
    }

SCORES = {
    ')': 1,
    ']': 2,
    '}': 3,
    '>': 4,
    }


def get_missing_chars(line: str) -> List[str]:
    """Get the missing characters of an otherwise syntactically valid line.

    Args:
        line (str): a line containing characters

    Returns:
        List[str]: a list of terminating characters to correct the line

    """
    stack: List[str] = []
    for char in line:
        if char in PAIRS_END:
            stack.pop()
        else:
            stack.append(char)

    return [PAIRS_START[char] for char in stack[::-1]]


def catch_first_syntax_error(line: str) -> Optional[str]:
    """Catch the first syntax error in a line, if error(s) exist.

    Args:
        line (str): a line containing characters

    Returns:
        str: if an error was found, the first error
        None: no error was found

    """
    stack: List[str] = []
    for char in line:
        if char in PAIRS_END:
            if not stack or PAIRS_END[char] != stack[-1]:
                return char
            else:
                stack.pop()
        else:
            stack.append(char)

    return None


def fix_incomplete_lines(contents: List[str]) -> int:
    """Fix incomplete lines by appending correct matching characters.

    Remove all invalid lines first.

    Args:
        contents (List[str]): the file contents

    Returns:
        int: score of invalid first characters per line

    """
    scores = []
    for line in contents:
        score = 0
        first = catch_first_syntax_error(line)
        if not first:
            missing = get_missing_chars(line)
            for char in missing:
                score *= 5
                score += SCORES[char]
            scores.append(score)

    return int(median(scores))


def main() -> None:
    """Run the main code."""
    test_answer = 288957
    test_file = config.TestFile(test_answer)
    test = fix_incomplete_lines(test_file.contents)
    test_file.test(test)

    file = config.File()
    result = fix_incomplete_lines(file.contents)
    config.LOGGER.info(result)


if __name__ == '__main__':
    main()

Get the missing characters of an otherwise syntactically valid line.      Args:         line (str): a line containing characters      Returns:         List[str]: a list of terminating characters to correct the line Catch the first syntax error in a line, if error(s) exist.      Args:         line (str): a line containing characters      Returns:         str: if an error was found, the first error         None: no error was found Fix incomplete lines by appending correct matching characters.      Remove all invalid lines first.      Args:         contents (List[str]): the file contents      Returns:         int: score of invalid first characters per line Run the main code.