#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Copyright 2024-2025 Mike Fährmann
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.

import re


def pyprint(obj, indent=0, sort=None, oneline=True, lmin=0, lmax=16):

    if isinstance(obj, str):
        if obj.startswith("lit:"):
            return f'''{obj[4:]}'''

        if "\\" in obj or obj.startswith("re:"):
            prefix = "r"
        else:
            prefix = ""

        quote_beg = quote_end = '"'
        if "\n" in obj:
            quote_beg = '"""\\\n'
            quote_end = '\\\n"""'
        elif '"' in obj:
            obj = re.sub(r'(?<!\\)"', '\\"', obj)

        return f'''{prefix}{quote_beg}{obj}{quote_end}'''

    if isinstance(obj, bytes):
        return f'''b"{str(obj)[2:-1]}"'''

    if isinstance(obj, type):
        if obj.__module__ == "builtins":
            return f'''{obj.__name__}'''

        name = obj.__module__.rpartition(".")[2]
        if name[0].isdecimal():
            name = f"_{name}"
        return f'''{name}.{obj.__name__}'''

    if isinstance(obj, dict):
        if not obj:
            return "{}"
        if len(obj) == 1 and oneline:
            key, value = next(iter(obj.items()))
            return f'''{{"{key}": {pyprint(value, indent, sort)}}}'''

        if sort:
            if callable(sort):
                lst = [(sort(key, value), key, value)
                       for key, value in obj.items()]
                lst.sort()
                obj = {key: value for _, key, value in lst}
            else:
                keys = list(obj)
                keys.sort()
                obj = {key: obj[key] for key in keys}

        try:
            keylen = max(kl for kl in map(len, obj) if kl <= lmax)
        except Exception:
            keylen = lmin
        if keylen < lmin:
            keylen = lmin
        ws = " " * indent

        lines = ["{"]
        for key, value in obj.items():
            if key.startswith("#blank-"):
                lines.append("")
            else:
                lines.append(
                    f'''{ws}    "{key}"'''
                    f'''{' '*(keylen - len(key))}: '''
                    f'''{pyprint(value, indent+4, sort)},'''
                )
        lines.append(f'''{ws}}}''')
        return "\n".join(lines)

    if isinstance(obj, list):
        if not obj:
            return "[]"
        if len(obj) == 1 and oneline:
            return f'''[{pyprint(obj[0], indent, sort)}]'''

        ws = " " * indent
        lines = ["["]
        for value in obj:
            lines.append(f'''{ws}    {pyprint(value, indent+4, sort)},''')
        lines.append(f'''{ws}]''')
        return "\n".join(lines)

    if isinstance(obj, tuple):
        if not obj:
            return "()"
        if len(obj) == 1:
            return f'''({pyprint(obj[0], indent, sort)},)'''

        result = f'''({", ".join(pyprint(v, indent+4, sort) for v in obj)})'''
        if len(result) < 80:
            return result

        ws = " " * indent
        lines = ["("]
        for value in obj:
            lines.append(f'''{ws}    {pyprint(value, indent+4, sort)},''')
        lines.append(f'''{ws})''')
        return "\n".join(lines)

    if isinstance(obj, set):
        if not obj:
            return "set()"
        return f'''{{{", ".join(pyprint(v, indent+4, sort) for v in obj)}}}'''

    if obj.__class__.__name__ == "datetime":
        if (off := obj.utcoffset()) is not None:
            obj = obj.replace(tzinfo=None, microsecond=0) - off
        elif obj.microsecond:
            obj = obj.replace(microsecond=0)
        return f'''"dt:{obj}"'''

    return f'''{obj}'''
