/**
 * Shared helpers for safely traversing deeply-nested Facebook GraphQL JSON.
 */

/**
 * Safely traverse nested object paths.
 * Returns undefined if any segment is missing.
 */
export function dig(obj: unknown, ...path: string[]): unknown {
  let cur = obj;
  for (const key of path) {
    if (cur == null || typeof cur !== "object") return undefined;
    cur = (cur as Record<string, unknown>)[key];
  }
  return cur;
}

/** Extract a string field from an object, or undefined. */
export function asStr(
  obj: Record<string, unknown> | undefined | null,
  key?: string,
): string | undefined {
  if (!obj) return undefined;
  const val = key ? obj[key] : obj;
  return typeof val === "string" ? val : undefined;
}

/** Extract a numeric field from an object, or undefined. */
export function asNum(
  obj: Record<string, unknown> | undefined | null,
  key?: string,
): number | undefined {
  if (!obj) return undefined;
  const val = key ? obj[key] : obj;
  if (typeof val === "number" && !Number.isNaN(val)) return val;
  if (typeof val === "string") {
    const n = Number(val);
    if (!Number.isNaN(n)) return n;
  }
  return undefined;
}

/** Extract a boolean field from an object, or undefined. */
export function asBool(
  obj: Record<string, unknown> | undefined | null,
  key?: string,
): boolean | undefined {
  if (!obj) return undefined;
  const val = key ? obj[key] : obj;
  if (typeof val === "boolean") return val;
  return undefined;
}

/** Shallow clone an object for raw storage (drop functions, symbols). */
export function asObj(
  val: unknown,
): Record<string, unknown> | undefined {
  if (!val || typeof val !== "object") return undefined;
  try {
    return JSON.parse(JSON.stringify(val)) as Record<string, unknown>;
  } catch {
    return undefined;
  }
}

/**
 * Extract an array of edge nodes from a GraphQL connection.
 * Handles `{ edges: [{ node: ... }] }` pattern.
 */
export function edgeNodes<T = Record<string, unknown>>(
  connection: unknown,
): T[] {
  if (!connection || typeof connection !== "object") return [];
  const conn = connection as Record<string, unknown>;
  const edges = conn.edges ?? conn.nodes;
  if (!Array.isArray(edges)) return [];
  return edges
    .map((e) => {
      if (typeof e !== "object" || !e) return null;
      const edge = e as Record<string, unknown>;
      return (edge.node ?? edge) as T;
    })
    .filter((n): n is T => n !== null);
}

/** Unix epoch → ISO string. */
export function epochToIso(val: unknown): string | undefined {
  if (typeof val === "number") {
    // Seconds-based epoch (before year 10000 in ms means it's seconds)
    const ms = val < 1e12 ? val * 1000 : val;
    return new Date(ms).toISOString();
  }
  if (typeof val === "string") {
    const n = Number(val);
    if (!Number.isNaN(n)) return epochToIso(n);
  }
  return undefined;
}
