/**
 * @file safe-json.ts
 * @description Safe JSON parsing utilities with runtime type validation.
 * Prevents runtime crashes from corrupted or tampered localStorage/API data.
 */

/**
 * Safely parse a JSON string with runtime type validation.
 * Returns the fallback value if parsing fails or the parsed data
 * doesn't match the expected shape.
 *
 * @param json - The JSON string to parse
 * @param validator - A type guard function that validates the parsed data
 * @param fallback - The default value to return on failure
 * @returns The validated parsed data or the fallback value
 */
export function safeJsonParse<T>(
  json: string,
  validator: (data: unknown) => data is T,
  fallback: T
): T {
  try {
    const parsed: unknown = JSON.parse(json);
    if (validator(parsed)) {
      return parsed;
    }
    console.warn("safeJsonParse: parsed data failed validation, returning fallback");
    return fallback;
  } catch {
    console.warn("safeJsonParse: failed to parse JSON, returning fallback");
    return fallback;
  }
}

/**
 * Type guard: validates that a value is a non-null object
 */
function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

/**
 * Type guard: validates a chat Message shape
 * Checks for required fields: id (string), role (valid enum), content (string), createdAt (string)
 */
export function isMessage(value: unknown): boolean {
  if (!isRecord(value)) return false;
  const validRoles = ["user", "assistant", "system"];
  return (
    typeof value.id === "string" &&
    typeof value.role === "string" &&
    validRoles.includes(value.role) &&
    typeof value.content === "string" &&
    typeof value.createdAt === "string"
  );
}

/**
 * Type guard: validates an array of chat Messages
 */
export function isMessageArray(value: unknown): value is {
  id: string;
  role: "user" | "assistant" | "system";
  content: string;
  createdAt: string;
  thinking?: string;
  model?: string;
  provider?: string;
  promptTokens?: number;
  completionTokens?: number;
  totalTokens?: number;
}[] {
  return Array.isArray(value) && value.every(isMessage);
}

/**
 * Type guard: validates ChatOverlayState shape
 * Expects { isOpen: boolean, isMinimized: boolean }
 * Enforces invariant: cannot be minimized when closed
 */
export function isChatOverlayState(
  value: unknown
): value is { isOpen: boolean; isMinimized: boolean } {
  if (!isRecord(value)) return false;
  if (typeof value.isOpen !== "boolean" || typeof value.isMinimized !== "boolean") return false;
  // Invariant: cannot be minimized when closed
  if (!value.isOpen && value.isMinimized) return false;
  return true;
}

/**
 * Type guard: validates a WidgetPlacement shape
 * Checks for required fields: i (string), x/y/w/h (numbers)
 */
function isWidgetPlacement(value: unknown): boolean {
  if (!isRecord(value)) return false;
  return (
    typeof value.i === "string" &&
    typeof value.x === "number" &&
    typeof value.y === "number" &&
    typeof value.w === "number" &&
    typeof value.h === "number"
  );
}

/**
 * Type guard: validates a LayoutConfig shape
 * Expects { id: string, name: string, layout: WidgetPlacement[] }
 */
function isLayoutConfig(value: unknown): boolean {
  if (!isRecord(value)) return false;
  return (
    typeof value.id === "string" &&
    typeof value.name === "string" &&
    Array.isArray(value.layout) &&
    (value.layout as unknown[]).every(isWidgetPlacement)
  );
}

/**
 * Type guard: validates a Record<string, LayoutConfig> shape.
 * Uses a branded type approach to ensure compatibility with LayoutConfig consumers.
 */
export function isLayoutConfigRecord(
  value: unknown
): value is Record<string, { id: string; name: string; layout: unknown[] }> {
  if (!isRecord(value)) return false;
  return Object.values(value).every(isLayoutConfig);
}
