/**
 * Parse LLM output into JSON, handling common LLM quirks:
 * 1. Markdown code fences (```json ... ``` or ``` ... ```)
 * 2. Leading/trailing text around the JSON
 * 3. Nested objects where strings were expected (flatten them)
 */

/** Strip markdown code fences and extract the JSON object */
export function extractJson(raw: string): string | null {
  // Remove markdown code fences: ```json ... ``` or ``` ... ```
  let cleaned = raw.trim();

  // Try to extract from code fence
  const fenceMatch = cleaned.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
  if (fenceMatch) {
    cleaned = fenceMatch[1].trim();
  }

  // Find the outermost { ... } brace pair
  const start = cleaned.indexOf("{");
  const end = cleaned.lastIndexOf("}");
  if (start !== -1 && end > start) {
    return cleaned.substring(start, end + 1);
  }

  return null;
}

/** Parse JSON from LLM output, stripping fences and handling errors */
export function parseLlmJson<T>(raw: string): T | null {
  const jsonStr = extractJson(raw);
  if (!jsonStr) return null;

  try {
    return JSON.parse(jsonStr) as T;
  } catch (e) {
    console.error("JSON parse error:", e, "Input:", jsonStr);
    return null;
  }
}

/**
 * Flatten nested objects into display-friendly strings.
 * If a value is an object, stringify it nicely.
 * If a value is an array of objects, join them.
 */
export function flattenValue(val: unknown): string {
  if (val === null || val === undefined) return "";
  if (typeof val === "string") return val;
  if (typeof val === "number") return String(val);
  if (typeof val === "boolean") return String(val);
  if (Array.isArray(val)) {
    return val.map(flattenValue).join("\n");
  }
  if (typeof val === "object") {
    // Pretty-format the object as key: value pairs
    return Object.entries(val as Record<string, unknown>)
      .map(([k, v]) => {
        const formatted = typeof v === "string" ? v : JSON.stringify(v);
        return `**${formatKey(k)}**: ${formatted}`;
      })
      .join("\n");
  }
  return String(val);
}

/** Convert camelCase or snake_case keys to Title Case */
function formatKey(key: string): string {
  return key
    .replace(/_/g, " ")
    .replace(/([a-z])([A-Z])/g, "$1 $2")
    .replace(/\b\w/g, (c) => c.toUpperCase());
}

/** Safely get an array from a field that might be a string or array */
export function ensureArray(val: unknown): string[] {
  if (Array.isArray(val)) {
    return val.map((v) => (typeof v === "string" ? v : JSON.stringify(v)));
  }
  if (typeof val === "string") {
    // Try to parse as JSON array
    try {
      const parsed = JSON.parse(val);
      if (Array.isArray(parsed)) return parsed.map((v: unknown) => String(v));
    } catch {
      // Not JSON, return as single-element array
    }
    return [val];
  }
  return [];
}