import { basename, dirname, join, SEPARATOR } from '@std/path/posix';
import * as fflate from 'fflate/browser';
import { type ExistsOptions, type WriteOptions, type ZipOptions } from 'happy-opfs';
import { Err, Ok, RESULT_VOID, type IOResult, type VoidIOResult } from 'happy-rusty';
import type { ReadFileContent, ReadOptions, StatOptions, WriteFileContent } from './fs_define.ts';
import { errToMkdirResult, errToRemoveResult, fileErrorToResult, getAbsolutePath, getExistsResult, getFs, getReadFileEncoding, getWriteFileContents, isNotFoundError } from './mina_fs_shared.ts';

/**
 * 安全地调用同步接口。
 * @param op - 同步操作。
 * @param errToResult - 错误处理函数。
 * @returns
 */
function trySyncOp<T>(op: () => T, errToResult: (err: WechatMinigame.FileError) => IOResult<T> = fileErrorToResult): IOResult<T> {
    try {
        const res = op();
        return Ok(res);
    } catch (e: unknown) {
        return errToResult(e as WechatMinigame.FileError);
    }
}

/**
 * `mkdir` 的同步版本。
 */
export function mkdirSync(dirPath: string): VoidIOResult {
    const absPath = getAbsolutePath(dirPath);

    return trySyncOp(() => getFs().mkdirSync(absPath, true), errToMkdirResult);
}

/**
 * `move` 的同步版本。
 */
export function moveSync(srcPath: string, destPath: string): VoidIOResult {
    const absSrcPath = getAbsolutePath(srcPath);
    const absDestPath = getAbsolutePath(destPath);

    return trySyncOp(() => getFs().renameSync(absSrcPath, absDestPath));
}

/**
 * `readDir` 的同步版本。
 */
export function readDirSync(dirPath: string): IOResult<string[]> {
    const absPath = getAbsolutePath(dirPath);

    return trySyncOp(() => getFs().readdirSync(absPath));
}

/**
 * `readFile` 的同步版本。
 */
export function readFileSync(filePath: string, options: ReadOptions & {
    encoding: 'utf8',
}): IOResult<string>;
export function readFileSync(filePath: string, options?: ReadOptions & {
    encoding: 'binary',
}): IOResult<ArrayBuffer>;
export function readFileSync<T extends ReadFileContent>(filePath: string, options?: ReadOptions): IOResult<T> {
    const absPath = getAbsolutePath(filePath);
    const encoding = getReadFileEncoding(options);

    return trySyncOp(() => getFs().readFileSync(absPath, encoding) as T);
}

/**
 * `remove` 的同步版本。
 */
export function removeSync(path: string): VoidIOResult {
    const statRes = statSync(path);

    if (statRes.isErr()) {
        // 不存在当做成功
        return isNotFoundError(statRes.unwrapErr()) ? RESULT_VOID : statRes.asErr();
    }

    const absPath = getAbsolutePath(path);

    return trySyncOp(() => {
        // 文件夹还是文件
        if (statRes.unwrap().isDirectory()) {
            getFs().rmdirSync(absPath, true);
        } else {
            getFs().unlinkSync(absPath);
        }
    }, errToRemoveResult);
}

/**
 * `stat` 的同步版本。
 */
export function statSync(path: string): IOResult<WechatMinigame.Stats>;
export function statSync(path: string, options: StatOptions & {
    recursive: true;
}): IOResult<WechatMinigame.FileStats[]>;
export function statSync(path: string, options?: StatOptions): IOResult<WechatMinigame.Stats | WechatMinigame.FileStats[]>;
export function statSync(path: string, options?: StatOptions): IOResult<WechatMinigame.Stats | WechatMinigame.FileStats[]> {
    const absPath = getAbsolutePath(path);

    return trySyncOp(() => getFs().statSync(absPath, options?.recursive ?? false));
}

/**
 * `writeFile` 的同步版本。
 */
export function writeFileSync(filePath: string, contents: WriteFileContent, options?: WriteOptions): VoidIOResult {
    const absPath = getAbsolutePath(filePath);

    // 默认创建
    const { append = false, create = true } = options ?? {};

    if (create) {
        const res = mkdirSync(dirname(absPath));
        if (res.isErr()) {
            return res;
        }
    }

    const { data, encoding } = getWriteFileContents(contents);

    return trySyncOp(() => (append ? getFs().appendFileSync : getFs().writeFileSync)(absPath, data, encoding));
}

/**
 * `appendFile` 的同步版本。
 */
export function appendFileSync(filePath: string, contents: WriteFileContent): VoidIOResult {
    return writeFileSync(filePath, contents, {
        append: true,
    });
}

function copyFileSync(srcPath: string, destPath: string): VoidIOResult {
    return trySyncOp(() => (getFs().copyFile({
        srcPath,
        destPath,
    })));
}

/**
 * `copy` 的同步版本。
 */
export function copySync(srcPath: string, destPath: string): VoidIOResult {
    const absSrcPath = getAbsolutePath(srcPath);
    const absDestPath = getAbsolutePath(destPath);

    return statSync(absSrcPath, {
        recursive: true,
    }).andThen(statsArray => {
        // directory
        if (Array.isArray(statsArray)) {
            for (const { path, stats } of statsArray) {
                // 不能用join
                const srcEntryPath = absSrcPath + path;
                const destEntryPath = absDestPath + path;

                const res = (stats.isDirectory()
                    ? mkdirSync(destEntryPath)
                    : copyFileSync(srcEntryPath, destEntryPath));

                if (res.isErr()) {
                    return res;
                }
            }

            return RESULT_VOID;
        } else {
            // file
            return copyFileSync(absSrcPath, absDestPath);
        }
    });
}

/**
 * `exists` 的同步版本。
 */
export function existsSync(path: string, options?: ExistsOptions): IOResult<boolean> {
    const res = statSync(path);
    return getExistsResult(res, options);
}

/**
 * `emptyDir` 的同步版本。
 */
export function emptyDirSync(dirPath: string): VoidIOResult {
    const res = readDirSync(dirPath);
    if (res.isErr()) {
        return isNotFoundError(res.unwrapErr()) ? mkdirSync(dirPath) : res.asErr();
    }

    for (const name of res.unwrap()) {
        const res = removeSync(join(dirPath, name));
        if (res.isErr()) {
            return res.asErr();
        }
    }

    return RESULT_VOID;
}

/**
 * `readJsonFile` 的同步版本。
 */
export function readJsonFileSync<T>(filePath: string): IOResult<T> {
    return readTextFileSync(filePath).andThen(contents => {
        try {
            return Ok(JSON.parse(contents));
        } catch (e) {
            return Err(e as Error);
        }
    });
}

/**
 * `readTextFile` 的同步版本。
 */
export function readTextFileSync(filePath: string): IOResult<string> {
    return readFileSync(filePath, {
        encoding: 'utf8',
    });
}

/**
 * `unzip` 的同步版本。
 */
export function unzipSync(zipFilePath: string, targetPath: string): VoidIOResult {
    const absZipPath = getAbsolutePath(zipFilePath);
    const absTargetPath = getAbsolutePath(targetPath);

    return readFileSync(absZipPath).andThen(buffer => {
        const data = new Uint8Array(buffer);

        try {
            const unzipped = fflate.unzipSync(data);

            for (const path in unzipped) {
                // ignore directory
                if (path.at(-1) !== SEPARATOR) {
                    // 不能用 json，否则 http://usr 会变成 http:/usr
                    const res = writeFileSync(`${ absTargetPath }/${ path }`, unzipped[path]);
                    if (res.isErr()) {
                        return res.asErr();
                    }
                }
            }

            return RESULT_VOID;
        } catch (e) {
            return Err(e as fflate.FlateError);
        }
    });
}

/**
 * `zip` 的同步版本。
 */
export function zipSync(sourcePath: string, zipFilePath: string, options?: ZipOptions): VoidIOResult {
    const absSourcePath = getAbsolutePath(sourcePath);
    const absZipPath = getAbsolutePath(zipFilePath);

    return statSync(absSourcePath).andThen(stats => {
        const zipped: fflate.AsyncZippable = {};

        const sourceName = basename(absSourcePath);

        if (stats.isFile()) {
            // file
            const res = readFileSync(absSourcePath);
            if (res.isErr()) {
                return res.asErr();
            }

            zipped[sourceName] = new Uint8Array(res.unwrap());
        } else {
            // directory
            const res = statSync(absSourcePath, {
                recursive: true,
            });
            if (res.isErr()) {
                return res.asErr();
            }

            // default to preserve root
            const preserveRoot = options?.preserveRoot ?? true;

            for (const { path, stats } of res.unwrap()) {
                if (stats.isFile()) {
                    const entryName = preserveRoot ? join(sourceName, path) : path;
                    // 不能用 join，否则 http://usr 会变成 http:/usr
                    const res = readFileSync(absSourcePath + path);
                    if (res.isErr()) {
                        return res.asErr();
                    }

                    zipped[entryName] = new Uint8Array(res.unwrap());
                }
            }
        }

        try {
            const u8a = fflate.zipSync(zipped);
            return writeFileSync(absZipPath, u8a);
        } catch (e) {
            return Err(e as fflate.FlateError);
        }
    });
}