import { MODULE_ID } from '../constants.js';
import { Preset } from '../presets/preset.js';
import { isAudio, isImage, isVideo, loadImageVideoDimensions } from '../utils.js';

export async function uploadFiles(files, subDirectory = 'canvas', singlePreset = false, shiftKey = false) {
    let { source, bucket, target, overwrite } = game.settings.get(MODULE_ID, 'dragUpload');
    target += '/' + subDirectory + '/';

    const uploadedFiles = [];

    for (let file of files) {
        const name = file.name;

        let type;
        if (isVideo(name)) type = 'video';
        else if (isAudio(name)) type = 'audio';
        else if (isImage(name)) type = 'image';
        else {
            console.warn(`Invalid file format: ${name}`);
            continue;
        }

        const path = target + type;

        let existingNames;
        try {
            const result = await checkCreateUploadFolder(path, source, bucket);
            existingNames = result.files?.map((f) => decodeURIComponent(f)) ?? [];
            if (typeof ForgeVTT !== undefined) existingNames = existingNames.map((f) => f.split('/').pop());
        } catch (e) {
            console.error(e);
            continue;
        }

        if (!overwrite) {
            const uniqueName = getUniqueFileName(existingNames ?? [], file.name);
            if (!uniqueName === file.name) {
                file = new File([file], uniqueName, {
                    type: file.type,
                    lastModified: file.lastModified,
                });
            }
        }

        const result = await foundry.applications.apps.FilePicker.upload(
            source,
            path,
            file,
            {},
            source === 's3' ? { bucket } : {},
        );

        if (result.status === 'success') uploadedFiles.push(result.path);
        else console.warn('Failed to upload: ' + file.name, result);
    }

    return filesToPresets(uploadedFiles, singlePreset, shiftKey);
}

/**
 * Check if a folder exists, if not attempts to create it
 * @param {string} target
 * @param {string} source
 * @param {string} bucket
 * @returns {boolean} true if a folder exists, false if it doesn't
 */
async function checkCreateUploadFolder(target, source, bucket) {
    // Attempt to browse the folder
    // If the operation throws an error it means the folder does not exists and we will attempt to create it
    try {
        return await foundry.applications.apps.FilePicker.implementation.browse(
            source,
            target,
            source === 's3' ? { bucket } : {},
        );
    } catch (e) {
        const folders = target.split('/');
        if (folders.length > 1)
            await checkCreateUploadFolder(folders.slice(0, folders.length - 1).join('/'), source, bucket);
        await FilePicker.createDirectory(source, target, source === 's3' ? { bucket } : {});
    }

    return await foundry.applications.apps.FilePicker.browse(source, target, source === 's3' ? { bucket } : {});
}

async function filesToPresets(files, singlePreset = false, shiftKey = false) {
    const settings = game.settings.get(MODULE_ID, 'dragUpload');
    const presets = [];

    const templatePresets = {};
    const getTemplatePreset = async function (documentName) {
        if (!templatePresets[documentName]) {
            if (settings.presets[documentName])
                templatePresets[documentName] = await MassEdit.getPreset({
                    uuid: settings.presets[documentName],
                    full: true,
                });
            if (!templatePresets[documentName])
                templatePresets[documentName] = new Preset({ documentName, data: [{}] });
        }
        return templatePresets[documentName].clone();
    };

    for (const src of files) {
        if (isImage(src) || isVideo(src)) {
            const template = await getTemplatePreset(shiftKey ? 'Token' : 'Tile');
            const data = template.data[0];

            foundry.utils.setProperty(data, 'texture.src', src);
            if ((!data.width || !data.height) && template.documentName === 'Tile') {
                const { width, height } = await loadImageVideoDimensions(src);
                if (!data.width && !data.height) {
                    data.width = width;
                    data.height = height;
                } else if (!data.width) {
                    data.width = width * (data.height / height);
                } else if (!data.height) {
                    data.height = height * (data.width / width);
                }
            }
            template.img = src;
            template.name = src.split('/').pop().split('.')[0];
            presets.push(template);
        } else if (isAudio(src)) {
            const template = await getTemplatePreset('AmbientSound');
            template.data[0].path = src;
            template.name = src.split('/').pop().split('.')[0];
            presets.push(template);
        }
    }

    presets.forEach((p) => {
        p.id = foundry.utils.randomID();
    });

    if (presets.length > 1 && singlePreset) {
        return [packPresets(presets)];
    }

    return presets;
}

function getUniqueFileName(existingNames, desiredName) {
    const existing = new Set(existingNames);

    if (!existing.has(desiredName)) return desiredName;

    const dotIndex = desiredName.lastIndexOf('.');
    const hasExt = dotIndex > 0;
    const base = hasExt ? desiredName.slice(0, dotIndex) : desiredName;
    const ext = hasExt ? desiredName.slice(dotIndex) : '';

    const suffixMatch = base.match(/^(.*)_(\d+)$/);
    const rootBase = suffixMatch ? suffixMatch[1] : base;
    let counter = suffixMatch ? parseInt(suffixMatch[2], 10) + 1 : 1;

    let candidate;
    do {
        candidate = `${rootBase}_${counter}${ext}`;
        counter++;
    } while (existing.has(candidate));

    return candidate;
}

function packPresets(presets) {
    const rectangles = presets.map((p) => {
        const data = p.data[0];
        if (p.documentName === 'Tile') {
            return { width: data.width, height: data.height, preset: p };
        } else if (p.documentName === 'Token') {
            return {
                width: (data.width ?? 0) * canvas.dimensions.size,
                height: (data.height ?? 0) * canvas.dimensions.size,
            };
        } else {
            //AmbientSound
            const radius = ((data.radius ?? 20) / canvas.dimensions.distance) * canvas.dimensions.size;
            return { width: radius * 2, height: radius * 2, preset: p };
        }
    });

    let containerWidth = Math.ceil(Math.sqrt(rectangles.reduce((sum, r) => sum + r.width * r.height, 0)));
    let shelfY = 0;
    let shelfHeight = 0;
    let shelfX = 0;

    for (let rect of rectangles) {
        const { width, height } = rect;

        // New shelf needed
        if (shelfX + width > containerWidth) {
            shelfY += shelfHeight;
            shelfX = 0;
            shelfHeight = 0;
        }

        // Place rectangle
        rect.preset.data[0].x = shelfX;
        rect.preset.data[0].y = shelfY;
        if (rect.preset.documentName === 'AmbientSound') {
            rect.preset.data[0].x += width / 2;
            rect.preset.data[0].y += height / 2;
        }

        shelfX += width;
        shelfHeight = Math.max(shelfHeight, height);
    }

    const mainPreset =
        presets.find((p) => p.documentName === 'Token') ?? presets.find((p) => p.documentName === 'Tile') ?? presets[0];

    for (const preset of presets) {
        if (!preset.id === mainPreset.id) continue;

        if (preset.documentName === mainPreset.documentName) {
            mainPreset.data = mainPreset.data.concat(preset.data);
        } else {
            mainPreset.attached = mainPreset.attached.concat(
                preset.data.map((d) => {
                    return { documentName: preset.documentName, data: d };
                }),
            );
        }
    }

    mainPreset.name = 'Multi Preset';

    return mainPreset;
}
