import { isLike, ok } from "../../is.js";
import { createHash } from "node:crypto";
import { index } from "../../content-index/index.js";
import { DurableSyncManager } from "../../sync/index.js";
import { addEventListener } from "../../events/schedule/schedule.js";
import { dispatchEvent } from "../../events/index.js";
import { DurablePeriodicSyncManager } from "../../periodic-sync/index.js";
import { getInternalStorageBucket } from "../../storage-buckets/internal.js";
import { getOrigin } from "../../listen/index.js";
import { createServiceWorkerFetch } from "./execute-fetch.js";
import { SERVICE_WORKER_ID, SERVICE_WORKER_URL } from "../../config/index.js";
const STORE_NAME = "serviceWorker";
function getServiceWorkerRegistrationStore(internalBucket = getInternalStorageBucket()) {
    return internalBucket.getKeyValueStore(STORE_NAME, {
        counter: false
    });
}
export function listServiceWorkerIds(internalBucket = getInternalStorageBucket()) {
    const store = getServiceWorkerRegistrationStore(internalBucket);
    return store.keys();
}
export async function listServiceWorkers(internalBucket = getInternalStorageBucket()) {
    const store = getServiceWorkerRegistrationStore(internalBucket);
    const values = await store.values();
    return values.map(value => new DurableServiceWorkerRegistration(value, {
        internalBucket
    }));
}
export async function getServiceWorkerRegistrationState(internalBucket, serviceWorkerId) {
    const store = getServiceWorkerRegistrationStore(internalBucket);
    const existing = await store.get(serviceWorkerId);
    ok(existing, "Expected to find registered serviceWorkerId");
    return existing.registrationState;
}
export async function setServiceWorkerRegistrationState(internalBucket, serviceWorkerId, registrationState) {
    const store = getServiceWorkerRegistrationStore(internalBucket);
    const existing = await store.get(serviceWorkerId);
    ok(existing, "Expected to find registered serviceWorkerId");
    const next = {
        ...existing,
        registrationState,
        registrationStateAt: new Date().toISOString()
    };
    await store.set(serviceWorkerId, next);
    return next;
}
export async function deregisterServiceWorker(internalBucket, serviceWorkerId) {
    const store = getServiceWorkerRegistrationStore(internalBucket);
    await store.delete(serviceWorkerId);
}
export async function getDurableServiceWorkerRegistrationData(internalBucket, serviceWorkerId, options) {
    const store = getServiceWorkerRegistrationStore(internalBucket);
    const registration = await store.get(serviceWorkerId);
    ok(registration, "Service worker not registered");
    return registration;
}
export async function getDurableServiceWorkerRegistration(internalBucket, serviceWorkerId, options) {
    const registration = await getDurableServiceWorkerRegistrationData(internalBucket, serviceWorkerId);
    return new DurableServiceWorkerRegistration(registration, options);
}
const SERVICE_WORKER_STATES = [
    "parsed",
    "installing",
    "installed",
    "activating",
    "activated",
    "redundant"
];
function isServiceWorkerState(state) {
    return SERVICE_WORKER_STATES.includes(state);
}
function getServiceWorkerState(state) {
    if (isServiceWorkerState(state)) {
        return state;
    }
    return "parsed";
}
export class DurableServiceWorker {
    data;
    scriptURL;
    state;
    constructor(data) {
        this.data = data;
        this.scriptURL = data.url;
        this.state = getServiceWorkerState(data.registrationState);
    }
    async postMessage(message, ...args) {
        // TODO :)
    }
}
const DURABLE_SERVICE_WORKER_REGISTRATION_UPDATE = "serviceWorker:registration:update";
function isDurableServiceWorkerUpdateEvent(event) {
    return !!(isLike(event) &&
        event.type === DURABLE_SERVICE_WORKER_REGISTRATION_UPDATE &&
        event.update?.serviceWorkerId);
}
export function dispatchDurableServiceWorkerRegistrationUpdate(update, data) {
    const event = {
        virtual: true,
        ...data,
        type: DURABLE_SERVICE_WORKER_REGISTRATION_UPDATE,
        update,
    };
    return dispatchEvent(event);
}
export class DurableServiceWorkerRegistration {
    active;
    installing;
    waiting;
    index = index;
    sync;
    periodicSync;
    fetch;
    durable;
    isCurrentGlobalScope;
    unregisterListener;
    internalBucket;
    constructor(data, { isCurrentGlobalScope, internalBucket = getInternalStorageBucket() } = {}) {
        this.isCurrentGlobalScope = !!isCurrentGlobalScope;
        this.#onDurableData(data);
        if (this.isCurrentGlobalScope) {
            this.unregisterListener = addEventListener(DURABLE_SERVICE_WORKER_REGISTRATION_UPDATE, this.#onDurableDataEvent);
        }
        this.internalBucket = internalBucket;
        this.sync = new DurableSyncManager();
        this.periodicSync = new DurablePeriodicSyncManager();
        this.fetch = createServiceWorkerFetch(this);
    }
    #onDurableDataEvent = (event) => {
        ok(isDurableServiceWorkerUpdateEvent(event));
        this.#onDurableData(event.update);
    };
    #onDurableData = (data) => {
        this.durable = data;
        this.waiting = undefined;
        this.active = undefined;
        this.installing = undefined;
        if (data.registrationState === "activating" ||
            data.registrationState === "activated") {
            this.active = new DurableServiceWorker(data);
        }
        else if (data.registrationState === "installed") {
            this.waiting = new DurableServiceWorker(data);
        }
        else /* if (data.registrationState === "pending" || data.registrationState === "installing") */ {
            this.installing = new DurableServiceWorker(data);
        }
    };
    async getNotifications() {
        return [];
    }
    async showNotification(title, options) {
        // TODO
    }
    async unregister() {
        this.unregisterListener?.();
        this.unregisterListener = undefined;
        await deregisterServiceWorker(this.internalBucket, this.durable.serviceWorkerId);
    }
    async update() {
        // TODO this shouldn't be the thing updating this state...
        // Just happens to be a good place for it, but we will also be changing this _after_ we do the normal update
        // operation if it can be done
        const data = await getDurableServiceWorkerRegistrationData(this.internalBucket, this.durable.serviceWorkerId);
        this.#onDurableData(data);
    }
}
function getServiceWorkerUrl() {
    const { SERVICE_WORKER_URL } = process.env;
    return SERVICE_WORKER_URL || `file://${process.cwd()}/`;
}
function getServiceWorkerId(url) {
    const { SERVICE_WORKER_PARTITION } = process.env;
    const serviceWorkerIdHash = createHash("sha512");
    if (SERVICE_WORKER_PARTITION) {
        serviceWorkerIdHash.update(SERVICE_WORKER_PARTITION);
    }
    serviceWorkerIdHash.update(url);
    return serviceWorkerIdHash.digest().toString("hex");
}
export class DurableServiceWorkerContainer {
    internalBucket;
    constructor({ internalBucket = getInternalStorageBucket() } = {}) {
        this.internalBucket = internalBucket;
    }
    async register(url, options) {
        const instance = new URL(url, getServiceWorkerUrl());
        ok(instance.protocol === "file:" || instance.protocol === "data:", "Only file and data service workers supported at this time");
        const store = getServiceWorkerRegistrationStore(this.internalBucket);
        const envId = url === SERVICE_WORKER_URL ? SERVICE_WORKER_ID : undefined;
        const serviceWorkerId = envId ?? getServiceWorkerId(instance.toString());
        const existing = await store.get(serviceWorkerId);
        if (existing) {
            const instance = new DurableServiceWorkerRegistration(existing);
            if (existing.registrationState === "activating") {
                // TODO
            }
            return instance;
        }
        const workerOrigin = instance.protocol === "file:" ?
            getOrigin() :
            !instance.origin || instance.origin === "null" ?
                getOrigin() :
                instance.origin;
        const origin = options?.scope ?
            new URL(options.scope, workerOrigin).origin :
            workerOrigin;
        const baseURL = options?.scope ?
            new URL(options.scope, origin).toString() :
            new URL(url, origin).toString();
        const registeredAt = new Date().toISOString();
        const registration = {
            serviceWorkerId,
            createdAt: existing?.createdAt || registeredAt,
            registeredAt,
            registrationState: "pending",
            registrationStateAt: registeredAt,
            initialUrl: url.toString(),
            url: instance.toString(),
            origin,
            baseURL,
            options
        };
        await store.set(serviceWorkerId, registration);
        return new DurableServiceWorkerRegistration(registration);
    }
    async getRegistration(clientUrl) {
        ok(clientUrl, "Default client url not supported, please provide a client url to get");
        const instance = new URL(clientUrl, getServiceWorkerUrl());
        const serviceWorkerId = getServiceWorkerId(instance.toString());
        return getDurableServiceWorkerRegistration(this.internalBucket, serviceWorkerId);
    }
    async getRegistrations() {
        const store = getServiceWorkerRegistrationStore(this.internalBucket);
        const serviceWorkerIds = await store.keys();
        return await Promise.all(serviceWorkerIds.map(serviceWorkerId => getDurableServiceWorkerRegistration(this.internalBucket, serviceWorkerId)));
    }
    [Symbol.asyncIterator]() {
        const store = getServiceWorkerRegistrationStore(this.internalBucket);
        return store[Symbol.asyncIterator]();
    }
}
export const serviceWorker = new DurableServiceWorkerContainer();
//# sourceMappingURL=container.js.map