export type AddonPermission = 'geometry.read' | 'geometry.write' | 'project.read' | 'project.write' | 'network' | 'filesystem' | 'script.execute'
export type AddonManifest = { id: string; version: string; name: string; entrypoint: string; permissions: AddonPermission[]; dependencies: string[] }
export type AddonPackage = { manifest: AddonManifest; payload: string; digest: string; signer: string; signature: string }
export type AddonVerification = { valid: boolean; digest: string; signatureValid: boolean; permissionsValid: boolean; dependenciesValid: boolean; reason?: string }
export type InstalledAddon = { manifest: AddonManifest; digest: string; signer: string; state: 'installed' | 'disabled'; revision: number }
export type AddonSnapshot = { catalog: InstalledAddon[]; history: Record<string, InstalledAddon[]>; version: number }
export type AddonManagerApi = { verify(pkg: AddonPackage): AddonVerification; install(pkg: AddonPackage): InstalledAddon; update(pkg: AddonPackage): InstalledAddon; remove(id: string): void; rollback(id: string): InstalledAddon; snapshot(): AddonSnapshot; exportManifest(): string }
export type AddonManagerOptions = { trustedKeys: Record<string, string>; allowedPermissions?: AddonPermission[] }

const allowedByDefault: AddonPermission[] = ['geometry.read', 'project.read']
const clone = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T
const digestText = (value: string) => { let hash = 2166136261; for (let index = 0; index < value.length; index += 1) hash = Math.imul(hash ^ value.charCodeAt(index), 16777619); return (hash >>> 0).toString(16).padStart(8, '0') }
const canonicalManifest = (manifest: AddonManifest) => JSON.stringify({ ...manifest, permissions: [...manifest.permissions].sort(), dependencies: [...manifest.dependencies].sort() })
const canonicalContent = (manifest: AddonManifest, payload: string) => `${canonicalManifest(manifest)}\n${payload}`

export const createSignedAddonPackage = (manifest: AddonManifest, payload: string, signer: string, secret: string): AddonPackage => {
  const digest = digestText(canonicalContent(manifest, payload))
  return { manifest: clone(manifest), payload, digest, signer, signature: digestText(`${signer}:${secret}:${digest}`) }
}

export const createAddonManager = (options: AddonManagerOptions): AddonManagerApi => {
  const allowed = new Set(options.allowedPermissions ?? allowedByDefault)
  const installed = new Map<string, InstalledAddon>(); const history = new Map<string, InstalledAddon[]>(); let version = 0
  const verify = (pkg: AddonPackage): AddonVerification => {
    const digest = digestText(canonicalContent(pkg.manifest, pkg.payload)); const digestValid = digest === pkg.digest
    const secret = options.trustedKeys[pkg.signer]; const signatureValid = Boolean(secret && digestText(`${pkg.signer}:${secret}:${pkg.digest}`) === pkg.signature)
    const permissionsValid = pkg.manifest.permissions.every((permission) => allowed.has(permission))
    const dependenciesValid = pkg.manifest.dependencies.every((dependency) => installed.has(dependency))
    const reason = !digestValid ? 'package digest mismatch' : !signatureValid ? 'untrusted or invalid signature' : !permissionsValid ? 'permission exceeds policy' : !dependenciesValid ? 'dependency is not installed' : undefined
    return { valid: !reason, digest, signatureValid, permissionsValid, dependenciesValid, ...(reason ? { reason } : {}) }
  }
  const requireValid = (pkg: AddonPackage) => { const result = verify(pkg); if (!result.valid) throw new Error(`Addon ${pkg.manifest.id} rejected: ${result.reason}`); if (!pkg.manifest.id.trim() || !pkg.manifest.version.trim() || !pkg.manifest.entrypoint.trim()) throw new RangeError('Addon manifest identity and entrypoint are required.'); return result }
  const install = (pkg: AddonPackage) => { requireValid(pkg); if (installed.has(pkg.manifest.id)) throw new Error(`Addon ${pkg.manifest.id} is already installed.`); const record: InstalledAddon = { manifest: clone(pkg.manifest), digest: pkg.digest, signer: pkg.signer, state: 'installed', revision: 1 }; installed.set(record.manifest.id, record); history.set(record.manifest.id, [clone(record)]); version += 1; return clone(record) }
  const update = (pkg: AddonPackage) => { requireValid(pkg); const previous = installed.get(pkg.manifest.id); if (!previous) throw new Error(`Addon ${pkg.manifest.id} is not installed.`); if (previous.digest === pkg.digest) throw new Error(`Addon ${pkg.manifest.id} has no content change.`); const record: InstalledAddon = { manifest: clone(pkg.manifest), digest: pkg.digest, signer: pkg.signer, state: 'installed', revision: previous.revision + 1 }; installed.set(record.manifest.id, record); history.get(record.manifest.id)!.push(clone(record)); version += 1; return clone(record) }
  const remove = (id: string) => { if (!installed.delete(id)) throw new Error(`Addon ${id} is not installed.`); version += 1 }
  const rollback = (id: string) => { const entries = history.get(id) ?? []; if (entries.length < 2) throw new Error(`Addon ${id} has no previous revision.`); entries.pop(); const record = clone(entries[entries.length - 1]); installed.set(id, record); version += 1; return record }
  const snapshot = (): AddonSnapshot => ({ catalog: [...installed.values()].map(clone), history: Object.fromEntries([...history.entries()].map(([id, entries]) => [id, entries.map(clone)])), version })
  return { verify, install, update, remove, rollback, snapshot, exportManifest: () => `${JSON.stringify(snapshot(), null, 2)}\n` }
}
