const BRIDGE_CONFIG = window.__COCO_BRIDGE__ || {}
const DEFAULT_BASE_URL = BRIDGE_CONFIG.baseUrl || 'http://127.0.0.1:8765'
const STORAGE_KEY = 'coco.bridge.baseUrl'
const TOKEN_HEADER = 'X-CoCo-Bridge-Token'

function readStoredBaseUrl() {
    if (BRIDGE_CONFIG.baseUrl) return BRIDGE_CONFIG.baseUrl
    try {
        return localStorage.getItem(STORAGE_KEY) || DEFAULT_BASE_URL
    } catch {
        return DEFAULT_BASE_URL
    }
}

function storeBaseUrl(baseUrl) {
    try {
        localStorage.setItem(STORAGE_KEY, baseUrl)
    } catch {}
}

function normalizeBaseUrl(baseUrl) {
    return String(baseUrl || DEFAULT_BASE_URL).replace(/\/+$/, '')
}

function normalizePath(path) {
    return String(path || '').startsWith('/') ? path : `/${path}`
}

function readBridgeToken() {
    return String(BRIDGE_CONFIG.token || '')
}

function isBinaryBody(body) {
    return (
        typeof Blob !== 'undefined' && body instanceof Blob ||
        typeof FormData !== 'undefined' && body instanceof FormData ||
        typeof ArrayBuffer !== 'undefined' && body instanceof ArrayBuffer
    )
}

function createBridgeError(message, { status = 0, data = null } = {}) {
    const error = new Error(message)
    error.name = 'cocoBridgeError'
    error.status = status
    error.data = data
    return error
}

export class cocoBridge {
    constructor({ baseUrl = readStoredBaseUrl(), token = readBridgeToken() } = {}) {
        this.baseUrl = normalizeBaseUrl(baseUrl)
        this.token = String(token || '')
    }

    setBaseUrl(baseUrl) {
        this.baseUrl = normalizeBaseUrl(baseUrl)
        storeBaseUrl(this.baseUrl)
    }

    setToken(token) {
        this.token = String(token || '')
    }

    url(path) {
        return `${this.baseUrl}${normalizePath(path)}`
    }

    headers(headers = {}) {
        const next = { ...headers }
        if (this.token && !next[TOKEN_HEADER]) {
            next[TOKEN_HEADER] = this.token
        }
        return next
    }

    async request(path, { method = 'GET', body, headers = {}, expect = 'json', signal } = {}) {
        const init = {
            method,
            headers: this.headers(headers),
            signal,
        }

        if (body !== undefined) {
            if (isBinaryBody(body)) {
                init.body = body
            } else {
                init.headers['Content-Type'] ??= 'application/json'
                init.body = JSON.stringify(body)
            }
        }

        let response
        try {
            response = await fetch(this.url(path), init)
        } catch (error) {
            if (error?.name === 'AbortError') throw error
            throw createBridgeError(`无法连接 coco Bridge: ${error.message}`)
        }

        const data = await this.readResponse(response, expect)
        if (!response.ok || data?.ok === false) {
            throw createBridgeError(data?.error || `请求失败: ${response.status}`, {
                status: response.status,
                data,
            })
        }

        return data
    }

    async readResponse(response, expect) {
        if (expect === 'blob') return response.blob()
        if (expect === 'text') return response.text()

        const text = await response.text()
        if (!text) return null

        try {
            return JSON.parse(text)
        } catch {
            return text
        }
    }

    get(path, options = {}) {
        return this.request(path, { ...options, method: 'GET' })
    }

    post(path, body, options = {}) {
        return this.request(path, { ...options, method: 'POST', body })
    }

    remove(path, options = {}) {
        return this.request(path, { ...options, method: 'DELETE' })
    }

    upload(path, blob, { contentType = blob?.type || 'application/octet-stream', ...options } = {}) {
        return this.request(path, {
            ...options,
            method: options.method || 'POST',
            body: blob,
            headers: {
                'Content-Type': contentType,
                ...(options.headers || {}),
            },
        })
    }
}

export const bridge = new cocoBridge()
export default bridge
