import fs from 'fs';
import path from 'path';
import { randomId } from '../randomId.js';
import { enableDirectories, debugDirectories, debugIgnore, } from './featureFlags.js';
import createIgnore from 'ignore';
import { ignoreFilePattern, baseIgnore } from './config.js';
import { isDirectory } from './isDirectory.js';
// Import the image file utility
const isImageFile = (fileName) => {
    return (fileName.match(/\.(png|jpg|jpeg|gif|bmp|svg|webp)$/i) !== null);
};
/**
 * @param {string} fullPath - absolut path of the workspace root
 * @param {string} currentDirectoryPath - path where the ignore file is found, relative to fullPath
 * @param {string} fileName - name of the ignore file
 * @returns {string[]} parsed lines
 */
const parseIgnoreFile = (fullPath, currentDirectory, fileName) => {
    const filePath = path.join(fullPath, currentDirectory, fileName);
    const content = fs.readFileSync(filePath, 'utf8');
    const globs = content
        .split(/[\n\r]+/)
        .filter(
    // remove blank line and comments
    (line) => line.length > 0 && !line.startsWith('#'))
        .map((line) => {
        const { bang, slash, glob } = line.match(/^(?<bang>!?)(?<slash>\/?)(?<glob>.*)$/).groups;
        const hasSlash = Boolean(slash) || /\/.*\S/.test(glob);
        const relativeGlob = path.posix.join(currentDirectory.replace(
        // escape characters with special meaning in glob expressions
        /[*?!# \[\]\\]/g, (char) => '\\' + char), 
        // a pattern that doesn't include a slash (not counting a trailing one) matches files in any descendant directory od the current one
        hasSlash ? '' : '**', glob);
        // preserve leading `!` and `/` characters
        return bang + slash + relativeGlob;
    });
    if (debugIgnore) {
        console.debug('at', currentDirectory, 'parsing', fileName, 'obtained globs', globs);
    }
    return globs;
};
// Lists files from the file system,
// converts them into the VZCode internal
// ShareDB-compatible data structure.
export const computeInitialDocument = ({ fullPath }) => {
    // Isolate files, not directories.
    // Inspired by https://stackoverflow.com/questions/41472161/fs-readdir-ignore-directories
    // Initialize the document using our data structure for representing files.
    const initialDocument = {
        // `files`
        //  * Keys are file ids, which are random numbers.
        //  * Values are objects with properties:
        //    * text - the text content of the file
        //    * name - the file name
        files: {},
        // `isInteracting`
        //  * Whether the user is currently interacting with the document
        //    using an interactive code widget such as number dragger.
        //  * When true, the auto-save to the file system is changed to be
        //    more frequent (throttled not debounced).
        // isInteracting: false,
        // This is `undefined` initially.
    };
    /**
     * Stack for recursively traversing directories.
     * @type {string[]}
     */
    const files = [];
    const ignoreFileMatcher = createIgnore().add(ignoreFilePattern);
    const isIgnoreFile = (fileName) => ignoreFileMatcher.ignores(fileName);
    const unsearchedDirectories = [
        {
            currentDirectory: '.',
            ignore: createIgnore().add(baseIgnore),
        },
    ];
    while (unsearchedDirectories.length !== 0) {
        const { currentDirectory, ignore: parentIgnore } = unsearchedDirectories.pop();
        const currentDirectoryPath = path.join(fullPath, currentDirectory);
        const dirEntries = fs
            .readdirSync(currentDirectoryPath, {
            withFileTypes: true,
        })
            .filter((dirent) => enableDirectories ? true : dirent.isFile());
        // find .ignore or .gitignore files in the current directory
        const ignoreFiles = dirEntries
            .filter((dirent) => dirent.isFile() && isIgnoreFile(dirent.name))
            .map((file) => file.name);
        let ignore = parentIgnore;
        if (ignoreFiles.length > 0) {
            const globs = ignoreFiles.flatMap((fileName) => parseIgnoreFile(fullPath, currentDirectory, fileName));
            ignore = createIgnore().add(parentIgnore).add(globs);
        }
        const newFiles = dirEntries
            .filter((dirent) => {
            const relativePath = path.posix.join(currentDirectory, dirent.name) +
                (dirent.isDirectory() ? '/' : '');
            const keep = !ignore.ignores(relativePath);
            if (debugIgnore && !keep) {
                console.debug('at', currentDirectory, 'ignoring', relativePath);
            }
            return keep;
        })
            // Add a trailing slash for directories
            .map((dirent) => {
            const relativePath = path.posix.join(currentDirectory, dirent.name);
            if (!dirent.isDirectory()) {
                return relativePath;
            }
            unsearchedDirectories.push({
                currentDirectory: relativePath,
                ignore,
            });
            return relativePath + '/';
        });
        // console.log(currentDirectory);
        files.push(...newFiles);
    }
    files.forEach((file) => {
        const id = randomId();
        let text = null;
        if (!isDirectory(file)) {
            const filePath = path.join(fullPath, file);
            if (isImageFile(file)) {
                // Read image files as binary and convert to base64
                const buffer = fs.readFileSync(filePath);
                text = buffer.toString('base64');
            }
            else {
                // Read non-image files as UTF-8 text
                text = fs.readFileSync(filePath, 'utf-8');
            }
        }
        initialDocument.files[id] = {
            text,
            name: file,
        };
    });
    if (debugDirectories) {
        console.log('files:');
        console.log(files);
        console.log('initialDocument:');
        console.log(JSON.stringify(initialDocument, null, 2));
    }
    return initialDocument;
};
