import { t, ts } from "../../libs/i18n";
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";

const isDirectory = (e: FileSystemEntry): e is FileSystemDirectoryEntry => e.isDirectory
const isFile = (e: FileSystemEntry): e is FileSystemFileEntry => e.isFile

const getDirectory = (directory: FileSystemDirectoryEntry, path: string): Promise<FileSystemEntry> => new Promise((res, rej) => directory.getDirectory(path, {}, d => res(d), e => rej()));
const getParent = (directory: FileSystemDirectoryEntry): Promise<FileSystemEntry> => new Promise((res, rej) => directory.getParent(d => res(d), e => rej()));
const getFile = (directory: FileSystemDirectoryEntry, path: string): Promise<FileSystemEntry> => new Promise((res, rej) => directory.getFile(path, {}, d => res(d), e => rej()));
const getFiles = async (directory: FileSystemDirectoryEntry): Promise<Array<FileSystemEntry>> => {
    let reader = directory.createReader();
    let files: Array<FileSystemEntry> = [];
    let currentFiles: number = 1e9;
    while (currentFiles != 0) {
        let entries = await new Promise<Array<FileSystemEntry>>(r => reader.readEntries(r));
        files = files.concat(entries);
        currentFiles = entries.length;
    }
    return files;
};

const validateDirectories = async (base: FileSystemDirectoryEntry, path: string): Promise<boolean> => {
    const pathTrail = path.split("/");
    let directory: FileSystemDirectoryEntry = base;
    for (let part of pathTrail) {
        let newDirectory = await getDirectory(directory, part).catch(_ => null);
        if (newDirectory && isDirectory(newDirectory)) {
            directory = newDirectory;
        } else
            return false;
    };
    return true
}

const getDirectoryFromPath = async (base: FileSystemDirectoryEntry, path: string): Promise<FileSystemDirectoryEntry | null> => {
    const pathTrail = path.split("/");
    let directory: FileSystemDirectoryEntry = base;
    for (let part of pathTrail) {
        let newDirectory = await getDirectory(directory, part).catch(_ => null);
        if (newDirectory && isDirectory(newDirectory)) {
            directory = newDirectory;
        } else
            return null;
    };
    return directory;
}

const scanRecursive = async (root: FileSystemDirectoryEntry, target: string): Promise<FileSystemDirectoryEntry | undefined> => {
    let directories: FileSystemEntry[] = [root];

    while (directories.length > 0) {
        const directory = directories[0] as FileSystemDirectoryEntry;
        if (directory.isDirectory) {
            if (directory.name == target)
                return directory;
            let children: FileSystemEntry[] = await new Promise(r => directory.createReader().readEntries(d => r(d)));
            directories = [
                ...directories,
                ...(children.filter(v => v.isDirectory))
            ];
        }
        directories.shift();
    }

    return;
}

export let ddsDB: IDBDatabase | undefined ;

/* Technically, processName should be in the translation file but I figured it was such a small thing that it didn't REALLY matter... */
const DIRECTORY_PATHS = ([
    {
        folder: "ddsImage",
        processName: "Characters",
        path: "characterThumbnail",
        filter: (name: string) => name.substring(name.length - 6, name.length) == "02.dds",
        id: (name: string) => `0${name.substring(17, 21)}${name.substring(23, 24)}`
    },
    {
        folder: "namePlate",
        processName: "Nameplates",
        path: "nameplate",
        filter: () => true,
        id: (name: string) => name.substring(17, 25)
    },
    {
        folder: "avatarAccessory",
        processName: "Avatar Accessory Thumbnails",
        path: "avatarAccessoryThumbnail",
        filter: (name: string) => name.substring(14, 18) == "Icon",
        id: (name: string) => name.substring(19, 27)
    },
    {
        folder: "avatarAccessory",
        processName: "Avatar Accessories",
        path: "avatarAccessory",
        filter: (name: string) => name.substring(14, 17) == "Tex",
        id: (name: string) => name.substring(18, 26)
    },
    {
        folder: "texture",
        processName: "Surfboard Textures",
        useFileName: true,
        path: "surfboard",
        filter: (name: string) =>
            ([
                "CHU_UI_Common_Avatar_body_00.dds",
                "CHU_UI_Common_Avatar_face_00.dds",
                "CHU_UI_Common_01_v11.dds",
                "CHU_UI_TeamEmblem_01_v14.dds",
                "CHU_UI_title_rank_00_v10.dds"
            ]).includes(name),
        id: (name: string) => name
    }
] satisfies {folder: string, processName: string, path: string, useFileName?: boolean, filter: (name: string) => boolean, id: (name: string) => string}[] )

export const scanOptionFolder = async (optionFolder: FileSystemDirectoryEntry, progressUpdate: (progress: number, text: string) => void) => {
    let filesToProcess: Record<string, FileSystemFileEntry[]> = {};
    let directories = (await getFiles(optionFolder))
        .filter(directory => isDirectory(directory) && ((directory.name.substring(0, 1) == "A" && directory.name.length == 4) || directory.name == "surfboard"))

    for (let directory of directories)
        if (isDirectory(directory)) {
            for (const directoryData of DIRECTORY_PATHS) {
                let folder = await getDirectoryFromPath(directory, directoryData.folder).catch(_ => null) ?? [];
                if (folder) {
                    if (!filesToProcess[directoryData.path])
                        filesToProcess[directoryData.path] = [];
                    for (let dataFolderEntry of await getFiles(folder as FileSystemDirectoryEntry).catch(_ => null) ?? [])
                        if (isDirectory(dataFolderEntry)) {
                            for (let dataEntry of await getFiles(dataFolderEntry as FileSystemDirectoryEntry).catch(_ => null) ?? [])
                                if (isFile(dataEntry) && directoryData.filter(dataEntry.name))
                                    filesToProcess[directoryData.path].push(dataEntry);
                        } else if (isFile(dataFolderEntry) && directoryData.filter(dataFolderEntry.name))
                            filesToProcess[directoryData.path].push(dataFolderEntry);
                }
            }
        }

    let data = [];

    for (const [folder, files] of Object.entries(filesToProcess)) {
        let reference = DIRECTORY_PATHS.find(r => r.path == folder);
        for (const [idx, file] of files.entries()) {
            progressUpdate((idx / files.length) * 100, `${t("userbox.new.setup.processing_file")} ${reference?.processName ?? "?"}...`)
            data.push({
                path: `${folder}:${reference?.id(file.name)}`, name: file.name, blob: await new Promise<File>(res => file.file(res))
            });
        }
    }

    progressUpdate(100, `${t("userbox.new.setup.finalizing")}...`)

    let transaction = ddsDB?.transaction(['dds'], 'readwrite', { durability: "strict" })
    if (!transaction) return; // TODO: bubble error up to user
    transaction.onerror = e => e.preventDefault()
    let objectStore = transaction.objectStore('dds');
    for (let object of data)
        objectStore.put(object)

    // await transaction completion
    await new Promise(r => transaction.addEventListener("complete", r, {once: true}))
};

export function initializeDb() : Promise<void> {
    return new Promise(r => {
        const dbRequest = indexedDB.open("userboxChusanDDS", 1)
        dbRequest.addEventListener("upgradeneeded", (event) => {
            if (!(event.target instanceof IDBOpenDBRequest)) return
            ddsDB = event.target.result;
            if (!ddsDB) return;

            const store = ddsDB.createObjectStore('dds', { keyPath: 'path' });
            store.createIndex('path', 'path', { unique: true })
            store.createIndex('name', 'name', { unique: false })
            store.createIndex('blob', 'blob', { unique: false })
            r();
        });
        dbRequest.addEventListener("success", () => {
            ddsDB = dbRequest.result;
            r();
        })
    })
}

export async function userboxFileProcess(folder: FileSystemEntry, progressUpdate: (progress: number, progressString: string) => void): Promise<string | null> {
    if (!isDirectory(folder))
        return t("userbox.new.error.invalidFolder")

    initializeDb();
    const optionFolder = await scanRecursive(folder, "A001");
    if (optionFolder)
        await scanOptionFolder((await getParent(optionFolder)) as FileSystemDirectoryEntry, progressUpdate);
    const dataFolder = await scanRecursive(folder, "A000");
    if (dataFolder)
        await scanOptionFolder((await getParent(dataFolder)) as FileSystemDirectoryEntry, progressUpdate);
    useLocalStorage("userboxURL", "").value = "";
    useLocalStorage("userboxNew", false).value = true;
    useLocalStorage("userboxNewProfile", false).value = true;
    location.reload();

    return null
}
