export interface ApiError {
  message: string
  code?: string
  statusCode?: number
  originalError?: Error
}

const GENERIC_ERROR_MESSAGES = new Set(["error", "unknown", "unknownerror"])

const normalizeErrorText = (value: unknown): string | null => {
  if (typeof value !== "string") {
    return null
  }

  const trimmed = value.trim()
  return trimmed ? trimmed : null
}

const isSpecificErrorText = (value: string | null): value is string => {
  return !!value && !GENERIC_ERROR_MESSAGES.has(value.toLowerCase())
}

const getXmlTagText = (xml: string, tagName: string): string | null => {
  const match = xml.match(new RegExp(`<${tagName}[^>]*>([\\s\\S]*?)</${tagName}>`, "i"))
  return normalizeErrorText(match?.[1])
}

export const getXmlErrorCode = (xml: string): string | null => {
  const trimmed = xml.trim()
  if (!trimmed.startsWith("<")) return null

  if (typeof DOMParser !== "undefined") {
    try {
      const doc = new DOMParser().parseFromString(trimmed, "text/xml")
      if (!doc.querySelector("parsererror")) {
        return normalizeErrorText(doc.querySelector("Code")?.textContent)
      }
    } catch {
      // Fall through to regex-based parsing.
    }
  }

  return getXmlTagText(trimmed, "Code")
}

export const getXmlErrorMessage = (xml: string): string | null => {
  const trimmed = xml.trim()
  if (!trimmed.startsWith("<")) {
    return null
  }

  // Use DOMParser for proper XML parsing with automatic entity decoding
  if (typeof DOMParser !== "undefined") {
    try {
      const parser = new DOMParser()
      const doc = parser.parseFromString(trimmed, "text/xml")

      // Check for parse errors
      const parseError = doc.querySelector("parsererror")
      if (parseError) {
        return null
      }

      // Try to extract Message first (most specific)
      const message = doc.querySelector("Message")?.textContent?.trim()
      if (message && !GENERIC_ERROR_MESSAGES.has(message.toLowerCase())) {
        return message
      }

      // Fall back to Code
      const code = doc.querySelector("Code")?.textContent?.trim()
      if (code && !GENERIC_ERROR_MESSAGES.has(code.toLowerCase())) {
        return code
      }

      // Fall back to Error element text
      const error = doc.querySelector("Error")?.textContent?.trim()
      if (error && !GENERIC_ERROR_MESSAGES.has(error.toLowerCase())) {
        return error
      }

      return message || code || error || null
    } catch {
      // Fall through to regex-based parsing
    }
  }

  // Fallback: regex-based parsing (for SSR or if DOMParser fails)
  const message = getXmlTagText(trimmed, "Message")
  if (isSpecificErrorText(message)) {
    return message
  }

  const code = getXmlTagText(trimmed, "Code")
  if (isSpecificErrorText(code)) {
    return code
  }

  const error = getXmlTagText(trimmed, "Error")
  if (isSpecificErrorText(error)) {
    return error
  }

  return message ?? code ?? error
}

export const getServiceErrorMessage = (error: unknown): string | null => {
  if (error instanceof Error) {
    const directMessage = normalizeErrorText(error.message)
    if (isSpecificErrorText(directMessage)) {
      return directMessage
    }
  }

  if (!error || typeof error !== "object") {
    return null
  }

  const err = error as {
    Code?: unknown
    Message?: unknown
    name?: unknown
    message?: unknown
    Error?: {
      Code?: unknown
      Message?: unknown
      message?: unknown
    }
  }

  const codeCandidates = [
    normalizeErrorText(err.Code),
    normalizeErrorText(err.Error?.Code),
    normalizeErrorText(err.name),
  ]
  const messageCandidates = [
    normalizeErrorText(err.Message),
    normalizeErrorText(err.Error?.Message),
    normalizeErrorText(err.Error?.message),
    normalizeErrorText(err.message),
  ]

  for (const candidate of codeCandidates) {
    if (isSpecificErrorText(candidate)) {
      return candidate
    }
  }

  for (const candidate of messageCandidates) {
    if (isSpecificErrorText(candidate)) {
      return candidate
    }
  }

  return codeCandidates.find(Boolean) ?? messageCandidates.find(Boolean) ?? null
}

export class ConfigLoadError extends Error {
  code: "INVALID_URL" | "STORAGE_ERROR" | "NETWORK_ERROR" | "UNKNOWN_ERROR"
  originalError?: Error

  constructor(message: string, code: ConfigLoadError["code"] = "UNKNOWN_ERROR", originalError?: Error) {
    super(message)
    this.name = "ConfigLoadError"
    this.code = code
    this.originalError = originalError
  }
}

export const parseApiError = async (response: Response): Promise<string> => {
  try {
    const errorData = await response.clone().json()
    return (errorData as { message?: string }).message || JSON.stringify(errorData) || response.statusText
  } catch {
    try {
      const text = await response.clone().text()
      if (text) {
        if (text.trim().startsWith("<")) {
          return getXmlErrorMessage(text) ?? text
        }
        return text
      }
    } catch {
      // keep statusText
    }
  }
  return response.statusText
}

export const handleConfigError = (error: unknown, context: string): ConfigLoadError => {
  if (error instanceof ConfigLoadError) {
    return error
  }

  if (error instanceof Error) {
    if (error.message.includes("Invalid URL")) {
      return new ConfigLoadError(`Invalid URL in ${context}: ${error.message}`, "INVALID_URL", error)
    }
    if (error.message.includes("localStorage") || error.message.includes("storage")) {
      return new ConfigLoadError(`Storage error in ${context}: ${error.message}`, "STORAGE_ERROR", error)
    }
    if (error.message.includes("fetch") || error.message.includes("network")) {
      return new ConfigLoadError(`Network error in ${context}: ${error.message}`, "NETWORK_ERROR", error)
    }
  }

  return new ConfigLoadError(
    `Unknown error in ${context}: ${error}`,
    "UNKNOWN_ERROR",
    error instanceof Error ? error : new Error(String(error)),
  )
}
