export type FreestyleOptions =
    | { apiKey?: string; accessToken?: never; baseUrl?: string; fetch?: typeof fetch }
    | { accessToken: string; apiKey?: never; baseUrl?: string; fetch?: typeof fetch };

export class Freestyle {
    readonly vms: VmsNamespace;
    readonly domains: DomainsNamespace;
    readonly git: GitNamespace;
    readonly identities: IdentitiesNamespace;
    readonly dns: DnsNamespace;
    readonly vpc: VpcNamespace;
    /** @deprecated */
    readonly serverless: ServerlessNamespace;
    /** @deprecated */
    readonly cron: CronNamespace;

    constructor(options?: FreestyleOptions);

    whoami(): Promise<{ accountId: string; identityId?: string }>;

    fetch(path: string, init?: RequestInit): Promise<Response>;
}

export const freestyle: Freestyle;

export class VpcWireGuardNamespace {
    createEphemeral(options?: Record<string, never>): Promise<{
        vpcId: string;
        sessionId: string;
        endpointHost?: string;
        endpointPort: number;
        listenPort: number;
        interfaceName: string;
        serverPublicKey: string;
        clientPublicKey: string;
        clientPrivateKey?: string;
        serverTunnelIp: string;
        clientTunnelIp: string;
        vpcIp: string;
        clientAllowedIps: string[];
        vpcCidr: string;
        clientConfig: string;
        close(): Promise<void>;
    }>;

    deleteEphemeral(options: { sessionId: string }): Promise<void>;
}

export class VpcHandle {
    readonly wireguard: VpcWireGuardNamespace;
    readonly vpcId: string;
}

export class VpcNamespace {
    create(options?: { cidr?: string; name?: string }): Promise<{ vpcId: string; vpc: VpcHandle }>;

    configureWireGuardPeer(vpcId: string, options: {
        gatewayVmId: string;
        clientPublicKey: string;
    }): Promise<{
        vpcId: string;
        gatewayVmId: string;
        endpointHost?: string;
        endpointPort: number;
        listenPort: number;
        interfaceName: string;
        serverPublicKey: string;
        serverTunnelIp: string;
        clientTunnelIp: string;
        vpcCidr: string;
        clientConfig: string;
    }>;
}

class VmsNamespace {
    /** 
     * @example const { vm, vmId } = await freestyle.vms.create();
     */
    create(options?: {
        snapshotId?: string | null;
        name?: string | null;
        idleTimeoutSeconds?: number | null;
        persistence?: |
        { type: "persistent" } |
        { type: "sticky", priority: number } |
        { type: "ephemeral" },
        nics?: {
            default?: boolean;
            vpc: string;
            mode: "routed";
            ipv4: string | true;
        }[]
    }): Promise<{
        vm: Vm;
        vmId: string;
        domains: string[];
    }>;

    /**
     * @example const { vms } = await freestyle.vms.list();
     */
    list(): Promise<{
        vms: {
            id: string;
            state: "starting" | "running" | "suspending" | "suspended" | "stopped" | "lost";
            createdAt?: string | null;
            lastNetworkActivity?: string | null;
            cpuTimeSeconds?: number | null;
            snapshotId?: string | null;
            deleted?: boolean;
            createdDeclaratively?: boolean;
        }[];
        totalCount: number;
        runningCount: number;
        startingCount: number;
        suspendedCount: number;
        stoppedCount: number;
        userId?: string | null;
    }>;

    /**
     * @example const { vm } = await freestyle.vms.get({ vmId: "vmId" });
     */
    get(options: { vmId: string }): Promise<{ vm: Vm }>;

    delete(options: { vmId: string }): Promise<unknown>;
}

class Vm {
    start(options?: {
        idleTimeoutSeconds?: number
    }): Promise<unknown>;

    stop(): Promise<unknown>;

    snapshot(options?: { name?: string | null }): Promise<{
        snapshotId: string;
        sourceVmId: string;
        sourceVmInstanceId?: string | null;
    }>;

    fork(options: {
        count: number
    }): Promise<{ forks: { vm: Vm, vmId: string }[] }>;

    delete(): Promise<unknown>;

    exec(options: string | {
        command: string;
        terminal?: string;
        timeoutMs?: number;
    }): Promise<{
        stdout?: string | null;
        stderr?: string | null;
        statusCode?: number | null;
    }>;

    resize(options: {
        cpu?: number;
        memory?: number;
        storage?: number;
    }): Promise<unknown>;

    fs: {
        readFile(path: string): Promise<import("buffer").Buffer>;
        writeFile(path: string, content: import("buffer").Buffer): Promise<void>;
        readTextFile(path: string): Promise<string>;
        writeTextFile(path: string, content: string): Promise<void>;
        readDir(path: string): Promise<{ name: string; kind: string }[]>;
        mkdir(path: string): Promise<void>;
        remove(path: string): Promise<void>;
        exists(path: string): Promise<boolean>;
        stat(path: string): Promise<{
            size: number;
            isFile: boolean;
            isDirectory: boolean;
            isSymlink: boolean;
            permissions: string;
            owner: string;
            group: string;
            modified: string;
        }>;
    }
}

class DomainsNamespace {
    verifications: {
        /**
         * @example const verification = await freestyle.domains.verifications.create({ domain: "example.com" });
         */
        create(options: { domain: string }): Promise<{
            verificationId: string;
            instructions: string;
            record: {
                type: string;
                name: string;
                value: string;
            };
        }>;

        complete(options: { domain: string } | { verificationId: string }): Promise<{
            domain: string;
        }>;

        list(): Promise<{
            verificationCode: string;
            domain: string;
            createdAt: string;
        }[]>;

        cancel(options: {
            domain: string;
            verificationCode: string;
        }): Promise<{
            verificationCode: string;
            domain: string;
        }>;
    };

    certificates: {
        createWildcard(options: { domain: string }): Promise<{
            domain: string;
        }>;
    };

    mappings: {
        /**
         * @example await freestyle.domains.mappings.create({ domain: "app.example.com", vmId, vmPort: 3000 });
         */
        create(mapping: {
            domain: string;
            vmId: string;
            vmPort: number;
        }): Promise<{
            id: string;
            domain: string;
            vmId?: string | null;
            vmPort?: number | null;
            ownershipId: string;
            createdAt: string;
            unmappedAt?: string | null;
        }>;

        delete(options: { domain: string }): Promise<unknown>;

        list(options?: {
            domain?: string;
            domainOwnership?: string;
            limit?: number;
            cursor?: string;
        }): Promise<{
            mappings: {
                id: string;
                domain: string;
                vmId?: string | null;
                vmPort?: number | null;
                ownershipId: string;
                createdAt: string;
                unmappedAt?: string | null;
            }[];
        }>;
    };

    list(options?: {
        limit?: number;
        cursor?: string;
    }): Promise<{
        domain: string;
        accountId: string;
        createdAt: string;
        id: string;
        verifiedDns: boolean;
        implicitlyOwned: boolean;
        manageDns: boolean;
    }[]>;
}

type GitRepoCreateOptions = {
    name?: string | null;
    public?: boolean;
    defaultBranch?: string | null;
    source?: {
        url: string;
        branch?: string | null;
        rev?: string | null;
        depth?: number | null;
        allBranches?: boolean | null;
    };
}

type GitCommitFile =
    | {
        path: string;
        content: string;
        encoding?: "utf8" | "base64";
        deleted?: never;
    }
    | {
        path: string;
        content?: never;
        encoding?: never;
        deleted: true;
    };

type GitTrigger = {
    event: "push";
    branches?: string[] | null;
    globs?: string[] | null;
}

type GitTriggerAction = {
    action: "webhook";
    endpoint: string;
}

type GitRepositoryTrigger = {
    repositoryId: string;
    trigger: GitTrigger;
    action: GitTriggerAction;
    managed: boolean;
    id: string;
    createdAt: string;
}

class GitRepoTriggersNamespace {
    list(): Promise<{ triggers: GitRepositoryTrigger[] }>;

    create(options: {
        trigger: GitTrigger;
        action: GitTriggerAction;
    }): Promise<{ triggerId: string }>;

    delete(options: { triggerId: string }): Promise<unknown>;
}

class GitRepoGitHubSyncNamespace {
    get(): Promise<{ githubRepoName: string } | null>;

    enable(options: { githubRepoName: string }): Promise<void>;

    disable(): Promise<void>;
}

class GitNamespace {
    repos: GitReposNamespace;
}

class GitReposNamespace {
    /**
     * @example const { repo, repoId } = await freestyle.git.repos.create({ name: "my-repo" });
     */
    create(options: GitRepoCreateOptions): Promise<{
        repoId: string;
        repo: GitRepo;
    }>;

    list(options?: { limit?: number; cursor?: string }): Promise<{
        repositories: unknown[];
        total: number;
        offset: number;
    }>;

    ref(options: { repoId: string }): GitRepo;

    delete(options: { repoId: string }): Promise<unknown>;
}

class GitRepo {
    repoId: string;

    triggers: GitRepoTriggersNamespace;

    githubSync: GitRepoGitHubSyncNamespace;

    branches: {
        getDefaultBranch(): Promise<{ defaultBranch: string }>;
        setDefaultBranch(options: { defaultBranch: string }): Promise<unknown>;
        list(): Promise<unknown>;
        get(options: { branchName: string }): Promise<unknown>;
        create(options: { name: string; sha?: string }): Promise<unknown>;
    };

    contents: {
        get(options?: { path?: string; rev?: string }): Promise<unknown>;
        downloadTarball(options?: { rev?: string }): Promise<ArrayBuffer>;
        downloadZip(options?: { rev?: string }): Promise<ArrayBuffer>;
    };

    commits: {
        get(options: { sha: string }): Promise<unknown>;
        list(options?: {
            branch?: string;
            limit?: number;
            order?: "asc" | "desc";
            since?: string;
            until?: string;
            offset?: number;
        }): Promise<unknown>;
        create(options: {
            message: string;
            files: GitCommitFile[];
            branch?: string;
            author?: { name: string; email: string };
            expectedSha?: string;
        }): Promise<unknown>;
    };

    tags: {
        list(): Promise<unknown>;
        get(options: { tagName: string }): Promise<unknown>;
        getByHash(options: { sha: string }): Promise<unknown>;
    };

    compare(options: { base: string; head: string }): Promise<unknown>;

    search(options: {
        query: string;
        rev?: string;
        pathPattern?: string;
        excludePattern?: string;
        maxResults?: number;
        caseSensitive?: boolean;
        isRegex?: boolean;
        wholeWord?: boolean;
        offset?: number;
    }): Promise<GitSearchResult>;

    searchFiles(options: {
        query: string;
        rev?: string;
        pathPattern?: string;
        excludePattern?: string;
        maxResults?: number;
        caseSensitive?: boolean;
        isRegex?: boolean;
        offset?: number;
    }): Promise<GitFilenameSearchResult>;

    searchCommits(options: {
        query: string;
        rev?: string;
        maxResults?: number;
        caseSensitive?: boolean;
        isRegex?: boolean;
        offset?: number;
    }): Promise<GitCommitSearchResult>;

    searchDiffs(options: {
        query: string;
        rev?: string;
        pathPattern?: string;
        excludePattern?: string;
        maxResults?: number;
        caseSensitive?: boolean;
        isRegex?: boolean;
        wholeWord?: boolean;
        offset?: number;
    }): Promise<GitDiffSearchResult>;
}

type GitSearchLineMatch = {
    lineNumber: number;
    line: string;
    startColumn: number;
    endColumn: number;
    contextBefore: string[];
    contextAfter: string[];
}

type GitSearchResult = {
    totalFiles: number;
    totalMatches: number;
    hasMore: boolean;
    files: {
        path: string;
        extension: string | null;
        size: number;
        matches: GitSearchLineMatch[];
    }[];
}

type GitFilenameSearchResult = {
    totalFiles: number;
    hasMore: boolean;
    files: {
        path: string;
        extension: string | null;
        size: number;
    }[];
}

type GitCommitSearchResult = {
    totalCommits: number;
    hasMore: boolean;
    commits: {
        sha: string;
        message: string;
        fullMessage: string;
        authorName: string;
        authorEmail: string;
        timestamp: string;
    }[];
}

type GitDiffSearchResult = {
    totalCommits: number;
    hasMore: boolean;
    commits: {
        sha: string;
        message: string;
        authorName: string;
        authorEmail: string;
        timestamp: string;
        files: {
            path: string;
            matches: {
                line: string;
                lineNumber: number;
                startColumn: number;
                endColumn: number;
                isAddition: boolean;
            }[];
        }[];
    }[];
}

class IdentitiesNamespace {
    list(options?: { limit?: number; cursor?: string }): Promise<{
        identities: { id: string; managed: boolean }[];
        offset: number;
        total: number;
    }>;

    create(options?: {}): Promise<{
        identityId: string;
        identity: Identity;
    }>;

    ref(options: { identityId: string }): Identity;

    delete(options: { identityId: string }): Promise<unknown>;
}

class Identity {
    identityId: string;
    permissions: {
        git: {
            list(options?: { limit?: number; cursor?: string }): Promise<unknown>;
            get(options: { repoId: string }): Promise<{ identity: string; repo: string; accessLevel?: null | "read" | "write" }>;
            grant(options: { repoId: string; permission: "read" | "write" }): Promise<unknown>;
            update(options: { repoId: string; permission: "read" | "write" }): Promise<unknown>;
            revoke(options: { repoId: string }): Promise<unknown>;
        };
        vms: {
            list(options?: { limit?: number; cursor?: string }): Promise<unknown>;
            get(options: { vmId: string }): Promise<unknown>;
            grant(options: { vmId: string; allowedUsers?: string[] | null }): Promise<unknown>;
            update(options: { vmId: string; allowedUsers?: string[] | null }): Promise<unknown>;
            revoke(options: { vmId: string }): Promise<unknown>;
        };
    };
    tokens: {
        create(): Promise<{ tokenId: string; token: string }>;
        list(): Promise<{ tokens: { id: string }[] }>;
        revoke(options: { tokenId: string }): Promise<unknown>;
    };
}

type DnsRecordKind = "A" | "AAAA" | "CNAME" | "TXT" | "NS";

type DnsRecordInput = {
    kind: DnsRecordKind;
    name: string;
    value: string;
    ttl?: string | null;
    priority?: number | null;
}

type DnsRecord = {
    kind: DnsRecordKind;
    name: string;
    value: string;
    ttl: string;
    priority?: number | null;
    managed: boolean;
}

class DnsNamespace {
    records: {
        create(options: { domain: string; record: DnsRecordInput }): Promise<{ record: DnsRecord }>;
        list(options: { domain: string }): Promise<{ records: DnsRecord[] }>;
        delete(options: { domain: string; record: DnsRecord }): Promise<unknown>;
    };
}


export type EgressTransform = {
    headers?: Record<string, string>;
}

export type EgressDomainConfig = {
    transform?: EgressTransform[];
}

export type EgressIpConfig = {
    transform?: EgressTransform[];
}

export type EgressAllowRules = {
    domains?: Record<string, EgressDomainConfig[]>;
    ips?: Record<string, EgressIpConfig[]>;
}

export type EgressDenyRules = {
    ips?: Record<string, EgressIpConfig[]>;
}

export type EgressConfig = {
    allow?: EgressAllowRules;
    deny?: EgressDenyRules;
}

export type ServerlessRunConfig = {
    code: string;
    egress?: EgressConfig;
    [key: string]: unknown;
}

export type ServerlessDeploymentCreateOptions = ({
    repo: string;
    branch?: string;
    rootPath?: string;
} | {
    code: string;
} | {
    files: { path: string; content: string; encoding?: "utf-8" | "base64" }[];
} | {
    tarUrl: string;
}) & {
    name?: string;
    build?: boolean | {
        command: string;
        outDir?: string | null;
        envVars?: Record<string, string> | null;
    };
    waitForRollout?: boolean;
    domains?: string[];
    publicDir?: string;
    staticDir?: string;
    staticPathPrefix?: string;
    prerenderDir?: string;
    staticOnly?: boolean;
    redirects?: { source: string; destination: string; statusCode?: number; permanent?: boolean }[];
    rewrites?: { source: string; destination: string }[];
    dynamic?: { source: string; methods?: string[] }[];
    headers?: { source: string; headers: { key: string; value: string }[] }[];
    cleanUrls?: boolean;
    trailingSlash?: boolean;
    experimental?: { nextjsOptimization?: boolean };
    entrypointPath?: string;
    networkPermissions?: { action: "allow" | "deny"; domain: string; behavior: "exact" | "regex" }[];
    envVars?: Record<string, string>;
    nodeModules?: Record<string, string>;
    timeoutMs?: number;
    egress?: EgressConfig;
}

export class Deployment {
    readonly deploymentId: string;

    getDetails(): Promise<{
        info: {
            deploymentId: string;
            accountId: string;
            envVars: Record<string, string>;
            timeout: number;
            state: string;
            deployedAt?: string;
            domains: string[];
        };
        events: unknown[];
    }>;

    getLogs(options?: Record<string, unknown>): Promise<unknown>;

    fetch(url: string, init?: RequestInit): Promise<Response>;
}

export class DeploymentsNamespace {
    create(options: ServerlessDeploymentCreateOptions): Promise<{
        deploymentId: string;
        deployment: Deployment;
        domains: string[];
    }>;

    list(options?: { limit?: number; cursor?: string; search?: string }): Promise<unknown>;

    ref(options: { deploymentId: string }): Deployment;
}

export class RunsNamespace {
    create<T = unknown>(options: ServerlessRunConfig): Promise<{ result: T }>;

    getLogs(options: { runId: string;[key: string]: unknown }): Promise<unknown>;

    list(options?: { limit?: number; cursor?: string }): Promise<{
        runs: {
            runId: string;
            createdAt: string;
            startedAt?: string;
            status: "starting" | "running" | "complete";
        }[];
        nextCursor?: string;
        totalCount?: number;
    }>;
}

export class ServerlessNamespace {
    readonly deployments: DeploymentsNamespace;
    readonly runs: RunsNamespace;
}

export type CronSchedule = {
    id: string;
    accountId: string;
    deploymentId?: string | null;
    runConfig?: ServerlessRunConfig | null;
    name?: string | null;
    path?: string | null;
    cron: string;
    timezone: string;
    retries: number;
    payload: unknown;
    active: boolean;
    createdAt: string;
    updatedAt: string;
}

export type CronExecutionStatus = "queued" | "running" | "succeeded" | "failed" | "retry";

export type CronExecution = {
    id: string;
    scheduleId: string;
    deploymentId?: string | null;
    runAt: string;
    status: CronExecutionStatus;
    attempts: number;
    maxAttempts: number;
    lockedAt?: string | null;
    lockOwner?: string | null;
    lastError?: string | null;
    instanceId?: string | null;
    createdAt: string;
    updatedAt: string;
}

export type CronRunConfig = ServerlessRunConfig;

export type CreateCronJobParams = {
    deploymentId?: string;
    run?: CronRunConfig;
    name?: string;
    cron: string;
    timezone?: string;
    retries?: number;
    payload?: unknown;
    path?: string;
}

export class CronJob {
    readonly schedule: CronSchedule;

    update(params: {
        deploymentId?: string;
        run?: CronRunConfig;
        name?: string;
        cron?: string;
        timezone?: string;
        retries?: number;
        payload?: unknown;
        path?: string;
        active?: boolean;
    }): Promise<void>;

    enable(): Promise<void>;

    disable(): Promise<void>;

    executions(options?: { limit?: number; cursor?: string }): Promise<{
        executions: CronExecution[];
        nextCursor?: string;
        totalCount?: number;
    }>;

    successRate(options: { start: string | Date; end: string | Date }): Promise<{
        total: number;
        succeeded: number;
        failed: number;
        successRate: number;
        start: string;
        end: string;
    }>;
}

export class CronNamespace {
    schedule(options: CreateCronJobParams): Promise<{ job: CronJob }>;

    list(options?: { deploymentId?: string }): Promise<{ jobs: CronJob[] }>;
}

export class Deployment {
    readonly deploymentId: string;

    getDetails(): Promise<{
        info: {
            deploymentId: string;
            accountId: string;
            envVars: Record<string, string>;
            timeout: number;
            state: string;
            deployedAt?: string;
            domains: string[];
        };
        events: unknown[];
    }>;

    getLogs(options?: Record<string, unknown>): Promise<unknown>;

    fetch(url: string, init?: RequestInit): Promise<Response>;
}

export class DeploymentsNamespace {
    create(options: ({
        repo: string;
        branch?: string;
        rootPath?: string;
    } | {
        code: string;
    } | {
        files: { path: string; content: string; encoding?: "utf-8" | "base64" }[];
    } | {
        tarUrl: string;
    }) & {
        name?: string;
        build?: boolean | {
            command: string;
            outDir?: string | null;
            envVars?: Record<string, string> | null;
        };
        waitForRollout?: boolean;
        domains?: string[];
        publicDir?: string;
        staticDir?: string;
        staticPathPrefix?: string;
        prerenderDir?: string;
        staticOnly?: boolean;
        redirects?: { source: string; destination: string; statusCode?: number; permanent?: boolean }[];
        rewrites?: { source: string; destination: string }[];
        dynamic?: { source: string; methods?: string[] }[];
        headers?: { source: string; headers: { key: string; value: string }[] }[];
        cleanUrls?: boolean;
        trailingSlash?: boolean;
        experimental?: { nextjsOptimization?: boolean };
        entrypointPath?: string;
        networkPermissions?: { action: "allow" | "deny"; domain: string; behavior: "exact" | "regex" }[];
        envVars?: Record<string, string>;
        nodeModules?: Record<string, string>;
        timeoutMs?: number;
        egress?: {
            allow?: {
                domains?: Record<string, { transform?: { headers?: Record<string, string> }[] }[]>;
                ips?: Record<string, { transform?: { headers?: Record<string, string> }[] }[]>;
            };
            deny?: {
                ips?: Record<string, { transform?: { headers?: Record<string, string> }[] }[]>;
            };
        };
    }): Promise<{ deploymentId: string; deployment: Deployment; domains: string[] }>;

    list(options?: { limit?: number; cursor?: string; search?: string }): Promise<unknown>;

    ref(options: { deploymentId: string }): Deployment;
}

export class RunsNamespace {
    create<T = unknown>(options: {
        code: string;
        egress?: {
            allow?: {
                domains?: Record<string, { transform?: { headers?: Record<string, string> }[] }[]>;
                ips?: Record<string, { transform?: { headers?: Record<string, string> }[] }[]>;
            };
            deny?: {
                ips?: Record<string, { transform?: { headers?: Record<string, string> }[] }[]>;
            };
        };
        [key: string]: unknown;
    }): Promise<{ result: T }>;

    getLogs(options: { runId: string;[key: string]: unknown }): Promise<unknown>;

    list(options?: { limit?: number; cursor?: string }): Promise<{
        runs: {
            runId: string;
            createdAt: string;
            startedAt?: string;
            status: "starting" | "running" | "complete";
        }[];
        nextCursor?: string;
        totalCount?: number;
    }>;
}

export class ServerlessNamespace {
    readonly deployments: DeploymentsNamespace;
    readonly runs: RunsNamespace;
}

export class CronJob {
    readonly schedule: {
        id: string;
        accountId: string;
        deploymentId?: string | null;
        runConfig?: ({ code: string;[key: string]: unknown }) | null;
        name?: string | null;
        path?: string | null;
        cron: string;
        timezone: string;
        retries: number;
        payload: unknown;
        active: boolean;
        createdAt: string;
        updatedAt: string;
    };

    update(params: {
        deploymentId?: string;
        run?: { code: string;[key: string]: unknown };
        name?: string;
        cron?: string;
        timezone?: string;
        retries?: number;
        payload?: unknown;
        path?: string;
        active?: boolean;
    }): Promise<void>;

    enable(): Promise<void>;

    disable(): Promise<void>;

    executions(options?: { limit?: number; cursor?: string }): Promise<{
        executions: {
            id: string;
            scheduleId: string;
            deploymentId?: string | null;
            runAt: string;
            status: "queued" | "running" | "succeeded" | "failed" | "retry";
            attempts: number;
            maxAttempts: number;
            lockedAt?: string | null;
            lockOwner?: string | null;
            lastError?: string | null;
            instanceId?: string | null;
            createdAt: string;
            updatedAt: string;
        }[];
        nextCursor?: string;
        totalCount?: number;
    }>;

    successRate(options: { start: string | Date; end: string | Date }): Promise<{
        total: number;
        succeeded: number;
        failed: number;
        successRate: number;
        start: string;
        end: string;
    }>;
}

export class CronNamespace {
    schedule(options: {
        deploymentId?: string;
        run?: { code: string;[key: string]: unknown };
        name?: string;
        cron: string;
        timezone?: string;
        retries?: number;
        payload?: unknown;
        path?: string;
    }): Promise<{ job: CronJob }>;

    list(options?: { deploymentId?: string }): Promise<{ jobs: CronJob[] }>;
}