import type { Tone } from "./types/domain";

export function nonEmptyString(value: unknown): string | null {
  return typeof value === "string" && value.trim() ? value.trim() : null;
}

export function firstNonEmptyString(...values: unknown[]): string | null {
  for (const value of values) {
    const text = nonEmptyString(value);
    if (text) return text;
  }
  return null;
}

export function toneClass(value: unknown): Tone {
  const text = String(value ?? "source").toLowerCase();
  if (["ok", "pass", "healthy", "completed", "ready", "available", "dev-live", "live"].includes(text)) return "ok";
  if (["pending", "running", "checking", "loading", "busy", "active"].includes(text)) return "pending";
  if (["blocked", "failed", "failure", "timeout", "error", "canceled", "unavailable"].includes(text)) return "blocked";
  if (["warn", "warning", "degraded", "partial"].includes(text)) return "warn";
  if (["dry-run", "source", "unverified", "recorded"].includes(text)) return text as Tone;
  return "source";
}

export function shortToken(value: unknown, length = 12): string {
  const text = nonEmptyString(value);
  if (!text) return "unknown";
  if (text.length <= length) return text;
  return text.slice(0, length);
}

export function formatTimestamp(value: unknown): string {
  const text = nonEmptyString(value);
  if (!text) return "未知";
  const date = new Date(text);
  if (Number.isNaN(date.getTime())) return "未知";
  return formatBeijingTime(date);
}

const BEIJING_FORMATTER = new Intl.DateTimeFormat("zh-CN", {
  timeZone: "Asia/Shanghai",
  year: "numeric",
  month: "2-digit",
  day: "2-digit",
  hour: "2-digit",
  minute: "2-digit",
  second: "2-digit",
  hour12: false
});

const BEIJING_SHORT_FORMATTER = new Intl.DateTimeFormat("zh-CN", {
  timeZone: "Asia/Shanghai",
  month: "2-digit",
  day: "2-digit",
  hour: "2-digit",
  minute: "2-digit",
  hour12: false
});

function toBeijingParts(value: Date | string | number, withSeconds: boolean): Intl.DateTimeFormatPart[] | null {
  const date = value instanceof Date ? value : new Date(value);
  if (Number.isNaN(date.getTime())) return null;
  return (withSeconds ? BEIJING_FORMATTER : BEIJING_SHORT_FORMATTER).formatToParts(date);
}

export function formatBeijingTime(value: unknown, options: { withSeconds?: boolean; includeYear?: boolean } = {}): string {
  const parts = toBeijingParts(value as Date | string | number, Boolean(options.withSeconds));
  if (!parts) return "未知";
  const lookup = new Map(parts.map((part) => [part.type, part.value]));
  const year = lookup.get("year") ?? "";
  const month = lookup.get("month") ?? "00";
  const day = lookup.get("day") ?? "00";
  const hour = lookup.get("hour") ?? "00";
  const minute = lookup.get("minute") ?? "00";
  const second = lookup.get("second") ?? "00";
  const datePart = options.includeYear === false ? `${month}-${day}` : `${year}-${month}-${day}`;
  return options.withSeconds ? `${datePart} ${hour}:${minute}:${second}` : `${datePart} ${hour}:${minute}`;
}

export function formatDuration(ms: unknown): string {
  const value = Number(ms);
  if (!Number.isFinite(value) || value < 0) return "未知";
  if (value < 1000) return `${Math.round(value)}ms`;
  if (value < 60_000) return `${(value / 1000).toFixed(value < 10_000 ? 2 : 1)}s`;
  const minutes = Math.floor(value / 60_000);
  const seconds = Math.floor((value % 60_000) / 1000);
  return `${minutes}m${seconds.toString().padStart(2, "0")}s`;
}

export function relativeUpdatedLabel(timestampMs: number): string {
  if (!Number.isFinite(timestampMs) || timestampMs <= 0) return "未知";
  const delta = Date.now() - timestampMs;
  if (delta < 60_000) return "刚刚";
  if (delta < 60 * 60_000) return `${Math.max(1, Math.round(delta / 60_000))}m 前`;
  if (delta < 24 * 60 * 60_000) return `${Math.max(1, Math.round(delta / (60 * 60_000)))}h 前`;
  return new Date(timestampMs).toISOString().slice(0, 10);
}

export function nextProtocolId(prefix: string): string {
  const random = Math.random().toString(36).slice(2, 10);
  return `${prefix}_${Date.now().toString(36)}_${random}`;
}

export function asRecord(value: unknown): Record<string, unknown> | null {
  return value && typeof value === "object" && !Array.isArray(value) ? value as Record<string, unknown> : null;
}

export function asArray<T>(value: unknown): T[] {
  return Array.isArray(value) ? value as T[] : [];
}

export function clampNumber(value: unknown, fallback: number, min: number, max: number): number {
  const numeric = Number(value);
  if (!Number.isFinite(numeric)) return fallback;
  return Math.min(Math.max(Math.trunc(numeric), min), max);
}

export function jsonPreview(value: unknown, limit = 900): string {
  if (typeof value === "string") return value.slice(0, limit);
  try {
    return JSON.stringify(value, null, 2).slice(0, limit);
  } catch {
    return String(value).slice(0, limit);
  }
}
