"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UtilsLineCount = exports.UtilsClipboard = exports.UtilsFileSync = exports.UtilsDocker = exports.UtilsPasswords = exports.UtilsJava = exports.UtilsTaonWorker = exports.UtilsZip = exports.UtilsZipBrowser = exports.UtilsQuickFixes = exports.UtilsMd = exports.UtilsHttp = exports.UtilsNpm = void 0;
const node_crypto_1 = require("node:crypto"); // @backend
const node_util_1 = require("node:util"); // @backend
const ncp = require("copy-paste"); // @backend
const semver = require("semver"); // @backend
const sloc = require("sloc"); // @backend
const lib_1 = require("tnp-core/lib");
const lib_2 = require("tnp-core/lib");
const lib_3 = require("tnp-core/lib");
const lib_4 = require("tnp-core/lib");
//#endregion
//#region utils npm
var UtilsNpm;
(function (UtilsNpm) {
    UtilsNpm.isProperVersion = (npmVersion) => {
        //#region @backendFunc
        return semver.valid(npmVersion) !== null;
        //#endregion
    };
    //#region utils npm / is special version
    UtilsNpm.isSpecialVersion = (version) => {
        return lib_3.CoreModels.NpmSpecialVersions.includes(version);
    };
    //#endregion
    //#region utils npm /  clear version
    UtilsNpm.clearVersion = (version, options) => {
        options = options || {};
        const { removePrefixes, removeSuffix } = options || {};
        if (!version || UtilsNpm.isSpecialVersion(version)) {
            return version;
        }
        version = (version || '')
            .trim()
            .replace(new RegExp(lib_3.Utils.escapeStringForRegEx('undefined'), 'g'), '0');
        if (removePrefixes) {
            version = version.replace('^', '').replace('~', '');
        }
        let [major, minor, patch, alphaOrBetaOrRc] = version.split('.');
        if (removeSuffix) {
            alphaOrBetaOrRc = '';
        }
        return UtilsNpm.fixMajorVerNumber(`${major}.${minor}.${patch}${alphaOrBetaOrRc ? '.' + alphaOrBetaOrRc : ''}`);
    };
    //#endregion
    //#region utils npm / fix major version number
    UtilsNpm.fixMajorVerNumber = (version) => {
        if (!version || UtilsNpm.isSpecialVersion(version)) {
            return version;
        }
        version = (version || '')
            .trim()
            .replace(new RegExp(lib_3.Utils.escapeStringForRegEx('undefined'), 'g'), '0');
        const splited = version.split('.');
        let [major, minor, patch, alphaOrBetaOrRc] = splited;
        if (splited.length === 1) {
            minor = '0';
            patch = '0';
        }
        else if (splited.length === 2) {
            patch = '0';
        }
        return `${major}.${minor}.${patch}${alphaOrBetaOrRc ? '.' + alphaOrBetaOrRc : ''}`;
    };
    UtilsNpm.getLatestVersionFromNpm = async (packageName, options) => {
        //#region @backendFunc
        let { currentPackageVersion, latestType = 'major', skipAlphaBetaNext = true, } = options ?? {};
        const res = await fetch(`https://registry.npmjs.org/${packageName}`);
        if (!res.ok) {
            throw new Error(`Failed to fetch npm metadata for ${packageName}`);
        }
        const json = await res.json();
        let versions = Object.keys(json.versions)
            .filter(v => semver.valid(v))
            .sort(semver.compare);
        if (skipAlphaBetaNext) {
            versions = versions.filter(v => !semver.prerelease(v));
        }
        if (!versions.length) {
            throw new Error(`No valid versions found for ${packageName}`);
        }
        // MAJOR → ignore current version
        if (latestType === 'major' || !currentPackageVersion) {
            return versions.at(-1);
        }
        currentPackageVersion = UtilsNpm.clearVersion(currentPackageVersion, {
            removePrefixes: true,
            removeSuffix: true,
        });
        const current = semver.parse(currentPackageVersion);
        if (!current) {
            throw new Error(`Invalid currentPackageVersion: ${currentPackageVersion}`);
        }
        // MINOR → lock major
        if (latestType === 'minor') {
            const filtered = versions.filter(v => semver.major(v) === current.major);
            if (!filtered.length) {
                throw new Error(`No versions found for ${packageName} with major ${current.major}`);
            }
            return filtered.at(-1);
        }
        // PATCH → lock major + minor
        if (latestType === 'patch') {
            const filtered = versions.filter(v => semver.major(v) === current.major &&
                semver.minor(v) === current.minor);
            if (!filtered.length) {
                throw new Error(`No versions found for ${packageName} ${current.major}.${current.minor}.x`);
            }
            return filtered.at(-1);
        }
        throw new Error(`Unsupported latestType: ${latestType}`);
        //#endregion
    };
    //#endregion
    //#region utils npm / check if package version available
    UtilsNpm.checkIfPackageVersionAvailable = async (pkgName, pkgVersion) => {
        //#region @backendFunc
        const res = await fetch(`https://registry.npmjs.org/${pkgName}/${pkgVersion}`);
        return res.status === 200;
        //#endregion
    };
    //#endregion
    //#region utils npm / get last major versions
    UtilsNpm.getLastMajorVersions = async (pkgName) => {
        //#region @backendFunc
        try {
            const res = await fetch(`https://registry.npmjs.org/${pkgName}`);
            const json = await res.json();
            return Object.keys(json.versions).filter(v => v.startsWith(json['dist-tags'].latest.split('.')[0]));
        }
        catch (error) {
            return [];
        }
        //#endregion
    };
    //#endregion
    //#region helpers / get last minor versions for major
    UtilsNpm.getLastMinorVersionsForMajor = async (majorVer, pkgName) => {
        //#region @backendFunc
        try {
            const res = await fetch(`https://registry.npmjs.org/${pkgName}`);
            const json = await res.json();
            return Object.keys(json.versions).filter(v => v.startsWith(`${majorVer}.`));
        }
        catch (error) {
            return [];
        }
        //#endregion
    };
    UtilsNpm.getVerObj = (version) => {
        //#region @backendFunc
        return version
            .replace('^', '')
            .replace('~', '')
            .split('.')
            .map(Number)
            .reduce((acc, c, i) => {
            if (i === 0) {
                return { ...acc, ['major']: c };
            }
            else if (i === 1) {
                return { ...acc, ['minor']: c };
            }
            else {
                return { ...acc, ['patch']: c };
            }
        }, {});
        //#endregion
    };
    //#endregion
    /**
     * @deprecated TODO remvoe
     */
    UtilsNpm.getLastVersions = async (pkgName, currentVerObj, latestVerObj) => {
        //#region @backendFunc
        let someLastVersion = lib_3.Utils.uniqArray([
            ...(await UtilsNpm.getLastMajorVersions(pkgName)),
            ...(await UtilsNpm.getLastMinorVersionsForMajor(latestVerObj.major - 1, pkgName)),
            ...(await UtilsNpm.getLastMinorVersionsForMajor(latestVerObj.major - 2, pkgName)),
            ...(await UtilsNpm.getLastMinorVersionsForMajor(currentVerObj.major, pkgName)),
            ...(await UtilsNpm.getLastMinorVersionsForMajor(currentVerObj.major - 1, pkgName)),
            ...(await UtilsNpm.getLastMinorVersionsForMajor(currentVerObj.major - 2, pkgName)),
        ])
            .sort((a, b) => {
            const aVerObj = UtilsNpm.getVerObj(a);
            const bVerObj = UtilsNpm.getVerObj(b);
            if (aVerObj.major === bVerObj.major) {
                if (aVerObj.minor === bVerObj.minor) {
                    return aVerObj.patch - bVerObj.patch;
                }
                return aVerObj.minor - bVerObj.minor;
            }
            return aVerObj.major - bVerObj.major;
        })
            .reverse();
        return someLastVersion;
        //#endregion
    };
})(UtilsNpm || (exports.UtilsNpm = UtilsNpm = {}));
//#endregion
//#region utils http
var UtilsHttp;
(function (UtilsHttp) {
    //#region utils http / start http server
    UtilsHttp.startHttpServer = async (cwd, port, options) => {
        //#region @backendFunc
        options = options || {};
        const express = require('express');
        const app = express();
        // Serve static files from the provided cwd
        app.use(express.static(cwd));
        // Catch-all to handle any invalid routes (404 errors)
        app.use((req, res) => {
            res.status(404).send('File not found');
        });
        lib_4.Helpers.taskStarted(`Starting server.. http://localhost:${port}`);
        // Start the server
        const server = app.listen(port, () => {
            lib_4.Helpers.taskDone(options.startMessage ||
                `Server started at http://localhost:${port}, serving files from ${cwd}`);
        });
        return new Promise((resolve, reject) => {
            // Handle Ctrl+C (SIGINT) gracefully
            process.on('SIGINT', () => {
                server.close(() => resolve());
            });
        });
        //#endregion
    };
    //#endregion
})(UtilsHttp || (exports.UtilsHttp = UtilsHttp = {}));
//#endregion
//#region utils md
var UtilsMd;
(function (UtilsMd) {
    /**
     * extract assets pathes from .md file
     */
    UtilsMd.getAssets = (mdfileContent) => {
        //#region @backendFunc
        // Regular expressions for detecting assets
        const markdownImgRegex = /!\[.*?\]\((.*?)\)/g; // Markdown image syntax ![alt](src)
        const htmlImgRegex = /<img.*?src=["'](.*?)["']/g; // HTML image syntax <img src="path">
        const assets = [];
        let match;
        // Extract Markdown image links
        while ((match = markdownImgRegex.exec(mdfileContent)) !== null) {
            assets.push(match[1]); // Get the image path
        }
        // Extract HTML image links
        while ((match = htmlImgRegex.exec(mdfileContent)) !== null) {
            assets.push(match[1]); // Get the image path
        }
        return assets.map(r => r.replace(new RegExp(/^\.\//), ''));
        //#endregion
    };
    UtilsMd.getAssetsFromFile = (absPathToFile) => {
        //#region @backendFunc
        if (!lib_4.Helpers.exists(absPathToFile)) {
            return [];
        }
        if (lib_2.path.extname('absPathToFile').toLowerCase() !== '.md') {
            return [];
        }
        return UtilsMd.getAssets(lib_4.Helpers.readFile(absPathToFile));
        //#endregion
    };
    /**
     * Extract links to other Markdown files from a given Markdown content.
     * @param mdfileContent
     */
    UtilsMd.getLinksToOtherMdFiles = (mdfileContent) => {
        //#region @backendFunc
        // Regex pattern to match Markdown and HTML links to .md files
        const mdLinkPattern = /\[.*?\]\(([^)]+\.md)\)/g; // Matches [text](link.md)
        // const htmlLinkPattern = /<a\s+href=["']([^"']+\.md)["'].*?>/g; // Matches <a href="link.md">
        const links = new Set(); // Use a Set to avoid duplicate links
        // Find all Markdown-style links
        let match;
        while ((match = mdLinkPattern.exec(mdfileContent)) !== null) {
            links.add(match[1]);
        }
        // Find all HTML-style links
        // while ((match = htmlLinkPattern.exec(mdfileContent)) !== null) {
        //   links.add(match[1]);
        // }
        return Array.from(links); // Convert Set to Array and return
        //#endregion
    };
    UtilsMd.moveAssetsPathsToLevelFromFile = (absFilePath, level = 1) => {
        //#region @backendFunc
        if (!lib_4.Helpers.exists(absFilePath)) {
            return undefined;
        }
        if (lib_2.path.extname(absFilePath).toLowerCase() !== '.md') {
            return lib_1.UtilsFilesFoldersSync.readFile(absFilePath, {
                readImagesWithoutEncodingUtf8: true,
            });
        }
        return UtilsMd.moveAssetsPathsToLevel(lib_1.UtilsFilesFoldersSync.readFile(absFilePath), level);
        //#endregion
    };
    /**
     * Move asset paths to a higher directory level by adding "../" before each path.
     *
     * @param mdfileContent - The content of the .md file.
     * @param level - The number of levels to go up (default is 1).
     * @returns The modified content with updated asset paths.
     */
    UtilsMd.moveAssetsPathsToLevel = (mdfileContent, level = 1) => {
        //#region @backendFunc
        mdfileContent = mdfileContent || '';
        // Regular expressions for detecting assets
        if (!mdfileContent)
            return '';
        const prefix = '../'.repeat(level);
        // Markdown images: ![alt](path)
        const markdownRegex = /(!\[.*?]\()(\.\/|(?:\.\.\/)+)([^\s)]+?)(\))/g;
        // HTML images: <img ... src="path" ...>
        // More flexible: capture src="anything starting with ./ or ../"
        const htmlRegex = /(<img\b[^>]*?\ssrc=["'])(\.\/|(?:\.\.\/)+)([^"']+)(["'][^>]*>)/gi;
        const result = mdfileContent
            // First handle Markdown images
            .replace(markdownRegex, (match, before, rel, path, after) => {
            return `${before}${prefix}${path}${after}`;
        })
            // Then handle HTML images
            .replace(htmlRegex, (match, before, rel, path, after) => {
            return `${before}${prefix}${path}${after}`;
        });
        return result;
        //#endregion
    };
})(UtilsMd || (exports.UtilsMd = UtilsMd = {}));
//#endregion
//#region utils quickfixes
var UtilsQuickFixes;
(function (UtilsQuickFixes) {
    //#region replace sql-wasm.js faulty code content
    /**
     *
     * @param node_modules/sql.js/dist/sql-wasm.js
     */
    UtilsQuickFixes.replaceKnownFaultyCode = (contentofSQLWasmJS) => {
        //#region @backendFunc
        // console.log(`
        //   Applying quick fix for faulty minifed code
        //   `);
        const replace = [
            [
                [
                    `var packageJson = JSON.parse(fs.readFileSync`,
                    `(__nccwpck_require__.ab `,
                    `+ "package.json").toString());`,
                ].join(''),
                [
                    `var packageJson = JSON.parse(fs.existsSync`,
                    `(__nccwpck_require__.ab + `,
                    `"package.json") && fs.readFileSync(__nccwpck_require__.ab + "package.json").toString());`,
                ].join(''),
            ],
            [
                [
                    `var packageJson = JSON.parse(fs.readFileSync`,
                    `(path.join(__dirname, "../package.json")).toString());`,
                ].join(''),
                [
                    `var packageJson = JSON.parse(fs.existsSync`,
                    `(path.join(__dirname, "../package.json")) &&`,
                    ` fs.readFileSync(path.join(__dirname, "../package.json")).toString());`,
                ].join(''),
            ],
            [
                [`module = `, `undefined;`].join(''),
                [`/* module =`, ` undefined ; */`].join(''),
            ],
        ];
        replace.forEach(r => {
            contentofSQLWasmJS = contentofSQLWasmJS.replace(r[0], r[1]);
        });
        return contentofSQLWasmJS;
        //#endregion
    };
    //#endregion
    /**
     * for some reason electron is being bundled - and it is not needed for cli
     */
    UtilsQuickFixes.replaceElectronWithNothing = (jsContent, packageName) => {
        //#region @backendFunc
        return jsContent
            .replace(new RegExp(lib_3.Utils.escapeStringForRegEx(`mod${'ule.exports'} = ${'requ' + 'ire'}("${packageName}");`), 'g'), `/* --- replaced ${packageName} --- */`)
            .replace(new RegExp(lib_3.Utils.escapeStringForRegEx(`var ${lib_3._.snakeCase(packageName)}_1 = ${'req' + 'uire'}("${packageName}");`), 'g'), `/* --- replaced ${packageName} --- */`);
        // var electron_1 = require("electron");
        //#endregion
    };
})(UtilsQuickFixes || (exports.UtilsQuickFixes = UtilsQuickFixes = {}));
//#endregion
//#region utils zip browser
var UtilsZipBrowser;
(function (UtilsZipBrowser) {
    // <input type="file" id="folderInput" webkitdirectory />
    // ts
    // Copy
    // Edit
    // document.getElementById('folderInput').addEventListener('change', async (e) => {
    //   const input = e.target as HTMLInputElement;
    //   if (input.files) {
    //     const zipBlob = await zipDirBrowser(input.files);
    //     // Save the zip using FileSaver.js or URL.createObjectURL
    //     const url = URL.createObjectURL(zipBlob);
    //     const a = document.createElement('a');
    //     a.href = url;
    //     a.download = 'folder.zip';
    //     a.click();
    //     URL.revokeObjectURL(url);
    //   }
    // });
    // import JSZip from 'jszip';
    //   <input type="file" id="zipInput" />
    // ts
    // Copy
    // Edit
    // document.getElementById('zipInput').addEventListener('change', async (e) => {
    //   const input = e.target as HTMLInputElement;
    //   if (input.files?.[0]) {
    //     const entries = await unzipArchiveBrowser(input.files[0]);
    //     for (const [name, blob] of entries) {
    //       console.log(`Extracted file: ${name}`, blob);
    //     }
    //   }
    // });
    UtilsZipBrowser.zipDirBrowser = async (fileList) => {
        //   const zip = new JSZip();
        //   for (const file of Array.from(fileList)) {
        //     const relativePath = (file as any).webkitRelativePath || file.name;
        //     zip.file(relativePath, file);
        //   }
        //   return zip.generateAsync({ type: 'blob' });
        return void 0;
    };
    UtilsZipBrowser.unzipArchiveBrowser = async (zipBlob) => {
        //   const zip = await JSZip.loadAsync(zipBlob);
        //   const files = new Map<string, Blob>();
        //   for (const [filePath, fileObj] of Object.entries(zip.files)) {
        //     if (!fileObj.dir) {
        //       const content = await fileObj.async('blob');
        //       files.set(filePath, content);
        //     }
        //   }
        //   return files;
        return void 0;
    };
})(UtilsZipBrowser || (exports.UtilsZipBrowser = UtilsZipBrowser = {}));
//#endregion
//#region utils zip node
var UtilsZip;
(function (UtilsZip) {
    //#region split zip file
    UtilsZip.splitFile7Zip = async (inputPath, partSizeMB = 99) => {
        //#region @backendFunc
        const stat = lib_2.fse.statSync(inputPath);
        const partSize = partSizeMB * 1024 * 1024;
        if (stat.size <= partSize) {
            console.log('File is smaller than part size — no split needed.');
            return 0;
        }
        const { path7za } = await Promise.resolve().then(() => require('7zip-bin'));
        const baseName = lib_2.path.basename(inputPath, lib_2.path.extname(inputPath));
        const dirname = lib_2.path.dirname(inputPath);
        const output7zPath = lib_2.path.join(dirname, `${baseName}.7z`);
        return new Promise((resolve, reject) => {
            const args = [
                'a', // Add to archive
                output7zPath,
                inputPath,
                `-v${partSizeMB}m`, // ✅ Volume split flag
                '-mx=0', // No compression (optional: speeds it up)
            ];
            const proc = lib_2.child_process.spawn(path7za, args, { stdio: 'inherit' });
            proc.on('close', async (code) => {
                if (code !== 0)
                    return reject(new Error(`7za failed with code ${code}`));
                try {
                    const files = await lib_2.fse.readdir(dirname);
                    const partFiles = files.filter(f => f.startsWith(`${baseName}.7z.`) &&
                        /^[0-9]{3}$/.test(f.split('.').pop() || ''));
                    const count = partFiles.length;
                    console.log(`✅ Created ${count} part(s):`, partFiles);
                    resolve(count);
                }
                catch (err) {
                    reject(err);
                }
            });
        });
        //#endregion
    };
    /**
     * Splits a file into smaller parts if its size exceeds the specified part size.
     * @returns true if file was split, false if not needed
     */
    UtilsZip.splitFile = async (inputPath, partSizeMB = 99) => {
        //#region @backendFunc
        const stat = lib_2.fse.statSync(inputPath);
        const partSize = partSizeMB * 1024 * 1024;
        if (stat.size <= partSize) {
            console.log('File is smaller than part size — no split needed.');
            return 0;
        }
        return await new Promise((resolve, reject) => {
            const baseName = lib_2.path.basename(inputPath);
            const dirname = lib_2.path.dirname(inputPath);
            const input = lib_2.fse.createReadStream(inputPath);
            let partIndex = 0;
            let written = 0;
            let currentStream = lib_2.fse.createWriteStream(`${baseName}.part${partIndex}`);
            input.on('data', chunk => {
                let offset = 0;
                while (offset < chunk.length) {
                    if (written >= partSize) {
                        currentStream.end();
                        partIndex++;
                        currentStream = lib_2.fse.createWriteStream((0, lib_2.crossPlatformPath)([dirname, `${baseName}.part${partIndex}`]));
                        written = 0;
                    }
                    const toWrite = Math.min(partSize - written, chunk.length - offset);
                    currentStream.write(chunk.slice(offset, offset + toWrite));
                    written += toWrite;
                    offset += toWrite;
                }
            });
            input.on('end', () => {
                currentStream.end(() => {
                    console.log(`✅ Done splitting into ${partIndex + 1} parts.`);
                    resolve(partIndex + 1);
                });
            });
            input.on('error', reject);
            currentStream.on('error', reject);
        });
        //#endregion
    };
    //#endregion
    /**
     * @returns absolute path to zip file
     */
    UtilsZip.zipDir = async (absPathToDir, options) => {
        //#region @backendFunc
        const zipPath = `${lib_2.path.basename(absPathToDir)}.zip`;
        const destinationFilePath = (0, lib_2.crossPlatformPath)([
            lib_2.path.dirname(absPathToDir),
            zipPath,
        ]);
        if (options.overrideIfZipFileExists) {
            try {
                lib_4.Helpers.removeFileIfExists(destinationFilePath);
            }
            catch (error) { }
        }
        if (lib_4.Helpers.exists(destinationFilePath)) {
            lib_4.Helpers.info(`[${lib_1.config.frameworkName}-helpers] Zip file already exists: ${destinationFilePath}`);
            return destinationFilePath;
        }
        const yazl = await Promise.resolve().then(() => require('yazl')); // Use default import for yazl
        const pipeline = (await Promise.resolve().then(() => require('stream/promises'))).pipeline;
        const zipfile = new yazl.ZipFile();
        const addDirectoryToZip = async (dir, basePath) => {
            const entries = await lib_2.fse.promises.readdir(dir, { withFileTypes: true });
            for (const entry of entries) {
                const fullPath = lib_2.path.join(dir, entry.name);
                const relPath = lib_2.path.relative(basePath, fullPath).replace(/\\/g, '/');
                if (entry.isDirectory()) {
                    await addDirectoryToZip(fullPath, basePath);
                }
                else if (entry.isFile()) {
                    zipfile.addFile(fullPath, relPath);
                }
            }
        };
        await addDirectoryToZip(absPathToDir, absPathToDir);
        zipfile.end();
        await pipeline(zipfile.outputStream, lib_2.fse.createWriteStream(destinationFilePath));
        return destinationFilePath;
        //#endregion;
    };
    // Unzip: `/some/path/folder.zip` → `/some/path/folder`
    UtilsZip.unzipArchive = async (absPathToZip) => {
        //#region @backendFunc
        const yauzl = await Promise.resolve().then(() => require('yauzl')); // Use default import for yauzl
        const { mkdir, stat } = await Promise.resolve().then(() => require('fs/promises')); // Use default import for fs
        const pipeline = (await Promise.resolve().then(() => require('stream/promises'))).pipeline;
        const extractTo = absPathToZip.replace(/\.zip$/, '');
        await mkdir(extractTo, { recursive: true });
        return new Promise((resolve, reject) => {
            yauzl.open(absPathToZip, { lazyEntries: true }, (err, zipfile) => {
                if (err || !zipfile)
                    return reject(err);
                zipfile.readEntry();
                zipfile.on('entry', async (entry) => {
                    const filePath = lib_2.path.join(extractTo, entry.fileName);
                    if (/\/$/.test(entry.fileName)) {
                        await mkdir(filePath, { recursive: true });
                        zipfile.readEntry();
                    }
                    else {
                        await mkdir(lib_2.path.dirname(filePath), { recursive: true });
                        zipfile.openReadStream(entry, async (err, readStream) => {
                            if (err || !readStream)
                                return reject(err);
                            const writeStream = lib_2.fse.createWriteStream(filePath);
                            await pipeline(readStream, writeStream);
                            zipfile.readEntry();
                        });
                    }
                });
                zipfile.on('end', () => resolve());
                zipfile.on('error', reject);
            });
        });
        //#endregion
    };
})(UtilsZip || (exports.UtilsZip = UtilsZip = {}));
//#endregion
//#region utils worker
var UtilsTaonWorker;
(function (UtilsTaonWorker) {
    UtilsTaonWorker.getUniqueForTask = (task, location) => {
        if (!location) {
            throw new Error('[UtilsTaonWorker.getUniqueForTask()] Location must be provided');
        }
        if (!task) {
            throw new Error('[UtilsTaonWorker.getUniqueForTask()] Task must be provided');
        }
        location = (0, lib_2.crossPlatformPath)(location);
        return `task(${task?.trim()}) in ${location}`?.trim();
    };
})(UtilsTaonWorker || (exports.UtilsTaonWorker = UtilsTaonWorker = {}));
//#endregion
//#region utils java
var UtilsJava;
(function (UtilsJava) {
    //#region select jdk version
    UtilsJava.selectJdkVersion = async () => {
        //#region @backendFunc
        lib_4.Helpers.taskStarted(`Looking for JDK versions...`);
        const platform = lib_2.os.platform();
        let currentJava = '';
        let currentJavaLocation = '';
        try {
            currentJava = lib_2.child_process
                .execSync('java -version 2>&1')
                .toString()
                .split('\n')[0];
            currentJavaLocation = lib_2.child_process
                .execSync('which java')
                .toString()
                .trim();
        }
        catch {
            currentJava = '-- no selected --';
            currentJavaLocation = '--';
        }
        console.log(`\nCURRENT JAVA GLOBAL VERSION: ${currentJava}`);
        if (currentJavaLocation !== '--') {
            console.log(`FROM: ${currentJavaLocation}\n`);
        }
        let javaVersions = [];
        if (platform === 'darwin') {
            try {
                const result = lib_2.child_process
                    .execSync('/usr/libexec/java_home -V 2>&1')
                    .toString()
                    .split('\n')
                    .filter(l => l.includes('/Library/Java/JavaVirtualMachines'))
                    .map(l => {
                    const match = l.match(/(\/Library\/.*?\/Contents\/Home)/);
                    if (match) {
                        const version = l.match(/(?:jdk-|JDK )([\d._]+)/)?.[1] ?? 'unknown';
                        return { version, path: match[1] };
                    }
                })
                    .filter(Boolean);
                javaVersions.push(...result);
            }
            catch {
                console.warn('No versions found via /usr/libexec/java_home');
            }
            // ✅ Extra fallback for Homebrew + Corretto
            const fallbackDirs = [
                '/Library/Java/JavaVirtualMachines',
                '/usr/local/Cellar',
                '/opt/homebrew/Cellar',
            ];
            for (const baseDir of fallbackDirs) {
                try {
                    const dirs = lib_2.fse.readdirSync(baseDir, { withFileTypes: true });
                    for (const dir of dirs) {
                        if (dir.isDirectory() && /(jdk|corretto|openjdk)/i.test(dir.name)) {
                            // Cellar layout: .../openjdk@21/<version>/libexec/openjdk.jdk/Contents/Home
                            const homePath = baseDir.includes('Cellar')
                                ? lib_2.path.join(baseDir, dir.name, lib_2.fse.readdirSync(lib_2.path.join(baseDir, dir.name))[0], 'libexec', 'openjdk.jdk', 'Contents', 'Home')
                                : lib_2.path.join(baseDir, dir.name, 'Contents', 'Home');
                            javaVersions.push({
                                version: UtilsJava.detectJavaVersionMacOS(homePath),
                                path: homePath,
                            });
                        }
                    }
                }
                catch {
                    // ignore
                }
            }
        }
        else if (platform === 'linux') {
            const knownPaths = ['/usr/lib/jvm', '/usr/java', '/opt/java', '/opt/jdk'];
            for (const basePath of knownPaths) {
                try {
                    const dirs = lib_2.fse.readdirSync(basePath, { withFileTypes: true });
                    for (const dir of dirs) {
                        if (dir.isDirectory() && /(jdk|java|corretto)/i.test(dir.name)) {
                            const versionMatch = dir.name.match(/(\d+(?:\.\d+)+)/);
                            javaVersions.push({
                                version: versionMatch?.[1] ?? dir.name,
                                path: lib_2.path.join(basePath, dir.name),
                            });
                        }
                    }
                }
                catch {
                    // ignore
                }
            }
        }
        else if (platform === 'win32') {
            try {
                const output = lib_2.child_process.execSync('reg query "HKLM\\SOFTWARE\\JavaSoft\\Java Development Kit"', { encoding: 'utf8' });
                const lines = output
                    .split('\n')
                    .filter(line => line.includes('JavaSoft\\Java Development Kit'));
                for (const line of lines) {
                    const version = line.trim().split('\\').pop();
                    const pathOutput = lib_2.child_process.execSync(`reg query "${line.trim()}" /v JavaHome`, {
                        encoding: 'utf8',
                    });
                    const match = pathOutput.match(/JavaHome\s+REG_SZ\s+(.+)/);
                    if (match) {
                        javaVersions.push({
                            version,
                            path: match[1].trim(),
                        });
                    }
                }
            }
            catch {
                // Ignore registry failure
            }
            // Fallback dirs
            const fallbackDirs = [
                'C:\\Program Files\\Amazon Corretto',
                'C:\\Program Files\\Java',
                'C:\\Program Files\\Eclipse Adoptium',
                'C:\\Program Files\\Zulu',
                'C:\\Java',
            ];
            for (const baseDir of fallbackDirs) {
                try {
                    const subdirs = lib_2.fse.readdirSync(baseDir, { withFileTypes: true });
                    for (const dir of subdirs) {
                        if (dir.isDirectory() &&
                            /(jdk|corretto|zulu|temurin)/i.test(dir.name)) {
                            const versionMatch = dir.name.match(/(\d+(?:\.\d+)+)/);
                            javaVersions.push({
                                version: versionMatch?.[1] ?? dir.name,
                                path: lib_2.path.join(baseDir, dir.name),
                            });
                        }
                    }
                }
                catch {
                    // ignore
                }
            }
        }
        javaVersions = javaVersions
            .filter(j => j.version !== 'unknown') // drop unknowns
            .filter((j, index, self) => index ===
            self.findIndex(other => lib_2.path.resolve(other.path) === lib_2.path.resolve(j.path)));
        if (javaVersions.length === 0) {
            console.log('❌ No installed Java versions found.');
            return;
        }
        const selected = await lib_2.UtilsTerminal.select({
            question: 'Select Java version for global usage:',
            choices: javaVersions.map(j => ({
                name: `${j.version}  —  ${j.path}`,
                value: j,
            })),
        });
        return selected.path;
        //#endregion
    };
    //#endregion
    UtilsJava.detectJavaVersionMacOS = (javaHome) => {
        //#region @backendFunc
        try {
            // 1. Try to read "release" file shipped with every JDK
            const releaseFile = lib_2.path.join(javaHome, 'release');
            if (lib_2.fse.existsSync(releaseFile)) {
                const content = lib_2.fse.readFileSync(releaseFile, 'utf8');
                const match = content.match(/JAVA_VERSION="([^"]+)"/);
                if (match) {
                    return match[1];
                }
            }
            // 2. Try folder name (amazon-corretto-21.jdk → 21, valhalla-ea-23 → 23)
            const folder = lib_2.path.basename(javaHome);
            const matchFolder = folder.match(/(\d+(?:\.\d+)?)/);
            if (matchFolder) {
                return matchFolder[1];
            }
            return folder; // fallback: show folder name
        }
        catch {
            return 'unknown';
        }
        //#endregion
    };
    //#region update java home path
    UtilsJava.updateJavaHomePath = (selectedPath) => {
        //#region @backendFunc
        const platform = lib_2.os.platform();
        if (platform === 'darwin') {
            try {
                const shellPath = lib_2.path.resolve(lib_2.UtilsOs.getRealHomeDir(), '.zshrc'); // or .bash_profile
                lib_2.child_process.execSync(`export JAVA_HOME="${selectedPath}"`);
                console.log(`✅ JAVA_HOME set to ${selectedPath} (only in current session).`);
                console.log(`To make permanent, add to your shell profile:\n\nexport JAVA_HOME="${selectedPath}"\n`);
            }
            catch (err) {
                console.error('❌ Failed to set JAVA_HOME on macOS.');
            }
        }
        else if (platform === 'linux') {
            try {
                lib_2.child_process.execSync(`export JAVA_HOME="${selectedPath}"`);
                lib_2.child_process.execSync(`sudo update-alternatives --set java "${selectedPath}/bin/java"`);
                console.log(`✅ Set global Java to ${selectedPath}`);
            }
            catch {
                console.log(`⚠️ Could not update alternatives. Try manually:\nexport JAVA_HOME="${selectedPath}"`);
            }
        }
        else if (platform === 'win32') {
            try {
                lib_2.child_process.execSync(`setx JAVA_HOME "${selectedPath}"`);
                console.log(`✅ JAVA_HOME set globally to ${selectedPath}`);
                console.log(`⚠️ Restart your terminal or computer to apply changes.`);
            }
            catch {
                console.error('❌ Failed to set JAVA_HOME on Windows.');
            }
        }
        //#endregion
    };
    //#endregion
    //#region api methods / selectTomcatVersion
    UtilsJava.selectTomcatVersion = async () => {
        //#region @backendFunc
        const platform = lib_2.os.platform();
        let currentTomcat = process.env.TOMCAT_HOME || '';
        let tomcatVersions = [];
        console.log('\n🔍 Searching for installed Tomcat versions...');
        if (currentTomcat) {
            console.log(`CURRENT TOMCAT_HOME: ${currentTomcat}\n`);
        }
        if (platform === 'darwin' || platform === 'linux') {
            // Extended search directories for macOS/Linux
            const searchDirs = [
                '/usr/local', // will check for tomcat* here
                '/opt',
                '/usr/share',
                (0, lib_2.crossPlatformPath)([lib_2.UtilsOs.getRealHomeDir(), 'tomcat']),
            ];
            for (const base of searchDirs) {
                try {
                    if (!lib_2.fse.existsSync(base))
                        continue;
                    const subdirs = lib_2.fse.readdirSync(base, { withFileTypes: true });
                    for (const sub of subdirs) {
                        if (sub.isDirectory() &&
                            sub.name.toLowerCase().includes('tomcat')) {
                            const foundPath = lib_2.path.join(base, sub.name);
                            const versionGuess = sub.name.match(/(\d+\.\d+\.\d+)/)?.[1] || sub.name;
                            tomcatVersions.push({
                                version: versionGuess,
                                path: foundPath,
                            });
                        }
                    }
                }
                catch {
                    // ignore errors
                }
            }
        }
        else if (platform === 'win32') {
            const fallbackDirs = [
                'C:\\Program Files\\Apache Software Foundation',
                'C:\\Tomcat',
            ];
            for (const baseDir of fallbackDirs) {
                try {
                    if (!lib_2.fse.existsSync(baseDir))
                        continue;
                    const subdirs = lib_2.fse.readdirSync(baseDir, { withFileTypes: true });
                    for (const dir of subdirs) {
                        if (dir.isDirectory() &&
                            dir.name.toLowerCase().includes('tomcat')) {
                            const foundPath = lib_2.path.join(baseDir, dir.name);
                            const versionGuess = dir.name.match(/(\d+\.\d+\.\d+)/)?.[1] || dir.name;
                            tomcatVersions.push({
                                version: versionGuess,
                                path: foundPath,
                            });
                        }
                    }
                }
                catch {
                    // ignore
                }
            }
        }
        if (tomcatVersions.length === 0) {
            console.log('❌ No Tomcat installations found.');
            return;
        }
        const selected = await lib_2.UtilsTerminal.select({
            question: 'Select Tomcat installation for global usage:',
            choices: tomcatVersions.map(t => ({
                name: `Tomcat ${t.version} — ${t.path}`,
                value: t,
            })),
        });
        const selectedPath = selected.path;
        return selectedPath;
        //#endregion
    };
    //#endregion
    //#region update tomcat home path
    UtilsJava.updateTomcatHomePath = (selectedPath) => {
        //#region @backendFunc
        const platform = lib_2.os.platform();
        if (platform === 'darwin' || platform === 'linux') {
            try {
                lib_2.child_process.execSync(`export TOMCAT_HOME="${selectedPath}"`);
                console.log(`✅ TOMCAT_HOME set to ${selectedPath} (current session only).`);
                console.log(`To make permanent, add to your ~/.zshrc or ~/.bashrc:\n\nexport TOMCAT_HOME="${selectedPath}"\n`);
            }
            catch {
                console.error('❌ Failed to set TOMCAT_HOME.');
            }
        }
        else if (platform === 'win32') {
            try {
                lib_2.child_process.execSync(`setx TOMCAT_HOME "${selectedPath}"`);
                console.log(`✅ TOMCAT_HOME set globally to ${selectedPath}`);
                console.log(`⚠️ Restart your terminal or computer to apply changes.`);
            }
            catch {
                console.error('❌ Failed to set TOMCAT_HOME on Windows.');
            }
        }
        //#endregion
    };
    //#endregion
})(UtilsJava || (exports.UtilsJava = UtilsJava = {}));
//#endregion
//#region utils passwords
var UtilsPasswords;
(function (UtilsPasswords) {
    //#region hash password
    UtilsPasswords.hashPassword = (password) => {
        //#region @backendFunc
        return new Promise((resolve, reject) => {
            const salt = (0, node_crypto_1.randomBytes)(16);
            (0, node_crypto_1.scrypt)(password, salt, 64, (err, derivedKey) => {
                if (err)
                    return reject(err);
                // store salt + hash (hex or base64)
                resolve(salt.toString('hex') + ':' + derivedKey.toString('hex'));
            });
        });
        //#endregion
    };
    //#endregion
    //#region verify password
    UtilsPasswords.verifyPassword = (password, stored) => {
        //#region @backendFunc
        return new Promise((resolve, reject) => {
            const [saltHex, keyHex] = stored.split(':');
            const salt = Buffer.from(saltHex, 'hex');
            const key = Buffer.from(keyHex, 'hex');
            (0, node_crypto_1.scrypt)(password, salt, key.length, (err, derivedKey) => {
                if (err)
                    return reject(err);
                resolve((0, node_crypto_1.timingSafeEqual)(key, derivedKey));
            });
        });
        //#endregion
    };
    //#endregion
    // Example
    // (async () => {
    //   const hash = await hashPassword('super-secret');
    //   console.log('stored:', hash);
    //   const ok = await verifyPassword('super-secret', hash);
    //   console.log('valid?', ok);
    // })();
})(UtilsPasswords || (exports.UtilsPasswords = UtilsPasswords = {}));
//#endregion
//#region utils docker
var UtilsDocker;
(function (UtilsDocker) {
    //#region utils docker  / constants
    const DOCKER_TAON_PROJECT_LABEL_KEY = 'com.docker.compose.taon.project'; // change to your app name
    const DOCKER_TAON_PROJECT_LABEL_VALUE = 'true'; // change to your app name
    UtilsDocker.DOCKER_LABEL_KEY = 'com.docker.compose.project'; // change to your app name
    UtilsDocker.DOCKER_TAON_PROJECT_LABEL = `${DOCKER_TAON_PROJECT_LABEL_KEY}=${DOCKER_TAON_PROJECT_LABEL_VALUE}`;
    //#endregion
    //#region utils docker / clean images by docker label
    UtilsDocker.cleanImagesAndContainersByDockerLabel = async (labelKey, labelValue) => {
        //#region @backendFunc
        const label = `${labelKey}=${labelValue}`;
        const execAsync = (0, node_util_1.promisify)(lib_2.child_process.exec);
        if (!(await lib_2.UtilsOs.isDockerAvailable())) {
            lib_4.Helpers.warn('Docker is not available in the system. Skipping cleanup.', false);
            return;
        }
        try {
            console.log(`🧹 Cleaning containers with label: ${label}`);
            const { stdout: containers } = await execAsync(`docker ps -a -q --filter "label=${label}"`);
            if (containers.trim()) {
                await execAsync(`docker rm -f ${containers.trim().replace(/\s+/g, ' ')}`);
                console.log(`✅ Removed containers:\n${containers}`);
            }
            else {
                console.log(`ℹ️ No containers found for label: ${label}`);
            }
            console.log(`🧹 Cleaning images with label: ${label}`);
            const { stdout: images } = await execAsync(`docker images -q --filter "label=${label}"`);
            if (images.trim()) {
                await execAsync(`docker rmi -f ${images.trim().replace(/\s+/g, ' ')}`);
                console.log(`✅ Removed images:\n${images}`);
            }
            else {
                console.log(`ℹ️ No images found for label: ${label}`);
            }
        }
        catch (err) {
            console.error(`❌ Error cleaning Docker label ${label}:`, err.message || err);
        }
        //#endregion
    };
    //#endregion
    //#region utils docker / get docker compose up/down command
    /**
     * @returns cmd + args array
     * you can use with child_process.spawn
     * const [cmd, ...args] = getDockerComposeActionCommand('up');
     * child.spawn(cmd, args, { ... });
     *
     * @param action 'up' | 'down'
     */
    UtilsDocker.getDockerComposeActionCommand = (action, options) => {
        options = options || {};
        options.skipBuild = !!options?.skipBuild;
        const composeFileName = options?.composeFileName || 'docker-compose.yml';
        return ['docker', 'compose', '-f', composeFileName].concat(action === 'up'
            ? options.skipBuild
                ? ['up']
                : ['up', '--build']
            : ['down']);
    };
    //#endregion
    //#region utils docker /  get docker compose up/down child process
    UtilsDocker.getDockerComposeActionChildProcess = (action, options) => {
        //#region @backendFunc
        options = options || {};
        const cwd = options?.cwd || process.cwd();
        const env = {
            ...process.env,
            ...(options?.env || {}),
        };
        if (options.useFirstYmlFound) {
            const foundYml = lib_4.Helpers.getFilesFrom(cwd, { recursive: false }).find(f => f.endsWith('.yml') || f.endsWith('.yaml'));
            options.composeFileName = foundYml ? lib_2.path.basename(foundYml) : void 0;
        }
        const [cmd, ...args] = UtilsDocker.getDockerComposeActionCommand(action, options);
        const child = lib_2.child_process.spawn(cmd, args, {
            env,
            cwd,
            stdio: options.stdio || 'inherit', // inherit stdio so output shows in terminal
        });
        return child;
        //#endregion
    };
    //#endregion
    //#region utils docker / remove all taon containers and images from docker
    UtilsDocker.removeAllTaonContainersAndImagesFromDocker = async () => {
        //#region @backendFunc
        await UtilsDocker.cleanImagesAndContainersByDockerLabel(DOCKER_TAON_PROJECT_LABEL_KEY, DOCKER_TAON_PROJECT_LABEL_VALUE);
        //#endregion
    };
    //#endregion
    //#region utils docker / link podman as docker if necessary
    /**
     * @TODO @REFACTOR use async stuff
     */
    UtilsDocker.linkPodmanAsDockerIfNecessary = async () => {
        //#region @backendFunc
        const isWin = process.platform === 'win32';
        const isMac = process.platform === 'darwin';
        const isLinux = process.platform === 'linux';
        const hasDocker = await lib_2.UtilsOs.commandExistsAsync('docker');
        const hasPodman = await lib_2.UtilsOs.commandExistsAsync('podman');
        // Rule: if docker already exists → do nothing
        if (hasDocker) {
            console.log('docker command already exists. Skipping Podman → docker linking.');
            return;
        }
        // Rule: if no podman → nothing to do
        if (!hasPodman) {
            console.log('podman not found. Cannot create docker alias.');
            return;
        }
        console.log('podman found, docker not found → creating docker → podman link...');
        try {
            if (isLinux || isMac) {
                // Find podman binary path
                const podmanPath = lib_4.Helpers.commandOutputAsString('command -v podman').trim();
                const dockerPath = (0, lib_2.crossPlatformPath)(lib_2.path.resolve('/usr/local/bin/docker'));
                // Remove old symlink if broken
                lib_4.Helpers.removeFileIfExists(dockerPath);
                lib_4.Helpers.createSymLink(podmanPath, dockerPath);
                console.log(`Created symlink: ${dockerPath} → ${podmanPath}`);
            }
            else if (isWin) {
                // Windows: Best effort via Podman Desktop or WSL
                // Option 1: Try to use Podman Desktop's podman.exe (common location)
                const commonPaths = [
                    `${process.env.LOCALAPPDATA}\\Programs\\Podman\\podman.exe`,
                    `${process.env.ProgramFiles}\\RedHat\\Podman\\podman.exe`,
                ];
                let podmanExe = commonPaths.find(p => lib_4.Helpers.exists(p));
                if (!podmanExe) {
                    // Fallback: ask WSL for podman path
                    try {
                        podmanExe = lib_4.Helpers.commandOutputAsString('wsl podman --version').includes('podman version')
                            ? 'wsl podman'.trim()
                            : null;
                    }
                    catch {
                        podmanExe = null;
                    }
                }
                if (!podmanExe) {
                    console.log('Could not locate podman.exe or WSL podman. Skipping Windows shim.');
                    return;
                }
                // Create docker.bat in a user-writable PATH directory
                const userBin = (0, lib_2.crossPlatformPath)(lib_2.path.resolve(process.env.USERPROFILE || '', 'bin'));
                const dockerBatPath = (0, lib_2.crossPlatformPath)(lib_2.path.resolve(userBin, 'docker.bat'));
                // Ensure directory exists and is in PATH
                lib_2.child_process.execSync(`mkdir "${userBin}" 2>nul`, {
                    shell: 'cmd.exe',
                });
                const pathAdd = `setx PATH "%PATH%;${userBin}"`;
                lib_2.child_process.execSync(pathAdd, { shell: 'cmd.exe', stdio: 'ignore' });
                const batContent = podmanExe.startsWith('wsl')
                    ? `@echo off\r\nwsl podman %*`
                    : `@echo off\r\n"${podmanExe}" %*`;
                require('fs').writeFileSync(dockerBatPath, batContent);
                console.log(`Created docker.bat shim → ${dockerBatPath}`);
                console.log('Note: You may need to restart your terminal for PATH to update.');
            }
            console.log('Successfully linked docker → podman');
        }
        catch (err) {
            console.error('Failed to create docker → podman link:', err instanceof Error ? err.message : err);
        }
        //#endregion
    };
    //#endregion
})(UtilsDocker || (exports.UtilsDocker = UtilsDocker = {}));
//#endregion
//#region utils file sync
/**
 * ! TODO @IN_PROGRESS @LAST
 */
var UtilsFileSync;
(function (UtilsFileSync) {
    //#region constants
    // ───── SAFETY SETTINGS ─────────────────────────────
    // Minimum realistic photo/video size (in bytes) before we even try
    const MIN_PHOTO_SIZE = 50_000; // ~50 KB — anything smaller is probably placeholder
    const MIN_VIDEO_SIZE = 500_000; // ~500 KB
    // How long we wait after the file stops growing before processing
    const STABILIZATION_MS = 5000; // 5 seconds is bulletproof
    //#region @backend
    const execAsync = (0, node_util_1.promisify)(lib_2.child_process.exec);
    //#endregion
    //#region for folders
    UtilsFileSync.forFolders = async (folder) => {
        //#region @backendFunc
        const hasFFmpeg = await lib_2.UtilsOs.commandExistsAsync('ffmpeg');
        if (!hasFFmpeg) {
            lib_4.Helpers.error(`FFmpeg is not installed or not available in PATH. Please install FFmpeg to use the safe watcher.`);
            return;
        }
        const hasFFprobe = await lib_2.UtilsOs.commandExistsAsync('ffprobe');
        if (!hasFFprobe) {
            lib_4.Helpers.error(`FFprobe is not installed or not available in PATH. Please install FFprobe to use the safe watcher.`);
            return;
        }
        //#region watcher setup
        await lib_2.fse.mkdirp(folder.tempConvertFolder);
        const processed = new Set();
        const pending = new Map();
        const watcherData = {
            ...folder,
            processed,
            pending,
        };
        //#endregion
        //#region terminal menu
        lib_4.Helpers.info(`Starting safe watcher with the following settings:
    ${lib_1.chalk.bold('Android folder')}:\n${folder.androidFolder}
    ${lib_1.chalk.bold('MacOS Photos library')}:\n${folder.macPhotosLibrary}
    Temporary conversion folder:\n${folder.tempConvertFolder}

    `);
        if (!folder.skipTerminalMenu) {
            const proceed = await lib_2.UtilsTerminal.confirm({
                message: `Proceed with starting the watcher?`,
            });
        }
        //#endregion
        // ───── MAIN WATCHER ─────────────────────────────────
        lib_1.chokidar
            .watch(folder.androidFolder, {
            //#region watcher options
            ignored: /(^|[\/\\])\../,
            persistent: true,
            ignoreInitial: false,
            awaitWriteFinish: {
                stabilityThreshold: STABILIZATION_MS,
                pollInterval: 500,
            },
            // On Windows 11 + MTP devices, native events are unreliable → force polling
            usePolling: true,
            interval: 2000,
            binaryInterval: 3000,
            //#endregion
        })
            .on('add', filePath => {
            //#region handle add
            // Clear any old timer
            if (pending.has(filePath))
                clearTimeout(pending.get(filePath));
            // Wait until the file stops growing for STABILIZATION_MS
            const timer = setTimeout(() => {
                pending.delete(filePath);
                safeProcess(filePath);
            }, STABILIZATION_MS + 1000);
            pending.set(filePath, timer);
            //#endregion
        })
            .on('change', filePath => {
            //#region handle change
            // File is still being written → reset timer
            if (pending.has(filePath))
                clearTimeout(pending.get(filePath));
            pending.set(filePath, setTimeout(() => {
                pending.delete(filePath);
                safeProcess(filePath);
            }, STABILIZATION_MS));
            //#endregion
        });
        //#region log startup info
        console.log(`Safe watcher started`);
        console.log(`Android folder : ${watcherData.androidFolder}`);
        console.log(`macOS Photos   : ${watcherData.macPhotosLibrary}`);
        console.log(`Waiting ${STABILIZATION_MS / 1000}s after file stops growing...`);
        //#endregion
        //#endregion
    };
    //#endregion
    //#region is hevc
    async function isHevc(file) {
        //#region @backendFunc
        try {
            const { stdout } = await execAsync(`ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of csv=p=0 "${file}"`);
            const codec = stdout.trim(); // ← remove newline
            return codec === 'hevc' || codec === 'hvc1'; // both mean HEVC/H.265
        }
        catch {
            return false;
        }
        //#endregion
    }
    //#endregion
    //#region safe process
    const safeProcess = async (filePath, wacherData) => {
        //#region @backendFunc
        if (wacherData.processed.has(filePath))
            return;
        const ext = lib_2.path.extname(filePath).toLowerCase();
        const stat = await lib_2.fse.stat(filePath);
        // ───── REJECT OBVIOUS PLACEHOLDERS ─────────────────
        if (stat.size < 10_000) {
            console.log(`Skipped tiny/placeholder file: ${lib_2.path.basename(filePath)} (${stat.size} bytes)`);
            wacherData.processed.add(filePath);
            return;
        }
        // ───── REJECT TOO-SMALL PHOTOS/VIDEOS ───────────────
        const isVideo = /\.(mp4|mov|avi|mkv|webm)$/i.test(ext);
        const minSize = isVideo ? MIN_VIDEO_SIZE : MIN_PHOTO_SIZE;
        if (stat.size < minSize) {
            console.log(`Still downloading or placeholder → ${lib_2.path.basename(filePath)} (${(stat.size /
                1024 /
                1024).toFixed(1)} MB)`);
            return; // don't mark as processed yet — wait for it to grow
        }
        wacherData.processed.add(filePath);
        const filename = lib_2.path.basename(filePath);
        try {
            if (ext === '.heic' || ext === '.heif') {
                // HEIC can be copied directly — Apple Photos loves them
                await lib_2.fse.copyFile(filePath, lib_2.path.join(wacherData.macPhotosLibrary, filename));
                console.log(`HEIC copied: ${filename} (${(stat.size / 1024 / 1024).toFixed(1)} MB)`);
            }
            else if (isVideo && (await isHevc(filePath))) {
                const outName = filename.replace(/\.[^.]+$/, '_iphone.mp4');
                const tempOut = lib_2.path.join(wacherData.tempConvertFolder, outName);
                console.log(`Converting HEVC → H.264: ${filename} (${(stat.size /
                    1024 /
                    1024).toFixed(1)} MB)`);
                await execAsync(`ffmpeg -i "${filePath}" -c:v libx264 -preset veryfast -crf 18 -pix_fmt yuv420p -movflags +faststart -c:a aac -b:a 192k "${tempOut}" -y`);
                await lib_2.fse.copyFile(tempOut, lib_2.path.join(wacherData.macPhotosLibrary, outName));
                console.log(`Converted: ${outName}`);
            }
            else {
                await lib_2.fse.copyFile(filePath, lib_2.path.join(wacherData.macPhotosLibrary, filename));
                console.log(`Copied: ${filename} (${(stat.size / 1024 / 1024).toFixed(1)} MB)`);
            }
        }
        catch (err) {
            console.error(`Failed ${filename}:`, err.message);
            wacherData.processed.delete(filePath); // retry later if it was a temporary error
        }
        //#endregion
    };
    //#endregion
})(UtilsFileSync || (exports.UtilsFileSync = UtilsFileSync = {}));
//#endregion
//#region utils clipboard
var UtilsClipboard;
(function (UtilsClipboard) {
    UtilsClipboard.copyText = async (textToCopy) => {
        //#region @backend
        await new Promise(resolve => {
            ncp.copy(textToCopy, function () {
                lib_4.Helpers.log(`Copied to clipboard !`);
                resolve(void 0);
            });
        });
        //#endregion
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
    };
    UtilsClipboard.pasteText = async () => {
        //#region @backend
        return await new Promise((resolve, reject) => {
            ncp.paste(function (__, p) {
                lib_4.Helpers.log(`Paster from to clipboard !`);
                resolve(p);
            });
        });
        //#endregion
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        /* */
        return '';
    };
})(UtilsClipboard || (exports.UtilsClipboard = UtilsClipboard = {}));
//#endregion
//#region utils line count
var UtilsLineCount;
(function (UtilsLineCount) {
    //#region utils line count / constatns
    const skip = [
        'node_modules',
        '.',
        'tmp-',
        'environments',
        'dist',
        'docs',
        'bundle',
        'browser',
    ];
    const emptyTotal = {
        source: 0,
        comment: 0,
        single: 0,
        block: 0,
        empty: 0,
        total: 0,
    };
    //#endregion
    //#region utils line count / walk
    const walk = (folder, total, extensions) => {
        //#region @backendFunc
        if (!lib_2.fse.existsSync(folder)) {
            return;
        }
        const entries = lib_2.fse.readdirSync(folder, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = lib_2.path.join(folder, entry.name);
            if (skip.some(s => entry.name.startsWith(s))) {
                continue;
            }
            if (entry.isDirectory()) {
                // console.log('Processing: ', path.basename(fullPath));
                walk(fullPath, total, extensions);
            }
            else if (extensions.includes(lib_2.path.extname(entry.name))) {
                const code = lib_2.fse.readFileSync(fullPath, 'utf8');
                const stats = sloc(code, lib_2.path.extname(entry.name).slice(1)); // e.g., "ts" or "js"
                for (const key in total) {
                    total[key] += stats[key] ?? 0;
                }
            }
        }
        //#endregion
    };
    //#endregion
    //#region utils line count / for porject
    UtilsLineCount.forPorject = (project, opt) => {
        //#region @backendFunc
        opt = opt || {};
        opt.displayInfoAll = opt.displayInfoAll ?? true;
        opt.displayInfoChildren = opt.displayInfoChildren ?? false;
        opt.searchFolders = opt.searchFolders ?? [
            'src',
            'libraries',
            'projects',
            'custom',
            'external',
        ];
        const extensions = opt.extensionsOnly ?? ['.ts', '.tsx', '.html', '.scss'];
        console.log('Counting SLOC for extensions: ', extensions.join(', '));
        const projectsTotal = [];
        let totalAll = lib_3._.cloneDeep(emptyTotal);
        let searchProjsCount = 0;
        // walk(crossPlatformPath([this.cwd, 'src']));
        const walkProject = (project) => {
            if (project.getValueFromJSONC('taon.jsonc', 'isCoreProject')) {
                return;
            }
            lib_4.Helpers.info(`Processing ${project.genericName}`);
            searchProjsCount++;
            const stats = lib_3._.cloneDeep(emptyTotal);
            for (const folder of opt.searchFolders) {
                walk(project.pathFor(folder), stats, extensions);
            }
            projectsTotal.push({
                project,
                total: stats,
            });
            if (opt.displayInfoChildren) {
                console.log(`📊 SLOC Results ${lib_1.chalk.bold(project.genericName)}:`);
                console.table(stats);
            }
            for (const key in stats) {
                totalAll[key] += stats[key] ?? 0;
            }
            for (const child of project.children) {
                walkProject(child);
            }
        };
        walkProject(project);
        if (opt.displayInfoAll) {
            console.log('📊 SLOC Results:');
            console.table(totalAll);
        }
        return {
            totalAll: typeof emptyTotal,
            projectsTotal,
        };
        //#endregion
    };
    //#endregion
})(UtilsLineCount || (exports.UtilsLineCount = UtilsLineCount = {}));
//#endregion
//# sourceMappingURL=utils.js.map