import { readdir, readFile, stat } from "fs/promises";
import { extname, join } from "path";
/**
 * Icon file patterns to search for, in priority order.
 * Patterns starting with '*' are glob patterns (e.g., icon-*.png).
 */
export const ICON_PATTERNS = [
    "favicon.ico",
    "favicon.png",
    "favicon.svg",
    "favico.ico",
    "favico.png",
    "favico.svg",
    "icon.png",
    "icon.svg",
    "app-icon.png",
    "app-icon.svg",
    "apple-touch-icon.png",
    "icon-*.png",
    "logo.png",
    "logo.svg",
];
/**
 * Directories or directory paths to search first (in priority order).
 */
export const PRIORITY_DIRS = ["public", "static", "priv/static", "assets", "images", "img"];
/**
 * Monorepo package directory patterns to scan (e.g., packages/app, apps/web).
 */
export const MONOREPO_PACKAGE_DIRS = ["packages", "apps"];
/**
 * Directories to ignore during search.
 */
export const IGNORED_DIRS = [
    ".git",
    "node_modules",
    "dist",
    "build",
    ".next",
    ".nuxt",
    ".output",
    "coverage",
    ".cache",
    "vendor",
    "src",
    "lib",
    "test",
    "tests",
    "__tests__",
];
const MAX_ICON_SIZE = 32 * 1024; // 32KB max
function getPngDimensions(buffer) {
    // PNG header: 89 50 4E 47 0D 0A 1A 0A
    if (buffer.length < 24)
        return null;
    if (buffer[0] !== 0x89 || buffer[1] !== 0x50 || buffer[2] !== 0x4e || buffer[3] !== 0x47) {
        return null;
    }
    // Width and height are at bytes 16-19 and 20-23 (big endian)
    const width = buffer.readUInt32BE(16);
    const height = buffer.readUInt32BE(20);
    return { width, height };
}
function getJpegDimensions(buffer) {
    // JPEG starts with FF D8 FF
    if (buffer.length < 4)
        return null;
    if (buffer[0] !== 0xff || buffer[1] !== 0xd8)
        return null;
    let offset = 2;
    while (offset < buffer.length - 8) {
        if (buffer[offset] !== 0xff) {
            offset++;
            continue;
        }
        const marker = buffer[offset + 1];
        // SOF0-SOF2 markers contain dimensions
        if (marker >= 0xc0 && marker <= 0xc2) {
            const height = buffer.readUInt16BE(offset + 5);
            const width = buffer.readUInt16BE(offset + 7);
            return { width, height };
        }
        // Skip to next marker
        const length = buffer.readUInt16BE(offset + 2);
        offset += 2 + length;
    }
    return null;
}
function getGifDimensions(buffer) {
    // GIF header: GIF87a or GIF89a
    if (buffer.length < 10)
        return null;
    if (buffer[0] !== 0x47 || buffer[1] !== 0x49 || buffer[2] !== 0x46)
        return null;
    // Width and height at bytes 6-7 and 8-9 (little endian)
    const width = buffer.readUInt16LE(6);
    const height = buffer.readUInt16LE(8);
    return { width, height };
}
function getWebpDimensions(buffer) {
    // WEBP: RIFF....WEBP
    if (buffer.length < 30)
        return null;
    if (buffer.toString("ascii", 0, 4) !== "RIFF")
        return null;
    if (buffer.toString("ascii", 8, 12) !== "WEBP")
        return null;
    const chunkType = buffer.toString("ascii", 12, 16);
    if (chunkType === "VP8 ") {
        // Lossy format - dimensions at offset 26-27 and 28-29
        const width = buffer.readUInt16LE(26) & 0x3fff;
        const height = buffer.readUInt16LE(28) & 0x3fff;
        return { width, height };
    }
    else if (chunkType === "VP8L") {
        // Lossless format
        const bits = buffer.readUInt32LE(21);
        const width = (bits & 0x3fff) + 1;
        const height = ((bits >> 14) & 0x3fff) + 1;
        return { width, height };
    }
    return null;
}
function getImageDimensions(buffer, mimeType) {
    switch (mimeType) {
        case "image/png":
            return getPngDimensions(buffer);
        case "image/jpeg":
            return getJpegDimensions(buffer);
        case "image/gif":
            return getGifDimensions(buffer);
        case "image/webp":
            return getWebpDimensions(buffer);
        case "image/x-icon":
            // ICO files are typically square, trust them
            return { width: 1, height: 1 };
        case "image/svg+xml":
            // SVG can be any aspect ratio but icons are typically square, trust them
            return { width: 1, height: 1 };
        default:
            return null;
    }
}
function isSquareImage(buffer, mimeType) {
    const dimensions = getImageDimensions(buffer, mimeType);
    if (!dimensions)
        return false;
    return dimensions.width === dimensions.height;
}
function getMimeType(filename) {
    const ext = extname(filename).toLowerCase();
    switch (ext) {
        case ".ico":
            return "image/x-icon";
        case ".png":
            return "image/png";
        case ".svg":
            return "image/svg+xml";
        case ".jpg":
        case ".jpeg":
            return "image/jpeg";
        case ".gif":
            return "image/gif";
        case ".webp":
            return "image/webp";
        default:
            return "application/octet-stream";
    }
}
function matchesPattern(filename, pattern) {
    if (pattern.includes("*")) {
        // Convert glob pattern to regex
        const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*/g, ".*");
        return new RegExp(`^${regexPattern}$`).test(filename);
    }
    return filename === pattern;
}
async function isExistingFile(fullPath) {
    try {
        const stats = await stat(fullPath);
        return stats.isFile();
    }
    catch {
        return false;
    }
}
async function isExistingDirectory(fullPath) {
    try {
        const stats = await stat(fullPath);
        return stats.isDirectory();
    }
    catch {
        return false;
    }
}
async function findIconInDir(dir, patterns) {
    let entries;
    try {
        entries = await readdir(dir);
    }
    catch {
        return null;
    }
    // Collect candidate paths in priority order (pattern, then entry)
    const candidatePaths = [];
    for (const pattern of patterns) {
        for (const entry of entries) {
            if (matchesPattern(entry, pattern)) {
                candidatePaths.push(join(dir, entry));
            }
        }
    }
    const existsResults = await Promise.all(candidatePaths.map((p) => isExistingFile(p)));
    const foundIndex = existsResults.findIndex((exists) => exists);
    return foundIndex === -1 ? null : (candidatePaths[foundIndex] ?? null);
}
async function searchPriorityDirs(basePath, ignoredDirsSet, remainingDepth) {
    const priorityPaths = PRIORITY_DIRS.map((priorityDir) => join(basePath, priorityDir));
    const existenceResults = await Promise.all(priorityPaths.map((priorityPath) => isExistingDirectory(priorityPath)));
    const searchResults = await Promise.all(priorityPaths.map((priorityPath, index) => existenceResults[index]
        ? searchDirRecursively(priorityPath, ICON_PATTERNS, ignoredDirsSet, remainingDepth)
        : Promise.resolve(null)));
    return searchResults.find((result) => result !== null) ?? null;
}
async function searchDirRecursively(dir, patterns, ignoredDirs, maxDepth, currentDepth = 0) {
    if (currentDepth > maxDepth) {
        return null;
    }
    // First check this directory for icons
    const found = await findIconInDir(dir, patterns);
    if (found) {
        return found;
    }
    // Then recurse into subdirectories
    let entries;
    try {
        entries = await readdir(dir);
    }
    catch {
        return null;
    }
    const candidatePaths = entries
        .filter((entry) => !ignoredDirs.has(entry))
        .map((entry) => join(dir, entry));
    const isDirResults = await Promise.all(candidatePaths.map((fullPath) => isExistingDirectory(fullPath)));
    const recursionResults = await Promise.all(candidatePaths.map((fullPath, index) => isDirResults[index]
        ? searchDirRecursively(fullPath, patterns, ignoredDirs, maxDepth, currentDepth + 1)
        : Promise.resolve(null)));
    return recursionResults.find((result) => result !== null) ?? null;
}
/**
 * Find a project icon/favicon in the given directory.
 * Searches priority directories first, then falls back to scanning the root.
 *
 * @param projectDir - The root directory of the project to search
 * @param maxDepth - Maximum depth to search (default: 3)
 * @returns The absolute path to the found icon, or null if not found
 */
export async function findProjectIcon(projectDir, maxDepth = 3) {
    const ignoredDirsSet = new Set(IGNORED_DIRS);
    // First search priority directories
    const priorityResult = await searchPriorityDirs(projectDir, ignoredDirsSet, maxDepth - 1);
    if (priorityResult) {
        return priorityResult;
    }
    // Then search monorepo package directories (packages/*, apps/*)
    const monoPaths = MONOREPO_PACKAGE_DIRS.map((monoDir) => join(projectDir, monoDir));
    const monoEntries = await Promise.all(monoPaths.map(async (monoPath) => {
        try {
            return await readdir(monoPath);
        }
        catch {
            return null;
        }
    }));
    const monoResults = await Promise.all(monoPaths.map(async (monoPath, monoIdx) => {
        const packageEntries = monoEntries[monoIdx];
        if (!packageEntries)
            return null;
        const packagePaths = packageEntries.map((packageName) => join(monoPath, packageName));
        const isDirResults = await Promise.all(packagePaths.map((packagePath) => isExistingDirectory(packagePath)));
        const packageResults = await Promise.all(packagePaths.map(async (packagePath, idx) => {
            if (!isDirResults[idx])
                return null;
            const packagePriorityResult = await searchPriorityDirs(packagePath, ignoredDirsSet, maxDepth - 1);
            if (packagePriorityResult)
                return packagePriorityResult;
            return await findIconInDir(packagePath, ICON_PATTERNS);
        }));
        return packageResults.find((result) => result !== null) ?? null;
    }));
    const monoMatch = monoResults.find((result) => result !== null);
    if (monoMatch) {
        return monoMatch;
    }
    // Then search root and any other non-priority directories
    const found = await findDirRecursively(projectDir);
    if (found) {
        return found;
    }
    return null;
}
async function findDirRecursively(dir, maxDepth = 2, currentDepth = 0) {
    const ignoredDirsSet = new Set(IGNORED_DIRS);
    const priorityDirsSet = new Set(PRIORITY_DIRS);
    if (currentDepth > maxDepth) {
        return null;
    }
    // Check root for icons
    const found = await findIconInDir(dir, ICON_PATTERNS);
    if (found) {
        return found;
    }
    // Don't recurse further from root - we already searched priority dirs
    if (currentDepth === 0) {
        return null;
    }
    let entries;
    try {
        entries = await readdir(dir);
    }
    catch {
        return null;
    }
    const candidatePaths = entries
        .filter((entry) => !ignoredDirsSet.has(entry) && !priorityDirsSet.has(entry))
        .map((entry) => join(dir, entry));
    const isDirResults = await Promise.all(candidatePaths.map((fullPath) => isExistingDirectory(fullPath)));
    const recursionResults = await Promise.all(candidatePaths.map((fullPath, index) => isDirResults[index]
        ? findDirRecursively(fullPath, maxDepth, currentDepth + 1)
        : Promise.resolve(null)));
    return recursionResults.find((result) => result !== null) ?? null;
}
/**
 * Find and read a project icon/favicon, returning it as base64.
 * Only returns square icons smaller than MAX_ICON_SIZE (32KB).
 *
 * @param projectDir - The root directory of the project to search
 * @returns The icon data with mime type, or null if not found
 */
export async function getProjectIcon(projectDir) {
    const iconPath = await findProjectIcon(projectDir);
    if (!iconPath) {
        return null;
    }
    try {
        const stats = await stat(iconPath);
        if (stats.size > MAX_ICON_SIZE) {
            return null;
        }
        const buffer = await readFile(iconPath);
        const mimeType = getMimeType(iconPath);
        // Only return square images
        if (!isSquareImage(buffer, mimeType)) {
            return null;
        }
        const data = buffer.toString("base64");
        return { data, mimeType };
    }
    catch {
        return null;
    }
}
//# sourceMappingURL=project-icon.js.map