import { alchemy } from "../alchemy.js";
import { Resource } from "../resource.js";
import { createNeonApi } from "./api.js";
import { formatConnectionUri, formatRole, waitForOperations, } from "./utils.js";
export const NeonBranch = Resource("neon::Branch", async function (id, props) {
    const api = createNeonApi(props);
    const name = props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
    const projectId = typeof props.project === "string" ? props.project : props.project.id;
    const parentBranchId = typeof props.parentBranch === "string"
        ? props.parentBranch
        : props.parentBranch?.id;
    switch (this.phase) {
        case "delete": {
            if (this.output?.id) {
                const res = await api.deleteProjectBranch({
                    path: {
                        project_id: this.output.projectId,
                        branch_id: this.output.id,
                    },
                    throwOnError: false,
                });
                if (res.error && res.response.status !== 404) {
                    throw new Error(`Failed to delete branch: ${res.error.message}`, {
                        cause: res.error,
                    });
                }
            }
            return this.destroy();
        }
        case "create": {
            if (this.scope.adopt || props.adopt) {
                try {
                    return await fetchBranch(api, { ...props, projectId, name });
                }
                catch (error) {
                    if (!(error instanceof NeonBranchNotFound)) {
                        throw error;
                    }
                    // branch not found, continue with creation
                }
            }
            const { data } = await api.createProjectBranch({
                path: {
                    project_id: projectId,
                },
                body: {
                    branch: {
                        name,
                        protected: props.protected,
                        parent_id: parentBranchId,
                        parent_lsn: props.parentLsn,
                        parent_timestamp: props.parentTimestamp,
                        expires_at: props.expiresAt,
                        init_source: props.initSource,
                    },
                    endpoints: props.endpoints,
                },
            });
            // Endpoints have fields that are updated after operations are complete.
            await waitForOperations(api, data.operations);
            const endpoints = await Promise.all(data.endpoints.map((endpoint) => api
                .getProjectEndpoint({
                path: {
                    project_id: projectId,
                    endpoint_id: endpoint.id,
                },
            })
                .then((res) => res.data.endpoint)));
            return {
                id: data.branch.id,
                name: data.branch.name,
                projectId: data.branch.project_id,
                protected: data.branch.protected,
                default: data.branch.default,
                parentBranchId: data.branch.parent_id,
                parentLsn: data.branch.parent_lsn,
                parentTimestamp: data.branch.parent_timestamp,
                initSource: data.branch.init_source,
                createdAt: new Date(data.branch.created_at),
                updatedAt: new Date(data.branch.updated_at),
                expiresAt: data.branch.expires_at
                    ? new Date(data.branch.expires_at)
                    : undefined,
                endpoints,
                databases: data.databases,
                roles: data.roles.map(formatRole),
                connectionUris: data.connection_uris?.map(formatConnectionUri) ?? [],
            };
        }
        case "update": {
            if (this.output.projectId !== projectId ||
                (parentBranchId && this.output.parentBranchId !== parentBranchId) ||
                (props.parentLsn && this.output.parentLsn !== props.parentLsn) ||
                (props.parentTimestamp &&
                    this.output.parentTimestamp !== props.parentTimestamp) ||
                this.output.initSource !== (props.initSource ?? "parent-data")) {
                this.replace();
            }
            const { data } = await api.updateProjectBranch({
                path: {
                    project_id: projectId,
                    branch_id: this.output.id,
                },
                body: {
                    branch: {
                        name: name !== this.output.name ? name : undefined, // prevents 400: cannot set branch to the same name
                        protected: props.protected ?? false,
                        expires_at: props.expiresAt ?? null,
                    },
                },
            });
            return {
                ...this.output,
                name,
                protected: data.branch.protected,
                updatedAt: new Date(data.branch.updated_at),
                expiresAt: data.branch.expires_at
                    ? new Date(data.branch.expires_at)
                    : undefined,
            };
        }
    }
});
async function fetchBranch(api, props) {
    const branchList = [];
    let branchesResponse = await api.listProjectBranches({
        path: { project_id: props.projectId },
        query: {
            search: props.name,
        },
    });
    branchList.push(...(branchesResponse.data?.branches ?? []));
    while (branchesResponse.data?.pagination?.next) {
        branchesResponse = await api.listProjectBranches({
            path: { project_id: props.projectId },
            query: {
                search: props.name,
                cursor: branchesResponse.data.pagination.next,
            },
        });
        branchList.push(...(branchesResponse.data?.branches ?? []));
    }
    const branchesMatchingName = branchList.filter((b) => b.name === props.name);
    if (branchesMatchingName.length === 0) {
        throw new NeonBranchNotFound(props.name);
    }
    if (branchesMatchingName.length > 1) {
        throw new Error(`Multiple branches found with name "${props.name}". Name must be unique when adopting a branch.`);
    }
    const branch = branchesMatchingName[0];
    const databases = await api.listProjectBranchDatabases({
        path: {
            branch_id: branch.id,
            project_id: props.projectId,
        },
    });
    const endpoints = await api.listProjectBranchEndpoints({
        path: { project_id: props.projectId, branch_id: branch.id },
    });
    const roles = await api.listProjectBranchRoles({
        path: {
            project_id: props.projectId,
            branch_id: branch.id,
        },
    });
    const connectionUris = await Promise.all(databases.data.databases.map((database) => api
        .getConnectionUri({
        path: { project_id: props.projectId },
        query: {
            branch_id: branch.id,
            database_name: database.name,
            role_name: database.owner_name,
            pooled: false,
        },
    })
        .then((res) => {
        const url = new URL(res.data.uri);
        return {
            connection_uri: alchemy.secret(res.data.uri),
            connection_parameters: {
                database: database.name,
                host: url.host,
                port: 5432,
                user: url.username,
                password: alchemy.secret(url.password),
            },
        };
    })));
    return {
        id: branch.id,
        projectId: branch.project_id,
        createdAt: new Date(branch.created_at),
        updatedAt: new Date(branch.updated_at),
        expiresAt: branch.expires_at ? new Date(branch.expires_at) : undefined,
        initSource: branch.init_source,
        name: branch.name,
        protected: branch.protected,
        default: branch.default,
        parentBranchId: branch.parent_id,
        parentTimestamp: branch.parent_timestamp,
        parentLsn: branch.parent_lsn,
        roles: roles.data.roles.map(formatRole),
        endpoints: endpoints.data.endpoints,
        databases: databases.data.databases,
        connectionUris,
    };
}
class NeonBranchNotFound extends Error {
    constructor(name) {
        super(`Branch ${name} not found`);
    }
}
//# sourceMappingURL=branch.js.map