"""Utility functions for nanomon."""

from __future__ import annotations

import time
import uuid
from abc import ABC, abstractmethod
from datetime import datetime, timezone
from typing import Protocol


class Clock(Protocol):
    """Protocol for time operations, allowing injection for testing."""

    def now_utc(self) -> datetime:
        """Return current UTC datetime."""
        ...

    def perf_counter_ns(self) -> int:
        """Return performance counter in nanoseconds."""
        ...


class SystemClock:
    """Default clock using system time."""

    def now_utc(self) -> datetime:
        """Return current UTC datetime."""
        return datetime.now(timezone.utc)

    def perf_counter_ns(self) -> int:
        """Return performance counter in nanoseconds."""
        return time.perf_counter_ns()


def generate_uuid() -> str:
    """Generate a new UUID string."""
    return str(uuid.uuid4())


def ns_to_ms(nanoseconds: int) -> int:
    """Convert nanoseconds to milliseconds."""
    return nanoseconds // 1_000_000


def deduplicate_tags(default_tags: list[str] | None, run_tags: list[str] | None) -> list[str]:
    """Merge and deduplicate tags while preserving order.

    Args:
        default_tags: Default tags from NanomonRunContext
        run_tags: Tags specific to this run

    Returns:
        Deduplicated list preserving order (defaults first, then run-specific)
    """
    seen: set[str] = set()
    result: list[str] = []

    for tag in default_tags or []:
        if tag not in seen:
            seen.add(tag)
            result.append(tag)

    for tag in run_tags or []:
        if tag not in seen:
            seen.add(tag)
            result.append(tag)

    return result


def merge_metadata(
    default_metadata: dict[str, str | int | float | bool | None] | None,
    run_metadata: dict[str, str | int | float | bool | None] | None,
) -> dict[str, str | int | float | bool | None]:
    """Merge metadata dictionaries with run metadata taking precedence.

    Args:
        default_metadata: Default metadata from NanomonRunContext
        run_metadata: Metadata specific to this run

    Returns:
        Merged dictionary with run metadata overriding defaults
    """
    result: dict[str, str | int | float | bool | None] = {}
    if default_metadata:
        result.update(default_metadata)
    if run_metadata:
        result.update(run_metadata)
    return result
