import { computed, ref, watch } from "vue"
import { useClipboard } from "@vueuse/core"
import { BUILTIN_FORMATS } from "./formats/builtins"
import type {
  ExportFormat,
  FormatEntry,
  SerializeResult,
  ExportSuccessPayload,
  ExportErrorPayload,
} from "./types"

export * from "./types"

// ─── Types ────────────────────────────────────────────────────────────────────

interface UseExportOptions {
  data: () => unknown
  formats: () => FormatEntry[]
  defaultFormat: () => string | undefined
  filename: () => string
  enabled?: () => boolean
  onSuccess: (payload: ExportSuccessPayload) => void
  onError: (payload: ExportErrorPayload) => void
}

// ─── Composable ───────────────────────────────────────────────────────────────

export function useExport(options: UseExportOptions) {
  const {
    data,
    formats,
    defaultFormat,
    filename,
    enabled,
    onSuccess,
    onError,
  } = options

  // ── Resolve format list ────────────────────────────────────────────────────

  const resolvedFormats = computed<ExportFormat[]>(() => {
    return formats()
      .map((entry): ExportFormat | null => {
        if (typeof entry === "string") {
          return BUILTIN_FORMATS[entry] ?? null
        }
        // Custom format — validate minimum required fields
        if (
          entry &&
          typeof entry.id === "string" &&
          typeof entry.serialize === "function"
        ) {
          return entry as ExportFormat
        }
        console.warn("[RacletteExport] Invalid format entry, skipping:", entry)
        return null
      })
      .filter((f): f is ExportFormat => f !== null)
  })

  // ── Active format ──────────────────────────────────────────────────────────

  const activeFormatId = ref<string>("")

  // Initialize / sync when formats or defaultFormat changes
  watch(
    [resolvedFormats, defaultFormat],
    ([fmts, dflt]) => {
      if (!fmts.length) {
        activeFormatId.value = ""
        return
      }
      const preferred = dflt ?? ""
      const found = fmts.find((f) => f.id === preferred)
      // Keep current selection if still valid, otherwise fall back to first
      const currentStillValid = fmts.some((f) => f.id === activeFormatId.value)
      if (!currentStillValid) {
        activeFormatId.value = found?.id ?? fmts[0].id
      }
    },
    { immediate: true },
  )

  const activeFormat = computed<ExportFormat | undefined>(() =>
    resolvedFormats.value.find((f) => f.id === activeFormatId.value),
  )

  // ── Serialization ──────────────────────────────────────────────────────────

  const serializeResult = computed<SerializeResult>(() => {
    if (enabled && !enabled()) {
      // Defer serialization until explicitly enabled by the caller.
      return { ok: true, value: "" }
    }

    const fmt = activeFormat.value
    if (!fmt) return { ok: false, error: "No format selected." }

    try {
      const value = fmt.serialize(data())
      if (typeof value !== "string") {
        throw new TypeError(
          `serialize() must return a string, got ${typeof value}`,
        )
      }
      return { ok: true, value }
    } catch (err) {
      const message = err instanceof Error ? err.message : String(err)
      onError({ formatId: fmt.id, action: "serialize", error: err })
      return { ok: false, error: message }
    }
  })

  // ── Clipboard ──────────────────────────────────────────────────────────────

  const { copy, isSupported: clipboardSupported } = useClipboard()
  const copyState = ref<"idle" | "success" | "error">("idle")
  let copyResetTimer: ReturnType<typeof setTimeout> | null = null

  async function copyToClipboard() {
    const fmt = activeFormat.value
    if (!fmt) return
    if (serializeResult.value.ok === false) return

    try {
      await copy(serializeResult.value.value)
      copyState.value = "success"
      onSuccess({ formatId: fmt.id, action: "copy" })
    } catch (err) {
      copyState.value = "error"
      onError({ formatId: fmt.id, action: "copy", error: err })
    } finally {
      if (copyResetTimer) clearTimeout(copyResetTimer)
      copyResetTimer = setTimeout(() => {
        copyState.value = "idle"
      }, 2000)
    }
  }

  // ── Download ───────────────────────────────────────────────────────────────

  function downloadFile() {
    const fmt = activeFormat.value
    if (!fmt) return
    if (serializeResult.value.ok === false) return

    try {
      const blob = new Blob([serializeResult.value.value], {
        type: `${fmt.mimeType};charset=utf-8`,
      })
      const url = URL.createObjectURL(blob)
      const anchor = document.createElement("a")
      anchor.href = url
      anchor.download = `${filename() || "export"}.${fmt.extension}`
      anchor.style.display = "none"
      document.body.appendChild(anchor)
      anchor.click()
      document.body.removeChild(anchor)
      URL.revokeObjectURL(url)
      onSuccess({ formatId: fmt.id, action: "download" })
    } catch (err) {
      onError({ formatId: fmt.id, action: "download", error: err })
    }
  }

  return {
    resolvedFormats,
    activeFormatId,
    activeFormat,
    serializeResult,
    copyToClipboard,
    downloadFile,
    copyState,
    clipboardSupported,
  }
}
