"""
Copyright (c) 2026 Dubalu International LLC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""
from __future__ import annotations

from typing import ClassVar

__version__ = "1.1.1"

__all__ = [
    "NAMESPACE_DNS",
    "NAMESPACE_OID",
    "NAMESPACE_URL",
    "NAMESPACE_X500",
    "UUID",
    "decode",
    "decode_uuid",
    "encode",
    "encode_uuid",
    "unserialise",
    "uuid",
    "uuid1",
    "uuid3",
    "uuid4",
    "uuid5",
]

import uuid as _uuid

try:
    from . import base_x
except ValueError:
    import base_x  # type: ignore[import-not-found, no-redef]

try:
    from .mertwis import MT19937
except ValueError:
    from mertwis import MT19937  # type: ignore[import-not-found, no-redef]


builtins_bytes = bytes


def fnv_1a(num: int) -> int:
    """Calculate FNV-1a hash of an integer.

    Args:
        num: Integer to hash.

    Returns:
        FNV-1a hash value.
    """
    fnv = 0xcbf29ce484222325
    while num:
        fnv ^= num & 0xff
        fnv *= 0x100000001b3
        fnv &= 0xffffffffffffffff
        num >>= 8
    return fnv


def xor_fold(num: int, bits: int) -> int:
    """XOR-fold number to specified number of bits.

    Args:
        num: Number to fold.
        bits: Target bit width.

    Returns:
        Folded value.
    """
    folded = 0
    while num:
        folded ^= num
        num >>= bits
    return folded


def unserialise(serialised: bytes) -> list[UUID]:
    """Deserialize bytes to list of UUIDs.

    Args:
        serialised: Serialized UUID bytes.

    Returns:
        List of UUID objects.
    """
    uuids: list[UUID] = []
    while serialised:
        uuid_obj, length = UUID._unserialise(serialised)
        uuids.append(uuid_obj)
        serialised = serialised[length:]
    return uuids


def encode(serialised: bytes, encoding: str = "encoded") -> str:
    """Encode serialized UUID bytes to specific format.

    Args:
        serialised: Serialized UUID bytes.
        encoding: Output encoding format ('encoded', 'guid', 'urn').

    Returns:
        Encoded string.

    Raises:
        ValueError: If serialised is invalid.
    """
    if isinstance(serialised, bytes):
        if encoding == "guid":
            return ";".join(f"{{{u}}}" for u in unserialise(serialised))
        if encoding == "urn":
            return "urn:uuid:" + ";".join(str(u) for u in unserialise(serialised))
        if encoding == "encoded" and serialised[0] != 1 and (
            (serialised[-1] & 1) or (len(serialised) >= 6 and serialised[-6] & 2)
        ):
            return "~" + UUID.ENCODER.encode(serialised)
        return ";".join(str(u) for u in unserialise(serialised))
    raise ValueError(f"Invalid serialised UUID: {serialised!r}")


def decode(encoded: str | list[str] | tuple[str, ...]) -> bytes:
    """Decode encoded UUID to serialized bytes.

    Args:
        encoded: Encoded UUID string, list, or tuple.

    Returns:
        Serialized UUID bytes.

    Raises:
        ValueError: If encoded format is invalid.
    """
    if isinstance(encoded, str) and len(encoded) > 2:
        if encoded[0] == "{" and encoded[-1] == "}":
            encoded = encoded[1:-1]
        elif encoded.startswith("urn:uuid:"):
            encoded = encoded[9:]
        if encoded:
            encoded = encoded.split(";")
    if isinstance(encoded, (list, tuple)):
        return b"".join(UUID._decode(u) for u in encoded)
    raise ValueError(f"Invalid encoded UUID: {encoded!r}")


def encode_uuid(uuid: UUID | _uuid.UUID | str) -> str:
    """Encode UUID object to string.

    Args:
        uuid: UUID object or string representation.

    Returns:
        Encoded UUID string.

    Raises:
        ValueError: If uuid is None.
    """
    if uuid is None:
        raise ValueError("Cannot encode None")
    if not isinstance(uuid, UUID):
        uuid = UUID(hex=uuid) if isinstance(uuid, str) else UUID(int=uuid.int)
    return uuid.encode()


def decode_uuid(code: str) -> UUID:
    """Decode encoded string to UUID object.

    Args:
        code: Encoded UUID string.

    Returns:
        UUID object.
    """
    return UUID.unserialise(decode(code))


class UUID(_uuid.UUID):
    """Compact UUID with efficient serialization.

    Anonymous UUID is 00000000-0000-1000-8000-010000000000

    Layout:
    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                           time_low                            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |           time_mid            |       time_hi_and_version     |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |clk_seq_hi_res |  clk_seq_low  |          node (0-1)           |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    |                         node (2-5)                            |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    time = 60 bits
    clock = 14 bits
    node = 48 bits
    """

    ENCODER: base_x.BaseX = base_x.b59

    UUID_TIME_EPOCH: int = 0x01b21dd213814000
    UUID_TIME_YEAR: int = 0x00011f0241243c00
    UUID_TIME_INITIAL: int = UUID_TIME_EPOCH + (2016 - 1970) * UUID_TIME_YEAR

    UUID_MIN_SERIALISED_LENGTH: int = 2
    UUID_MAX_SERIALISED_LENGTH: int = 17
    UUID_LENGTH: int = 36

    TIME_BITS: int = 60
    VERSION_BITS: int = 64 - TIME_BITS
    COMPACTED_BITS: int = 1
    SALT_BITS: int = 7
    CLOCK_BITS: int = 14
    NODE_BITS: int = 48
    PADDING_BITS: int = 64 - COMPACTED_BITS - SALT_BITS - CLOCK_BITS
    PADDING1_BITS: int = 64 - COMPACTED_BITS - NODE_BITS - CLOCK_BITS
    VARIANT_BITS: int = 2

    TIME_MASK: int = ((1 << TIME_BITS) - 1)
    SALT_MASK: int = ((1 << SALT_BITS) - 1)
    CLOCK_MASK: int = ((1 << CLOCK_BITS) - 1)
    NODE_MASK: int = ((1 << NODE_BITS) - 1)
    COMPACTED_MASK: int = ((1 << COMPACTED_BITS) - 1)
    VERSION_MASK: int = ((1 << VERSION_BITS) - 1)
    VARIANT_MASK: int = ((1 << VARIANT_BITS) - 1)

    # Lengths table
    VL: ClassVar[list[list[list[int]]]] = [
        [[0x1c, 0xfc], [0x1c, 0xfc]],  # 4:  00011100 11111100  00011100 11111100
        [[0x18, 0xfc], [0x18, 0xfc]],  # 5:  00011000 11111100  00011000 11111100
        [[0x14, 0xfc], [0x14, 0xfc]],  # 6:  00010100 11111100  00010100 11111100
        [[0x10, 0xfc], [0x10, 0xfc]],  # 7:  00010000 11111100  00010000 11111100
        [[0x04, 0xfc], [0x40, 0xc0]],  # 8:  00000100 11111100  01000000 11000000
        [[0x0a, 0xfe], [0xa0, 0xe0]],  # 9:  00001010 11111110  10100000 11100000
        [[0x08, 0xfe], [0x80, 0xe0]],  # 10: 00001000 11111110  10000000 11100000
        [[0x02, 0xff], [0x20, 0xf0]],  # 11: 00000010 11111111  00100000 11110000
        [[0x03, 0xff], [0x30, 0xf0]],  # 12: 00000011 11111111  00110000 11110000
        [[0x0c, 0xff], [0xc0, 0xf0]],  # 13: 00001100 11111111  11000000 11110000
        [[0x0d, 0xff], [0xd0, 0xf0]],  # 14: 00001101 11111111  11010000 11110000
        [[0x0e, 0xff], [0xe0, 0xf0]],  # 15: 00001110 11111111  11100000 11110000
        [[0x0f, 0xff], [0xf0, 0xf0]],  # 16: 00001111 11111111  11110000 11110000
    ]

    def __init__(
        self,
        hex: str | _uuid.UUID | None = None,
        bytes: bytes | None = None,
        bytes_le: bytes | None = None,
        fields: tuple[int, int, int, int, int, int] | None = None,
        int: int | None = None,
        version: int | None = None,
        data: str | bytes | None = None,
    ) -> None:
        """Initialize UUID instance.

        Args:
            hex: UUID hex string or UUID object.
            bytes: UUID as 16-byte string.
            bytes_le: UUID as 16-byte little-endian string.
            fields: Six-field tuple.
            int: UUID as 128-bit integer.
            version: UUID version number.
            data: Compact data representation (str encoded as UTF-8, or raw bytes).
        """
        u = self._make_uuid(hex=hex, bytes=bytes, bytes_le=bytes_le, fields=fields, int=int, version=version, data=data)
        # Initialize parent UUID with int value
        super().__init__(int=u.int)

    @classmethod
    def _make_uuid(
        cls,
        hex: str | _uuid.UUID | None = None,
        bytes: bytes | None = None,
        bytes_le: bytes | None = None,
        fields: tuple[int, int, int, int, int, int] | None = None,
        int: int | None = None,
        version: int | None = None,
        data: str | bytes | None = None,
    ) -> _uuid.UUID:
        """Create standard UUID from parameters.

        Args:
            hex: UUID hex string or UUID object.
            bytes: UUID as 16-byte string.
            bytes_le: UUID as 16-byte little-endian string.
            fields: Six-field tuple.
            int: UUID as 128-bit integer.
            version: UUID version number.
            data: Compact data representation (str encoded as UTF-8, or raw bytes).

        Returns:
            Standard UUID object.

        Raises:
            TypeError: If wrong number of arguments provided.
            ValueError: If data exceeds 15 bytes.
        """
        if [hex, bytes, bytes_le, fields, int, data].count(None) != 5:
            raise TypeError("need one of hex, bytes, bytes_le, fields, or int")
        if data is not None:
            data_bytes = data if isinstance(data, builtins_bytes) else data.encode("utf-8")
            num = 0
            for d in data_bytes:
                num <<= 8
                num |= d
            node = ((num << 1) & 0xfe0000000000) | num & 0x00ffffffffff
            num >>= 47
            clock_seq_low = num & 0xff
            num >>= 8
            clock_seq_hi_variant = num & 0x3f
            num >>= 6
            time_low = num & 0xffffffff
            num >>= 32
            time_mid = num & 0xffff
            num >>= 16
            time_hi_version = num & 0xfff
            num >>= 12
            if num:
                raise ValueError("UUIDs can only store as much as 15 bytes")
            time_hi_version |= 0x1000  # Version 1
            clock_seq_hi_variant |= 0x80  # Variant: RFC 4122
            node |= 0x010000000000  # Multicast bit set
            fields = (time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node)
        elif hex is not None:
            resolved = cls._decode_uuid(hex, 1) if isinstance(hex, str) else hex
            int, hex = resolved.int, None
        hex_str: str | None = hex if isinstance(hex, str) else None
        return _uuid.UUID(hex=hex_str, bytes=bytes, bytes_le=bytes_le, fields=fields, int=int, version=version)

    @classmethod
    def _is_serialised(cls, serialised: bytes, count: int | None = None) -> bool:
        """Check if bytes are valid serialized UUID format.

        Args:
            serialised: Bytes to check.
            count: Optional count of UUIDs expected.

        Returns:
            True if valid serialized format, False otherwise.
        """
        while serialised:
            if count is not None:
                if not count:
                    return False
                count -= 1
            size = len(serialised)
            if size < 2:
                return False
            byte0 = serialised[0]
            if byte0 == 1:
                length = 17
            else:
                length = size
                q = bool(byte0 & 0xf0)
                for i in range(13):
                    if cls.VL[i][q][0] == (byte0 & cls.VL[i][q][1]):
                        length = i + 4
                        break
            if size < length:
                return False
            serialised = serialised[length:]
        return True

    @classmethod
    def _decode(cls, encoded: str, count: int | None = None) -> bytes:
        """Decode encoded string to serialized bytes.

        Args:
            encoded: Encoded UUID string.
            count: Optional expected count of UUIDs.

        Returns:
            Serialized UUID bytes.
        """
        if len(encoded) >= 7 and encoded[0] == "~":
            serialised = cls.ENCODER.decode(encoded)
            if cls._is_serialised(serialised, count):
                return serialised
        u = cls(_uuid.UUID(encoded))
        return u.serialise()

    @classmethod
    def _decode_uuid(cls, encoded: str, count: int | None = None) -> _uuid.UUID:
        """Decode encoded string to UUID object.

        Args:
            encoded: Encoded UUID string.
            count: Optional expected count of UUIDs.

        Returns:
            UUID object.
        """
        if len(encoded) >= 7 and encoded[0] == "~":
            serialised = cls.ENCODER.decode(encoded)
            if cls._is_serialised(serialised, count):
                return cls.unserialise(serialised)
        return _uuid.UUID(encoded)

    @classmethod
    def _unserialise_condensed(cls, serialised: bytes) -> tuple[UUID, int]:
        """Deserialize condensed UUID format.

        Args:
            serialised: Serialized UUID bytes in condensed format.

        Returns:
            Tuple of (UUID object, length consumed).

        Raises:
            ValueError: If serialized data is invalid.
        """
        size = len(serialised)
        length = size
        byte0 = serialised[0]
        q = bool(byte0 & 0xf0)
        for i in range(13):
            if cls.VL[i][q][0] == (byte0 & cls.VL[i][q][1]):
                length = i + 4
                break
        if size < length:
            raise ValueError("Bad encoded uuid")

        list_bytes_ = bytearray(serialised[:length])
        byte0 &= ~cls.VL[i][q][1]
        list_bytes_[0] = byte0

        meat = 0
        for b in list_bytes_:
            meat <<= 8
            meat |= b

        compacted = meat & 1
        meat >>= cls.COMPACTED_BITS

        if compacted:
            salt = meat & cls.SALT_MASK
            meat >>= cls.SALT_BITS
            clock = meat & cls.CLOCK_MASK
            meat >>= cls.CLOCK_BITS
            time = meat & cls.TIME_MASK
            node = cls._calculate_node(time, clock, salt)
        else:
            node = meat & cls.NODE_MASK
            meat >>= cls.NODE_BITS
            clock = meat & cls.CLOCK_MASK
            meat >>= cls.CLOCK_BITS
            time = meat & cls.TIME_MASK

        if time:
            if compacted:
                time = ((time << cls.CLOCK_BITS) + cls.UUID_TIME_INITIAL) & cls.TIME_MASK
            elif not (node & 0x010000000000):
                time = (time + cls.UUID_TIME_INITIAL) & cls.TIME_MASK

        time_low = time & 0xffffffff
        time_mid = (time >> 32) & 0xffff
        time_hi_version = (time >> 48) & 0xfff
        time_hi_version |= 0x1000
        clock_seq_low = clock & 0xff
        clock_seq_hi_variant = (clock >> 8) & 0x3f | 0x80  # Variant: RFC 4122

        return cls(fields=(time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node)), length

    @classmethod
    def _unserialise_full(cls, serialised: bytes) -> tuple[UUID, int]:
        """Deserialize full UUID format.

        Args:
            serialised: Serialized UUID bytes in full format.

        Returns:
            Tuple of (UUID object, length consumed).

        Raises:
            ValueError: If serialized data is too short.
        """
        if len(serialised) < 17:
            raise ValueError(f"Bad encoded uuid {serialised!r}")
        return cls(bytes=serialised[1:17]), 17

    @classmethod
    def _unserialise(cls, serialised: bytes) -> tuple[UUID, int]:
        """Deserialize UUID from bytes.

        Args:
            serialised: Serialized UUID bytes.

        Returns:
            Tuple of (UUID object, length consumed).

        Raises:
            ValueError: If serialized data is invalid.
        """
        if serialised is None or len(serialised) < 2:
            raise ValueError(f"Bad encoded uuid {serialised!r}")

        if serialised and serialised[0] == 1:
            return cls._unserialise_full(serialised)
        return cls._unserialise_condensed(serialised)

    @classmethod
    def _calculate_node(cls, time: int, clock: int, salt: int) -> int:
        """Calculate node from time, clock, and salt using deterministic PRNG.

        Args:
            time: Time value.
            clock: Clock value.
            salt: Salt value.

        Returns:
            Calculated node value.
        """
        if not time and not clock and not salt:
            return 0x010000000000
        seed = 0
        seed ^= fnv_1a(time)
        seed ^= fnv_1a(clock)
        seed ^= fnv_1a(salt)
        g = MT19937(seed & 0xffffffff)
        node = g()
        node <<= 32
        node |= g()
        node &= cls.NODE_MASK & ~cls.SALT_MASK
        node |= salt
        node |= 0x010000000000  # set multicast bit
        return node

    @classmethod
    def _serialise(cls, self: UUID) -> tuple[bytes, int | None, int | None, int | None]:
        """Serialize UUID to compact bytes.

        Args:
            self: UUID instance to serialize.

        Returns:
            Tuple of (serialized_bytes, compacted_node, compacted_time, compacted_clock).
        """
        variant = self.bytes[8] & 0xc0
        version = self.bytes[6] >> 4
        if variant == 0x80 and version == 1:
            node = self.node & cls.NODE_MASK
            clock = self.clock_seq & cls.CLOCK_MASK
            time = self.time & cls.TIME_MASK
            compacted_time = ((time - cls.UUID_TIME_INITIAL) & cls.TIME_MASK) if time else 0
            compacted_time_clock = compacted_time & cls.CLOCK_MASK
            compacted_time >>= cls.CLOCK_BITS
            compacted_clock = clock ^ compacted_time_clock
            if node & 0x010000000000:
                salt = node & cls.SALT_MASK
            else:
                salt = fnv_1a(node)
                salt = xor_fold(salt, cls.SALT_BITS)
                salt = salt & cls.SALT_MASK
            compacted_node = cls._calculate_node(compacted_time, compacted_clock, salt)
            compacted = node == compacted_node

            if compacted:
                meat = compacted_time
                meat <<= cls.CLOCK_BITS
                meat |= compacted_clock
                meat <<= cls.SALT_BITS
                meat |= salt
                meat <<= cls.COMPACTED_BITS
                meat |= 1
            else:
                if not (node & 0x010000000000) and time:
                    time = (time - cls.UUID_TIME_INITIAL) & cls.TIME_MASK
                meat = time
                meat <<= cls.CLOCK_BITS
                meat |= clock
                meat <<= cls.NODE_BITS
                meat |= node
                meat <<= cls.COMPACTED_BITS

            serialised_list: list[int] = []
            while meat or len(serialised_list) < 4:
                serialised_list.append(meat & 0xff)
                meat >>= 8
            length = len(serialised_list) - 4
            if serialised_list[-1] & cls.VL[length][0][1]:
                if serialised_list[-1] & cls.VL[length][1][1]:
                    serialised_list.append(cls.VL[length + 1][0][0])
                else:
                    serialised_list[-1] |= cls.VL[length][1][0]
            else:
                serialised_list[-1] |= cls.VL[length][0][0]
            serialised = bytes(reversed(serialised_list))
            if compacted_time:
                compacted_time = ((compacted_time << cls.CLOCK_BITS) + cls.UUID_TIME_INITIAL) & cls.TIME_MASK
        else:
            compacted_node = None
            compacted_time = None
            compacted_clock = None
            serialised = bytes([0x01]) + self.bytes
        return serialised, compacted_node, compacted_time, compacted_clock

    @classmethod
    def new(cls, data: str | bytes | None = None, compacted: bool | None = None) -> UUID:
        """Create new UUID instance.

        Args:
            data: Optional compact data representation (encoded as UTF-8).
            compacted: Whether to create compacted UUID.

        Returns:
            New UUID instance.
        """
        if data is not None:
            return UUID(data=data)
        num = UUID(_uuid.uuid1())
        if compacted or compacted is None:
            num = num.compact_crush() or num
        return num

    @classmethod
    def unserialise(cls, serialised: bytes) -> UUID:
        """Deserialize bytes to UUID.

        Args:
            serialised: Serialized UUID bytes.

        Returns:
            UUID object.

        Raises:
            ValueError: If serialized data is invalid.
        """
        uuid_obj, length = cls._unserialise(serialised)
        if length > len(serialised):
            raise ValueError(f"Invalid serialised uuid {serialised!r}")
        return uuid_obj

    def serialise(self) -> bytes:
        """Serialize UUID to compact bytes.

        Returns:
            Serialized bytes.
        """
        if "_serialised" not in self.__dict__:
            (
                self.__dict__["_serialised"],
                self.__dict__["_compacted_node"],
                self.__dict__["_compacted_time"],
                self.__dict__["_compacted_clock"],
            ) = self._serialise(self)
        result: bytes = self.__dict__["_serialised"]
        return result

    def compact_crush(self) -> UUID | None:
        """Create compacted version of UUID if possible.

        Returns:
            Compacted UUID or None if already compact.
        """
        if "_compacted_node" not in self.__dict__:
            self.serialise()
        node = self.__dict__["_compacted_node"]
        if node is not None:
            time = self.__dict__["_compacted_time"]
            clock = self.__dict__["_compacted_clock"]
            time_low = time & 0xffffffff
            time_mid = (time >> 32) & 0xffff
            time_hi_version = (time >> 48) & 0xfff
            time_hi_version |= 0x1000
            clock_seq_low = clock & 0xff
            clock_seq_hi_variant = (clock >> 8) & 0x3f | 0x80  # Variant: RFC 4122
            return self.__class__(
                fields=(time_low, time_mid, time_hi_version, clock_seq_hi_variant, clock_seq_low, node),
            )
        return None

    def encode(self, encoding: str = "encoded") -> str:
        """Encode UUID to string.

        Args:
            encoding: Encoding format.

        Returns:
            Encoded string.
        """
        return encode(self.serialise(), encoding)

    def data(self) -> bytes:
        """Get compact data representation of UUID.

        Returns:
            Compact data bytes.
        """
        num = 0
        version = self.version
        variant = self.clock_seq_hi_variant & 0x80
        if variant == 0x80 and version == 1 and self.node & 0x010000000000:
            num <<= 12
            num |= self.time_hi_version & 0xfff
            num <<= 16
            num |= self.time_mid & 0xffff
            num <<= 32
            num |= self.time_low & 0xffffffff
            num <<= 6
            num |= self.clock_seq_hi_variant & 0x3f
            num <<= 8
            num |= self.clock_seq_low & 0xff
            num <<= 47
            num |= ((self.node & 0xfe0000000000) >> 1) | (self.node & 0x00ffffffffff)
        data_list = []
        while num:
            data_list.append(num & 0xff)
            num >>= 8
        return bytes(reversed(data_list))

    def iscompact(self) -> bool:
        """Check if UUID is in compact form.

        Returns:
            True if UUID is compact, False otherwise.
        """
        if "_compacted_node" not in self.__dict__:
            self.serialise()
        is_compact: bool = self.__dict__["_compacted_node"] == self.node
        return is_compact


# Compatibility with builtin uuid

def uuid(data: str | bytes | None = None, compacted: bool | None = None) -> UUID:
    """Create new UUID.

    Args:
        data: Optional compact data representation (encoded as UTF-8).
        compacted: Whether to create compacted UUID.

    Returns:
        UUID instance.
    """
    return UUID.new(data=data, compacted=compacted)


def uuid1(node: int | None = None, clock_seq: int | None = None) -> UUID:
    """Generate UUID version 1.

    Args:
        node: Optional node value.
        clock_seq: Optional clock sequence.

    Returns:
        UUID v1 instance.
    """
    return UUID(_uuid.uuid1(node=node, clock_seq=clock_seq))


def uuid3(namespace: _uuid.UUID, name: str) -> UUID:
    """Generate UUID version 3 (MD5 hash).

    Args:
        namespace: Namespace UUID.
        name: Name string.

    Returns:
        UUID v3 instance.
    """
    return UUID(_uuid.uuid3(namespace, name))


def uuid4() -> UUID:
    """Generate UUID version 4 (random).

    Returns:
        UUID v4 instance.
    """
    return UUID(_uuid.uuid4())


def uuid5(namespace: _uuid.UUID, name: str) -> UUID:
    """Generate UUID version 5 (SHA-1 hash).

    Args:
        namespace: Namespace UUID.
        name: Name string.

    Returns:
        UUID v5 instance.
    """
    return UUID(_uuid.uuid5(namespace, name))


# The following standard UUIDs are for use with uuid3() or uuid5().

NAMESPACE_DNS = UUID("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
NAMESPACE_URL = UUID("6ba7b811-9dad-11d1-80b4-00c04fd430c8")
NAMESPACE_OID = UUID("6ba7b812-9dad-11d1-80b4-00c04fd430c8")
NAMESPACE_X500 = UUID("6ba7b814-9dad-11d1-80b4-00c04fd430c8")
