import json
from typing import Any, Callable

__all__ = ['pprint']


def pprint(
    data: Any,
    *,
    indent: int = 2,
    default: Callable[[Any], Any] = lambda o: o.__dict__,
    pretty: bool = True,
    max_items: int = 0,
) -> None:
    """
    JSON 직렬화 가능한 파이썬 객체를 예쁘게 출력합니다.
    """
    if not pretty:
        print(
            json.dumps(
                data,
                indent=indent,
                default=default,
                ensure_ascii=False,
                separators=(", ", ": "),
            )
        )
        return None

    output_buffer: list[str] = []
    # 적당히 큰 캐시를 만들고, 필요 시 동적으로 확장합니다.
    indent_cache: list[str] = ["", *((" " * (i * indent)) for i in range(1, 32))]

    def _ensure_indent(level: int) -> None:
        """요청된 level까지 indent_cache를 확장합니다."""
        nonlocal indent_cache
        if level < len(indent_cache):
            return
        start = len(indent_cache)
        indent_cache.extend(" " * (i * indent) for i in range(start, level + 1))

    def _format_sequence(seq: Any, level: int) -> None:
        """list/tuple 공통 포매팅."""
        _ensure_indent(level + 1)
        current_indent = indent_cache[level]
        next_indent = indent_cache[level + 1]

        length = len(seq)
        if length == 0:
            output_buffer.append("[]")
            return
        if length == 1 and not max_items:
            output_buffer.append('[')
            _formatter(seq[0], level)
            output_buffer.append(']')
            return

        output_buffer.append('[\n')

        limit = length if not max_items else min(length, int(max_items))
        for i in range(limit):
            if i:
                output_buffer.append(',\n')
            output_buffer.append(next_indent)
            _formatter(seq[i], level + 1)

        # 잘린 경우 뒷부분 생략 표시
        if max_items and length > limit:
            output_buffer.append(',\n')
            output_buffer.append(next_indent)
            output_buffer.append('...')

        output_buffer.append('\n')
        output_buffer.append(current_indent)
        output_buffer.append(']')

    def _format_mapping(mapping: dict[Any, Any], level: int) -> None:
        """dict 포매팅."""
        _ensure_indent(level + 1)
        current_indent = indent_cache[level]
        next_indent = indent_cache[level + 1]

        length = len(mapping)
        if length == 0:
            output_buffer.append("{}")
            return
        if length == 1 and not max_items:
            key, value = next(iter(mapping.items()))
            output_buffer.append('{')
            output_buffer.append(json.dumps(key, ensure_ascii=False))
            output_buffer.append(': ')
            _formatter(value, level)
            output_buffer.append('}')
            return

        output_buffer.append('{\n')

        items_iter = iter(mapping.items())
        limit = length if not max_items else min(length, int(max_items))

        for i in range(limit):
            if i:
                output_buffer.append(',\n')
            key, value = next(items_iter)
            output_buffer.append(next_indent)
            output_buffer.append(json.dumps(key, ensure_ascii=False))
            output_buffer.append(': ')
            _formatter(value, level + 1)

        # 잘린 경우 뒷부분 생략 표시
        if max_items and length > limit:
            output_buffer.append(',\n')
            output_buffer.append(next_indent)
            output_buffer.append('"..."')
            output_buffer.append(': ')
            output_buffer.append('"..."')

        output_buffer.append('\n')
        output_buffer.append(current_indent)
        output_buffer.append('}')

    def _formatter(obj: Any, level: int) -> None:
        # --- 1. 기본 타입 우선 처리 ---
        if isinstance(obj, str):
            output_buffer.append(json.dumps(obj, ensure_ascii=False))
            return
        if obj is True:
            output_buffer.append('true')
            return
        if obj is False:
            output_buffer.append('false')
            return
        if obj is None:
            output_buffer.append('null')
            return
        if isinstance(obj, (int, float)):
            output_buffer.append(str(obj))
            return

        # --- 2. 컬렉션 타입 확인 및 처리 ---
        if isinstance(obj, (list, tuple)):
            _format_sequence(obj, level)
            return

        if isinstance(obj, dict):
            _format_mapping(obj, level)
            return

        # --- 3. 위 모든 타입에 해당하지 않는 경우 (사용자 정의 객체 등) ---
        if default:
            _formatter(default(obj), level)
        else:
            raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")

    _formatter(data, 0)
    print("".join(output_buffer))
    return None