import currify from 'currify';
import wraptile from 'wraptile';
import {promisify} from 'es6-promisify';
import exec from 'execon';
import load from 'load.js';
import {tryToCatch} from 'try-to-catch';
import {encode} from '#common/entity';
import {removeExtension} from './remove-extension.js';
import {setListeners} from './set-listeners.js';
import {getNextCurrentName} from './get-next-current-name.js';

const {DOM, CloudCmd} = globalThis;

const removeQuery = (a) => a.replace(/\?.*/, '');

const Name = 'Operation';

const {config} = CloudCmd;
const {Dialog, Images} = DOM;

const authCheck = wraptile(_authCheck);
const Operation = {};

let Loaded;

let copyFn;
let moveFn;
let deleteFn;
let extractFn;
let packZipFn;
let packTarFn;

const Info = DOM.CurrentInfo;
const showLoad = Images.show.load.bind(null, 'top');

const processFiles = currify(_processFiles);

const noFilesCheck = () => {
    const {length} = DOM.getActiveFiles();
    const is = Boolean(!length);
    
    if (is)
        return Dialog.alert.noFiles();
    
    return is;
};

export const init = promisify((callback) => {
    showLoad();
    
    exec.series([
        DOM.loadSocket,
        async (callback) => {
            if (config('dropbox'))
                return callback();
            
            const {prefix, prefixSocket} = CloudCmd;
            
            await loadAll();
            await initOperations(prefix, prefixSocket, callback);
        },
        (callback) => {
            Loaded = true;
            Images.hide();
            callback();
        },
    ], callback);
});

function _authCheck(spawn, ok) {
    const accept = wraptile(ok);
    const alertDialog = wraptile(Dialog.alert);
    
    spawn.on('accept', accept(spawn));
    spawn.on('reject', alertDialog('Wrong credentials!'));
    spawn.emit('auth', config('username'), config('password'));
}

const onConnect = currify((fn, operator) => {
    setOperations(operator);
    fn();
});

async function initOperations(prefix, socketPrefix, fn) {
    socketPrefix = `${socketPrefix}/fileop`;
    
    const operator = await globalThis.fileop({
        prefix,
        socketPrefix,
    });
    
    operator.on('connect', authCheck(operator, onConnect(fn)));
}

function setOperations(operator) {
    packTarFn = ({from, to, names}, callback) => {
        const operation = 'Tar';
        const listen = setListeners({
            operation,
            callback,
            noContinue: true,
            from,
            to,
        });
        
        operator
            .tar(from, to, names)
            .then(listen);
    };
    
    packZipFn = ({from, to, names}, callback) => {
        const operation = 'Zip';
        const listen = setListeners({
            operation,
            callback,
            noContinue: true,
            from,
            to,
        });
        
        operator
            .zip(from, to, names)
            .then(listen);
    };
    
    deleteFn = (from, files, callback) => {
        from = removeQuery(from);
        
        const operation = 'Delete';
        const listen = setListeners({
            operation,
            callback,
            from,
        });
        
        operator
            .remove(from, files)
            .then(listen);
    };
    
    copyFn = ({from, to, names}, callback) => {
        const operation = 'Copy';
        const listen = setListeners({
            operation,
            callback,
            from,
            to,
            names,
        });
        
        operator
            .copy(from, to, names)
            .then(listen);
    };
    
    moveFn = ({from, to, names}, callback) => {
        const operation = 'Move';
        const listen = setListeners({
            operation,
            callback,
            from,
            to,
        });
        
        operator
            .move(from, to, names)
            .then(listen);
    };
    
    extractFn = ({from, to}, callback) => {
        const operation = 'Extract';
        const listen = setListeners({
            operation,
            callback,
            noContinue: true,
            from,
            to,
        });
        
        operator
            .extract(from, to)
            .then(listen);
    };
}

function getPacker(type) {
    if (type === 'zip')
        return packZipFn;
    
    return packTarFn;
}

export function hide() {
    CloudCmd.View.hide();
}

export function show(operation, data) {
    if (!Loaded)
        return;
    
    if (operation === 'copy')
        return Operation.copy(data);
    
    if (operation === 'move')
        return Operation.move(data);
    
    if (operation === 'delete')
        return Operation.delete();
    
    if (operation === 'delete:silent')
        return Operation.deleteSilent();
    
    if (operation === 'pack')
        return Operation.pack();
    
    if (operation === 'extract')
        return Operation.extract();
}

Operation.copy = processFiles({
    type: 'copy',
});

Operation.move = processFiles({
    type: 'move',
});

Operation.delete = promptDelete;

Operation.deleteSilent = deleteSilent;

Operation.pack = () => {
    const isZip = config('packer') === 'zip';
    twopack('pack', isZip ? 'zip' : 'tar');
};

Operation.extract = () => {
    twopack('extract');
};

/**
 * prompt and delete current file or selected files
 *
 * @currentFile
 */
async function promptDelete() {
    if (noFilesCheck())
        return;
    
    const msgAsk = 'Do you really want to delete the ';
    const msgSel = 'selected ';
    
    const files = DOM.getActiveFiles();
    const names = DOM.getFilenames(files);
    const n = names.length;
    
    let msg;
    
    if (n) {
        let name = '';
        
        for (let i = 0; i < 5 && i < n; i++)
            name += '\n' + names[i];
        
        if (n >= 5)
            name += '\n...';
        
        msg = msgAsk + msgSel + n + ' files/directories?\n' + encode(name);
    } else {
        const current = DOM.getCurrentFile();
        const isDir = DOM.isCurrentIsDir(current);
        const getType = (isDir) => isDir ? 'directory' : 'file';
        
        const type = getType(isDir) + ' ';
        
        const name = DOM.getCurrentName(current);
        
        msg = msgAsk + msgSel + type + name + '?';
    }
    
    const [cancel] = await Dialog.confirm(msg);
    
    if (cancel)
        return;
    
    deleteSilent(files);
}

/**
 * delete current or selected files
 *
 * @files
 */
function deleteSilent(files = DOM.getActiveFiles()) {
    const query = '?files';
    const path = Info.dirPath;
    
    if (noFilesCheck())
        return;
    
    showLoad();
    
    const removedNames = DOM.getFilenames(files);
    const names = DOM.CurrentInfo.files.map(DOM.getCurrentName);
    const currentName = DOM.getCurrentName();
    const nextCurrentName = getNextCurrentName(currentName, names, removedNames);
    
    deleteFn(path + query, removedNames, async () => {
        await CloudCmd.refresh();
        
        const names = Info.files.map(DOM.getCurrentName);
        const isCurrent = names.includes(currentName);
        
        const name = isCurrent ? currentName : nextCurrentName;
        
        DOM.setCurrentByName(name);
    });
}

/*
 * process files (copy or move)
 * @param data
 * @param operation
 */
async function _processFiles(options, data) {
    let selFiles;
    let files;
    let panel;
    let shouldAsk;
    let ok;
    
    let from = '';
    let to = '';
    
    let names = [];
    
    if (data) {
        ({
            from,
            to,
            names,
        } = data);
        
        ({panel} = Info);
    } else {
        from = Info.dirPath;
        to = DOM.getNotCurrentDirPath();
        selFiles = DOM.getSelectedFiles();
        names = DOM.getFilenames(selFiles);
        data = {};
        shouldAsk = true;
        panel = Info.panelPassive;
    }
    
    if (!names.length)
        names.push(DOM.getCurrentName());
    
    const [name] = names;
    
    const sameName = DOM.getCurrentByName(name, panel);
    
    if (!data && noFilesCheck())
        return;
    
    const {type} = options;
    
    const isCopy = type === 'copy';
    const option = isCopy ? 'confirmCopy' : 'confirmMove';
    const title = isCopy ? 'Copy' : 'Rename/Move';
    const operation = isCopy ? copyFn : moveFn;
    
    if (shouldAsk && config(option)) {
        const [cancel, newTo] = await prompt(title, to, names.map(encode));
        
        if (!cancel)
            ask(newTo);
        
        return;
    }
    
    ask(to);
    
    function ask(to) {
        ok = from !== to && to;
        
        if (ok && !shouldAsk || !sameName)
            return go();
        
        const str = `"${name}" already exist. Overwrite?`;
        const cancel = false;
        
        Dialog
            .confirm(str, {
                cancel,
            })
            .then(go);
        
        function go() {
            showLoad();
            
            files = {
                from,
                to,
                names,
            };
            
            operation(files, async () => {
                await DOM.Storage.remove(from);
                
                const {panel, panelPassive} = Info;
                
                if (!Info.isOnePanel)
                    CloudCmd.refresh({
                        panel: panelPassive,
                        noCurrent: true,
                    });
                
                CloudCmd.refresh({
                    panel,
                });
            });
        }
    }
}

function checkEmpty(name, operation) {
    if (!operation)
        throw Error(`${name} could not be empty!`);
}

function twopack(operation, type) {
    let op;
    let fileFrom;
    let currentName = Info.name;
    
    const {path, dirPath} = Info;
    
    const activeFiles = DOM.getActiveFiles();
    const names = DOM.getFilenames(activeFiles);
    
    checkEmpty('operation', operation);
    
    if (!names.length)
        return Dialog.alert.noFiles();
    
    switch(operation) {
    case 'extract':
        op = extractFn;
        
        fileFrom = {
            from: path,
            to: dirPath,
        };
        
        currentName = removeExtension(currentName);
        
        break;
    
    case 'pack':
        op = getPacker(type);
        
        if (names.length > 1)
            currentName = Info.dir;
        
        currentName += DOM.getPackerExt(type);
        
        fileFrom = {
            from: dirPath,
            to: dirPath + currentName,
            names,
        };
        break;
    }
    
    showLoad();
    
    op(fileFrom, (error) => {
        !error && CloudCmd.refresh({
            currentName,
        });
    });
}

async function prompt(msg, to, names) {
    const n = names.length;
    const [name] = names;
    
    msg += ' ';
    
    if (names.length > 1)
        msg += `${n} file(s)`;
    else
        msg += `"${name}"`;
    
    msg += ' to';
    
    return await Dialog.prompt(msg, to);
}

globalThis.CloudCmd[Name] = {
    init,
    hide,
    show,
};

async function loadAll() {
    const {prefix} = globalThis.CloudCmd;
    const file = `${prefix}/fileop/fileop.js`;
    
    const [error] = await tryToCatch(load.js, file);
    
    if (error)
        Dialog.alert(error.message);
    
    Loaded = true;
}
