import re
from typing import Optional, Tuple, cast

from .ast import Location
from .location import SourceLocation, get_location
from .source import Source


__all__ = ["print_location", "print_source_location"]


def print_location(location: Location) -> str:
    """Render a helpful description of the location in the GraphQL Source document."""
    return print_source_location(
        location.source, get_location(location.source, location.start)
    )


_re_newline = re.compile(r"\r\n|[\n\r]")


def print_source_location(source: Source, source_location: SourceLocation) -> str:
    """Render a helpful description of the location in the GraphQL Source document."""
    first_line_column_offset = source.location_offset.column - 1
    body = " " * first_line_column_offset + source.body

    line_index = source_location.line - 1
    line_offset = source.location_offset.line - 1
    line_num = source_location.line + line_offset

    column_offset = first_line_column_offset if source_location.line == 1 else 0
    column_num = source_location.column + column_offset
    location_str = f"{source.name}:{line_num}:{column_num}\n"

    lines = _re_newline.split(body)  # works a bit different from splitlines()
    location_line = lines[line_index]

    # Special case for minified documents
    if len(location_line) > 120:
        sub_line_index, sub_line_column_num = divmod(column_num, 80)
        sub_lines = [
            location_line[i : i + 80] for i in range(0, len(location_line), 80)
        ]

        return location_str + print_prefixed_lines(
            (str(line_num), sub_lines[0]),
            *[("", sub_line) for sub_line in sub_lines[1 : sub_line_index + 1]],
            (" ", " " * (sub_line_column_num - 1) + "^"),
            (
                "",
                sub_lines[sub_line_index + 1]
                if sub_line_index < len(sub_lines) - 1
                else None,
            ),
        )

    return location_str + print_prefixed_lines(
        (f"{line_num - 1}", lines[line_index - 1] if line_index > 0 else None),
        (f"{line_num}", location_line),
        ("", " " * (column_num - 1) + "^"),
        (
            f"{line_num + 1}",
            lines[line_index + 1] if line_index < len(lines) - 1 else None,
        ),
    )


def print_prefixed_lines(*lines: Tuple[str, Optional[str]]) -> str:
    """Print lines specified like this: ("prefix", "string")"""
    existing_lines = [
        cast(Tuple[str, str], line) for line in lines if line[1] is not None
    ]
    pad_len = max(len(line[0]) for line in existing_lines)
    return "\n".join(
        map(
            lambda line: line[0].rjust(pad_len)
            + (" | " + line[1] if line[1] else " |"),
            existing_lines,
        )
    )
