internal/dashboard/frontend/src/utils/formatting.ts

import { useTimeAgo } from "@vueuse/core";

/**
 * Formats ISO timestamp to display format (HH:MM:SS.mmm)
 * @param time ISO timestamp to format
 * @param showDate Whether to show the date
 * @returns Formatted date and time as a string
 */
export function formatTimestamp(
  time: string,
  showDate: boolean = false,
): string {
  if (!time) return "";
  const date = new Date(time);
  if (isNaN(date.getTime())) return time;

  const hours = date.getHours().toString().padStart(2, "0");
  const minutes = date.getMinutes().toString().padStart(2, "0");
  const seconds = date.getSeconds().toString().padStart(2, "0");
  const ms = date.getMilliseconds().toString().padStart(3, "0");

  const timeStr = `${hours}:${minutes}:${seconds}.${ms}`;

  if (showDate) {
    const year = date.getFullYear();
    const month = (date.getMonth() + 1).toString().padStart(2, "0");
    const day = date.getDate().toString().padStart(2, "0");
    return `${year}-${month}-${day} ${timeStr}`;
  }
  return timeStr;
}

export function formatLocalNumber(number: number | undefined): string {
  if (!number) return "0";
  return number.toLocaleString();
}

/**
 * Formats a date and time for local display using toLocaleString()
 * @param time Date and time to format
 * @returns Formatted date and time as a string
 */
export function formatLocalDateTime(
  time: string,
  noSec: boolean = false,
): string {
  if (!time) return "";
  const date = new Date(time);
  if (isNaN(date.getTime())) return time;
  if (noSec) {
    return date.toLocaleString([], {
      day: "2-digit",
      month: "2-digit",
      year: "2-digit",
      hour: "2-digit",
      minute: "2-digit",
    });
  }
  return date.toLocaleString();
}

/**
 * Formats a date and time for API use (YYYY-MM-DD HH:MM:SS.mmm)
 * @param time Date and time to format
 * @returns Formatted date and time as a string
 */
export function formatDateTimeForAPI(time: string): string {
  if (!time) return "";
  const d = new Date(time);
  if (isNaN(d.getTime())) return time.replace("T", " ") + ":00";
  return d.toISOString();
}

/**
 * Formats a date for datetime-local input (YYYY-MM-DDTHH:mm)
 * @param time Date and time to format
 * @returns Formatted date and time as a string
 */
export function formatDateForInput(time: string): string {
  if (!time) return "";
  const d = new Date(time);
  if (isNaN(d.getTime())) return "";

  const year = d.getFullYear();
  const month = (d.getMonth() + 1).toString().padStart(2, "0");
  const day = d.getDate().toString().padStart(2, "0");
  const hours = d.getHours().toString().padStart(2, "0");
  const minutes = d.getMinutes().toString().padStart(2, "0");

  return `${year}-${month}-${day}T${hours}:${minutes}`;
}

/**
 * Formats a JSON object for display in a HTML span
 * @param json JSON object to format
 * @param indent Number of spaces to indent the JSON object
 * @returns Formatted JSON object as a string
 */
export function formatJsonForDisplay(json: any, indent: number = 0): string {
  const indentSpace = (n: number) => "  ".repeat(n);

  function isPlainObject(obj: any) {
    return Object.prototype.toString.call(obj) === "[object Object]";
  }

  function escapeHtml(str: string): string {
    return str
      .replace(/&/g, "&")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }

  function valueToHtml(val: any, depth: number): string {
    if (val === null) {
      return `<span class="v-null">null</span>`;
    }
    if (typeof val === "string") {
      return `<span class="v-str">${escapeHtml(val)}</span>`;
    }
    if (typeof val === "number") {
      return `<span class="v-num">${escapeHtml(val.toString())}</span>`;
    }
    if (typeof val === "boolean") {
      return `<span class="v-bool">${escapeHtml(val.toString())}</span>`;
    }
    if (Array.isArray(val)) {
      if (val.length === 0) return `<span class="v-arr">[]</span>`;
      return val
        .map(
          (item) =>
            `${indentSpace(depth + 1)}- ${valueToHtml(item, depth + 1)}`,
        )
        .join("\n");
    }
    if (isPlainObject(val)) {
      const keys = Object.keys(val);
      if (keys.length === 0) return `<span class="v-obj">{}</span>`;
      return keys
        .map((k) => {
          let valueHtml = valueToHtml(val[k], depth + 1);
          // If the value is an object, array, or otherwise multi-line, add a newline after the key
          if (
            (isPlainObject(val[k]) && Object.keys(val[k]).length > 0) ||
            (Array.isArray(val[k]) && val[k].length > 0)
          ) {
            return `${indentSpace(depth)}<span class="k">${escapeHtml(k)}</span>:\n${valueHtml}`;
          } else {
            return `${indentSpace(depth)}<span class="k">${escapeHtml(k)}</span>: ${valueHtml}`;
          }
        })
        .join("\n");
    }
    return `<span class="v-unk">${escapeHtml(String(val))}</span>`;
  }

  return valueToHtml(json, indent);
}

/**
 * Converts an IP address to an integer for color mapping
 * @param ip IP address to convert to integer
 * @returns Integer representation of the IP address
 */
export function ipToInt(ip: string): number {
  const parts = ip.split(".");
  if (parts.length !== 4) return 0;
  const p0 = parseInt(parts[0] || "0");
  const p1 = parseInt(parts[1] || "0");
  const p2 = parseInt(parts[2] || "0");
  const p3 = parseInt(parts[3] || "0");
  return p0 * 16777216 + p1 * 65536 + p2 * 256 + p3;
}

/**
 * Converts an integer back to an IP address string
 * @param int Integer representation of the IP address
 * @returns IP address string
 */
function intToIp(int: number): string {
  return [
    (int >>> 24) & 0xff,
    (int >>> 16) & 0xff,
    (int >>> 8) & 0xff,
    int & 0xff,
  ].join(".");
}

/**
 * Masks an IP address with a given number of mask bits
 * @param ip IP address string
 * @param maskBits Number of mask bits
 * @returns Masked IP address string
 */
export function maskIp(ip: string, maskBits: number): string {
  const int = ipToInt(ip);
  const mask = maskBits === 0 ? 0 : (~0 << (32 - maskBits)) >>> 0;
  return intToIp((int & mask) >>> 0);
}

export function timeAgo(time: string | undefined): string {
  if (!time) return "N/A";
  return useTimeAgo(new Date(time)).value;
}

/**
 * Splits a label into address and mask if it's a CIDR subnet
 * @param label The label to split
 * @returns Object with address and mask
 */
export function splitSubnet(label: string | number): {
  address: string;
  mask: string;
} {
  const str = String(label);
  const match = str.match(/^(.+)(\/\d+)$/);
  if (match) {
    return { address: match[1] as string, mask: match[2] as string };
  }
  return { address: str, mask: "" };
}