import { apply } from "./apply.js";
import { DestroyStrategy } from "./destroy.js";
import { Scope as _Scope } from "./scope.js";
import { createAndSendEvent } from "./util/telemetry.js";
export const PROVIDERS = (globalThis.ALCHEMY_PROVIDERS ??= new Map());
const HANDLERS = (globalThis.ALCHEMY_HANDLERS ??= new Map());
const DYNAMIC_RESOURCE_RESOLVERS = (globalThis.ALCHEMY_DYNAMIC_RESOURCE_RESOLVERS ??= []);
/**
 * Register a function that will be called if a Resource Type cannot be found during deletion.
 */
export function registerDynamicResource(handler) {
    DYNAMIC_RESOURCE_RESOLVERS.push(handler);
}
export function resolveDeletionHandler(typeName) {
    const provider = PROVIDERS.get(typeName);
    if (provider) {
        return provider;
    }
    for (const handler of DYNAMIC_RESOURCE_RESOLVERS) {
        const result = handler(typeName);
        if (result) {
            return result;
        }
    }
    return undefined;
}
export const ResourceID = Symbol.for("alchemy::ResourceID");
export const ResourceFQN = Symbol.for("alchemy::ResourceFQN");
export const ResourceKind = Symbol.for("alchemy::ResourceKind");
export const ResourceScope = Symbol.for("alchemy::ResourceScope");
export const ResourceSeq = Symbol.for("alchemy::ResourceSeq");
export function Resource(type, ...args) {
    const [options, handler] = args.length === 2 ? args : [undefined, args[0]];
    if (PROVIDERS.has(type)) {
        // We want Alchemy to work in a PNPM monorepo environment unfortunately,
        // global registries do not work because PNPM's symlinks result in multiple
        // instances of alchemy being loaded even if the package version is the
        // same (peers do not fix this).
        // MITIGATION: to ensure that most cases of accidental double-registration
        // are caught, we're going to check the handler's toString() to see if it's
        // the same as the previous handler. if it is, we're going to overwrite it.
        if (HANDLERS.get(type).toString() !== handler.toString()) {
            throw new Error(`Resource ${type} already exists`);
        }
    }
    HANDLERS.set(type, handler);
    const provider = (async (resourceID, props) => {
        const scope = _Scope.current;
        if (resourceID.includes(":")) {
            // we want to use : as an internal separator for resources
            throw new Error(`ID cannot include colons: ${resourceID}`);
        }
        if (scope.resources.has(resourceID)) {
            // TODO(sam): do we want to throw?
            // it's kind of awesome that you can re-create a resource and call apply
            const otherResource = scope.resources.get(resourceID);
            if (otherResource?.[ResourceKind] !== type) {
                scope.fail();
                const error = new Error(`Resource ${resourceID} already exists in the stack and is of a different type: '${otherResource?.[ResourceKind]}' !== '${type}'`);
                await createAndSendEvent({
                    event: "resource.error",
                    resource: type,
                    phase: scope.phase,
                    status: "unknown",
                    duration: 0,
                    replaced: false,
                }, error);
                throw error;
            }
        }
        // get a sequence number (unique within the scope) for the resource
        const seq = scope.seq();
        const meta = {
            [ResourceKind]: type,
            [ResourceID]: resourceID,
            [ResourceFQN]: scope.fqn(resourceID),
            [ResourceSeq]: seq,
            [ResourceScope]: scope,
            [DestroyStrategy]: options?.destroyStrategy ?? "sequential",
        };
        const promise = apply(meta, props, options);
        const resource = Object.assign(promise, meta);
        scope.resources.set(resourceID, resource);
        return resource;
    });
    provider.type = type;
    provider.handler = handler;
    provider.options = options;
    PROVIDERS.set(type, provider);
    return provider;
}
//# sourceMappingURL=resource.js.map