import type { JsonValue } from "../../shared/src/index";
import type { ProviderSocket } from "./types";

export function jsonResponse(body: unknown, status = 200): Response {
  return new Response(JSON.stringify(body, null, 2), {
    status,
    headers: {
      "content-type": "application/json; charset=utf-8",
      "access-control-allow-origin": "*",
      "access-control-allow-methods": "GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS",
      "access-control-allow-headers": "content-type,x-provider-token",
    },
  });
}

export function textResponse(text: string, status = 200): Response {
  return new Response(text, {
    status,
    headers: {
      "content-type": "text/plain; charset=utf-8",
      "access-control-allow-origin": "*",
    },
  });
}

export function readLimit(url: URL, defaultLimit: number): number {
  const raw = url.searchParams.get("limit");
  if (raw === null) return defaultLimit;
  const parsed = Number(raw);
  if (!Number.isInteger(parsed) || parsed <= 0) return defaultLimit;
  return Math.min(parsed, 500);
}

export function errorToJson(error: unknown): JsonValue {
  if (error instanceof Error) {
    return { name: error.name, message: error.message, stack: error.stack ?? null };
  }
  return String(error);
}

export function compactJson(value: unknown, depth = 0): JsonValue {
  if (value === null || typeof value === "number" || typeof value === "boolean") return value;
  if (typeof value === "string") return value.length > 600 ? `${value.slice(0, 600)}...<truncated:${value.length}>` : value;
  if (Array.isArray(value)) {
    const items = value.slice(0, 20).map((item) => compactJson(item, depth + 1));
    if (value.length > 20) items.push({ truncatedItems: value.length - 20 });
    return items;
  }
  if (typeof value === "object" && value !== null) {
    if (depth >= 4) return "<truncated:depth>";
    const entries = Object.entries(value as Record<string, unknown>).slice(0, 30);
    const result: Record<string, JsonValue> = {};
    for (const [key, item] of entries) result[key] = compactJson(item, depth + 1);
    const total = Object.keys(value as Record<string, unknown>).length;
    if (total > entries.length) result.truncatedKeys = total - entries.length;
    return result;
  }
  return String(value);
}

export function wsSendJson(ws: ProviderSocket, value: unknown): void {
  if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(value));
}

export function isJsonRecord(value: unknown): value is Record<string, JsonValue> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

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

export function nestedNumber(value: unknown, objectKey: string, numberKey: string): number {
  const parsed = Number(recordValue(recordValue(value, objectKey), numberKey));
  return Number.isFinite(parsed) ? parsed : 0;
}

function recordValue(value: unknown, key: string): unknown {
  if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
  return (value as Record<string, unknown>)[key];
}

export function redactDatabaseUrl(value: string): string {
  try {
    const url = new URL(value);
    if (url.password) url.password = "***";
    return url.toString();
  } catch {
    return "<invalid-url>";
  }
}

export function truncateText(value: string, maxBytes: number): string {
  return value.length <= maxBytes ? value : value.slice(0, maxBytes);
}

export function rowIso(value: Date | string | null | undefined): string | null {
  if (value === null || value === undefined) return null;
  return value instanceof Date ? value.toISOString() : String(value);
}
