"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDefaultWorkspacePath = getDefaultWorkspacePath;
exports.getWorkspaceCachePath = getWorkspaceCachePath;
exports.loadWorkspaceCache = loadWorkspaceCache;
exports.saveWorkspaceCache = saveWorkspaceCache;
exports.findCurrentWorkspaceRoot = findCurrentWorkspaceRoot;
exports.loadWorkspaceCacheWithCurrentContext = loadWorkspaceCacheWithCurrentContext;
exports.listWorkspaces = listWorkspaces;
exports.initWorkspace = initWorkspace;
exports.inspectWorkspace = inspectWorkspace;
exports.refreshWorkspaces = refreshWorkspaces;
exports.isMacProtectedWorkspaceRoot = isMacProtectedWorkspaceRoot;
exports.printNoCachedWorkspacesGuidance = printNoCachedWorkspacesGuidance;
exports.getWorkspaceRefreshExample = getWorkspaceRefreshExample;
const node_fs_1 = require("node:fs");
const node_os_1 = __importDefault(require("node:os"));
const node_path_1 = require("node:path");
const init_1 = require("./init");
const output_1 = require("../output");
const CACHE_VERSION = 1;
const CATALOGUE_FILE = 'catalogue.json';
const SKIP_DIRECTORY_NAMES = new Set(['node_modules', 'build', 'dist', '.next', '.git', 'output', 'tmp', 'Pods']);
const WORKSPACE_SEARCH_DEPTH = 6;
const WORKSPACE_CONTENT_DEPTH = 6;
class WorkspaceScanError extends Error {
    constructor(message) {
        super(message);
        this.name = 'WorkspaceScanError';
    }
}
function getDefaultWorkspacePath(homeDir = node_os_1.default.homedir()) {
    return (0, node_path_1.join)(homeDir, 'PlayDrop');
}
function getWorkspaceCachePath() {
    const configPath = process.env.PLAYDROP_CONFIG_PATH;
    if (typeof configPath === 'string' && configPath.trim().length > 0) {
        return (0, node_path_1.join)((0, node_path_1.dirname)((0, node_path_1.resolve)(configPath.trim())), 'workspaces.json');
    }
    return (0, node_path_1.join)(node_os_1.default.homedir(), '.playdrop', 'workspaces.json');
}
function nowIso() {
    return new Date().toISOString();
}
function emptyCache() {
    return {
        version: CACHE_VERSION,
        defaultWorkspacePath: getDefaultWorkspacePath(),
        workspaces: [],
    };
}
function loadWorkspaceCache() {
    const cachePath = getWorkspaceCachePath();
    try {
        const parsed = JSON.parse((0, node_fs_1.readFileSync)(cachePath, 'utf8'));
        const defaultWorkspacePath = typeof parsed.defaultWorkspacePath === 'string' && parsed.defaultWorkspacePath.trim().length > 0
            ? (0, node_path_1.resolve)(parsed.defaultWorkspacePath)
            : getDefaultWorkspacePath();
        const entries = Array.isArray(parsed.workspaces)
            ? parsed.workspaces.map(normalizeWorkspaceEntry).filter((entry) => Boolean(entry))
            : [];
        return {
            version: CACHE_VERSION,
            defaultWorkspacePath,
            workspaces: dedupeWorkspaces(entries),
        };
    }
    catch {
        return emptyCache();
    }
}
function saveWorkspaceCache(cache) {
    const next = {
        version: CACHE_VERSION,
        defaultWorkspacePath: (0, node_path_1.resolve)(cache.defaultWorkspacePath || getDefaultWorkspacePath()),
        workspaces: dedupeWorkspaces(cache.workspaces.map(normalizeWorkspaceEntry).filter((entry) => Boolean(entry))),
    };
    const cachePath = getWorkspaceCachePath();
    (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(cachePath), { recursive: true });
    (0, node_fs_1.writeFileSync)(cachePath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
    return next;
}
function normalizeWorkspaceEntry(raw) {
    if (!raw || typeof raw !== 'object') {
        return null;
    }
    const record = raw;
    const rawPath = typeof record.path === 'string' ? record.path.trim() : '';
    if (!rawPath) {
        return null;
    }
    const absolutePath = (0, node_path_1.resolve)(rawPath);
    const source = record.source === 'created' || record.source === 'found' || record.source === 'inspected' || record.source === 'cached'
        ? record.source
        : 'cached';
    return {
        path: absolutePath,
        name: typeof record.name === 'string' && record.name.trim().length > 0
            ? record.name.trim()
            : basename(absolutePath),
        source,
        exists: typeof record.exists === 'boolean' ? record.exists : undefined,
        lastSeenAt: typeof record.lastSeenAt === 'string' ? record.lastSeenAt : undefined,
        lastScannedAt: typeof record.lastScannedAt === 'string' ? record.lastScannedAt : undefined,
        appsCount: numberOrZero(record.appsCount),
        assetsCount: numberOrZero(record.assetsCount),
        packsCount: numberOrZero(record.packsCount),
    };
}
function numberOrZero(value) {
    return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? Math.floor(value) : 0;
}
function basename(absolutePath) {
    const parts = absolutePath.split(/[\\/]/).filter(Boolean);
    return parts[parts.length - 1] ?? absolutePath;
}
function dedupeWorkspaces(entries) {
    const byPath = new Map();
    for (const entry of entries) {
        byPath.set((0, node_path_1.resolve)(entry.path), { ...entry, path: (0, node_path_1.resolve)(entry.path) });
    }
    return [...byPath.values()].sort((left, right) => left.name.localeCompare(right.name));
}
function entryWithExists(entry) {
    return { ...entry, exists: (0, node_fs_1.existsSync)(entry.path) };
}
function findCurrentWorkspaceRoot(startPath = process.cwd()) {
    let currentPath = (0, node_path_1.resolve)(startPath);
    try {
        const stats = (0, node_fs_1.statSync)(currentPath);
        if (!stats.isDirectory()) {
            currentPath = (0, node_path_1.dirname)(currentPath);
        }
    }
    catch {
        return null;
    }
    while (true) {
        if ((0, node_fs_1.existsSync)((0, node_path_1.join)(currentPath, CATALOGUE_FILE))) {
            return currentPath;
        }
        const parentPath = (0, node_path_1.dirname)(currentPath);
        if (parentPath === currentPath) {
            return null;
        }
        currentPath = parentPath;
    }
}
function loadWorkspaceCacheWithCurrentContext(options = {}) {
    const cache = normalizeCacheDefaultPath(loadWorkspaceCache(), options.homeDir);
    const currentWorkspacePath = findCurrentWorkspaceRoot(options.cwd ?? process.cwd());
    if (!currentWorkspacePath) {
        return cache;
    }
    const entry = inspectWorkspacePath(currentWorkspacePath, 'inspected');
    const saved = saveWorkspaceCache({
        ...cache,
        workspaces: upsertWorkspace(cache.workspaces, entry),
    });
    return {
        ...saved,
        currentWorkspacePath,
    };
}
function normalizeCacheDefaultPath(cache, homeDir) {
    if (!homeDir || cache.workspaces.length > 0) {
        return cache;
    }
    return {
        ...cache,
        defaultWorkspacePath: getDefaultWorkspacePath(homeDir),
    };
}
async function listWorkspaces(options = {}) {
    let cache;
    try {
        cache = loadWorkspaceCacheWithCurrentContext(options);
    }
    catch (error) {
        printWorkspaceScanError(error);
        return null;
    }
    const listed = {
        ...cache,
        workspaces: cache.workspaces.map(entryWithExists),
    };
    if (options.json) {
        (0, output_1.printJson)(listed);
    }
    else {
        printWorkspaceList(listed, options.platform);
    }
    return listed;
}
async function initWorkspace(targetPath, options = {}) {
    const homeDir = options.homeDir ?? node_os_1.default.homedir();
    const hasExplicitTarget = Boolean(targetPath && targetPath.trim().length > 0);
    const workspacePath = (0, node_path_1.resolve)(hasExplicitTarget ? targetPath : getDefaultWorkspacePath(homeDir));
    const bootstrapWorkspace = options.initProject ?? init_1.init;
    const summary = await bootstrapWorkspace(workspacePath, { quiet: Boolean(options.json) });
    if (!summary) {
        return null;
    }
    const entry = inspectWorkspacePath(workspacePath, 'created');
    const cache = loadWorkspaceCache();
    saveWorkspaceCache({
        ...cache,
        defaultWorkspacePath: hasExplicitTarget ? cache.defaultWorkspacePath || getDefaultWorkspacePath(homeDir) : workspacePath,
        workspaces: upsertWorkspace(cache.workspaces, entry),
    });
    if (options.json) {
        (0, output_1.printJson)(entry);
    }
    return entry;
}
async function inspectWorkspace(targetPath, options = {}) {
    const workspacePath = (0, node_path_1.resolve)(targetPath && targetPath.trim().length > 0 ? targetPath : process.cwd());
    if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(workspacePath, CATALOGUE_FILE))) {
        console.error(`No catalogue.json found at ${workspacePath}.`);
        console.error('Run "playdrop workspaces init" to create a workspace, or pass a workspace path.');
        process.exitCode = 1;
        return null;
    }
    let entry;
    try {
        entry = inspectWorkspacePath(workspacePath, 'inspected');
    }
    catch (error) {
        printWorkspaceScanError(error);
        return null;
    }
    const cache = loadWorkspaceCache();
    saveWorkspaceCache({
        ...cache,
        workspaces: upsertWorkspace(cache.workspaces, entry),
    });
    if (options.json) {
        (0, output_1.printJson)(entry);
    }
    else {
        printWorkspaceEntry(entry);
    }
    return entry;
}
async function refreshWorkspaces(rootArgs = [], options = {}) {
    const cache = loadWorkspaceCache();
    const explicitRoots = rootArgs.length > 0;
    const roots = explicitRoots
        ? rootArgs
        : dedupeStrings([cache.defaultWorkspacePath, ...cache.workspaces.map((entry) => entry.path)]);
    const resolvedRoots = roots.map((root) => (0, node_path_1.resolve)(root));
    const initiallySkippedRoots = explicitRoots
        ? []
        : resolvedRoots.filter((root) => isMacProtectedWorkspaceRoot(root, options.homeDir, options.platform));
    const skippedSet = new Set(initiallySkippedRoots);
    const scannedRoots = resolvedRoots.filter((root) => !skippedSet.has(root));
    const context = {
        explicitRoots,
        homeDir: options.homeDir,
        platform: options.platform,
        allowedProtectedRoots: explicitRoots
            ? resolvedRoots.filter((root) => isMacProtectedWorkspaceRoot(root, options.homeDir, options.platform))
            : [],
        skippedRoots: skippedSet,
    };
    const found = [];
    try {
        for (const root of scannedRoots) {
            findWorkspacesAt(root, 0, new Set(), found, context);
        }
    }
    catch (error) {
        printWorkspaceScanError(error);
        return null;
    }
    const nextWorkspaces = dedupeWorkspaces([
        ...cache.workspaces,
        ...found,
    ]);
    saveWorkspaceCache({
        ...cache,
        workspaces: nextWorkspaces,
    });
    const skippedRoots = [...context.skippedRoots];
    const payload = { scannedRoots, skippedRoots, workspaces: dedupeWorkspaces(found) };
    if (options.json) {
        (0, output_1.printJson)(payload);
    }
    else {
        console.log(`Scanned ${scannedRoots.length} root${scannedRoots.length === 1 ? '' : 's'}.`);
        if (skippedRoots.length > 0) {
            console.log(`Skipped ${skippedRoots.length} macOS protected root${skippedRoots.length === 1 ? '' : 's'}; pass the path explicitly to scan it.`);
        }
        printWorkspaceEntries(payload.workspaces);
    }
    return payload;
}
function isMacProtectedWorkspaceRoot(rootPath, homeDir = node_os_1.default.homedir(), platform = process.platform) {
    if (platform !== 'darwin') {
        return false;
    }
    const absoluteRoot = (0, node_path_1.resolve)(rootPath);
    const protectedRoots = ['Desktop', 'Documents', 'Downloads'].map((name) => (0, node_path_1.resolve)((0, node_path_1.join)(homeDir, name)));
    return protectedRoots.some((protectedRoot) => absoluteRoot === protectedRoot || isPathInside(absoluteRoot, protectedRoot));
}
function isPathInside(candidate, parent) {
    return candidate.startsWith(`${parent}/`);
}
function isAllowedProtectedPath(candidate, allowedProtectedRoots) {
    return allowedProtectedRoots.some((allowedRoot) => candidate === allowedRoot || isPathInside(candidate, allowedRoot));
}
function dedupeStrings(values) {
    return [...new Set(values.filter((value) => value.trim().length > 0).map((value) => (0, node_path_1.resolve)(value)))];
}
function upsertWorkspace(entries, entry) {
    return dedupeWorkspaces([...entries, entry]);
}
function findWorkspacesAt(rootPath, depth, seen, found, context) {
    if (depth > WORKSPACE_SEARCH_DEPTH) {
        return;
    }
    const absolutePath = (0, node_path_1.resolve)(rootPath);
    if (isMacProtectedWorkspaceRoot(absolutePath, context.homeDir, context.platform)
        && !isAllowedProtectedPath(absolutePath, context.allowedProtectedRoots)) {
        context.skippedRoots.add(absolutePath);
        return;
    }
    if (seen.has(absolutePath)) {
        return;
    }
    seen.add(absolutePath);
    let stats;
    try {
        stats = (0, node_fs_1.statSync)(absolutePath);
    }
    catch (error) {
        if (depth === 0 && context.explicitRoots) {
            throw new WorkspaceScanError(`Cannot scan ${absolutePath}: ${formatErrorMessage(error)}`);
        }
        return;
    }
    if (!stats.isDirectory()) {
        return;
    }
    if ((0, node_fs_1.existsSync)((0, node_path_1.join)(absolutePath, CATALOGUE_FILE))) {
        found.push(inspectWorkspacePath(absolutePath, 'found'));
        return;
    }
    let children;
    try {
        children = (0, node_fs_1.readdirSync)(absolutePath);
    }
    catch (error) {
        throw new WorkspaceScanError(`Cannot read ${absolutePath}: ${formatErrorMessage(error)}`);
    }
    for (const child of children) {
        if (child.startsWith('.') || SKIP_DIRECTORY_NAMES.has(child)) {
            continue;
        }
        findWorkspacesAt((0, node_path_1.join)(absolutePath, child), depth + 1, seen, found, context);
    }
}
function inspectWorkspacePath(workspacePath, source) {
    const absolutePath = (0, node_path_1.resolve)(workspacePath);
    const counts = countWorkspaceCatalogues(absolutePath, 0);
    const timestamp = nowIso();
    return {
        path: absolutePath,
        name: basename(absolutePath),
        source,
        exists: (0, node_fs_1.existsSync)(absolutePath),
        lastSeenAt: timestamp,
        lastScannedAt: timestamp,
        appsCount: counts.apps,
        assetsCount: counts.assets,
        packsCount: counts.packs,
    };
}
function countWorkspaceCatalogues(rootPath, depth) {
    const counts = { apps: 0, assets: 0, packs: 0 };
    if (depth > WORKSPACE_CONTENT_DEPTH) {
        return counts;
    }
    const cataloguePath = (0, node_path_1.join)(rootPath, CATALOGUE_FILE);
    if ((0, node_fs_1.existsSync)(cataloguePath)) {
        const catalogueCounts = readCatalogueCounts(cataloguePath);
        counts.apps += catalogueCounts.apps;
        counts.assets += catalogueCounts.assets;
        counts.packs += catalogueCounts.packs;
    }
    let children;
    try {
        children = (0, node_fs_1.readdirSync)(rootPath);
    }
    catch {
        return counts;
    }
    for (const child of children) {
        if (child.startsWith('.') || SKIP_DIRECTORY_NAMES.has(child)) {
            continue;
        }
        const childPath = (0, node_path_1.join)(rootPath, child);
        try {
            if (!(0, node_fs_1.statSync)(childPath).isDirectory()) {
                continue;
            }
        }
        catch {
            continue;
        }
        const childCounts = countWorkspaceCatalogues(childPath, depth + 1);
        counts.apps += childCounts.apps;
        counts.assets += childCounts.assets;
        counts.packs += childCounts.packs;
    }
    return counts;
}
function readCatalogueCounts(cataloguePath) {
    try {
        const parsed = JSON.parse((0, node_fs_1.readFileSync)(cataloguePath, 'utf8'));
        return {
            apps: collectionCount(parsed.apps),
            assets: collectionCount(parsed.assets),
            packs: Math.max(collectionCount(parsed.packs), collectionCount(parsed.assetPacks)),
        };
    }
    catch (error) {
        throw new WorkspaceScanError(`Invalid catalogue.json at ${cataloguePath}: ${formatErrorMessage(error)}`);
    }
}
function formatErrorMessage(error) {
    return error instanceof Error ? error.message : String(error);
}
function printWorkspaceScanError(error) {
    const message = error instanceof Error ? error.message : String(error);
    console.error(message);
    process.exitCode = 1;
}
function collectionCount(value) {
    if (Array.isArray(value)) {
        return value.length;
    }
    if (value && typeof value === 'object') {
        return Object.keys(value).length;
    }
    return 0;
}
function printWorkspaceList(cache, platform = process.platform) {
    if (cache.workspaces.length === 0) {
        printNoCachedWorkspacesGuidance(cache.defaultWorkspacePath, platform);
        return;
    }
    console.log(`Cached workspaces: ${cache.workspaces.length}`);
    if (cache.currentWorkspacePath) {
        console.log(`Current workspace: ${cache.currentWorkspacePath}`);
    }
    printWorkspaceEntries(cache.workspaces);
}
function printNoCachedWorkspacesGuidance(defaultWorkspacePath, platform = process.platform) {
    console.log('Cached workspaces: 0');
    console.log('');
    console.log('No cached PlayDrop workspaces yet.');
    console.log('To find existing workspaces:');
    console.log('  Run: playdrop workspaces refresh <folder>');
    console.log(`  Example: ${getWorkspaceRefreshExample(platform)}`);
    if (platform === 'darwin') {
        console.log('  This scans the folder you choose and macOS may ask for permission.');
    }
    else {
        console.log('  This scans only the folder you choose.');
    }
    console.log('');
    console.log('To create the recommended first workspace:');
    console.log('  Run: playdrop workspaces init');
    console.log(`  Creates: ${defaultWorkspacePath}`);
}
function getWorkspaceRefreshExample(platform = process.platform) {
    return platform === 'win32'
        ? 'playdrop workspaces refresh %USERPROFILE%\\Documents'
        : 'playdrop workspaces refresh ~/Documents';
}
function printWorkspaceEntries(entries) {
    if (entries.length === 0) {
        console.log('No PlayDrop workspaces found.');
        return;
    }
    for (const entry of entries) {
        printWorkspaceEntry(entry);
    }
}
function printWorkspaceEntry(entry) {
    const existsLabel = entry.exists === false ? 'missing' : 'available';
    console.log(`${entry.path} (${existsLabel})`);
    console.log(`  ${entry.appsCount} apps, ${entry.assetsCount} assets, ${entry.packsCount} packs`);
}
