import { put, list, del, head } from '@vercel/blob';
import fs from 'fs';
import path from 'path';
// Check if we're in local development mode (no blob token)
const IS_LOCAL_DEV = !process.env.BLOB_READ_WRITE_TOKEN;
// Local storage directory for development
const LOCAL_STORAGE_DIR = path.join(process.cwd(), '.devdoc-storage');
/**
 * Generate a unique project slug
 */ export function generateProjectSlug(name) {
    const base = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').substring(0, 30);
    const suffix = Math.random().toString(36).substring(2, 8);
    return `${base}-${suffix}`;
}
/**
 * Get the blob path for a project's content
 */ function getProjectBlobPath(slug) {
    return `projects/${slug}/content.json`;
}
/**
 * Get the blob path for a project's metadata
 */ function getProjectMetadataPath(slug) {
    return `projects/${slug}/metadata.json`;
}
/**
 * Get the blob path for individual files (reserved for future use)
 */ // eslint-disable-next-line @typescript-eslint/no-unused-vars
function _getFileBlobPath(slug, filePath) {
    return `projects/${slug}/files/${filePath}`;
}
/**
 * Store project content in Vercel Blob (or local filesystem in dev)
 */ export async function storeProjectContent(slug, name, docsJson, files, themeJson, openApiSpecs) {
    const now = new Date().toISOString();
    const content = {
        slug,
        name,
        docsJson: JSON.stringify(docsJson),
        themeJson: themeJson ? JSON.stringify(themeJson) : undefined,
        openApiSpecs: openApiSpecs ? Object.fromEntries(Object.entries(openApiSpecs).map(([k, v])=>[
                k,
                JSON.stringify(v)
            ])) : undefined,
        files,
        createdAt: now,
        updatedAt: now
    };
    // Use local filesystem in development
    if (IS_LOCAL_DEV) {
        const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
        fs.mkdirSync(projectDir, {
            recursive: true
        });
        const contentPath = path.join(projectDir, 'content.json');
        fs.writeFileSync(contentPath, JSON.stringify(content, null, 2));
        const metadata = {
            slug,
            name,
            createdAt: now,
            updatedAt: now,
            blobUrl: `file://${contentPath}`
        };
        fs.writeFileSync(path.join(projectDir, 'metadata.json'), JSON.stringify(metadata, null, 2));
        return {
            url: `file://${contentPath}`,
            slug
        };
    }
    // Store main content bundle in Vercel Blob
    const contentBlob = await put(getProjectBlobPath(slug), JSON.stringify(content), {
        access: 'public',
        contentType: 'application/json',
        allowOverwrite: true
    });
    // Store metadata separately for quick lookups
    const metadata = {
        slug,
        name,
        createdAt: now,
        updatedAt: now,
        blobUrl: contentBlob.url
    };
    await put(getProjectMetadataPath(slug), JSON.stringify(metadata), {
        access: 'public',
        contentType: 'application/json',
        allowOverwrite: true
    });
    return {
        url: contentBlob.url,
        slug
    };
}
/**
 * Update existing project content
 */ export async function updateProjectContent(slug, docsJson, files, themeJson, openApiSpecs) {
    // Get existing content to preserve createdAt
    const existing = await getProjectContent(slug);
    const now = new Date().toISOString();
    const content = {
        slug,
        name: existing?.name || slug,
        docsJson: JSON.stringify(docsJson),
        themeJson: themeJson ? JSON.stringify(themeJson) : undefined,
        openApiSpecs: openApiSpecs ? Object.fromEntries(Object.entries(openApiSpecs).map(([k, v])=>[
                k,
                JSON.stringify(v)
            ])) : undefined,
        files,
        createdAt: existing?.createdAt || now,
        updatedAt: now
    };
    // Use local filesystem in development
    if (IS_LOCAL_DEV) {
        const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
        fs.mkdirSync(projectDir, {
            recursive: true
        });
        const contentPath = path.join(projectDir, 'content.json');
        fs.writeFileSync(contentPath, JSON.stringify(content, null, 2));
        const metadata = {
            slug,
            name: content.name,
            createdAt: content.createdAt,
            updatedAt: now,
            blobUrl: `file://${contentPath}`
        };
        fs.writeFileSync(path.join(projectDir, 'metadata.json'), JSON.stringify(metadata, null, 2));
        return {
            url: `file://${contentPath}`
        };
    }
    // Overwrite content in Vercel Blob
    const contentBlob = await put(getProjectBlobPath(slug), JSON.stringify(content), {
        access: 'public',
        contentType: 'application/json',
        allowOverwrite: true
    });
    // Update metadata
    const metadata = {
        slug,
        name: content.name,
        createdAt: content.createdAt,
        updatedAt: now,
        blobUrl: contentBlob.url
    };
    await put(getProjectMetadataPath(slug), JSON.stringify(metadata), {
        access: 'public',
        contentType: 'application/json',
        allowOverwrite: true
    });
    return {
        url: contentBlob.url
    };
}
/**
 * Get project content from Vercel Blob (or local filesystem in dev)
 */ export async function getProjectContent(slug) {
    try {
        // Use local filesystem in development
        if (IS_LOCAL_DEV) {
            const contentPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'content.json');
            if (!fs.existsSync(contentPath)) {
                return null;
            }
            const data = fs.readFileSync(contentPath, 'utf-8');
            return JSON.parse(data);
        }
        const blobPath = getProjectBlobPath(slug);
        const blobInfo = await head(blobPath);
        if (!blobInfo) {
            return null;
        }
        const response = await fetch(blobInfo.url);
        if (!response.ok) {
            return null;
        }
        const content = await response.json();
        return content;
    } catch (error) {
        console.error(`[Blob] Error fetching project ${slug}:`, error);
        return null;
    }
}
/**
 * Get project metadata (quick lookup without full content)
 */ export async function getProjectMetadata(slug) {
    try {
        const blobPath = getProjectMetadataPath(slug);
        const blobInfo = await head(blobPath);
        if (!blobInfo) {
            return null;
        }
        const response = await fetch(blobInfo.url);
        if (!response.ok) {
            return null;
        }
        const metadata = await response.json();
        return metadata;
    } catch (error) {
        console.error(`[Blob] Error fetching metadata for ${slug}:`, error);
        return null;
    }
}
/**
 * Get a specific file from project content
 */ export async function getProjectFile(slug, filePath) {
    const content = await getProjectContent(slug);
    if (!content) {
        return null;
    }
    const file = content.files.find((f)=>f.path === filePath);
    return file?.content || null;
}
/**
 * Get docs.json for a project
 */ export async function getProjectDocsJson(slug) {
    const content = await getProjectContent(slug);
    if (!content) {
        return null;
    }
    try {
        return JSON.parse(content.docsJson);
    } catch  {
        return null;
    }
}
/**
 * Check if a project exists
 */ export async function projectExists(slug) {
    try {
        // Use local filesystem in development
        if (IS_LOCAL_DEV) {
            const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
            // Check for either metadata.json (full project) or apikey.json (registered project)
            const metadataPath = path.join(projectDir, 'metadata.json');
            const apiKeyPath = path.join(projectDir, 'apikey.json');
            return fs.existsSync(metadataPath) || fs.existsSync(apiKeyPath);
        }
        // In production, check both metadata and apikey paths
        const metadataPath = getProjectMetadataPath(slug);
        const apiKeyPath = getApiKeyBlobPath(slug);
        const [metadataInfo, apiKeyInfo] = await Promise.all([
            head(metadataPath).catch(()=>null),
            head(apiKeyPath).catch(()=>null)
        ]);
        return metadataInfo !== null || apiKeyInfo !== null;
    } catch  {
        return false;
    }
}
/**
 * Delete a project and all its content
 */ export async function deleteProject(slug) {
    try {
        // List all blobs for this project
        const { blobs } = await list({
            prefix: `projects/${slug}/`
        });
        // Delete all blobs
        for (const blob of blobs){
            await del(blob.url);
        }
    } catch (error) {
        console.error(`[Blob] Error deleting project ${slug}:`, error);
        throw error;
    }
}
/**
 * List all projects (for admin purposes)
 */ export async function listProjects() {
    try {
        const { blobs } = await list({
            prefix: 'projects/'
        });
        // Filter for metadata files only
        const metadataBlobs = blobs.filter((b)=>b.pathname.endsWith('/metadata.json'));
        const projects = [];
        for (const blob of metadataBlobs){
            try {
                const response = await fetch(blob.url);
                if (response.ok) {
                    const metadata = await response.json();
                    projects.push(metadata);
                }
            } catch  {
            // Skip invalid entries
            }
        }
        return projects;
    } catch (error) {
        console.error('[Blob] Error listing projects:', error);
        return [];
    }
}
/**
 * Get the blob path for a project's API key
 */ function getApiKeyBlobPath(slug) {
    return `projects/${slug}/apikey.json`;
}
const REGISTRY_PATH = 'registry/domains.json';
const LOCAL_REGISTRY_PATH = path.join(LOCAL_STORAGE_DIR, 'registry', 'domains.json');
/**
 * Get the domain registry (cached in memory for performance)
 */ let registryCache = null;
let registryCacheTime = 0;
const CACHE_TTL = 5000 // 5 seconds
;
async function getRegistry() {
    const now = Date.now();
    // Return cached if fresh
    if (registryCache && now - registryCacheTime < CACHE_TTL) {
        return registryCache;
    }
    try {
        if (IS_LOCAL_DEV) {
            if (fs.existsSync(LOCAL_REGISTRY_PATH)) {
                const data = fs.readFileSync(LOCAL_REGISTRY_PATH, 'utf-8');
                registryCache = JSON.parse(data);
                registryCacheTime = now;
                return registryCache;
            }
        } else {
            const blobInfo = await head(REGISTRY_PATH).catch(()=>null);
            if (blobInfo) {
                const response = await fetch(blobInfo.url);
                if (response.ok) {
                    registryCache = await response.json();
                    registryCacheTime = now;
                    return registryCache;
                }
            }
        }
    } catch (error) {
        console.error('[Registry] Error loading registry:', error);
    }
    // Return empty registry if not found
    return {
        domains: {},
        customDomains: {},
        projectToCustomDomain: {},
        apiKeys: {},
        updatedAt: new Date().toISOString()
    };
}
/**
 * Save the domain registry
 */ async function saveRegistry(registry) {
    registry.updatedAt = new Date().toISOString();
    if (IS_LOCAL_DEV) {
        const dir = path.dirname(LOCAL_REGISTRY_PATH);
        fs.mkdirSync(dir, {
            recursive: true
        });
        fs.writeFileSync(LOCAL_REGISTRY_PATH, JSON.stringify(registry, null, 2));
    } else {
        await put(REGISTRY_PATH, JSON.stringify(registry), {
            access: 'public',
            contentType: 'application/json',
            allowOverwrite: true
        });
    }
    // Update cache
    registryCache = registry;
    registryCacheTime = Date.now();
}
/**
 * Check if a subdomain is registered (O(1) lookup)
 */ export async function isSubdomainRegistered(subdomain) {
    const registry = await getRegistry();
    return subdomain in registry.domains;
}
/**
 * Register a new subdomain in the registry
 */ export async function registerSubdomain(subdomain, projectId, name, apiKey) {
    const registry = await getRegistry();
    const now = new Date().toISOString();
    registry.domains[subdomain] = {
        subdomain,
        projectId,
        name,
        createdAt: now
    };
    registry.apiKeys[apiKey] = subdomain;
    await saveRegistry(registry);
}
/**
 * Validate API key and get subdomain (O(1) lookup)
 */ export async function validateApiKeyFromRegistry(apiKey) {
    if (!apiKey || !apiKey.startsWith('sk_live_') || apiKey.length !== 40) {
        return null;
    }
    const registry = await getRegistry();
    return registry.apiKeys[apiKey] || null;
}
/**
 * Get domain entry from registry
 */ export async function getDomainEntry(subdomain) {
    const registry = await getRegistry();
    return registry.domains[subdomain] || null;
}
// =============================================================================
// Custom Domain Management - One custom domain per project (free)
// =============================================================================
/**
 * Generate a verification token for domain ownership
 */ export function generateVerificationToken() {
    const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
    let token = 'devdoc-verify=';
    for(let i = 0; i < 24; i++){
        token += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return token;
}
/**
 * Check if a custom domain is already registered (O(1) lookup)
 */ export async function isCustomDomainRegistered(customDomain) {
    const registry = await getRegistry();
    return customDomain in (registry.customDomains || {});
}
/**
 * Check if a project already has a custom domain (O(1) lookup)
 */ export async function getProjectCustomDomain(projectSlug) {
    const registry = await getRegistry();
    const customDomain = registry.projectToCustomDomain?.[projectSlug];
    if (!customDomain) return null;
    return registry.customDomains?.[customDomain] || null;
}
/**
 * Get custom domain entry by domain name (O(1) lookup)
 */ export async function getCustomDomainEntry(customDomain) {
    const registry = await getRegistry();
    return registry.customDomains?.[customDomain] || null;
}
/**
 * Look up project slug from custom domain (O(1) lookup)
 * Used by middleware for routing
 */ export async function lookupCustomDomain(customDomain) {
    const registry = await getRegistry();
    const entry = registry.customDomains?.[customDomain];
    // Only return if domain is active
    if (entry && entry.status === 'active') {
        return entry;
    }
    return null;
}
/**
 * Add a custom domain to a project
 * Returns error if project already has a custom domain (limit: 1 per project)
 */ export async function addCustomDomain(projectSlug, customDomain) {
    const registry = await getRegistry();
    // Initialize custom domain maps if they don't exist
    if (!registry.customDomains) {
        registry.customDomains = {};
    }
    if (!registry.projectToCustomDomain) {
        registry.projectToCustomDomain = {};
    }
    // Check if project already has a custom domain (limit: 1 per project)
    const existingDomain = registry.projectToCustomDomain[projectSlug];
    if (existingDomain) {
        return {
            success: false,
            error: `Project already has a custom domain: ${existingDomain}. Remove it first before adding a new one.`
        };
    }
    // Check if this domain is already registered to another project
    if (registry.customDomains[customDomain]) {
        return {
            success: false,
            error: `Domain ${customDomain} is already registered to another project.`
        };
    }
    // Verify the project exists
    const projectExists = await getDomainEntry(projectSlug);
    if (!projectExists) {
        return {
            success: false,
            error: `Project ${projectSlug} not found. Deploy your project first.`
        };
    }
    const now = new Date().toISOString();
    const verificationToken = generateVerificationToken();
    const entry = {
        customDomain,
        projectSlug,
        status: 'pending',
        verificationToken,
        createdAt: now
    };
    // Add to both lookups
    registry.customDomains[customDomain] = entry;
    registry.projectToCustomDomain[projectSlug] = customDomain;
    await saveRegistry(registry);
    return {
        success: true,
        entry
    };
}
/**
 * Update custom domain status
 */ export async function updateCustomDomainStatus(customDomain, status, additionalFields) {
    const registry = await getRegistry();
    if (!registry.customDomains?.[customDomain]) {
        return false;
    }
    registry.customDomains[customDomain] = {
        ...registry.customDomains[customDomain],
        status,
        lastCheckedAt: new Date().toISOString(),
        ...additionalFields
    };
    // Set verifiedAt when transitioning to dns_verified
    if (status === 'dns_verified' && !registry.customDomains[customDomain].verifiedAt) {
        registry.customDomains[customDomain].verifiedAt = new Date().toISOString();
    }
    await saveRegistry(registry);
    return true;
}
/**
 * Remove a custom domain from a project
 */ export async function removeCustomDomain(customDomain, projectSlug) {
    const registry = await getRegistry();
    // Check if domain exists and belongs to this project
    const entry = registry.customDomains?.[customDomain];
    if (!entry) {
        return {
            success: false,
            error: `Domain ${customDomain} not found.`
        };
    }
    if (entry.projectSlug !== projectSlug) {
        return {
            success: false,
            error: `Domain ${customDomain} does not belong to this project.`
        };
    }
    // Remove from both lookups
    delete registry.customDomains[customDomain];
    delete registry.projectToCustomDomain[projectSlug];
    await saveRegistry(registry);
    return {
        success: true
    };
}
/**
 * List all custom domains (for admin purposes)
 */ export async function listCustomDomains() {
    const registry = await getRegistry();
    return Object.values(registry.customDomains || {});
}
/**
 * Generate a secure API key
 */ export function generateApiKey() {
    const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
    let key = 'sk_live_';
    for(let i = 0; i < 32; i++){
        key += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return key;
}
/**
 * Store API key for a project
 */ export async function storeProjectApiKey(slug, apiKey) {
    const keyData = {
        key: apiKey,
        slug,
        createdAt: new Date().toISOString()
    };
    // Use local filesystem in development
    if (IS_LOCAL_DEV) {
        const projectDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug);
        fs.mkdirSync(projectDir, {
            recursive: true
        });
        fs.writeFileSync(path.join(projectDir, 'apikey.json'), JSON.stringify(keyData, null, 2));
        return;
    }
    await put(getApiKeyBlobPath(slug), JSON.stringify(keyData), {
        access: 'public',
        contentType: 'application/json',
        allowOverwrite: true
    });
}
/**
 * Get API key data for a project
 */ export async function getProjectApiKey(slug) {
    try {
        // Use local filesystem in development
        if (IS_LOCAL_DEV) {
            const keyPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'apikey.json');
            if (!fs.existsSync(keyPath)) {
                return null;
            }
            const data = fs.readFileSync(keyPath, 'utf-8');
            return JSON.parse(data);
        }
        const blobPath = getApiKeyBlobPath(slug);
        const blobInfo = await head(blobPath);
        if (!blobInfo) {
            return null;
        }
        const response = await fetch(blobInfo.url);
        if (!response.ok) {
            return null;
        }
        return await response.json();
    } catch (error) {
        console.error(`[Blob] Error fetching API key for ${slug}:`, error);
        return null;
    }
}
/**
 * Validate an API key and return the associated project slug
 * O(1) lookup from registry
 */ export async function validateApiKey(apiKey) {
    // API key format: sk_live_<32chars>
    if (!apiKey || !apiKey.startsWith('sk_live_') || apiKey.length !== 40) {
        return null;
    }
    try {
        // O(1) registry lookup
        const slug = await validateApiKeyFromRegistry(apiKey);
        if (slug) {
            // Update last used timestamp
            await updateApiKeyLastUsed(slug);
        }
        return slug;
    } catch (error) {
        console.error('[Blob] Error validating API key:', error);
        return null;
    }
}
/**
 * Update the lastUsedAt timestamp for an API key
 */ async function updateApiKeyLastUsed(slug) {
    try {
        if (IS_LOCAL_DEV) {
            const keyPath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'apikey.json');
            if (fs.existsSync(keyPath)) {
                const data = JSON.parse(fs.readFileSync(keyPath, 'utf-8'));
                data.lastUsedAt = new Date().toISOString();
                fs.writeFileSync(keyPath, JSON.stringify(data, null, 2));
            }
        } else {
            const blobPath = getApiKeyBlobPath(slug);
            const blobInfo = await head(blobPath).catch(()=>null);
            if (blobInfo) {
                const response = await fetch(blobInfo.url);
                if (response.ok) {
                    const keyData = await response.json();
                    keyData.lastUsedAt = new Date().toISOString();
                    await put(blobPath, JSON.stringify(keyData), {
                        access: 'public',
                        contentType: 'application/json',
                        allowOverwrite: true
                    });
                }
            }
        }
    } catch  {
    // Non-critical, ignore errors
    }
}
/**
 * Regenerate API key for a project (requires old key for auth)
 */ export async function regenerateApiKey(slug, oldApiKey) {
    // Validate old key first
    const validSlug = await validateApiKey(oldApiKey);
    if (validSlug !== slug) {
        return null;
    }
    // Generate new key
    const newKey = generateApiKey();
    // Update registry: remove old key, add new key
    const registry = await getRegistry();
    delete registry.apiKeys[oldApiKey];
    registry.apiKeys[newKey] = slug;
    await saveRegistry(registry);
    // Also update project's apikey.json
    await storeProjectApiKey(slug, newKey);
    return newKey;
}
/**
 * Get the blob path for a project's assets
 */ export function getAssetBlobPath(slug, fileName) {
    return `projects/${slug}/assets/${fileName}`;
}
/**
 * List all assets for a project
 */ export async function listProjectAssets(slug) {
    try {
        if (IS_LOCAL_DEV) {
            const assetsDir = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'assets');
            if (!fs.existsSync(assetsDir)) {
                return [];
            }
            const files = fs.readdirSync(assetsDir);
            return files.map((fileName)=>{
                const filePath = path.join(assetsDir, fileName);
                const stats = fs.statSync(filePath);
                return {
                    path: `projects/${slug}/assets/${fileName}`,
                    url: `file://${filePath}`,
                    fileName,
                    size: stats.size,
                    contentType: getContentType(fileName),
                    uploadedAt: stats.mtime.toISOString()
                };
            });
        }
        const { blobs } = await list({
            prefix: `projects/${slug}/assets/`
        });
        return blobs.map((blob)=>{
            const fileName = blob.pathname.split('/').pop() || '';
            return {
                path: blob.pathname,
                url: blob.url,
                fileName,
                size: blob.size,
                contentType: getContentType(fileName),
                uploadedAt: blob.uploadedAt.toISOString()
            };
        });
    } catch (error) {
        console.error(`[Blob] Error listing assets for ${slug}:`, error);
        return [];
    }
}
/**
 * Delete an asset
 */ export async function deleteProjectAsset(slug, fileName) {
    try {
        if (IS_LOCAL_DEV) {
            const filePath = path.join(LOCAL_STORAGE_DIR, 'projects', slug, 'assets', fileName);
            if (fs.existsSync(filePath)) {
                fs.unlinkSync(filePath);
                return true;
            }
            return false;
        }
        const blobPath = getAssetBlobPath(slug, fileName);
        const blobInfo = await head(blobPath).catch(()=>null);
        if (blobInfo) {
            await del(blobInfo.url);
            return true;
        }
        return false;
    } catch (error) {
        console.error(`[Blob] Error deleting asset ${fileName} for ${slug}:`, error);
        return false;
    }
}
/**
 * Helper to get content type from file extension
 */ function getContentType(fileName) {
    const ext = fileName.split('.').pop()?.toLowerCase();
    const types = {
        'jpg': 'image/jpeg',
        'jpeg': 'image/jpeg',
        'png': 'image/png',
        'gif': 'image/gif',
        'webp': 'image/webp',
        'svg': 'image/svg+xml',
        'ico': 'image/x-icon',
        'pdf': 'application/pdf',
        'mp4': 'video/mp4',
        'webm': 'video/webm',
        'mp3': 'audio/mpeg',
        'wav': 'audio/wav',
        'woff': 'font/woff',
        'woff2': 'font/woff2',
        'ttf': 'font/ttf',
        'otf': 'font/otf'
    };
    return types[ext || ''] || 'application/octet-stream';
}
