const { sessionStorage, navigator, document, location, screen, fetch } = globalThis

let ENDPOINT = '/t'
const SK_UID = '_u'
const SK_HASH = '_h'

let encKey = null

async function importKey(b64) {
    const raw = Uint8Array.from(globalThis.atob(b64), c => c.codePointAt(0))
    return globalThis.crypto.subtle.importKey('raw', raw, 'AES-GCM', false, ['encrypt', 'decrypt'])
}

async function encrypt(data) {
    if (!encKey) return null
    const json = new globalThis.TextEncoder().encode(JSON.stringify(data))
    const iv = globalThis.crypto.getRandomValues(new Uint8Array(12))
    const encrypted = await globalThis.crypto.subtle.encrypt({ name: 'AES-GCM', iv, tagLength: 128 }, encKey, json)
    const buf = new Uint8Array(encrypted)
    const combined = new Uint8Array(12 + buf.length)
    combined.set(iv)
    combined.set(buf, 12)
    return globalThis.btoa(String.fromCodePoint(...combined))
}

async function decrypt(b64) {
    if (!encKey) return null
    const raw = Uint8Array.from(globalThis.atob(b64), c => c.codePointAt(0))
    const iv = raw.slice(0, 12)
    const data = raw.slice(12)
    try {
        const decrypted = await globalThis.crypto.subtle.decrypt({ name: 'AES-GCM', iv, tagLength: 128 }, encKey, data)
        return JSON.parse(new globalThis.TextDecoder().decode(decrypted))
    } catch {
        return null
    }
}

function clearSession() {
    sessionStorage.removeItem(SK_UID)
    sessionStorage.removeItem(SK_HASH)
}

async function send(data, expectResponse = false) {
    const d = await encrypt(data)
    if (!d) return null
    try {
        if (!expectResponse && navigator.sendBeacon) {
            navigator.sendBeacon(ENDPOINT, JSON.stringify({ d }))
            return null
        }
        const res = await fetch(ENDPOINT, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ d }),
            keepalive: true,
        })
        if (res.status === 403) {
            clearSession()
            return null
        }
        if (!res.ok || res.status === 204) return null
        const json = await res.json()
        return json.d ? await decrypt(json.d) : null
    } catch {
        return null
    }
}

async function getOrCreateVisitor() {
    const uid = sessionStorage.getItem(SK_UID)
    const hash = sessionStorage.getItem(SK_HASH)
    if (uid && hash) return { uid, hash }

    const resp = await send({
        sw: screen.width,
        sh: screen.height,
        l: navigator.language || null,
    }, true)

    if (!resp?.uid || !resp?.h) return null

    sessionStorage.setItem(SK_UID, resp.uid)
    sessionStorage.setItem(SK_HASH, resp.h)
    return { uid: resp.uid, hash: resp.h }
}

async function trackPageView(visitor) {
    await send({
        uid: visitor.uid,
        h: visitor.hash,
        u: location.pathname + location.search,
        t: document.title,
        r: document.referrer || null,
    })
}

export async function initAnalytics() {
    const keyB64 = document.body.dataset.k
    const ep = document.body.dataset.e
    if (!keyB64 || !ep) return
    ENDPOINT = ep

    try {
        encKey = await importKey(keyB64)
    } catch {
        return
    }

    let visitor = await getOrCreateVisitor()
    if (!visitor) return

    await trackPageView(visitor)

    // If trackPageView got 403 (stale session), retry with fresh visitor
    if (!sessionStorage.getItem(SK_UID)) {
        visitor = await getOrCreateVisitor()
        if (!visitor) return
        await trackPageView(visitor)
    }

    const authUserId = document.body.dataset.uid
    if (authUserId) {
        await setAuth(Number.parseInt(authUserId, 10))
    }
}

export async function setAuth(userId) {
    const uid = sessionStorage.getItem(SK_UID)
    const hash = sessionStorage.getItem(SK_HASH)
    if (!uid || !hash || !encKey) return

    await send({ uid, h: hash, setUser: userId })
}
