import axios from 'axios'

let lastToastKey = ''
let lastToastAt = 0

const RETRYABLE_STATUS = new Set([408, 425, 429, 500, 502, 503, 504])
const MAX_RETRY_COUNT = 1
const RETRY_BASE_DELAY_MS = 300
const TOAST_STYLE_ID = 'zsglpt-lite-toast-style'

function ensureToastStyle() {
  if (typeof document === 'undefined') return
  if (document.getElementById(TOAST_STYLE_ID)) return
  const style = document.createElement('style')
  style.id = TOAST_STYLE_ID
  style.textContent = `
    .zsglpt-lite-toast-wrap {
      position: fixed;
      right: 16px;
      top: 16px;
      z-index: 9999;
      display: flex;
      flex-direction: column;
      gap: 8px;
      pointer-events: none;
    }
    .zsglpt-lite-toast {
      max-width: min(88vw, 420px);
      padding: 10px 12px;
      border-radius: 10px;
      color: #fff;
      font-size: 13px;
      font-weight: 600;
      box-shadow: 0 8px 20px rgba(15, 23, 42, 0.24);
      opacity: 0;
      transform: translateY(-6px);
      transition: all .18s ease;
    }
    .zsglpt-lite-toast.is-visible {
      opacity: 1;
      transform: translateY(0);
    }
    .zsglpt-lite-toast.is-error {
      background: linear-gradient(135deg, #ef4444, #dc2626);
    }
  `
  document.head.appendChild(style)
}

function ensureToastWrap() {
  if (typeof document === 'undefined') return null
  ensureToastStyle()
  let wrap = document.querySelector('.zsglpt-lite-toast-wrap')
  if (wrap) return wrap
  wrap = document.createElement('div')
  wrap.className = 'zsglpt-lite-toast-wrap'
  document.body.appendChild(wrap)
  return wrap
}

function showLiteToast(message) {
  const wrap = ensureToastWrap()
  if (!wrap) return
  const node = document.createElement('div')
  node.className = 'zsglpt-lite-toast is-error'
  node.textContent = String(message || '请求失败')
  wrap.appendChild(node)
  requestAnimationFrame(() => node.classList.add('is-visible'))
  window.setTimeout(() => node.classList.remove('is-visible'), 2300)
  window.setTimeout(() => node.remove(), 2600)
}

function toastErrorOnce(key, message, minIntervalMs = 1500) {
  const now = Date.now()
  if (key === lastToastKey && now - lastToastAt < minIntervalMs) return
  lastToastKey = key
  lastToastAt = now
  showLiteToast(message)
}

function getCookie(name) {
  const escaped = String(name || '').replace(/([.*+?^${}()|[\]\\])/g, '\\$1')
  const match = document.cookie.match(new RegExp(`(?:^|; )${escaped}=([^;]*)`))
  return match ? decodeURIComponent(match[1]) : ''
}

function isIdempotentMethod(method) {
  return ['GET', 'HEAD', 'OPTIONS'].includes(String(method || 'GET').toUpperCase())
}

function shouldRetryRequest(error) {
  const config = error?.config
  if (!config || config.__no_retry) return false
  if (!isIdempotentMethod(config.method)) return false

  const retried = Number(config.__retry_count || 0)
  if (retried >= MAX_RETRY_COUNT) return false

  const code = String(error?.code || '')
  if (code === 'ECONNABORTED' || code === 'ERR_NETWORK') return true

  const status = Number(error?.response?.status || 0)
  return RETRYABLE_STATUS.has(status)
}

function delay(ms) {
  return new Promise((resolve) => {
    window.setTimeout(resolve, Math.max(0, Number(ms || 0)))
  })
}

async function retryRequestOnce(error, client) {
  const config = error?.config || {}
  const retried = Number(config.__retry_count || 0)
  config.__retry_count = retried + 1

  const backoffMs = RETRY_BASE_DELAY_MS * (retried + 1)
  await delay(backoffMs)
  return client.request(config)
}

export const publicApi = axios.create({
  baseURL: '/api',
  timeout: 30_000,
  withCredentials: true,
})

publicApi.interceptors.request.use((config) => {
  const method = String(config?.method || 'GET').toUpperCase()
  if (!['GET', 'HEAD', 'OPTIONS'].includes(method)) {
    const token = getCookie('csrf_token')
    if (token) {
      config.headers = config.headers || {}
      config.headers['X-CSRF-Token'] = token
    }
  }
  return config
})

publicApi.interceptors.response.use(
  (response) => response,
  (error) => {
    if (shouldRetryRequest(error)) {
      return retryRequestOnce(error, publicApi)
    }

    const status = error?.response?.status
    const payload = error?.response?.data
    const message = payload?.error || payload?.message || error?.message || '请求失败'

    if (status === 401) {
      const pathname = window.location?.pathname || ''
      // 登录页面不弹通知，让 LoginPage.vue 自己处理错误显示
      if (!pathname.startsWith('/login')) {
        toastErrorOnce('401', message || '登录已过期，请重新登录', 3000)
        window.location.href = '/login'
      }
    } else if (status === 403) {
      toastErrorOnce('403', message || '无权限', 5000)
    } else if (error?.code === 'ECONNABORTED') {
      toastErrorOnce('timeout', '请求超时', 3000)
    } else if (!status) {
      toastErrorOnce(`net:${message}`, message, 3000)
    }

    return Promise.reject(error)
  },
)
