/**
 * ./src/classes/ovary.d/bind-live-fs.ts
 * penguins-eggs v.25.7.x / ecmascript 2020
 * author: Piero Proietti
 * email: piero.proietti@gmail.com
 * license: MIT
 */
// packages
import fs from 'fs';
import path from 'node:path';
import os from 'os';
// classes
import { exec } from '../../lib/utils.js';
import Utils from '../utils.js';
// functions
import rexec from './rexec.js';
// _dirname
const __dirname = path.dirname(new URL(import.meta.url).pathname);
/**
 * Esegue il bind del filesystem live e
 * crea lo script bind
 */
export async function bindLiveFs() {
    if (this.verbose) {
        console.log('Ovary: bindLiveFs');
    }
    /**
     * dirs = readdirsync /
     */
    const dirs = fs.readdirSync('/');
    const startLine = '#############################################################';
    const endLine = '#\n';
    let lnkDest = '';
    const cmd = '';
    const cmds = [];
    cmds.push('# NOTE: cdrom, dev, live, media, mnt, proc, run, sys and tmp', `#       need just a mkdir in ${this.settings.work_dir.merged}`);
    cmds.push(`# host: ${os.hostname()} user: ${await Utils.getPrimaryUser()}\n`);
    for (const dir of dirs) {
        cmds.push(startLine);
        const statDir = fs.lstatSync(`/${dir}`);
        if (statDir.isSymbolicLink()) {
            /**
             * Link
             */
            cmds.push(`# /${dir} is a symbolic link to /${lnkDest}`);
            lnkDest = fs.readlinkSync(`/${dir}`);
            if (fs.existsSync(`${this.settings.work_dir.merged}/${dir}`)) {
                cmds.push('# SymbolicLink exist... skip');
            }
            else if (fs.existsSync(lnkDest)) {
                cmds.push(`ln -s ${this.settings.work_dir.merged}/${lnkDest} ${this.settings.work_dir.merged}/${lnkDest}`);
            }
            else {
                cmds.push(await rexec(`cp -r /${dir} ${this.settings.work_dir.merged}`, this.verbose));
            }
        }
        else if (statDir.isDirectory()) {
            /**
             * Directory
             */
            cmds.push(`# /${dir} is a directory`);
            if (dir !== 'ci' && dir !== 'lost+found') {
                if (this.copied(dir)) {
                    cmds.push(`# /${dir} is copied if not exists on filesystem.squashfs`);
                    const chkDir = path.join(this.settings.work_dir.merged, dir);
                    cmds.push(`if ! [ -d "${chkDir}" ]; then`);
                    cmds.push(await rexec(`   cp -a /${dir} ${this.settings.work_dir.merged}/`, this.verbose), `fi`);
                    continue;
                }
                else if (this.mergedAndOverlay(dir)) {
                    cmds.push(`# /${dir} mergedAndOverlay (rw)\n`, '# create mountpoint lower');
                    cmds.push(await makeIfNotExist(`${this.settings.work_dir.lowerdir}/${dir}`), `# first: mount /${dir} rw in ${this.settings.work_dir.lowerdir}/${dir}`);
                    cmds.push(await rexec(`mount --bind --make-slave /${dir} ${this.settings.work_dir.lowerdir}/${dir}`, this.verbose), '# now remount it ro');
                    cmds.push(await rexec(`mount -o remount,bind,ro ${this.settings.work_dir.lowerdir}/${dir}`, this.verbose), `\n# second: create mountpoint upper, work and ${this.settings.work_dir.merged} and mount ${dir}`);
                    cmds.push(await makeIfNotExist(`${this.settings.work_dir.upperdir}/${dir}`, this.verbose));
                    cmds.push(await makeIfNotExist(`${this.settings.work_dir.workdir}/${dir}`, this.verbose));
                    cmds.push(await makeIfNotExist(`${this.settings.work_dir.merged}/${dir}`, this.verbose), `\n# thirth: mount /${dir} rw in ${this.settings.work_dir.merged}`);
                    cmds.push(await rexec(`mount -t overlay overlay -o lowerdir=${this.settings.work_dir.lowerdir}/${dir},upperdir=${this.settings.work_dir.upperdir}/${dir},workdir=${this.settings.work_dir.workdir}/${dir} ${this.settings.work_dir.merged}/${dir}`, this.verbose));
                }
                else if (this.merged(dir)) {
                    cmds.push(`# /${dir} merged (ro)`);
                    cmds.push(await makeIfNotExist(`${this.settings.work_dir.merged}/${dir}`, this.verbose));
                    cmds.push(await rexec(`mount --bind --make-slave /${dir} ${this.settings.work_dir.merged}/${dir}`, this.verbose));
                    cmds.push(await rexec(`mount -o remount,bind,ro ${this.settings.work_dir.merged}/${dir}`, this.verbose));
                }
                else {
                    cmds.push(`# /${dir} just created`);
                    cmds.push(await makeIfNotExist(`${this.settings.work_dir.merged}/${dir}`, this.verbose));
                }
            }
            /**
             * File
             */
        }
        else if (statDir.isFile()) {
            cmds.push(`# /${dir} is just a file`);
            if (fs.existsSync(`${this.settings.work_dir.merged}/${dir}`)) {
                cmds.push('# file exist... skip');
            }
            else {
                cmds.push(await rexec(`cp -p /${dir} ${this.settings.work_dir.merged}`, this.verbose));
            }
        }
        cmds.push(endLine);
    }
    // ==============================================================================
    // FIX CRUCIALE USR MERGE (Eseguito LIVE via rexec)
    // ==============================================================================
    cmds.push(startLine, '# FORCE USR MERGE LAYOUT (Fix for Debian 12+ / RISC-V)', '# Ensures bin, sbin, lib, lib64 point to usr/... inside the merged dir');
    const linkNames = ['bin', 'sbin', 'lib', 'lib64'];
    const mergedDir = this.settings.work_dir.merged;
    for (const link of linkNames) {
        const linkPath = `${mergedDir}/${link}`;
        // 1. Se esiste una directory fisica (errore rsync), la rimuoviamo subito
        if (fs.existsSync(linkPath) && !fs.lstatSync(linkPath).isSymbolicLink() && fs.lstatSync(linkPath).isDirectory()) {
            // Usiamo exec normale per la pulizia, o rexec se vuoi loggarlo
            cmds.push(await rexec(`rm -rf ${linkPath}`, this.verbose));
        }
        // 2. Creiamo il link se non esiste (o se l'abbiamo appena rimosso)
        if (!fs.existsSync(linkPath)) {
            // Eseguiamo SUBITO il comando e lo salviamo nella lista cmds
            cmds.push(await rexec(`ln -sf usr/${link} ${linkPath}`, this.verbose));
        }
    }
    cmds.push(endLine);
    // Utils.writeXs(`${this.settings.config.snapshot_dir}bind`, cmds)
    Utils.writeXs(`${this.settings.work_dir.bin}bind`, cmds);
}
/**
 * ubind del fs live
 * @param verbose
 */
export async function uBindLiveFs() {
    if (this.verbose) {
        console.log('Ovary: uBindLiveFs');
    }
    const startLine = '#############################################################';
    const cmds = [];
    cmds.push('# NOTE: home, cdrom, dev, live, media, mnt, proc, run, sys and tmp', `#       need just to be removed in ${this.settings.work_dir.merged}`);
    cmds.push(`# host: ${os.hostname()} user: ${await Utils.getPrimaryUser()}\n`);
    if (fs.existsSync(this.settings.work_dir.merged)) {
        const bindDirs = fs.readdirSync(this.settings.work_dir.merged, {
            withFileTypes: true
        });
        for (const dir of bindDirs) {
            const dirname = dir.name;
            const hostPath = `/${dirname}`;
            cmds.push(startLine);
            // FIX: Se il file/dir non esiste sull'HOST (es. lib64 che abbiamo creato noi),
            // lo rimuoviamo semplicemente dalla destinazione e passiamo al prossimo.
            if (!fs.existsSync(hostPath)) {
                cmds.push(`\n# ${dirname} does not exist on host (custom created) -> remove`);
                cmds.push(await rexec(`rm -rf ${this.settings.work_dir.merged}/${dirname}`, this.verbose));
                continue;
            }
            // Da qui in poi siamo sicuri che hostPath esiste, quindi fs.statSync non crasherà
            if (fs.statSync(hostPath).isDirectory()) {
                cmds.push(`\n# directory: ${dirname}`);
                if (this.copied(dirname)) {
                    cmds.push(`\n# ${dirname} was copied, do nothings`);
                    continue;
                }
                else if (this.mergedAndOverlay(dirname)) {
                    cmds.push(`\n# ${dirname} has overlay`, `\n# First, umount it from ${this.settings.config.snapshot_dir}`);
                    cmds.push(await rexec(`umount ${this.settings.work_dir.merged}/${dirname}`, this.verbose), `\n# Second, umount it from ${this.settings.work_dir.lowerdir}`);
                    cmds.push(await rexec(`umount ${this.settings.work_dir.lowerdir}/${dirname}`, this.verbose));
                }
                else if (this.merged(dirname)) {
                    cmds.push(await rexec(`umount ${this.settings.work_dir.merged}/${dirname}`, this.verbose));
                }
                cmds.push(`\n# remove in ${this.settings.work_dir.merged} and ${this.settings.work_dir.lowerdir}`);
                const nest = this.settings.config.snapshot_dir.split('/');
                // We can't remove first level nest
                if (dirname !== nest[1]) {
                    cmds.push(await rexec(`rm -rf ${this.settings.work_dir.merged}/${dirname}`, this.verbose));
                }
            }
            else if (fs.statSync(hostPath).isFile()) {
                cmds.push(`\n# ${dirname} = file`);
                cmds.push(await rexec(`rm -f ${this.settings.work_dir.merged}/${dirname}`, this.verbose));
            }
            else if (fs.statSync(hostPath).isSymbolicLink()) {
                cmds.push(`\n# ${dirname} = symbolicLink`);
                cmds.push(await rexec(`rm -f ${this.settings.work_dir.merged}/${dirname}`, this.verbose));
            }
        }
    }
    if (this.clone || this.fullcrypt) {
        cmds.push(await rexec(`umount ${this.settings.work_dir.merged}/home`, this.verbose));
    }
    Utils.writeXs(`${this.settings.work_dir.bin}ubind`, cmds);
}
/**
 * Crea il path se non esiste
 * @param path
 */
async function makeIfNotExist(path, verbose = false) {
    if (verbose) {
        console.log(`Ovary: makeIfNotExist(${path})`);
    }
    const echo = Utils.setEcho(verbose);
    let cmd = `# ${path} alreasy exist`;
    if (!fs.existsSync(path)) {
        cmd = `mkdir ${path} -p`;
        await exec(cmd, echo);
    }
    return cmd;
}
