// Which update to offer, from the forge's release index (DESIGN-android.md §6).
// Pure, no React Native: tested under Bare (test/update-pick.test.js).
const TAG = /^android-v(\d+\.\d+\.\d+)$/
const SEMVER = /^(\d+)\.(\d+)\.(\d+)$/
const APK = 'dist/cthuloid-lights.apk'
const SHA = APK + '.sha256'
export function compareSemver (a, b) {
const x = SEMVER.exec(a)
const y = SEMVER.exec(b)
for (let i = 1; i <= 3; i++) {
const d = Number(x[i]) - Number(y[i])
if (d !== 0) return d
}
return 0
}
// files: release-index rows { repo, version, path }; path is
// '<repo>/versions/<version>/<file>', served at <base>/releases/<repo>/<version>/<file>.
// Returns { version, apkUrl, shaUrl } for the newest COMPLETE release newer than
// `installed`, else null. Complete = both the APK and its .sha256 are listed: the
// forge backends replicate file by file, and a half-copied release must not be offered.
export function pickUpdate (files, installed, base) {
if (typeof installed !== 'string' || !SEMVER.test(installed)) return null
const seen = new Map() // semver -> Set of files
for (const f of files) {
if (!f || f.repo !== 'cthuloid') continue
const m = TAG.exec(f.version)
if (!m) continue
const prefix = 'cthuloid/versions/' + f.version + '/'
if (typeof f.path !== 'string' || !f.path.startsWith(prefix)) continue
if (!seen.has(m[1])) seen.set(m[1], new Set())
seen.get(m[1]).add(f.path.slice(prefix.length))
}
let best = null
for (const [v, have] of seen) {
if (!have.has(APK) || !have.has(SHA)) continue
if (compareSemver(v, installed) <= 0) continue
if (best === null || compareSemver(v, best) > 0) best = v
}
if (best === null) return null
const dir = base + '/releases/cthuloid/android-v' + best + '/'
return { version: best, apkUrl: dir + APK, shaUrl: dir + SHA }
}
// The .sha256 file is `sha256sum` output: '<hex> <name>\n'.
export function parseSha256 (text) {
const m = /^([0-9a-fA-F]{64})(\s|$)/.exec(String(text || '').trim())
return m ? m[1].toLowerCase() : null
}