import type { ExportFormat } from "../types"

// ─── Optional peer dep: js-yaml ───────────────────────────────────────────────
// Vite/ESM environments don't support require(). We attempt a static import at
// module initialisation time. If js-yaml is not installed the import will throw
// and jsYamlDump stays undefined — the serializer then shows a clear error.

type JsYamlDump = (data: unknown, options?: object) => string

let jsYamlDump: JsYamlDump | undefined

const jsonReplacer = (_key: string, value: unknown) => {
  // JSON doesn't support bigint values; preserve intent as a tagged string.
  if (typeof value === "bigint") {
    return `${value}n`
  }

  return value
}

try {
  // Static import string must be a literal so bundlers can analyse it.
  // The `@vite-ignore` comment suppresses the "dynamic import" warning.
  const mod = await import(/* @vite-ignore */ "js-yaml")
  jsYamlDump = (mod.dump ?? mod.default?.dump) as JsYamlDump | undefined
} catch {
  // js-yaml not installed — yamlFormat.serialize() will throw a helpful message
}

// ─── XML ──────────────────────────────────────────────────────────────────────

/**
 * Recursively converts a plain JS object / array to XML element strings.
 * - Object keys become element names (invalid XML chars are stripped).
 * - Arrays emit repeated elements using the parent key name (singularised naively).
 * - Primitive values become text content.
 */
function toXmlElements(value: unknown, tag: string, indent: string): string {
  const pad = indent + "  "

  if (value === null || value === undefined) {
    return `${indent}<${tag}/>`
  }

  if (Array.isArray(value)) {
    // Emit each item wrapped in a singular version of the tag.
    const child = tag.replace(/s$/, "") || "item"
    return value
      .map((item) => toXmlElements(item, child, indent))
      .join("\n")
  }

  if (typeof value === "object") {
    const children = Object.entries(value as Record<string, unknown>)
      .map(([k, v]) => toXmlElements(v, sanitiseTag(k), pad))
      .join("\n")
    return `${indent}<${tag}>\n${children}\n${indent}</${tag}>`
  }

  // Primitive — escape XML special chars.
  const escaped = String(value)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&apos;")
  return `${indent}<${tag}>${escaped}</${tag}>`
}

function sanitiseTag(key: string): string {
  // XML element names must start with a letter or underscore.
  const cleaned = key.replace(/[^a-zA-Z0-9_.-]/g, "_")
  return /^[^a-zA-Z_]/.test(cleaned) ? `_${cleaned}` : cleaned
}

export const xmlFormat: ExportFormat = {
  id: "xml",
  label: "XML",
  extension: "xml",
  mimeType: "application/xml",
  serialize(data) {
    const body = toXmlElements(data, "root", "")
    return `<?xml version="1.0" encoding="UTF-8"?>\n${body}`
  },
}

// ─── JSON ─────────────────────────────────────────────────────────────────────

export const jsonFormat: ExportFormat = {
  id: "json",
  label: "JSON",
  extension: "json",
  mimeType: "application/json",
  serialize(data) {
    return JSON.stringify(data, jsonReplacer, 2)
  },
}

// ─── YAML ─────────────────────────────────────────────────────────────────────

export const yamlFormat: ExportFormat = {
  id: "yaml",
  label: "YAML",
  extension: "yaml",
  mimeType: "application/yaml",
  serialize(data) {
    if (typeof jsYamlDump !== "function") {
      throw new Error(
        'The "js-yaml" package is required for YAML export.\n' +
          "Install it with:  npm install js-yaml\n" +
          "Types (optional): npm install -D @types/js-yaml",
      )
    }
    return jsYamlDump(data, { indent: 2, lineWidth: 120, noRefs: true })
  },
}

// ─── Registry ─────────────────────────────────────────────────────────────────

export const BUILTIN_FORMATS: Record<string, ExportFormat> = {
  json: jsonFormat,
  yaml: yamlFormat,
  xml: xmlFormat,
}
