import { useCallback, useMemo, useState, useTransition } from "react";

type JsonPrimitive = string | number | boolean | null;
export type JsonValue =
  | JsonPrimitive
  | ReadonlyArray<JsonValue>
  | Readonly<{ [key: string]: JsonValue }>;

export type JsonExpansionPolicy = {
  depth: number;
};

export function getJsonExpansionPolicy(_value: JsonValue): JsonExpansionPolicy {
  return { depth: Number.POSITIVE_INFINITY };
}

export type JsonBulkExpansion = {
  parsedData: JsonValue | null;
  policy: JsonExpansionPolicy | null;
  isExpanded: boolean;
  toggle: () => void;
  isPending: boolean;
  bulkDepth: number;
  bulkRevision: number;
};

type ParsedJsonText =
  | {
      kind: "json";
      data: JsonValue;
    }
  | {
      kind: "text";
    };

export function parseJsonText(text: string): ParsedJsonText {
  try {
    const parsed: unknown = JSON.parse(text);
    return { kind: "json", data: safeJsonValue(parsed) };
  } catch {
    return { kind: "text" };
  }
}

export function useJsonBulkExpansion(text: string | null): JsonBulkExpansion {
  const parsed = useMemo(() => (text === null ? null : parseJsonText(text)), [text]);
  const parsedData = parsed?.kind === "json" ? parsed.data : null;
  const policy = useMemo(
    () => (parsedData === null ? null : getJsonExpansionPolicy(parsedData)),
    [parsedData],
  );
  const [isExpanded, setIsExpanded] = useState(false);
  const [bulkDepth, setBulkDepth] = useState(0);
  const [bulkRevision, setBulkRevision] = useState(0);
  const [isPending, startTransition] = useTransition();

  const toggle = useCallback(() => {
    const nextExpanded = !isExpanded;
    const targetDepth = nextExpanded && policy !== null ? policy.depth : 0;
    startTransition(() => {
      setIsExpanded(nextExpanded);
      setBulkDepth(targetDepth);
      setBulkRevision((current) => current + 1);
    });
  }, [isExpanded, policy]);

  return { parsedData, policy, isExpanded, toggle, isPending, bulkDepth, bulkRevision };
}

export function safeJsonValue(value: unknown): JsonValue {
  if (value === null || value === undefined) return null;
  switch (typeof value) {
    case "string":
      return value;
    case "number":
      return value;
    case "boolean":
      return value;
    case "object": {
      if (Array.isArray(value)) {
        return value.map((item: unknown) => safeJsonValue(item));
      }
      const result: Record<string, JsonValue> = {};
      for (const key of Object.keys(value)) {
        const descriptor = Object.getOwnPropertyDescriptor(value, key);
        result[key] = safeJsonValue(descriptor?.value);
      }
      return result;
    }
    case "bigint":
    case "symbol":
    case "function":
    case "undefined":
      return String(value);
  }
  return null;
}
