/**
 * fileSystem
 *
 * @author Cator Vee  hi@catorv.com
 */
const fs = require('fs')
const path = require('path')

const shell = require('../utils/shell')
const { XUPD_FILE_NAME } = require('../constants')

let _rootDir = ''

/**
 * 返回项目根路径
 * @returns {string}
 */
function rootDir () {
    if (_rootDir) return _rootDir

    let dir = path.normalize(process.cwd())
    while (dir && dir !== '/') {
        const configFile = path.resolve(dir, XUPD_FILE_NAME)
        if (fs.existsSync(configFile)) {
            _rootDir = dir
            return dir
        }
        dir = path.dirname(dir)
    }

    console.fatal('not a xUPD project.')
}

/**
 * 设置项目跟路径值 (有风险，谨慎使用)
 * @param dir
 * @private
 */
function _setRootDir (dir) {
    _rootDir = path.normalize(dir)
}

/**
 * 解决项目相对路径
 * @param file
 * @returns {string}
 * @private
 */
function _resolve (file) {
    return path.resolve(rootDir(), file)
}

function _resolveAll (files) {
    return Array.from(files).map(_resolve)
}

/**
 * 判断文件是否存在
 * @param file
 * @returns {boolean}
 */
function exists (file) {
    return fs.existsSync(_resolve(file))
}

/**
 * 读取文件内容
 * @param file
 * @param options
 * @returns {Promise<unknown>}
 */
async function readFile (file, options) {
    return new Promise((resolve, reject) => {
        fs.readFile(_resolve(file), options, (error, data) => {
            error ? reject(error) : resolve(data)
        })
    })
}

/**
 * 将内容写入文件
 * @param file
 * @param data
 * @param options
 * @returns {Promise<unknown>}
 */
async function writeFile (file, data, options) {
    return new Promise(((resolve, reject) => {
        fs.writeFile(_resolve(file), data, options, _errorHandler(resolve, reject))
    }))
}

/**
 * 获取文件列表
 * @param rootPath
 * @param filter
 * @returns {[]}
 */
function allFilesAndDirectories (rootPath, filter) {
    if (typeof rootPath === 'function') {
        filter = rootPath
        rootPath = null
    }

    const dir = rootPath ? _resolve(rootPath) : rootDir()

    const walk = dir => {
        let result = []
        let files = fs.readdirSync(dir)
        for (let index = 0; index < files.length; index++) {
            const filepath = path.resolve(dir, files[index])
            const stat = fs.statSync(filepath)

            if (stat.isDirectory()) {
                result = result.concat(walk(filepath))
                result.push({ file: false, path: filepath })
            }

            if (stat.isFile()) {
                result.push({ file: true, path: filepath })
            }
        }
        return result
    }

    let result = walk(dir)
    if (filter) {
        result = result.filter(filter)
    }
    return result
}

/**
 * Moves source file(s) to dest.
 * @param source
 * @param dest
 * @returns {Promise<*>}
 */
async function mv (source, dest) {
    return shell.mv(_resolve(source), _resolve(dest))
}

/**
 * 删除文件/目录
 * @returns {Promise<*>}
 */
async function rm () {
    return shell.rm('-rf', _resolveAll(arguments))
}

/**
 * 创建目录(递归)
 * @returns {Promise<*>}
 */
async function mkdir () {
    return shell.mkdir('-p', _resolveAll(arguments))
}

/**
 * 对文件进行批量操作
 * @param source 被操作的文件
 * @param actions 操作指令列表
 * @returns {Promise<void>}
 */
async function batch (source, actions) {
    let file = _resolve(source)
    console.debug(`处理 ${path.relative(process.cwd(), file)}`)

    if (!fs.existsSync(file)) {
        console.fatal(`not such file "${file}"`)
        return
    }

    for (const action of actions) {
        if ('replace' in action) {
            console.debug(`  > replace "${action.replace}" to "${action.to}"`)
            await shell.sed('-i', action.replace, action.to, file)
        } else if ('moveTo' in action) {
            let dest = _resolve(action.moveTo)
            console.debug(`  > move to ${path.relative(process.cwd(), dest)}`)
            await shell.mv(file, dest)
            file = dest
        }
    }
}

function _errorHandler (resolve, reject) {
    return error => error ? reject(error) : resolve()
}

module.exports = {
    rootDir, _setRootDir, resolve: _resolve, allFilesAndDirectories,
    exists, readFile, writeFile, mv, rm, mkdir, batch
}
