let source_path;
let apps = [];
let sortBy = 'created_at';
let sortDirection = 'desc';
let currently_editing_app;
let dropped_items;
let search_query;
let originalValues = {};

const APP_CATEGORIES = [
    { id: 'games', label: 'Games' },
    { id: 'developer-tools', label: 'Developer Tools' },
    { id: 'photo-video', label: 'Photo & Video' },
    { id: 'productivity', label: 'Productivity' },
    { id: 'utilities', label: 'Utilities' },
    { id: 'education', label: 'Education' },
    { id: 'business', label: 'Business' },
    { id: 'social', label: 'Social' },
    { id: 'graphics-design', label: 'Graphics & Design' },
    { id: 'music-audio', label: 'Music & Audio' },
    { id: 'news', label: 'News' },
    { id: 'entertainment', label: 'Entertainment' },
    { id: 'finance', label: 'Finance' },
    { id: 'health-fitness', label: 'Health & Fitness' },
    { id: 'lifestyle', label: 'Lifestyle' },
];

async function init_apps () {
    setTimeout(async function () {
        puter.ui.onLaunchedWithItems(async function (items) {
            source_path = items[0].path;
            // if source_path is provided, this means that the user is creating a new app/updating an existing app
            // by deploying an existing Puter folder. So we create the app and deploy it.
            if ( source_path ) {
                // todo if there are no apps, go straight to creating a new app
                $('.insta-deploy-modal').get(0).showModal();
                // set item name
                $('.insta-deploy-item-name').html(html_encode(items[0].name));
            }
        });

        // Get dev profile. This is only for puter.com for now as we don't have dev profiles in self-hosted Puter
        if ( domain === 'puter.com' ) {
            puter.apps.getDeveloperProfile(async function (dev_profile) {
                window.developer = dev_profile;
                if ( dev_profile.approved_for_incentive_program && !dev_profile.joined_incentive_program ) {
                    $('#join-incentive-program').show();
                }

                // show earn money c2a only if dev is not approved for incentive program or has already joined
                if ( !dev_profile.approved_for_incentive_program || dev_profile.joined_incentive_program ) {
                    puter.kv.get('earn-money-c2a-closed').then((value) => {
                        if ( value?.result || value === true || value === 'true' )
                        {
                            return;
                        }

                        $('#earn-money').get(0).showModal();
                    });
                }

                // show payout method tab if dev has joined incentive program
                if ( dev_profile.joined_incentive_program ) {
                    $('.tab-btn[data-tab="payout-method"]').show();
                    $('#payout-method-email').html(dev_profile.paypal);
                    $('.tab-btn-separator').show();
                }
            });
        }
        // Get apps
        puter.apps.list({ icon_size: 64 }).then((resp) => {
            apps = resp;

            // hide loading
            puter.ui.hideSpinner();

            // set apps
            if ( apps.length > 0 ) {
                if ( window.activeTab === 'apps' ) {
                    $('#no-apps-notice').hide();
                    $('#app-list').show();
                }
                $('.app-card').remove();
                apps.forEach(app => {
                    $('#app-list-table > tbody').append(generate_app_card(app));
                });
                count_apps();
                sort_apps();
                activate_tippy();
            } else {
                $('#no-apps-notice').show();
            }
        });
    }, 1000);

}

/**
 * Refreshes the list of apps in the UI.
 *
 * @param {boolean} [show_loading=false] - Whether to show a loading indicator while refreshing.
 *
 */

window.refresh_app_list = (show_loading = false) => {
    if ( show_loading )
    {
        puter.ui.showSpinner();
    }
    // get apps
    setTimeout(function () {
        // uncheck the select all checkbox
        $('.select-all-apps').prop('checked', false);

        puter.apps.list({ icon_size: 64 }).then((apps_res) => {
            puter.ui.hideSpinner();
            apps = apps_res;
            if ( apps.length > 0 ) {
                if ( window.activeTab === 'apps' ) {
                    $('#no-apps-notice').hide();
                    $('#app-list').show();
                }
                $('.app-card').remove();
                apps.forEach(app => {
                    $('#app-list-table > tbody').append(generate_app_card(app));
                });
                count_apps();
                sort_apps();
            } else {
                $('#no-apps-notice').show();
                $('#app-list').hide();
            }
            activate_tippy();
            puter.ui.hideSpinner();
        });
    }, show_loading ? 1000 : 0);
};

$(document).on('click', '.create-an-app-btn', async function (e) {
    let title = await puter.ui.prompt('Please enter a title for your app:', 'My Awesome App');

    if ( title.length > 60 ) {
        puter.ui.alert('Title cannot be longer than 60.', [
            {
                label: 'Ok',
            },
        ]);
        // todo go back to create an app prompt and prefill the title input with the title the user entered
        return;
    }
    else if ( title ) {
        create_app(title);
    }
});

if ( ! (await puter.auth.getUser()).hasDevAccountAccess ) $('.setup-account-btn').hide();
$('.setup-account-btn').on('click', async () => {
    await puter.ui.openDevPaymentsAccount();
});

async function create_app (title, source_path = null, items = null) {
    // name
    let name = slugify(title, {
        lower: true,
        strict: true,
    });

    // icon
    let icon = await getBase64ImageFromUrl('./img/app.svg');

    // open the 'Creting new app...' modal
    let start_ts = Date.now();

    puter.ui.showSpinner();

    //----------------------------------------------------
    // Create app
    //----------------------------------------------------
    puter.apps.create({
        title: title,
        name: name,
        indexURL: 'https://dev-center.puter.com/coming-soon.html',
        icon: icon,
        description: ' ',
        maximizeOnStart: false,
        background: false,
        dedupeName: true,
        metadata: {
            window_resizable: true,
            fullpage_on_landing: true,
        },
    })
        .then(async (app) => {
            let app_dir;
            // ----------------------------------------------------
            // Create app directory in AppData
            // ----------------------------------------------------
            app_dir = await puter.fs.mkdir(`/${auth_username}/AppData/${dev_center_uid}/${app.uid}`,
                            { overwrite: true, recursive: true, rename: false });
            // ----------------------------------------------------
            // Create a router for the app with a fresh hostname
            // ----------------------------------------------------
            let subdomain = `${name}-${Math.random().toString(36).substring(2)}`;
            await puter.hosting.create(subdomain, app_dir.path);

            // ----------------------------------------------------
            // Update the app with the new hostname
            // ----------------------------------------------------
            puter.apps.update(app.name, {
                title: title,
                indexURL: source_path ? `${protocol}://${subdomain}.${static_hosting_domain}` : 'https://dev-center.puter.com/coming-soon.html',
                icon: icon,
                description: ' ',
                maximizeOnStart: false,
                background: false,
                metadata: {
                    category: null, // default category on creation
                    window_resizable: true,
                    fullpage_on_landing: true,
                },
            }).then(async (app) => {
                // refresh app list
                puter.apps.list({ icon_size: 64 }).then(async (resp) => {
                    apps = resp;
                    // Close the 'Creating new app...' modal
                    // but make sure it was shown for at least 2 seconds
                    setTimeout(() => {
                        // open edit app section
                        edit_app_section(app.name);

                        // set drop area if source_path was provided or items were dropped
                        if ( source_path || items ) {
                            $('.drop-area').removeClass('drop-area-hover');
                            $('.drop-area').addClass('drop-area-ready-to-deploy');
                        }
                        puter.ui.hideSpinner();
                        // deploy app if source_path was provided
                        if ( source_path ) {
                            deploy(app, source_path);
                        } else if ( items ) {
                            deploy(app, items);
                        }
                        activate_tippy();
                    }, (Date.now() - start_ts) > 2000 ? 1 : 2000 - (Date.now() - start_ts));
                });
            }).catch(async (err) => {
                console.log(err);
            });
            // ----------------------------------------------------
            // Create a "shortcut" on the desktop
            // ----------------------------------------------------
            puter.fs.upload(new File([], app.title),
                            `/${auth_username}/Desktop`,
                            {
                                name: app.title,
                                dedupeName: true,
                                overwrite: false,
                                appUID: app.uid,
                            });
            //----------------------------------------------------
            // Increment app count
            //----------------------------------------------------
            $('.app-count').html(parseInt($('.app-count').html() ?? 0) + 1);

        }).catch(async (err) => {
            $('#create-app-error').show();
            $('#create-app-error').html(err.message);
            // scroll to top so that user sees error message
            document.body.scrollTop = document.documentElement.scrollTop = 0;
        });
}

$(document).on('click', '.deploy-btn', function (e) {
    deploy(currently_editing_app, dropped_items);
});

$(document).on('click', '.edit-app, .go-to-edit-app', function (e) {
    const cur_app_name = $(this).attr('data-app-name');
    edit_app_section(cur_app_name);
});

$(document).on('click', '.delete-app', async function (e) {
});

// generate app link
function applink (app) {
    return `${protocol}://${domain}${port ? `:${port}` : ''}/app/${app.name}`;
}

/**
 * Generates the HTML for the app editing section.
 *
 * @param {Object} app - The app object containing details of the app to be edited.
 *  *
 * @returns {string} HTML string for the app editing section.
 *
 * @description
 * This function creates the HTML for the app editing interface, including:
 * - App icon and title display
 * - Options to open, add to desktop, or delete the app
 * - Tabs for deployment and settings
 * - Form fields for editing various app properties
 * - Display of app statistics
 *
 * The generated HTML includes interactive elements and placeholders for
 * dynamic content to be filled or updated by other functions.
 *
 * @example
 * const appEditHTML = generate_edit_app_section(myAppObject);
 * $('#edit-app').html(appEditHTML);
 */

function generate_edit_app_section (app) {
    if ( app.result )
    {
        app = app.result;
    }

    let maximize_on_start = app.maximize_on_start ? 'checked' : '';

    let h = '';
    h += `
        <div class="edit-app-navbar">
            <div style="flex-grow:1;">
                <img class="app-icon" data-uid="${html_encode(app.uid)}" src="${html_encode(!app.icon ? './img/app.svg' : app.icon)}">
                <h3 class="app-title" data-uid="${html_encode(app.uid)}">${html_encode(app.title)}${app.metadata?.locked ? lock_svg_tippy : ''}</h3>
                <div style="margin-top: 4px; margin-bottom: 4px;">
                    <span class="open-app-btn" data-app-uid="${html_encode(app.uid)}" data-app-name="${html_encode(app.name)}">Open</span>
                    <span style="margin: 5px; opacity: 0.3;">&bull;</span>
                    <span class="add-app-to-desktop" data-app-uid="${html_encode(app.uid)}" data-app-title="${html_encode(app.title)}">Add Shortcut to Desktop</span>
                    <span style="margin: 5px; opacity: 0.3;">&bull;</span>
                    <span title="Delete app" class="delete-app-settings" data-app-name="${html_encode(app.name)}" data-app-title="${html_encode(app.title)}" data-app-uid="${html_encode(app.uid)}">Delete</span>
                </div>
                <a class="app-url" target="_blank" data-uid="${html_encode(app.uid)}" href="${html_encode(applink(app))}">${html_encode(applink(app))}</a>
            </div>
            <button class="back-to-main-btn button button-default">Back</button>
        </div>

        <ul class="section-tab-buttons disable-user-select">
            <li class="section-tab-btn active" data-tab="deploy"><span>Deploy</span></li>
            <li class="section-tab-btn" data-tab="info"><span>Settings</span></li>
            <li class="section-tab-btn" data-tab="analytics"><span>Analytics</span></li>
        </ul>

        <div class="section-tab active" data-tab="deploy">
            <div class="success deploy-success-msg">
                New version deployed successfully 🎉<span class="close-success-msg">&times;</span>
                <p style="margin-bottom:0;"><span class="open-app button button-action" data-uid="${html_encode(app.uid)}" data-app-name="${html_encode(app.name)}">Give it a try!</span></p>
            </div>
            <div class="drop-area disable-user-select">${drop_area_placeholder}</div>
            <button class="deploy-btn disable-user-select button button-primary disabled">Deploy Now</button>
        </div>

        <div class="section-tab" data-tab="info">
            <form style="clear:both; padding-bottom: 50px;">
                <div class="error" id="edit-app-error"></div>
                <div class="success" id="edit-app-success">App has been successfully updated.<span class="close-success-msg">&times;</span>
                <p style="margin-bottom:0;"><span class="open-app button button-action" data-uid="${html_encode(app.uid)}" data-app-name="${html_encode(app.name)}">Give it a try!</span></p>
                </div>
                <input type="hidden" id="edit-app-uid" value="${html_encode(app.uid)}">

                <h3 style="font-size: 23px; border-bottom: 1px solid #EEE; margin-top: 40px;">Basic</h3>
                <label for="edit-app-title">Title</label>
                <input type="text" id="edit-app-title" placeholder="My Awesome App!" value="${html_encode(app.title)}">

                <label for="edit-app-name">Name</label>
                <input type="text" id="edit-app-name" placeholder="my-awesome-app" style="font-family: monospace;" value="${html_encode(app.name)}">

                <label for="edit-app-index-url">Index URL</label>
                <input type="text" id="edit-app-index-url" placeholder="https://example-app.com/index.html" value="${html_encode(app.index_url)}">
                
                <label for="edit-app-app-id">App ID</label>
                <div style="overflow:hidden;">
                    <input type="text" style="width: 362px; float:left;" class="app-uid" value="${html_encode(app.uid)}" readonly><span class="copy-app-uid" style="cursor: pointer; height: 35px; display: inline-block; width: 50px; text-align: center; line-height: 35px; margin-left:5px;">${copy_svg}</span>
                </div>

                <label for="edit-app-icon">Icon</label>
                <div id="edit-app-icon" style="background-image:url(${!app.icon ? './img/app.svg' : html_encode(app.icon)});" ${app.icon ? `data-url="${html_encode(app.icon)}"` : ''}  ${app.icon ? `data-base64="${html_encode(app.icon)}"` : ''} >
                    <div id="change-app-icon">Change App Icon</div>
                </div>
                <span id="edit-app-icon-delete" style="${app.icon ? 'display:block;' : ''}">Remove icon</span>

                ${generateSocialImageSection(app)}
                <label for="edit-app-description">Description</label>
                <textarea id="edit-app-description">${html_encode(app.description)}</textarea>
                
                <label for="edit-app-category">Category</label>
                <select id="edit-app-category" class="category-select">
                    <option value="">Select a category</option>
                    ${APP_CATEGORIES.map(category =>
                        `<option value="${html_encode(category.id)}" ${app.metadata?.category === category.id ? 'selected' : ''}>${html_encode(category.label)}</option>`).join('')}
                </select>

                <label for="edit-app-filetype-associations">File Associations</label>
               <p style="margin-top: 10px; font-size:13px;">A list of file type specifiers. For example if you include <code>.txt</code> your apps could be opened when a user clicks on a TXT file.</p>
               <p style="margin-top: 5px; font-size:13px;">You can paste multiple extensions at once (comma, space, or tab separated) or press comma to add each extension.</p>
               <textarea id="edit-app-filetype-associations"  placeholder="Paste multiple extensions like: .txt, .doc, .pdf, application/json">${JSON.stringify(app.filetype_associations.map(item => ({ 'value': item })), null, app.filetype_associations.length).replace(/</g, '\\u003c')}</textarea>

                <h3 style="font-size: 23px; border-bottom: 1px solid #EEE; margin-top: 50px; margin-bottom: 0px;">Window</h3>
                <div>
                    <input type="checkbox" id="edit-app-background" name="edit-app-background" value="true" style="margin-top:30px;" ${app.background ? 'checked' : ''}>
                    <label for="edit-app-background" style="display: inline;">Run as a background process.</label>
                </div>

                <div>
                    <input type="checkbox" id="edit-app-fullpage-on-landing" name="edit-app-fullpage-on-landing" value="true" style="margin-top:30px;" ${app.metadata?.fullpage_on_landing ? 'checked' : ''} ${app.background ? 'disabled' : ''}>
                    <label for="edit-app-fullpage-on-landing" style="display: inline;">Load in full-page mode when a user lands directly on this app.</label>
                </div>

                <div>
                    <input type="checkbox" id="edit-app-maximize-on-start" name="edit-app-maximize-on-start" value="true" style="margin-top:30px;" ${maximize_on_start ? 'checked' : ''} ${app.background ? 'disabled' : ''}>
                    <label for="edit-app-maximize-on-start" style="display: inline;">Maximize window on start</label>
                </div>
                
                <div>
                    <label for="edit-app-window-width">Initial window width</label>
                    <input type="number" id="edit-app-window-width" placeholder="680" value="${html_encode(app.metadata?.window_size?.width ?? 680)}" style="width:200px;" ${maximize_on_start || app.background ? 'disabled' : ''}>
                    <label for="edit-app-window-height">Initial window height</label>
                    <input type="number" id="edit-app-window-height" placeholder="380" value="${html_encode(app.metadata?.window_size?.height ?? 380)}" style="width:200px;" ${maximize_on_start || app.background ? 'disabled' : ''}>
                </div>

                <div style="margin-top:30px;">
                    <label for="edit-app-window-top">Initial window top</label>
                    <input type="number" id="edit-app-window-top" placeholder="100" value="${app.metadata?.window_position?.top ? html_encode(app.metadata.window_position.top) : ''}" style="width:200px;" ${maximize_on_start || app.background ? 'disabled' : ''}>
                    <label for="edit-app-window-left">Initial window left</label>
                    <input type="number" id="edit-app-window-left" placeholder="100" value="${app.metadata?.window_position?.left ? html_encode(app.metadata.window_position.left) : ''}" style="width:200px;" ${maximize_on_start || app.background ? 'disabled' : ''}>
                </div>

                <div style="margin-top:30px;">
                    <input type="checkbox" id="edit-app-window-resizable" name="edit-app-window-resizable" value="true" ${app.metadata?.window_resizable ? 'checked' : ''} ${app.background ? 'disabled' : ''}>
                    <label for="edit-app-window-resizable" style="display: inline;">Resizable window</label>
                </div>

                <div style="margin-top:30px;">
                    <input type="checkbox" id="edit-app-hide-titlebar" name="edit-app-hide-titlebar" value="true" ${app.metadata?.hide_titlebar ? 'checked' : ''} ${app.background ? 'disabled' : ''}>
                    <label for="edit-app-hide-titlebar" style="display: inline;">Hide window titlebar</label>
                </div>

                <div style="margin-top:30px;">
                    <input type="checkbox" id="edit-app-set-title-to-file" name="edit-app-set-title-to-file" value="true" ${app.metadata?.set_title_to_opened_file ? 'checked' : ''} ${app.background ? 'disabled' : ''}>
                    <label for="edit-app-set-title-to-file" style="display: inline;">Automatically set window title to opened file's name</label>
                    <p>This will set your app's window title to the opened file's name when a user opens a file in your app.</p>
                </div>

                <h3 style="font-size: 23px; border-bottom: 1px solid #EEE; margin-top: 50px; margin-bottom: 0px;">Misc</h3>
                <div style="margin-top:30px;">
                    <input type="checkbox" id="edit-app-locked" name="edit-app-locked" value="true" ${app.metadata?.locked ? 'checked' : ''}>
                    <label for="edit-app-locked" style="display: inline;">Delete Protection${lock_svg}</label>
                    <p>When enabled, the app cannot be deleted. This is useful for preventing accidental deletion of important apps.</p>
                </div>

                <div style="z-index: 999; box-shadow: 10px 10px 15px #8c8c8c; overflow: hidden; position: fixed; bottom: 0; background: white; padding: 10px; width: 100%; left: 0;">
                    <button type="button" class="edit-app-save-btn button button-primary" style="margin-right: 40px;">Save</button>
                    <button type="button" class="edit-app-reset-btn button button-secondary">Reset</button>
                </div>
            </form>
        </div>
        <div class="section-tab" data-tab="analytics">
            <label for="analytics-period">Period</label>
            <select id="analytics-period" class="category-select">
                <option value="today">Today</option>
                <option value="yesterday">Yesterday</option>
                <optgroup label="──────"></optgroup>
                <option value="this_week">This week</option>
                <option value="last_week">Last week</option>
                <option value="7d">Last 7 days</option>
                <option value="30d">Last 30 days</option>
                <optgroup label="──────"></optgroup>
                <option value="this_month">This month</option>
                <option value="last_month">Last month</option>
                <optgroup label="──────"></optgroup>
                <option value="this_year">This year</option>
                <option value="last_year">Last year</option>
                <optgroup label="──────"></optgroup>
                <option value="12m">Last 12 months</option>
                <option value="all">All time</option>
            </select>
            <div style="overflow:hidden;">
                <div class="analytics-card" id="analytics-users">
                    <h3 style="margin-top:0;">Users</h3>
                    <div class="count" style="font-size: 35px;"></div>
                </div>
                <div class="analytics-card" id="analytics-opens">
                    <h3 style="margin-top:0;">Opens</h3>
                    <div class="count" style="font-size: 35px;"></div>
                </div>
            </div>
            <hr style="margin-top: 50px;">
            <p>Timezone: UTC</p>
            <p>More analytics features coming soon...</p>
        </div>
    `;
    return h;
}

/* This function keeps track of the original values of the app before it is edited*/
function trackOriginalValues () {
    originalValues = {
        title: $('#edit-app-title').val(),
        name: $('#edit-app-name').val(),
        indexURL: $('#edit-app-index-url').val(),
        description: $('#edit-app-description').val(),
        icon: $('#edit-app-icon').attr('data-base64'),
        fileAssociations: $('#edit-app-filetype-associations').val(),
        category: $('#edit-app-category').val(),
        socialImage: $('#edit-app-social-image').attr('data-base64'),
        windowSettings: {
            width: $('#edit-app-window-width').val(),
            height: $('#edit-app-window-height').val(),
            top: $('#edit-app-window-top').val(),
            left: $('#edit-app-window-left').val(),
        },
        checkboxes: {
            maximizeOnStart: $('#edit-app-maximize-on-start').is(':checked'),
            background: $('#edit-app-background').is(':checked'),
            resizableWindow: $('#edit-app-window-resizable').is(':checked'),
            hideTitleBar: $('#edit-app-hide-titlebar').is(':checked'),
            locked: $('#edit-app-locked').is(':checked'),
            fullPageOnLanding: $('#edit-app-fullpage-on-landing').is(':checked'),
            setTitleToFile: $('#edit-app-set-title-to-file').is(':checked'),
        },
    };
}

/* This function compares for all fields and checks if anything has changed from before editting*/
function hasChanges () {
    // is icon changed
    if ( $('#edit-app-icon').attr('data-base64') !== originalValues.icon ) {
        return true;
    }

    // if social image is changed
    if ( $('#edit-app-social-image').attr('data-base64') !== originalValues.socialImage ) {
        return true;
    }

    // if any of the fields have changed
    return (
        $('#edit-app-title').val() !== originalValues.title ||
        $('#edit-app-name').val() !== originalValues.name ||
        $('#edit-app-index-url').val() !== originalValues.indexURL ||
        $('#edit-app-description').val() !== originalValues.description ||
        $('#edit-app-icon').attr('data-base64') !== originalValues.icon ||
        $('#edit-app-filetype-associations').val() !== originalValues.fileAssociations ||
        $('#edit-app-category').val() !== originalValues.category ||
        $('#edit-app-social-image').attr('data-base64') !== originalValues.socialImage ||
        $('#edit-app-window-width').val() !== originalValues.windowSettings.width ||
        $('#edit-app-window-height').val() !== originalValues.windowSettings.height ||
        $('#edit-app-window-top').val() !== originalValues.windowSettings.top ||
        $('#edit-app-window-left').val() !== originalValues.windowSettings.left ||
        $('#edit-app-maximize-on-start').is(':checked') !== originalValues.checkboxes.maximizeOnStart ||
        $('#edit-app-background').is(':checked') !== originalValues.checkboxes.background ||
        $('#edit-app-window-resizable').is(':checked') !== originalValues.checkboxes.resizableWindow ||
        $('#edit-app-hide-titlebar').is(':checked') !== originalValues.checkboxes.hideTitleBar ||
        $('#edit-app-locked').is(':checked') !== originalValues.checkboxes.locked ||
        $('#edit-app-fullpage-on-landing').is(':checked') !== originalValues.checkboxes.fullPageOnLanding ||
        $('#edit-app-set-title-to-file').is(':checked') !== originalValues.checkboxes.setTitleToFile
    );
}

/* This function enables or disables the save button if there are any changes made */
function toggleSaveButton () {
    if ( hasChanges() ) {
        $('.edit-app-save-btn').prop('disabled', false);
    } else {
        $('.edit-app-save-btn').prop('disabled', true);
    }
}

/* This function enables or disables the reset button if there are any changes made */
function toggleResetButton () {
    if ( hasChanges() ) {
        $('.edit-app-reset-btn').prop('disabled', false);
    } else {
        $('.edit-app-reset-btn').prop('disabled', true);
    }
}

window.reset_drop_area = () => {
    dropped_items = null;
    $('.drop-area').html(drop_area_placeholder);
    $('.drop-area').removeClass('drop-area-ready-to-deploy');
    $('.deploy-btn').addClass('disabled');
};

/* This function revers the changes made back to the original values of the edit form */
function resetToOriginalValues () {
    $('#edit-app-title').val(originalValues.title);
    $('#edit-app-name').val(originalValues.name);
    $('#edit-app-index-url').val(originalValues.indexURL);
    $('#edit-app-description').val(originalValues.description);
    $('#edit-app-filetype-associations').val(originalValues.fileAssociations);
    $('#edit-app-category').val(originalValues.category);
    $('#edit-app-window-width').val(originalValues.windowSettings.width);
    $('#edit-app-window-height').val(originalValues.windowSettings.height);
    $('#edit-app-window-top').val(originalValues.windowSettings.top);
    $('#edit-app-window-left').val(originalValues.windowSettings.left);
    $('#edit-app-maximize-on-start').prop('checked', originalValues.checkboxes.maximizeOnStart);
    $('#edit-app-background').prop('checked', originalValues.checkboxes.background);
    $('#edit-app-window-resizable').prop('checked', originalValues.checkboxes.resizableWindow);
    $('#edit-app-hide-titlebar').prop('checked', originalValues.checkboxes.hideTitleBar);
    $('#edit-app-locked').prop('checked', originalValues.checkboxes.locked);
    $('#edit-app-fullpage-on-landing').prop('checked', originalValues.checkboxes.fullPageOnLanding);
    $('#edit-app-set-title-to-file').prop('checked', originalValues.checkboxes.setTitleToFile);

    if ( originalValues.icon ) {
        $('#edit-app-icon').css('background-image', `url(${originalValues.icon})`);
        $('#edit-app-icon').attr('data-url', originalValues.icon);
        $('#edit-app-icon').attr('data-base64', originalValues.icon);
        $('#edit-app-icon-delete').show();
    } else {
        $('#edit-app-icon').css('background-image', '');
        $('#edit-app-icon').removeAttr('data-url');
        $('#edit-app-icon').removeAttr('data-base64');
        $('#edit-app-icon-delete').hide();
    }

    if ( originalValues.socialImage ) {
        $('#edit-app-social-image').css('background-image', `url(${originalValues.socialImage})`);
        $('#edit-app-social-image').attr('data-url', originalValues.socialImage);
        $('#edit-app-social-image').attr('data-base64', originalValues.socialImage);
    } else {
        $('#edit-app-social-image').css('background-image', '');
        $('#edit-app-social-image').removeAttr('data-url');
        $('#edit-app-social-image').removeAttr('data-base64');
    }
}

async function edit_app_section (cur_app_name, tab = 'deploy') {
    puter.ui.showSpinner();

    $('section:not(.sidebar)').hide();
    $('.tab-btn').removeClass('active');
    $('.tab-btn[data-tab="apps"]').addClass('active');

    let cur_app = await puter.apps.get(cur_app_name, { icon_size: 128, stats_period: 'today' });

    currently_editing_app = cur_app;

    // generate edit app section
    $('#edit-app').html(generate_edit_app_section(cur_app));
    trackOriginalValues(); // Track initial field values
    toggleSaveButton(); // Ensure Save button is initially disabled
    toggleResetButton(); // Ensure Reset button is initially disabled
    $('#edit-app').show();

    // analytics
    $('#analytics-users .count').html(cur_app.stats.user_count);
    $('#analytics-opens .count').html(cur_app.stats.open_count);

    render_analytics('today');

    // show the correct tab
    $('.section-tab').hide();
    $(`.section-tab[data-tab="${tab}"]`).show();
    $('.section-tab-buttons .section-tab-btn').removeClass('active');
    $(`.section-tab-buttons .section-tab-btn[data-tab="${tab}"]`).addClass('active');

    const filetype_association_input = document.querySelector('textarea[id=edit-app-filetype-associations]');
    let tagify = new Tagify(filetype_association_input, {
        pattern: /\.(?:[a-z0-9]+)|(?:[a-z]+\/(?:[a-z0-9.-]+|\*))/,
        delimiters: ',', // Use comma as delimiter
        duplicates: false, // Prevent duplicate tags
        enforceWhitelist: false,
        dropdown: {
            // show the dropdown immediately on focus (0 character typed)
            enabled: 0,
        },
        whitelist: [
            // MIME type patterns
            'text/*', 'image/*', 'audio/*', 'video/*', 'application/*',

            // Documents
            '.doc', '.docx', '.pdf', '.txt', '.odt', '.rtf', '.tex', '.md', '.pages', '.epub', '.mobi', '.azw', '.azw3', '.djvu', '.xps', '.oxps', '.fb2', '.textile', '.markdown', '.asciidoc', '.rst', '.wpd', '.wps', '.abw', '.zabw',

            // Spreadsheets
            '.xls', '.xlsx', '.csv', '.ods', '.numbers', '.tsv', '.gnumeric', '.xlt', '.xltx', '.xlsm', '.xltm', '.xlam', '.xlsb',

            // Presentations
            '.ppt', '.pptx', '.key', '.odp', '.pps', '.ppsx', '.pptm', '.potx', '.potm', '.ppam',

            // Images
            '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif', '.svg', '.webp', '.ico', '.psd', '.ai', '.eps', '.raw', '.cr2', '.nef', '.orf', '.sr2', '.heic', '.heif', '.avif', '.jxr', '.hdp', '.wdp', '.jng', '.xcf', '.pgm', '.pbm', '.ppm', '.pnm',

            // Video
            '.mp4', '.avi', '.mov', '.wmv', '.mkv', '.flv', '.webm', '.m4v', '.mpeg', '.mpg', '.3gp', '.3g2', '.ogv', '.vob', '.drc', '.gifv', '.mng', '.qt', '.yuv', '.rm', '.rmvb', '.asf', '.amv', '.m2v', '.svi',

            // Audio
            '.mp3', '.wav', '.aac', '.flac', '.ogg', '.m4a', '.wma', '.aiff', '.alac', '.ape', '.au', '.mid', '.midi', '.mka', '.pcm', '.ra', '.ram', '.snd', '.wv', '.opus',

            // Code/Development
            '.js', '.ts', '.html', '.css', '.json', '.xml', '.php', '.py', '.java', '.cpp', '.c', '.cs', '.h', '.hpp', '.hxx', '.rs', '.go', '.rb', '.pl', '.swift', '.kt', '.kts', '.scala', '.coffee', '.sass', '.scss', '.less', '.jsx', '.tsx', '.vue', '.sh', '.bash', '.zsh', '.fish', '.ps1', '.bat', '.cmd', '.sql', '.r', '.dart', '.f', '.f90', '.for', '.lua', '.m', '.mm', '.clj', '.erl', '.ex', '.exs', '.elm', '.hs', '.lhs', '.lisp', '.ml', '.mli', '.nim', '.pl', '.rkt', '.v', '.vhd',

            // Archives
            '.zip', '.rar', '.7z', '.tar', '.gz', '.bz2', '.xz', '.z', '.lz', '.lzma', '.tlz', '.txz', '.tgz', '.tbz2', '.bz', '.br', '.lzo', '.ar', '.cpio', '.shar', '.lrz', '.lz4', '.lz2', '.rz', '.sfark', '.sz', '.zoo',

            // Database
            '.db', '.sql', '.sqlite', '.sqlite3', '.dbf', '.mdb', '.accdb', '.db3', '.s3db', '.dbx',

            // Fonts
            '.ttf', '.otf', '.woff', '.woff2', '.eot', '.pfa', '.pfb', '.sfd',

            // CAD and 3D
            '.dwg', '.dxf', '.stl', '.obj', '.fbx', '.dae', '.3ds', '.blend', '.max', '.ma', '.mb', '.c4d', '.skp', '.usd', '.usda', '.usdc', '.abc',

            // Scientific/Technical
            '.mat', '.fig', '.nb', '.cdf', '.fits', '.fts', '.fit', '.gmsh', '.msh', '.fem', '.neu', '.hdf', '.h5', '.nx', '.unv',

            // System
            '.exe', '.dll', '.so', '.dylib', '.app', '.dmg', '.iso', '.img', '.bin', '.msi', '.apk', '.ipa', '.deb', '.rpm',

            // Directory
            '.directory',
        ],
    });

    // --------------------------------------------------------
    // Dragster
    // --------------------------------------------------------
    let drop_area_content = drop_area_placeholder;

    $('.drop-area').dragster({
        enter: function (dragsterEvent, event) {
            drop_area_content = $('.drop-area').html();
            $('.drop-area').addClass('drop-area-hover');
            $('.drop-area').html(drop_area_placeholder);
        },
        leave: function (dragsterEvent, event) {
            $('.drop-area').html(drop_area_content);
            $('.drop-area').removeClass('drop-area-hover');
        },
        drop: async function (dragsterEvent, event) {
            const e = event.originalEvent;
            e.stopPropagation();
            e.preventDefault();

            // hide previous success message
            $('.deploy-success-msg').fadeOut();

            // remove hover class
            $('.drop-area').removeClass('drop-area-hover');

            //----------------------------------------------------
            // Puter items dropped
            //----------------------------------------------------
            if ( e.detail?.items?.length > 0 ) {
                let items = e.detail.items;

                // ----------------------------------------------------
                // One Puter file dropped
                // ----------------------------------------------------
                if ( items.length === 1 && !items[0].isDirectory ) {
                    if ( items[0].name.toLowerCase() === 'index.html' ) {
                        dropped_items = items[0].path;
                        $('.drop-area').removeClass('drop-area-hover');
                        $('.drop-area').addClass('drop-area-ready-to-deploy');
                        drop_area_content = '<p style="margin-bottom:0; font-weight: 500;">index.html</p><p>Ready to deploy 🚀</p><p class="reset-deploy"><span>Cancel</span></p>';
                        $('.drop-area').html(drop_area_content);

                        // enable deploy button
                        $('.deploy-btn').removeClass('disabled');

                    } else {
                        puter.ui.alert('You need to have an index.html file in your deployment.', [
                            {
                                label: 'Ok',
                            },
                        ]);
                        $('.drop-area').removeClass('drop-area-ready-to-deploy');
                        $('.deploy-btn').addClass('disabled');
                        dropped_items = [];
                    }
                    return;
                }
                // ----------------------------------------------------
                // Multiple Puter files dropped
                // ----------------------------------------------------
                else if ( items.length > 1 ) {
                    let hasIndexHtml = false;
                    for ( let item of items ) {
                        if ( item.name.toLowerCase() === 'index.html' ) {
                            hasIndexHtml = true;
                            break;
                        }
                    }

                    if ( hasIndexHtml ) {
                        dropped_items = items;
                        $('.drop-area').removeClass('drop-area-hover');
                        $('.drop-area').addClass('drop-area-ready-to-deploy');
                        drop_area_content = `<p style="margin-bottom:0; font-weight: 500;">${items.length} items</p><p>Ready to deploy 🚀</p><p class="reset-deploy"><span>Cancel</span></p>`;
                        $('.drop-area').html(drop_area_content);

                        // enable deploy button
                        $('.deploy-btn').removeClass('disabled');
                    } else {
                        puter.ui.alert('You need to have an index.html file in your deployment.', [
                            {
                                label: 'Ok',
                            },
                        ]);
                        $('.drop-area').removeClass('drop-area-ready-to-deploy');
                        $('.drop-area').removeClass('drop-area-hover');
                        $('.deploy-btn').addClass('disabled');
                        dropped_items = [];
                    }
                    return;
                }
                // ----------------------------------------------------
                // One Puter directory dropped
                // ----------------------------------------------------
                else if ( items.length === 1 && items[0].isDirectory ) {
                    let children = await puter.fs.readdir(items[0].path);
                    // check if index.html exists, if found, deploy entire directory
                    for ( let child of children ) {
                        if ( child.name === 'index.html' ) {
                            // deploy(currently_editing_app, items[0].path);
                            dropped_items = items[0].path;
                            let rootItems = '';

                            if ( children.length === 1 )
                            {
                                rootItems = children[0].name;
                            }
                            else if ( children.length === 2 )
                            {
                                rootItems = `${children[0].name}, ${children[1].name}`;
                            }
                            else if ( children.length === 3 )
                            {
                                rootItems = `${children[0].name}, ${children[1].name}, and${children[1].name}`;
                            }
                            else if ( children.length > 3 )
                            {
                                rootItems = `${children[0].name}, ${children[1].name}, and ${children.length - 2} more item${children.length - 2 > 1 ? 's' : ''}`;
                            }

                            $('.drop-area').removeClass('drop-area-hover');
                            $('.drop-area').addClass('drop-area-ready-to-deploy');
                            drop_area_content = `<p style="margin-bottom:0; font-weight: 500;">${rootItems}</p><p>Ready to deploy 🚀</p><p class="reset-deploy"><span>Cancel</span></p>`;
                            $('.drop-area').html(drop_area_content);

                            // enable deploy button
                            $('.deploy-btn').removeClass('disabled');
                            return;
                        }
                    }

                    // no index.html in directory
                    puter.ui.alert(index_missing_error, [
                        {
                            label: 'Ok',
                        },
                    ]);
                    $('.drop-area').removeClass('drop-area-ready-to-deploy');
                    $('.deploy-btn').addClass('disabled');
                    dropped_items = [];
                }

                return false;
            }

            //-----------------------------------------------------------------------------
            // Local items dropped
            //-----------------------------------------------------------------------------
            if ( !e.dataTransfer || !e.dataTransfer.items || e.dataTransfer.items.length === 0 )
            {
                return;
            }

            // get dropped items
            dropped_items = await puter.ui.getEntriesFromDataTransferItems(e.dataTransfer.items);

            // generate a flat array of full paths from the dropped items
            let paths = [];
            for ( let item of dropped_items ) {
                paths.push(`/${item.fullPath ?? item.filepath}`);
            }

            // generate a directory tree from the paths
            let tree = generateDirTree(paths);

            dropped_items = setRootDirTree(tree, dropped_items);

            // alert if no index.html in root
            if ( ! hasRootIndexHtml(tree) ) {
                puter.ui.alert(index_missing_error, [
                    {
                        label: 'Ok',
                    },
                ]);
                $('.drop-area').removeClass('drop-area-ready-to-deploy');
                $('.deploy-btn').addClass('disabled');
                dropped_items = [];
                return;
            }

            // Get all keys (directories and files) in the root
            const rootKeys = Object.keys(tree);

            // generate a list of items in the root in the form of a string (e.g. /index.html, /css/style.css) with maximum of 3 items
            let rootItems = '';

            if ( rootKeys.length === 1 )
            {
                rootItems = rootKeys[0];
            }
            else if ( rootKeys.length === 2 )
            {
                rootItems = `${rootKeys[0]}, ${rootKeys[1]}`;
            }
            else if ( rootKeys.length === 3 )
            {
                rootItems = `${rootKeys[0]}, ${rootKeys[1]}, and${rootKeys[1]}`;
            }
            else if ( rootKeys.length > 3 )
            {
                rootItems = `${rootKeys[0]}, ${rootKeys[1]}, and ${rootKeys.length - 2} more item${rootKeys.length - 2 > 1 ? 's' : ''}`;
            }

            rootItems = html_encode(rootItems);
            $('.drop-area').removeClass('drop-area-hover');
            $('.drop-area').addClass('drop-area-ready-to-deploy');
            drop_area_content = `<p style="margin-bottom:0; font-weight: 500;">${rootItems}</p><p>Ready to deploy 🚀</p><p class="reset-deploy"><span>Cancel</span></p>`;
            $('.drop-area').html(drop_area_content);

            // enable deploy button
            $('.deploy-btn').removeClass('disabled');

            return false;
        },
    });

    // Focus on the first input
    $('#edit-app-title').focus();

    try {
        activate_tippy();
    } catch (e) {
        console.log('no tippy:', e);
    }

    // Custom function to handle bulk pasting of file extensions
    if ( tagify ) {
        // Create a completely separate paste handler
        const handleBulkPaste = function (e) {
            const clipboardData = e.clipboardData || window.clipboardData;
            if ( ! clipboardData ) return;

            const pastedText = clipboardData.getData('text');
            if ( ! pastedText ) return;

            // Check if the pasted text contains delimiters
            if ( /[,;\t\s]/.test(pastedText) ) {
                e.stopPropagation();
                e.preventDefault();

                // Process the pasted text to extract extensions
                const extensions = pastedText.split(/[,;\t\s]+/)
                    .map(ext => ext.trim())
                    .filter(ext => ext && (ext.startsWith('.') || ext.includes('/')));

                if ( extensions.length > 0 ) {
                    // Get existing values to prevent duplicates
                    const existingValues = tagify.value.map(tag => tag.value);

                    // Only add extensions that don't already exist
                    const newExtensions = extensions.filter(ext => !existingValues.includes(ext));

                    if ( newExtensions.length > 0 ) {
                        // Add the new tags
                        tagify.addTags(newExtensions);

                        // Update the UI
                        setTimeout(() => {
                            toggleSaveButton();
                            toggleResetButton();
                        }, 10);
                    }
                }

                // Clear the input element to prevent any text concatenation
                setTimeout(() => {
                    if ( tagify.DOM.input ) {
                        tagify.DOM.input.textContent = '';
                    }
                }, 10);
            }
        };

        // Add the paste handler directly to the tagify wrapper element
        const tagifyWrapper = tagify.DOM.scope;
        if ( tagifyWrapper ) {
            tagifyWrapper.addEventListener('paste', handleBulkPaste, true);
        }

        // Also add it to the input element for better coverage
        if ( tagify.DOM.input ) {
            tagify.DOM.input.addEventListener('paste', handleBulkPaste, true);
        }

        // Add a comma key handler to support adding tags with comma
        tagify.DOM.input.addEventListener('keydown', function (e) {
            if ( e.key === ',' && tagify.DOM.input.textContent.trim() ) {
                e.preventDefault();

                const text = tagify.DOM.input.textContent.trim();

                // Only add valid extensions
                if ( (text.startsWith('.') || text.includes('/')) &&
                    tagify.settings.pattern.test(text) ) {

                    // Check for duplicates
                    const existingValues = tagify.value.map(tag => tag.value);

                    if ( ! existingValues.includes(text) ) {
                        tagify.addTags([text]);

                        // Update UI
                        setTimeout(() => {
                            toggleSaveButton();
                            toggleResetButton();
                        }, 10);
                    }

                    // Always clear the input
                    tagify.DOM.input.textContent = '';
                }
            }
        });
    }
}

$(document).on('click', '.edit-app-save-btn', async function (e) {
    const title = $('#edit-app-title').val();
    const name = $('#edit-app-name').val();
    const index_url = $('#edit-app-index-url').val();
    const description = $('#edit-app-description').val();
    const uid = $('#edit-app-uid').val();
    const height = $('#edit-app-window-height').val();
    const width = $('#edit-app-window-width').val();
    const top = $('#edit-app-window-top').val();
    const left = $('#edit-app-window-left').val();
    const category = $('#edit-app-category').val();

    let filetype_associations = $('#edit-app-filetype-associations').val();

    let icon;

    let error;

    //validation
    if ( title === '' )
    {
        error = '<strong>Title</strong> is required.';
    }
    else if ( title.length > 60 )
    {
        error = `<strong>Title</strong> cannot be longer than ${60}.`;
    }
    else if ( name === '' )
    {
        error = '<strong>Name</strong> is required.';
    }
    else if ( name.length > 60 )
    {
        error = `<strong>Name</strong> cannot be longer than ${60}.`;
    }
    else if ( index_url === '' )
    {
        error = '<strong>Index URL</strong> is required.';
    }
    else if ( ! name.match(/^[a-zA-Z0-9-_-]+$/) )
    {
        error = '<strong>Name</strong> can only contain letters, numbers, dash (-) and underscore (_).';
    }
    else if ( ! is_valid_url(index_url) )
    {
        error = '<strong>Index URL</strong> must be a valid url.';
    }
    else if ( !index_url.toLowerCase().startsWith('https://') && !index_url.toLowerCase().startsWith('http://') )
    {
        error = '<strong>Index URL</strong> must start with \'https://\' or \'http://\'.';
    }
    // height must be a number
    else if ( isNaN(height) )
    {
        error = '<strong>Window Height</strong> must be a number.';
    }
    // height must be greater than 0
    else if ( height <= 0 )
    {
        error = '<strong>Window Height</strong> must be greater than 0.';
    }
    // width must be a number
    else if ( isNaN(width) )
    {
        error = '<strong>Window Width</strong> must be a number.';
    }
    // width must be greater than 0
    else if ( width <= 0 )
    {
        error = '<strong>Window Width</strong> must be greater than 0.';
    }
    // top must be a number
    else if ( top && isNaN(top) )
    {
        error = '<strong>Window Top</strong> must be a number.';
    }
    // left must be a number
    else if ( left && isNaN(left) )
    {
        error = '<strong>Window Left</strong> must be a number.';
    }

    // download icon from URL
    else {
        let icon_url = $('#edit-app-icon').attr('data-url');
        let icon_base64 = $('#edit-app-icon').attr('data-base64');

        if ( icon_base64 ) {
            icon = icon_base64;
        } else if ( icon_url ) {
            icon = await getBase64ImageFromUrl(icon_url);
            let app_max_icon_size = 5 * 1024 * 1024;
            if ( icon.length > app_max_icon_size )
            {
                error = `Icon cannot be larger than ${byte_format(app_max_icon_size)}`;
            }
            // make sure icon is an image
            else if ( !icon.startsWith('data:image/') && !icon.startsWith('data:application/octet-stream') )
            {
                error = 'Icon must be an image.';
            }
        } else {
            icon = null;
        }
    }

    // parse filetype_associations
    if ( filetype_associations !== '' ) {
        filetype_associations = JSON.parse(filetype_associations);
        filetype_associations = filetype_associations.map((type) => {
            const fileType = type.value;
            if (
                !fileType ||
                fileType === '.' ||
                fileType === '/'
            ) {
                error = '<strong>File Association Type</strong> must be valid.';
                return null; // Return null for invalid cases
            }
            const lower = fileType.toLocaleLowerCase();

            if ( fileType.includes('/') ) {
                return lower;
            } else if ( fileType.includes('.') ) {
                return `.${lower.split('.')[1]}`;
            } else {
                return `.${lower}`;
            }
        }).filter(Boolean);
    }

    // error?
    if ( error ) {
        $('#edit-app-error').show();
        $('#edit-app-error').html(error);
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        return;
    }

    // show working spinner
    puter.ui.showSpinner();

    // disable submit button
    $('.edit-app-save-btn').prop('disabled', true);

    let socialImageUrl = null;
    if ( $('#edit-app-social-image').attr('data-base64') ) {
        socialImageUrl = await handleSocialImageUpload(name, $('#edit-app-social-image').attr('data-base64'));
    } else if ( $('#edit-app-social-image').attr('data-url') ) {
        socialImageUrl = $('#edit-app-social-image').attr('data-url');
    }

    puter.apps.update(currently_editing_app.name, {
        title: title,
        name: name,
        indexURL: index_url,
        icon: icon,
        description: description,
        maximizeOnStart: $('#edit-app-maximize-on-start').is(':checked'),
        background: $('#edit-app-background').is(':checked'),
        metadata: {
            fullpage_on_landing: $('#edit-app-fullpage-on-landing').is(':checked'),
            social_image: socialImageUrl,
            category: category || null,
            window_size: {
                width: width ?? 800,
                height: height ?? 600,
            },
            window_position: {
                top: top,
                left: left,
            },
            window_resizable: $('#edit-app-window-resizable').is(':checked'),
            hide_titlebar: $('#edit-app-hide-titlebar').is(':checked'),
            locked: $('#edit-app-locked').is(':checked') ?? false,
            set_title_to_opened_file: $('#edit-app-set-title-to-file').is(':checked'),
        },
        filetypeAssociations: filetype_associations,
    }).then(async (app) => {
        currently_editing_app = app;
        trackOriginalValues(); // Update original values after save
        toggleSaveButton(); //Disable Save Button after succesful save
        toggleResetButton(); //DIsable Reset Button after succesful save
        $('#edit-app-error').hide();
        $('#edit-app-success').show();
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        // Update open-app-btn
        $(`.open-app-btn[data-app-uid="${uid}"]`).attr('data-app-name', app.name);
        $(`.open-app[data-uid="${uid}"]`).attr('data-app-name', app.name);
        // Update title
        $(`.app-title[data-uid="${uid}"]`).html(html_encode(app.title));
        // Update app link
        $(`.app-url[data-uid="${uid}"]`).html(applink(app));
        $(`.app-url[data-uid="${uid}"]`).attr('href', applink(app));
        // Update icons
        $(`.app-icon[data-uid="${uid}"]`).attr('src', html_encode(app.icon ? app.icon : './img/app.svg'));
        $(`[data-app-uid="${uid}"]`).attr('data-app-title', html_encode(app.title));
        $(`[data-app-name="${uid}"]`).attr('data-app-name', html_encode(app.name));
    }).catch((err) => {
        $('#edit-app-success').hide();
        $('#edit-app-error').show();
        $('#edit-app-error').html(err.error?.message);
        // scroll to top so that user sees error message
        document.body.scrollTop = document.documentElement.scrollTop = 0;
        // re-enable submit button
        $('.edit-app-save-btn').prop('disabled', false);
    }).finally(() => {
        puter.ui.hideSpinner();
    });
});

$(document).on('input change', '#edit-app input, #edit-app textarea, #edit-app select', () => {
    toggleSaveButton();
    toggleResetButton();
});

$(document).on('click', '.edit-app-reset-btn', function () {
    resetToOriginalValues();
    toggleSaveButton(); // Disable Save button since values are reverted to original
    toggleResetButton(); //Disable Reset button since values are reverted to original
});

$(document).on('click', '.open-app-btn', async function (e) {
    puter.ui.launchApp($(this).attr('data-app-name'));
});

$(document).on('click', '.edit-app-open-app-btn', async function (e) {
    puter.ui.launchApp($(this).attr('data-app-name'));
});

$(document).on('click', '.delete-app-settings', async function (e) {
    let app_uid = $(this).attr('data-app-uid');
    let app_name = $(this).attr('data-app-name');
    let app_title = $(this).attr('data-app-title');

    // check if app is locked
    const app_data = await puter.apps.get(app_name, { icon_size: 16 });

    if ( app_data.metadata?.locked ) {
        puter.ui.alert(`<strong>${app_data.title}</strong> is locked and cannot be deleted.`, [
            {
                label: 'Ok',
            },
        ], {
            type: 'warning',
        });
        return;
    }

    // confirm delete
    const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(app_title)}</strong>?`,
                    [
                        {
                            label: 'Yes, delete permanently',
                            value: 'delete',
                            type: 'danger',
                        },
                        {
                            label: 'Cancel',
                        },
                    ]);

    if ( alert_resp === 'delete' ) {
        let init_ts = Date.now();
        puter.ui.showSpinner();
        puter.apps.delete(app_name).then(async (app) => {
            setTimeout(() => {
                puter.ui.hideSpinner();
                $('.back-to-main-btn').trigger('click');
            },
            // make sure the modal was shown for at least 2 seconds
            (Date.now() - init_ts) > 2000 ? 1 : 2000 - (Date.now() - init_ts));
            // get app directory
            puter.fs.stat({
                path: `/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
                returnSubdomains: true,
            }).then(async (stat) => {
                // delete subdomain associated with the app dir
                puter.hosting.delete(stat.subdomains[0].subdomain);
                // delete app directory
                puter.fs.delete(`/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
                                { recursive: true });
            });
        }).catch(async (err) => {
            setTimeout(() => {
                puter.ui.hideSpinner();
                puter.ui.alert(err?.message, [
                    {
                        label: 'Ok',
                    },
                ]);
            },
            (Date.now() - init_ts) > 2000 ? 1 : (2000 - (Date.now() - init_ts)));
        });
    }
});

$(document).on('click', '.edit-app', async function (e) {
    $('#edit-app-uid').val($(this).attr('data-app-uid'));
});

$(document).on('click', '.back-to-main-btn', function (e) {
    $('section:not(.sidebar)').hide();
    $('.tab-btn').removeClass('active');
    $('.tab-btn[data-tab="apps"]').addClass('active');

    // get apps
    puter.ui.showSpinner();
    setTimeout(function () {
        puter.apps.list({ icon_size: 64 }).then((apps_res) => {
            // uncheck the select all checkbox
            $('.select-all-apps').prop('checked', false);

            puter.ui.hideSpinner();
            apps = apps_res;
            if ( apps.length > 0 ) {
                if ( window.activeTab === 'apps' ) {
                    $('#no-apps-notice').hide();
                    $('#app-list').show();
                }
                $('.app-card').remove();
                apps.forEach(app => {
                    $('#app-list-table > tbody').append(generate_app_card(app));
                });
                count_apps();
                sort_apps();
                activate_tippy();
            } else
            {
                $('#no-apps-notice').show();
            }
        });
    }, 1000);
});

function count_apps () {
    let count = 0;
    $('.app-card').each(function () {
        count++;
    });
    $('.app-count').html(count ? count : '');
    return count;
}

$(document).on('click', '#edit-app-icon-delete', async function (e) {
    $('#edit-app-icon').css('background-image', '');
    $('#edit-app-icon').removeAttr('data-url');
    $('#edit-app-icon').removeAttr('data-base64');
    $('#edit-app-icon-delete').hide();

    toggleSaveButton();
    toggleResetButton();
});

$(document).on('click', '#edit-app-icon', async function (e) {
    const res2 = await puter.ui.showOpenFilePicker({
        accept: 'image/*',
    });

    const icon = await puter.fs.read(res2.path);
    // convert blob to base64
    const reader = new FileReader();
    reader.readAsDataURL(icon);

    reader.onloadend = function () {
        let image = reader.result;
        // Get file extension
        let fileExtension = res2.name.split('.').pop();

        // Get MIME type
        let mimeType = getMimeType(fileExtension);

        // Replace MIME type in the data URL
        image = image.replace('data:application/octet-stream;base64', `data:${mimeType};base64`);

        $('#edit-app-icon').css('background-image', `url(${image})`);
        $('#edit-app-icon').attr('data-base64', image);
        $('#edit-app-icon-delete').show();

        toggleSaveButton();
        toggleResetButton();
    };
});

/**
 * Generates HTML for an individual app card in the app list.
 *
 * @param {Object} app - The app object containing details of the app.
 *  *
 * @returns {string} HTML string representing the app card.
 *
 * @description
 * This function creates an HTML string for an app card, which includes:
 * - Checkbox for app selection
 * - App icon and title
 * - Links to open, edit, add to desktop, or delete the app
 * - Display of app statistics (user count, open count)
 * - Creation date
 * - Incentive program status badge (if applicable)
 *
 * The generated HTML is designed to be inserted into the app list table.
 * It includes data attributes for various interactive features and
 * event handling.
 *
 * @example
 * const appCardHTML = generate_app_card(myAppObject);
 * $('#app-list-table > tbody').append(appCardHTML);
 */
function generate_app_card (app) {
    let h = '';
    h += `<tr class="app-card" data-uid="${html_encode(app.uid)}" data-title="${html_encode(app.title)}" data-name="${html_encode(app.name)}" style="height: 86px;">`;
    // check box
    h += '<td style="height: 60px; width: 20px; display: flex ; align-items: center;">';
    h += '<div style="width: 20px; height: 20px; margin-top: 20px; margin-right: 10px; flex-shrink:0;">';
    h += `<input type="checkbox" class="app-checkbox" data-app-uid="${html_encode(app.uid)}" data-app-name="${html_encode(app.name)}">`;
    h += '</div>';
    h += '</td>';

    // App info (title, category, toolbar)
    h += '<td style="height: 72px; width: 450px;">';

    // Wrapper for icon + content side by side
    h += '<div style="display: flex; flex-direction: row; align-items: center; height: 86px; overflow: hidden;">';

    // Icon
    h += `<div class="go-to-edit-app" data-app-name="${html_encode(app.name)}" data-app-title="${html_encode(app.title)}" data-app-locked="${html_encode(app.metadata?.locked)}" data-app-uid="${html_encode(app.uid)}" style="
      background-position: center;
      background-repeat: no-repeat;
      background-size: 92%;
      background-image: url(${app.icon === null ? './img/app.svg' : app.icon});
      width: 60px;
      height: 60px;
      margin-right: 10px;
      color: #414b56;
      cursor: pointer;
      background-color: white;
      border-radius: 3px;
      flex-shrink: 0;
    "></div>`;

    // App info content
    h += '<div style="display: flex; flex-direction: column; justify-content: space-between; height: 100%; overflow: visible;">';

    // Info block with fixed layout
    h += '<div style="display: flex; flex-direction: column; justify-content: center; padding-left: 10px; flex-grow: 1; overflow: hidden; gap: 1px; height: 100%;">';

    // Title
    h += `<h3 class="go-to-edit-app app-card-title" style="
    margin: 0;
    font-size: 16px;
    line-height: 20px;
    height: 20px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  " data-app-name="${html_encode(app.name)}" data-app-title="${html_encode(app.title)}" data-app-uid="${html_encode(app.uid)}">
    ${html_encode(app.title)}${app.metadata?.locked ? lock_svg_tippy : ''}
  </h3>`;

    // Category (optional)
    if ( app.metadata?.category ) {
        const category = APP_CATEGORIES.find(c => c.id === app.metadata.category);
        if ( category ) {
            h += `<span class="app-category" >${html_encode(category.label)}</span>`;
        }
    }

    // Link
    h += `<a class="app-card-link" href="${html_encode(applink(app))}" target="_blank" style="
    font-size: 13px;
    margin: 2px 0 0 0;
    color: #2563eb;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    text-decoration: none;
  ">${html_encode(applink(app))}</a>`;

    h += '</div>';

    h += '</div>'; // end info column
    h += '</div>'; // end row
    h += '</td>';

    // users count
    h += '<td style="margin-top:10px; font-size:15px; vertical-align:middle;">';
    h += `<span class="stats-cell" data-app-name="${html_encode(app.name)}" data-app-uid="${html_encode(app.uid)}" title="Users" style="margin-right:20px;"><img src="./img/users.svg">${number_format((app.stats.referral_count ?? 0) + app.stats.user_count)}</span>`;
    h += '</td>';

    // opens
    h += '<td style="margin-top:10px; font-size:15px; vertical-align:middle;">';
    h += `<span class="stats-cell" data-app-name="${html_encode(app.name)}" data-app-uid="${html_encode(app.uid)}" title="Opens"><img src="./img/views.svg">${number_format(app.stats.open_count)}</span>`;
    h += '</td>';

    // Created
    h += '<td style="margin-top:10px; font-size:15px; vertical-align:middle;">';
    h += `<span title="Created" style="width: 130px; display: inline-block; font-size: 14px;">${moment(app.created_at).format('MMM Do, YYYY')}</span>`;
    h += '</td>';

    h += '<td style="vertical-align:middle; min-width:200px;">';
    h += '<div style="overflow: hidden; height: 100%; display: flex; justify-content: center; align-items: center;">';
    // "Approved for listing"
    h += `<span class="tippy approval-badge approval-badge-lsiting ${app.approved_for_listing ? 'active' : ''}" title="${app.approved_for_listing ? '✅ Approved for listing in the App Center' : '❌ Not approved for listing in the App Center'}"></span>`;

    // "Approved for opening items"
    h += `<span class="tippy approval-badge approval-badge-opening ${app.approved_for_opening_items ? 'active' : ''}" title="${app.approved_for_opening_items ? '✅ Approved for opening items' : '❌ Not approved for opening items'}"></span>`;

    // "Approved for incentive program"
    h += `<span class="tippy approval-badge approval-badge-incentive ${app.approved_for_incentive_program ? 'active' : ''}" title="${app.approved_for_incentive_program ? '✅ Approved for the incentive program' : '❌ Not approved for the incentive program'}"></span>`;
    h += '</div>';
    h += '</td>';

    // options
    h += `<td style="vertical-align: middle;"><img class="options-icon options-icon-app" data-app-name="${html_encode(app.name)}" data-app-uid="${html_encode(app.uid)}" data-app-title="${html_encode(app.title)}" src="./img/options.svg"></td>`;

    h += '</tr>';
    return h;
}

$('th.sort').on('click', function (e) {
    // determine what column to sort by
    const sortByColumn = $(this).attr('data-column');

    // toggle sort direction
    if ( sortByColumn === sortBy ) {
        if ( sortDirection === 'asc' )
        {
            sortDirection = 'desc';
        }
        else
        {
            sortDirection = 'asc';
        }
    }
    else {
        sortBy = sortByColumn;
        sortDirection = 'desc';
    }

    // update arrow
    $('.sort-arrow').css('display', 'none');
    $('#app-list-table').find('th').removeClass('sorted');
    $(this).find(`.sort-arrow-${sortDirection}`).css('display', 'inline');
    $(this).addClass('sorted');

    sort_apps();
});

function sort_apps () {
    let sorted_apps;

    // sort
    if ( sortDirection === 'asc' ) {
        sorted_apps = apps.sort((a, b) => {
            if ( sortBy === 'name' ) {
                return a[sortBy].localeCompare(b[sortBy]);
            } else if ( sortBy === 'created_at' ) {
                return new Date(a[sortBy]) - new Date(b[sortBy]);
            } else if ( sortBy === 'user_count' || sortBy === 'open_count' ) {
                return a.stats[sortBy] - b.stats[sortBy];
            } else {
                a[sortBy] > b[sortBy] ? 1 : -1;
            }
        });
    } else {
        sorted_apps = apps.sort((a, b) => {
            if ( sortBy === 'name' ) {
                return b[sortBy].localeCompare(a[sortBy]);
            } else if ( sortBy === 'created_at' ) {
                return new Date(b[sortBy]) - new Date(a[sortBy]);
            } else if ( sortBy === 'user_count' || sortBy === 'open_count' ) {
                return b.stats[sortBy] - a.stats[sortBy];
            } else {
                b[sortBy] > a[sortBy] ? 1 : -1;
            }
        });
    }
    // refresh app list
    $('.app-card').remove();
    sorted_apps.forEach(app => {
        $('#app-list-table > tbody').append(generate_app_card(app));
    });

    count_apps();

    // show apps that match search_query and hide apps that don't
    if ( search_query ) {
        // show apps that match search_query and hide apps that don't
        apps.forEach((app) => {
            if ( app.title.toLowerCase().includes(search_query.toLowerCase()) ) {
                $(`.app-card[data-name="${html_encode(app.name)}"]`).show();
            } else {
                $(`.app-card[data-name="${html_encode(app.name)}"]`).hide();
            }
        });
    }
}

/**
 * Checks if the items being deployed contain a .git directory
 * @param {Array|string} items - Items to check (can be path string or array of items)
 * @returns {Promise<boolean>} - True if .git directory is found
 */
async function hasGitDirectory (items) {
    // Case 1: Single Puter path
    if ( typeof items === 'string' && (items.startsWith('/') || items.startsWith('~')) ) {
        const stat = await puter.fs.stat(items);
        if ( stat.is_dir ) {
            const files = await puter.fs.readdir(items);
            return files.some(file => file.name === '.git' && file.is_dir);
        }
        return false;
    }

    // Case 2: Array of Puter items
    if ( Array.isArray(items) && items[0]?.uid ) {
        return items.some(item => item.name === '.git' && item.is_dir);
    }

    // Case 3: Local items (DataTransferItems)
    if ( Array.isArray(items) ) {
        for ( let item of items ) {
            if ( item.fullPath?.includes('/.git/') ||
                item.path?.includes('/.git/') ||
                item.filepath?.includes('/.git/') ) {
                return true;
            }
        }
    }

    return false;
}

/**
 * Shows a warning dialog about .git directory deployment
 * @returns {Promise<boolean>} - True if the user wants to proceed with deployment
 */
async function showGitWarningDialog () {
    try {
        // Check if the user has chosen to skip the warning
        const skipWarning = await puter.kv.get('skip-git-warning');

        // Log retrieved value for debugging
        console.log('Retrieved skip-git-warning:', skipWarning);

        // If the user opted to skip the warning, proceed without showing it
        if ( skipWarning === true ) {
            return true;
        }
    } catch ( error ) {
        console.error('Error accessing KV store:', error);
        // If KV store access fails, fall back to showing the dialog
    }

    // Create the modal dialog
    const modal = document.createElement('div');
    modal.innerHTML = `
        <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); z-index: 10000;">
            <h3 style="margin-top: 0;">Warning: Git Repository Detected</h3>
            <p>A .git directory was found in your deployment files. Deploying .git directories may:</p>
            <ul>
                <li>Expose sensitive information like commit history and configuration</li>
                <li>Significantly increase deployment size</li>
            </ul>
            <div style="margin-top: 15px; display: flex; align-items: center;">
                <input type="checkbox" id="skip-git-warning" style="margin-right: 10px;">
                <label for="skip-git-warning" style="margin-top:0;">Don't show this warning again</label>
            </div>
            <div style="margin-top: 15px; display: flex; justify-content: flex-end;">
                <button id="cancel-deployment" style="margin-right: 10px; padding: 10px 15px; background: #f0f0f0; border: none; border-radius: 4px; cursor: pointer;">Cancel</button>
                <button id="continue-deployment" style="padding: 10px 15px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer;">Continue Deployment</button>
            </div>
        </div>
        <div style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 9999;"></div>
    `;
    document.body.appendChild(modal);

    return new Promise((resolve) => {
        // Handle "Continue Deployment"
        document.getElementById('continue-deployment').addEventListener('click', async () => {
            try {
                const skipChecked = document.getElementById('skip-git-warning')?.checked;
                if ( skipChecked ) {
                    console.log("Saving 'skip-git-warning' preference as true");
                    await puter.kv.set('skip-git-warning', true);
                }
            } catch ( error ) {
                console.error('Error saving user preference to KV store:', error);
            } finally {
                document.body.removeChild(modal);
                resolve(true); // Continue deployment
            }
        });

        // Handle "Cancel Deployment"
        document.getElementById('cancel-deployment').addEventListener('click', () => {
            document.body.removeChild(modal);
            resolve(false); // Cancel deployment
        });
    });
}

window.deploy = async function (app, items) {
    // Check for .git directory before proceeding
    try {
        if ( await hasGitDirectory(items) ) {
            const shouldProceed = await showGitWarningDialog();
            if ( ! shouldProceed ) {
                reset_drop_area();
                return;
            }
        }
    } catch ( err ) {
        console.error('Error checking for .git directory:', err);
    }
    let appdata_dir, current_app_dir;

    // disable deploy button
    $('.deploy-btn').addClass('disabled');

    // change drop area text
    $('.drop-area').html(`${deploying_spinner} <div>Deploying <span class="deploy-percent">(0%)</span></div>`);

    if ( typeof items === 'string' && (items.startsWith('/') || items.startsWith('~')) ) {
        $('.drop-area').removeClass('drop-area-hover');
        $('.drop-area').addClass('drop-area-ready-to-deploy');
    }

    // --------------------------------------------------------------------
    // Get current directory, we need to delete the existing hostname
    // later on
    // --------------------------------------------------------------------
    try {
        current_app_dir = await puter.fs.stat({
            path: `/${auth_username}/AppData/${dev_center_uid}/${app.uid ?? app.uuid}`,
            returnSubdomains: true,
        });
    } catch ( err ) {
        console.log(err);
    }

    // --------------------------------------------------------------------
    // Delete existing hostnames attached to this app directory if they exist
    // --------------------------------------------------------------------
    if ( current_app_dir?.subdomains.length > 0 ) {
        for ( let subdomain of current_app_dir?.subdomains ) {
            puter.hosting.delete(subdomain.subdomain);
        }
    }

    // --------------------------------------------------------------------
    // Delete existing app directory
    // --------------------------------------------------------------------
    try {
        await puter.fs.delete(current_app_dir.path);
    } catch ( err ) {
        console.log(err);
    }

    // --------------------------------------------------------------------
    // Make an app directory under AppData
    // if the directory already exists, it should be overwritten
    // --------------------------------------------------------------------
    try {
        appdata_dir = await puter.fs.mkdir(
                        // path
                        `/${auth_username}/AppData/${dev_center_uid}/${app.uid ?? app.uuid}`,
                        // options
                        { overwrite: true, recursive: true, rename: false });
    } catch ( err ) {
        console.log(err);
    }

    // --------------------------------------------------------------------
    // (A) One Puter Item: If 'items' is a string and starts with /, it's a path to a Puter item
    // --------------------------------------------------------------------
    if ( typeof items === 'string' && (items.startsWith('/') || items.startsWith('~')) ) {
        // perform stat on 'items'
        const stat = await puter.fs.stat(items);

        // --------------------------------------------------------------------
        // Puter Directory
        // --------------------------------------------------------------------
        // Perform readdir on 'items'
        // todo there is apparently a bug in Puter where sometimes path is literally missing from the items
        // returned by readdir. This is the 'path' that readdit didn't return a path for: "~/Desktop/particle-clicker-master"
        if ( stat.is_dir ) {
            const files = await puter.fs.readdir(items);
            // copy the 'files' to the app directory
            if ( files.length > 0 ) {
                for ( let file of files ) {
                    // perform copy
                    await puter.fs.copy(file.path,
                                    appdata_dir.path,
                                    { overwrite: true });
                    // update progress
                    $('.deploy-percent').text(`(${Math.round((files.indexOf(file) / files.length) * 100)}%)`);
                }
            }
        }
        // --------------------------------------------------------------------
        // Puter File
        // --------------------------------------------------------------------
        else {
            // copy the 'files' to the app directory
            await puter.fs.copy(items,
                            appdata_dir.path,
                            { overwrite: true });
        }

        // generate new hostname with a random suffix
        let hostname = `${currently_editing_app.name}-${(Math.random() + 1).toString(36).substring(7)}`;

        // --------------------------------------------------------------------
        // Create a router for the app with the fresh hostname
        // we change hostname every time to prevent caching issues
        // --------------------------------------------------------------------
        puter.hosting.create(hostname, appdata_dir.path).then(async (res) => {
            // TODO this endpoint needs to be able to update only the specified fields
            puter.apps.update(currently_editing_app.name, {
                indexURL: `${protocol}://${hostname}.${static_hosting_domain}`,
                title: currently_editing_app.title,
                name: currently_editing_app.name,
                icon: currently_editing_app.icon,
                description: currently_editing_app.description,
                maximizeOnStart: currently_editing_app.maximize_on_start,
                background: currently_editing_app.background,
                filetypeAssociations: currently_editing_app.filetype_associations,
            });
            // set the 'Index URL' field for the 'Settings' tab
            $('#edit-app-index-url').val(`${protocol}://${hostname}.${static_hosting_domain}`);
            // show success message
            $('.deploy-success-msg').show();
            // reset drop area
            reset_drop_area();
        });
    }
    // --------------------------------------------------------------------
    // (B) Multiple Puter Items: If `items` is an Array `items[0]` has `uid`
    // then it's a Puter Item Array.
    // --------------------------------------------------------------------
    else if ( Array.isArray(items) && items[0].uid ) {
        // If there's no index.html in the root, return
        if ( ! hasRootIndexHtml )
        {
            return;
        }

        // copy the 'files' to the app directory
        for ( let item of items ) {
            // perform copy
            await puter.fs.copy(item.fullPath ? item.fullPath : item.path ? item.path : item.filepath,
                            appdata_dir.path,
                            { overwrite: true });
            // update progress
            $('.deploy-percent').text(`(${Math.round((items.indexOf(item) / items.length) * 100)}%)`);
        }

        // generate new hostname with a random suffix
        let hostname = `${currently_editing_app.name}-${(Math.random() + 1).toString(36).substring(7)}`;

        // --------------------------------------------------------------------
        // Create a router for the app with the fresh hostname
        // we change hostname every time to prevent caching issues
        // --------------------------------------------------------------------
        puter.hosting.create(hostname, appdata_dir.path).then(async (res) => {
            // TODO this endpoint needs to be able to update only the specified fields
            puter.apps.update(currently_editing_app.name, {
                indexURL: `${protocol}://${hostname}.${static_hosting_domain}`,
                title: currently_editing_app.title,
                name: currently_editing_app.name,
                icon: currently_editing_app.icon,
                description: currently_editing_app.description,
                maximizeOnStart: currently_editing_app.maximize_on_start,
                background: currently_editing_app.background,
                filetypeAssociations: currently_editing_app.filetype_associations,
            });
            // set the 'Index URL' field for the 'Settings' tab
            $('#edit-app-index-url').val(`${protocol}://${hostname}.${static_hosting_domain}`);
            // show success message
            $('.deploy-success-msg').show();
            // reset drop area
            reset_drop_area();
        });
    }

    // --------------------------------------------------------------------
    // (C) Local Items: Upload new deploy
    // --------------------------------------------------------------------
    else {
        puter.fs.upload(items,
                        `/${auth_username}/AppData/${dev_center_uid}/${currently_editing_app.uid}`,
                        {
                            dedupeName: false,
                            overwrite: false,
                            parsedDataTransferItems: true,
                            createMissingAncestors: true,
                            progress: function (operation_id, op_progress) {
                                $('.deploy-percent').text(`(${op_progress}%)`);
                            },
                        }).then(async (uploaded) => {
            // new hostname
            let hostname = `${currently_editing_app.name}-${(Math.random() + 1).toString(36).substring(7)}`;

            // ----------------------------------------
            // Create a router for the app with a fresh hostname
            // we change hostname every time to prevent caching issues
            // ----------------------------------------
            puter.hosting.create(hostname, appdata_dir.path).then(async (res) => {
                // TODO this endpoint needs to be able to update only the specified fields
                puter.apps.update(currently_editing_app.name, {
                    indexURL: `${protocol}://${hostname}.${static_hosting_domain}`,
                    title: currently_editing_app.title,
                    name: currently_editing_app.name,
                    icon: currently_editing_app.icon,
                    description: currently_editing_app.description,
                    maximizeOnStart: currently_editing_app.maximize_on_start,
                    background: currently_editing_app.background,
                    filetypeAssociations: currently_editing_app.filetype_associations,
                });
                // set the 'Index URL' field for the 'Settings' tab
                $('#edit-app-index-url').val(`${protocol}://${hostname}.${static_hosting_domain}`);
                // show success message
                $('.deploy-success-msg').show();
                // reset drop area
                reset_drop_area();
            });
        });
    }
};

function generateDirTree (paths) {
    const root = {};

    for ( let path of paths ) {
        let parts = path.split('/');
        let currentNode = root;
        for ( let part of parts ) {
            if ( ! part ) continue; // skip empty parts, especially leading one
            if ( ! currentNode[part] ) {
                currentNode[part] = {};
            }
            currentNode = currentNode[part];
        }
    }

    return root;
}

function setRootDirTree (tree, items) {
    // Get all keys (directories and files) in the root
    const rootKeys = Object.keys(tree);

    // If there's only one object in the root, check if it's non-empty and return it
    if ( rootKeys.length === 1 && typeof tree[rootKeys[0]] === 'object' && Object.keys(tree[rootKeys[0]]).length > 0 ) {
        let newItems = [];
        for ( let item of items ) {
            if ( item.fullPath )
            {
                item.finalPath = item.fullPath.replace(rootKeys[0], '');
            }
            else if ( item.path )
            {
                item.path = item.path.replace(rootKeys[0], '');
            }
            else
            {
                item.filepath = item.filepath.replace(rootKeys[0], '');
            }

            newItems.push(item);
        }
        return newItems;
    } else {
        return items;
    }
}

function hasRootIndexHtml (tree) {
    // Check if index.html exists in the root
    if ( tree['index.html'] ) {
        return true;
    }

    // Get all keys (directories and files) in the root
    const rootKeys = Object.keys(tree);

    // If there's only one directory in the root, check if index.html exists in that directory
    if ( rootKeys.length === 1 && typeof tree[rootKeys[0]] === 'object' && tree[rootKeys[0]]['index.html'] ) {
        return true;
    }

    return false;
}

$(document).on('click', '.open-app', function (e) {
    puter.ui.launchApp($(this).attr('data-app-name'));
});

$(document).on('click', '.insta-deploy-to-new-app', async function (e) {
    $('.insta-deploy-modal').get(0).close();
    let title = await puter.ui.prompt('Please enter a title for your app:', 'My Awesome App');

    if ( title.length > 60 ) {
        puter.ui.alert('Title cannot be longer than 60.', [
            {
                label: 'Ok',
            },
        ]);
        // todo go back to create an app prompt and prefill the title input with the title the user entered
        $('.insta-deploy-modal').get(0).showModal();
    }
    else if ( title ) {
        if ( source_path ) {
            create_app(title, source_path);
            source_path = null;
        } else {
            create_app(title, null, dropped_items);
            dropped_items = null;
        }
    } else
    {
        $('.insta-deploy-modal').get(0).showModal();
    }

    return;

});

$(document).on('click', '.insta-deploy-to-existing-app', function (e) {
    $('.insta-deploy-modal').get(0).close();
    $('.insta-deploy-existing-app-select').get(0).showModal();
    $('.insta-deploy-existing-app-list').html(`<div style="margin: 100px auto 10px auto; width: 40px; height:40px;">${loading_spinner}</div>`);
    puter.apps.list({ icon_size: 64 }).then((apps) => {
        setTimeout(() => {
            $('.insta-deploy-existing-app-list').html('');
            if ( apps.length === 0 )
            {
                $('.insta-deploy-existing-app-list').html(`
                    <div class="no-existing-apps">
                    <img src="./img/apps-black.svg" style="width: 40px; height: 40px; opacity: 0.2; display: block; margin: 100px auto 10px auto;">
                        You have no existing apps.
                    </div>
                `);
            }
            else {
                for ( let app of apps ) {
                    $('.insta-deploy-existing-app-list').append(
                                    `<div class="insta-deploy-app-selector" data-uid="${app.uid}" data-name="${html_encode(app.name)}">
                            <img class="insta-deploy-app-icon" data-uid="${app.uid}" data-name="${html_encode(app.name)}" src="${app.icon ? html_encode(app.icon) : './img/app.svg'}">
                            <span style="display: inline-block; font-weight: 500; overflow: hidden; text-overflow: ellipsis; width: 180px; text-wrap: nowrap;" data-uid="${app.uid}" data-uid="${html_encode(app.name)}">${html_encode(app.title)}</span>
                            <div style="margin-top: 10px; font-size:14px; opacity:0.7; display:inline-block;">
                                <span title="Users" style="width:90px; display: inline-block;"><img style="width: 15px; margin-right: 5px; margin-bottom: -2px;" src="./img/users.svg">${number_format((app.stats.referral_count ?? 0) + app.stats.user_count)}</span>
                                <span title="Opens" style="display: inline-block;"><img style="width: 15px; margin-right: 5px; margin-bottom: -2px;" src="./img/views.svg">${number_format(app.stats.open_count)}</span>
                            </div>
                        </div>`);
                }
            }
        }, 500);
    });

    // todo reset .insta-deploy-existing-app-list on close
});

$(document).on('click', '.insta-deploy-app-selector', function (e) {
    $('.insta-deploy-app-selector').removeClass('active');
    $(this).addClass('active');

    // enable deploy button
    $('.insta-deploy-existing-app-deploy-btn').removeClass('disabled');
});

$(document).on('click', '.insta-deploy-existing-app-deploy-btn', function (e) {
    $('.insta-deploy-existing-app-deploy-btn').addClass('disabled');
    $('.insta-deploy-existing-app-select')?.get(0)?.close();

    const app_item = $('.insta-deploy-app-selector.active');

    // load the 'App Settings' section
    edit_app_section(app_item.attr('data-name'));

    $('.drop-area').removeClass('drop-area-hover');
    $('.drop-area').addClass('drop-area-ready-to-deploy');
    let drop_area_content = '<p style="margin-bottom:0; font-weight: 500;">Ready to deploy 🚀</p><p class="reset-deploy"><span>Cancel</span></p>';
    $('.drop-area').html(drop_area_content);

    // deploy
    console.log('data uid is present?', $(e.target).attr('data-uid'), app_item.attr('data-uid'));
    deploy({ uid: app_item.attr('data-uid') }, source_path ?? dropped_items);
    $('.insta-deploy-existing-app-list').html('');
});

$(document).on('click', '.insta-deploy-cancel', function (e) {
    $(this).closest('dialog')?.get(0)?.close();
});
$(document).on('click', '.insta-deploy-existing-app-back', function (e) {
    $('.insta-deploy-existing-app-select')?.get(0)?.close();
    $('.insta-deploy-modal')?.get(0)?.showModal();
    // disable deploy button
    $('.insta-deploy-existing-app-deploy-btn').addClass('disabled');

    // todo disable the 'an existing app' option if there are no existing apps
});

$('.insta-deploy-existing-app-select').on('close', function (e) {
    $('.insta-deploy-existing-app-list').html('');
});

$('.refresh-app-list').on('click', function (e) {
    puter.ui.showSpinner();

    puter.apps.list({ icon_size: 64 }).then((resp) => {
        setTimeout(() => {
            apps = resp;

            $('.app-card').remove();
            apps.forEach(app => {
                $('#app-list-table > tbody').append(generate_app_card(app));
            });

            count_apps();

            // preserve search query
            if ( search_query ) {
                // show apps that match search_query and hide apps that don't
                apps.forEach((app) => {
                    if ( app.title.toLowerCase().includes(search_query.toLowerCase()) ) {
                        $(`.app-card[data-name="${app.name}"]`).show();
                    } else {
                        $(`.app-card[data-name="${app.name}"]`).hide();
                    }
                });
            }

            // preserve sort
            sort_apps();
            activate_tippy();

            puter.ui.hideSpinner();
        }, 1000);
    });
});

$(document).on('click', '.search-apps', function (e) {
    e.stopPropagation();
    e.preventDefault();
    // don't let click bubble up to window
    e.stopImmediatePropagation();
});

$(document).on('input change keyup keypress keydown paste cut', '.search-apps', function (e) {
    search_apps();
});

window.search_apps = function () {
    // search apps for query
    search_query = $('.search-apps').val().toLowerCase();
    if ( search_query === '' ) {
        // hide 'clear search' button
        $('.search-clear-apps').hide();
        // show all apps again
        $('.app-card').show();
        // remove 'has-value' class from search input
        $('.search-apps').removeClass('has-value');
    } else {
        // show 'clear search' button
        $('.search-clear-apps').show();
        // show apps that match search_query and hide apps that don't
        apps.forEach((app) => {
            if (
                app.title.toLowerCase().includes(search_query.toLowerCase())
                || app.name.toLowerCase().includes(search_query.toLowerCase())
                || app.description.toLowerCase().includes(search_query.toLowerCase())
                || app.uid.toLowerCase().includes(search_query.toLowerCase())
            )
            {
                $(`.app-card[data-name="${app.name}"]`).show();
            } else {
                $(`.app-card[data-name="${app.name}"]`).hide();
            }
        });
        // add 'has-value' class to search input
        $('.search-apps').addClass('has-value');
    }
};

$(document).on('click', '.search-clear-apps', function (e) {
    $('.search-apps').val('');
    $('.search-apps').trigger('change');
    $('.search-apps').focus();
    search_query = '';
    // remove 'has-value' class from search input
    $('.search-apps').removeClass('has-value');
});

$(document).on('click', '.app-checkbox', function (e) {
    // was shift key pressed?
    if ( e.originalEvent && e.originalEvent.shiftKey ) {
        // select all checkboxes in range
        const currentIndex = $('.app-checkbox').index(this);
        const startIndex = Math.min(window.last_clicked_app_checkbox_index, currentIndex);
        const endIndex = Math.max(window.last_clicked_app_checkbox_index, currentIndex);

        // set all checkboxes in range to the same state as current checkbox
        for ( let i = startIndex; i <= endIndex; i++ ) {
            const checkbox = $('.app-checkbox').eq(i);
            checkbox.prop('checked', $(this).is(':checked'));
            // activate row
            if ( $(checkbox).is(':checked') )
            {
                $(checkbox).closest('tr').addClass('active');
            }
            else
            {
                $(checkbox).closest('tr').removeClass('active');
            }
        }
    }

    // determine if select-all checkbox should be checked, indeterminate, or unchecked
    if ( $('.app-checkbox:checked').length === $('.app-checkbox').length ) {
        $('.select-all-apps').prop('indeterminate', false);
        $('.select-all-apps').prop('checked', true);
    } else if ( $('.app-checkbox:checked').length > 0 ) {
        $('.select-all-apps').prop('indeterminate', true);
        $('.select-all-apps').prop('checked', false);
    }
    else {
        $('.select-all-apps').prop('indeterminate', false);
        $('.select-all-apps').prop('checked', false);
    }

    // activate row
    if ( $(this).is(':checked') )
    {
        $(this).closest('tr').addClass('active');
    }
    else
    {
        $(this).closest('tr').removeClass('active');
    }

    // enable delete button if at least one checkbox is checked
    if ( $('.app-checkbox:checked').length > 0 )
    {
        $('.delete-apps-btn').removeClass('disabled');
    }
    else
    {
        $('.delete-apps-btn').addClass('disabled');
    }

    // store the index of the last clicked checkbox
    window.last_clicked_app_checkbox_index = $('.app-checkbox').index(this);
});

function remove_app_card (app_uid, callback = null) {
    $(`.app-card[data-uid="${app_uid}"]`).fadeOut(200, function () {
        $(this).remove();
        if ( $('.app-card').length === 0 ) {
            $('section:not(.sidebar)').hide();
            $('#no-apps-notice').show();
        } else {
            $('section:not(.sidebar)').hide();
            $('#app-list').show();
        }

        // update select-all-apps checkbox's state
        if ( $('.app-checkbox:checked').length === 0 ) {
            $('.select-all-apps').prop('indeterminate', false);
            $('.select-all-apps').prop('checked', false);
        }
        else if ( $('.app-checkbox:checked').length === $('.app-card').length ) {
            $('.select-all-apps').prop('indeterminate', false);
            $('.select-all-apps').prop('checked', true);
        }
        else {
            $('.select-all-apps').prop('indeterminate', true);
        }

        count_apps();
        if ( callback ) callback();
    });
}

$(document).on('click', '.delete-apps-btn', async function (e) {
    // show confirmation alert
    let resp = await puter.ui.alert('Are you sure you want to delete the selected apps?', [
        {
            label: 'Delete',
            type: 'danger',
            value: 'delete',
        },
        {
            label: 'Cancel',
        },
    ], {
        type: 'warning',
    });

    if ( resp === 'delete' ) {
        // show 'deleting' modal
        puter.ui.showSpinner();

        let start_ts = Date.now();
        const apps = $('.app-checkbox:checked').toArray();

        // delete all checked apps
        for ( let app of apps ) {
            // get app uid
            const app_uid = $(app).attr('data-app-uid');
            const app_name = $(app).attr('data-app-name');

            // get app
            const app_data = await puter.apps.get(app_name, { icon_size: 64 });

            if ( app_data.metadata?.locked ) {
                if ( apps.length === 1 ) {
                    puter.ui.alert(`<strong>${app_data.title}</strong> is locked and cannot be deleted.`, [
                        {
                            label: 'Ok',
                        },
                    ], {
                        type: 'warning',
                    });

                    break;
                }

                let resp = await puter.ui.alert(`<strong>${app_data.title}</strong> is locked and cannot be deleted.`, [
                    {
                        label: 'Skip and Continue',
                        value: 'Continue',
                        type: 'primary',
                    },
                    {
                        label: 'Cancel',
                    },
                ], {
                    type: 'warning',
                });

                if ( resp === 'Cancel' )
                {
                    break;
                }
                else if ( resp === 'Continue' )
                {
                    continue;
                }
                else
                {
                    continue;
                }
            }

            // delete app
            await puter.apps.delete(app_name);

            // remove app card
            remove_app_card(app_uid);

            try {
                // get app directory
                const stat = await puter.fs.stat({
                    path: `/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
                    returnSubdomains: true,
                });
                // delete subdomain associated with the app directory
                if ( stat?.subdomains[0]?.subdomain ) {
                    await puter.hosting.delete(stat.subdomains[0].subdomain);
                }
                // delete app directory
                await puter.fs.delete(`/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
                                { recursive: true });
                count_apps();
            } catch ( err ) {
                console.log(err);
            }
        }

        // close 'deleting' modal
        setTimeout(() => {
            puter.ui.hideSpinner();
            if ( $('.app-checkbox:checked').length === 0 ) {
                // disable delete button
                $('.delete-apps-btn').addClass('disabled');
                // reset the 'select all' checkbox
                $('.select-all-apps').prop('indeterminate', false);
                $('.select-all-apps').prop('checked', false);
            }
        }, (start_ts - Date.now()) > 500 ? 0 : 500);
    }
});

$(document).on('change', '.select-all-apps', function (e) {
    if ( $(this).is(':checked') ) {
        $('.app-checkbox').prop('checked', true);
        $('.app-card').addClass('active');
        $('.delete-apps-btn').removeClass('disabled');
    } else {
        $('.app-checkbox').prop('checked', false);
        $('.app-card').removeClass('active');
        $('.delete-apps-btn').addClass('disabled');
    }
});

// if edit-app-maximize-on-start is checked, disable window size and position fields
$(document).on('change', '#edit-app-maximize-on-start', function (e) {
    if ( $(this).is(':checked') ) {
        $('#edit-app-window-width, #edit-app-window-height').prop('disabled', true);
        $('#edit-app-window-top, #edit-app-window-left').prop('disabled', true);
    } else {
        $('#edit-app-window-width, #edit-app-window-height').prop('disabled', false);
        $('#edit-app-window-top, #edit-app-window-left').prop('disabled', false);
    }
});

$(document).on('change', '#edit-app-background', function (e) {
    if ( $('#edit-app-background').is(':checked') ) {
        disable_window_settings();
    } else {
        enable_window_settings();
    }
});

function disable_window_settings () {
    $('#edit-app-maximize-on-start').prop('disabled', true);
    $('#edit-app-fullpage-on-landing').prop('disabled', true);
    $('#edit-app-window-width, #edit-app-window-height').prop('disabled', true);
    $('#edit-app-window-top, #edit-app-window-left').prop('disabled', true);
    $('#edit-app-window-resizable').prop('disabled', true);
    $('#edit-app-hide-titlebar').prop('disabled', true);
}

function enable_window_settings () {
    $('#edit-app-maximize-on-start').prop('disabled', false);
    $('#edit-app-fullpage-on-landing').prop('disabled', false);
    $('#edit-app-window-width, #edit-app-window-height').prop('disabled', false);
    $('#edit-app-window-top, #edit-app-window-left').prop('disabled', false);
    $('#edit-app-window-resizable').prop('disabled', false);
    $('#edit-app-hide-titlebar').prop('disabled', false);
}

$(document).on('click', '.reset-deploy', function (e) {
    reset_drop_area();
});

window.initializeAssetsDirectory = async () => {
    try {
        // Check if assets_url exists
        const existingURL = await puter.kv.get('assets_url');
        if ( ! existingURL ) {
            // Create assets directory
            const assetsDir = await puter.fs.mkdir(`/${auth_username}/AppData/${dev_center_uid}/assets`,
                            { overwrite: false });

            // Publish the directory
            const hostname = `assets-${Math.random().toString(36).substring(2)}`;
            const route = await puter.hosting.create(hostname, assetsDir.path);

            // Store the URL
            await puter.kv.set('assets_url', `https://${hostname}.puter.site`);
        }
    } catch ( err ) {
        console.error('Error initializing assets directory:', err);
    }
};

window.generateSocialImageSection = (app) => {
    return `
        <label for="edit-app-social-image">Social Graph Image (1200×630 strongly recommended)</label>
        <div id="edit-app-social-image" class="social-image-preview" ${app.metadata?.social_image ? `style="background-image:url(${html_encode(app.metadata.social_image)})" data-url="${html_encode(app.metadata.social_image)}" data-base64="${html_encode(app.metadata.social_image)}"` : ''}>
            <div id="change-social-image">Change Social Image</div>
        </div>
        <span id="edit-app-social-image-delete" style="${app.metadata?.social_image ? 'display:block;' : ''}">Remove social image</span>
        <p class="social-image-help">This image will be displayed when your app is shared on social media.</p>
    `;
};

$(document).on('click', '#edit-app-social-image', async function (e) {
    const res = await puter.ui.showOpenFilePicker({
        accept: 'image/*',
    });

    const socialImage = await puter.fs.read(res.path);
    // Convert blob to base64 for preview
    const reader = new FileReader();
    reader.readAsDataURL(socialImage);

    reader.onloadend = function () {
        let image = reader.result;
        // Get file extension
        let fileExtension = res.name.split('.').pop();
        // Get MIME type
        let mimeType = getMimeType(fileExtension);
        // Replace MIME type in the data URL
        image = image.replace('data:application/octet-stream;base64', `data:image/${mimeType};base64`);

        $('#edit-app-social-image').css('background-image', `url(${image})`);
        $('#edit-app-social-image').attr('data-base64', image);
        $('#edit-app-social-image-delete').show();

        toggleSaveButton();
        toggleResetButton();
    };
});

$(document).on('click', '#edit-app-social-image-delete', async function (e) {
    $('#edit-app-social-image').css('background-image', '');
    $('#edit-app-social-image').removeAttr('data-url');
    $('#edit-app-social-image').removeAttr('data-base64');
    $('#edit-app-social-image-delete').hide();
});

window.handleSocialImageUpload = async (app_name, socialImageData) => {
    if ( ! socialImageData ) return null;

    try {
        const assets_url = await puter.kv.get('assets_url');
        if ( ! assets_url ) throw new Error('Assets URL not found');

        // Convert base64 to blob
        const base64Response = await fetch(socialImageData);
        const blob = await base64Response.blob();

        // Get assets directory path
        const assetsDir = `/${auth_username}/AppData/${dev_center_uid}/assets`;

        // Upload new image
        await puter.fs.upload(new File([blob], `${app_name}.png`, { type: 'image/png' }),
                        assetsDir,
                        { overwrite: true });

        return `${assets_url}/${app_name}.png`;
    } catch ( err ) {
        console.error('Error uploading social image:', err);
        throw err;
    }
};

$(document).on('click', '.copy-app-uid', function (e) {
    const appUID = $('#edit-app-uid').val();
    navigator.clipboard.writeText(appUID);
    // change to 'copied'
    $(this).html('Copied');
    setTimeout(() => {
        $(this).html(copy_svg);
    }, 2000);
});

$(document).on('change', '#analytics-period', async function (e) {
    let period = $(this).val();
    render_analytics(period);
});

async function render_analytics (period) {
    puter.ui.showSpinner();

    // set a sensible stats_grouping based on the selected period
    let stats_grouping;

    if ( period === 'today' || period === 'yesterday' ) {
        stats_grouping = 'hour';
    }
    else if ( period === 'this_week' || period === 'last_week' || period === 'this_month' || period === 'last_month' || period === '7d' || period === '30d' ) {
        stats_grouping = 'day';
    }
    else if ( period === 'this_year' || period === 'last_year' || period === '12m' || period === 'all' ) {
        stats_grouping = 'month';
    }

    const app = await puter.apps.get(currently_editing_app.name,
                    {
                        icon_size: 16,
                        stats_period: period,
                        stats_grouping: stats_grouping,
                    });

    $('#analytics-users .count').html(number_format(app.stats.user_count));
    $('#analytics-opens .count').html(number_format(app.stats.open_count));

    // Clear existing chart if any
    $('#analytics-chart').remove();
    $('.analytics-container').remove();

    // Create new canvas
    const container = $('<div class="analytics-container" style="width:100%; height:400px; margin-top:30px;"></div>');
    const canvas = $('<canvas id="analytics-chart"></canvas>');
    container.append(canvas);
    $('#analytics-opens').parent().after(container);

    // Format the data
    const labels = app.stats.grouped_stats.open_count.map(item => {
        let date;
        if ( stats_grouping === 'month' ) {
            // Handle YYYY-MM format explicitly
            const [year, month] = item.period.split('-');
            date = new Date(parseInt(year), parseInt(month) - 1); // month is 0-based in JS
        } else {
            date = new Date(item.period);
        }

        if ( stats_grouping === 'hour' ) {
            return date.toLocaleString('en-US', { hour: 'numeric', hour12: true }).toLowerCase();
        } else if ( stats_grouping === 'day' ) {
            return date.toLocaleString('en-US', { month: 'short', day: 'numeric' });
        } else {
            return date.toLocaleString('en-US', { month: 'short', year: 'numeric' });
        }
    });
    const openData = app.stats.grouped_stats.open_count.map(item => item.count);
    const userData = app.stats.grouped_stats.user_count.map(item => item.count);

    // Create chart
    const ctx = document.getElementById('analytics-chart').getContext('2d');
    new Chart(ctx, {
        type: 'line',
        data: {
            labels: labels,
            datasets: [
                {
                    label: 'Opens',
                    data: openData,
                    borderColor: '#346beb',
                    tension: 0,
                    fill: false,
                },
                {
                    label: 'Users',
                    data: userData,
                    borderColor: '#27cc32',
                    tension: 0,
                    fill: false,
                },
            ],
        },
        options: {
            responsive: true,
            maintainAspectRatio: false,
            scales: {
                x: {
                    display: true,
                    title: {
                        display: true,
                        text: 'Period',
                    },
                    ticks: {
                        maxRotation: 45,
                        minRotation: 45,
                    },
                },
                y: {
                    display: true,
                    beginAtZero: true,
                    title: {
                        display: true,
                        text: 'Count',
                    },
                    ticks: {
                        precision: 0, // Show whole numbers only
                        stepSize: 1, // Increment by 1
                    },
                },
            },
        },
    });

    puter.ui.hideSpinner();
}

$(document).on('click', '.stats-cell', function (e) {
    edit_app_section($(this).attr('data-app-name'), 'analytics');
});

function app_context_menu (app_name, app_title, app_uid) {
    puter.ui.contextMenu({
        items: [
            {
                label: 'Open App',
                type: 'primary',
                action: () => {
                    puter.ui.launchApp(app_name);
                },
            },
            '-',
            {
                label: 'Edit',
                type: 'primary',
                action: () => {
                    edit_app_section(app_name);
                },
            },
            {
                label: 'Add Shortcut to Desktop',
                type: 'primary',
                action: () => {
                    puter.fs.upload(new File([], app_title),
                                    `/${auth_username}/Desktop`,
                                    {
                                        name: app_title,
                                        dedupeName: true,
                                        overwrite: false,
                                        appUID: app_uid,
                                    }).then(async (uploaded) => {
                        puter.ui.alert(`<strong>${app_title}</strong> shortcut has been added to your desktop.`, [
                            {
                                label: 'Ok',
                                type: 'primary',
                            },
                        ], {
                            type: 'success',
                        });
                    });

                },
            },
            '-',
            {
                label: 'Delete',
                type: 'danger',
                action: () => {
                    attempt_delete_app(app_name, app_title, app_uid);
                },
            },
        ],
    });

}
$(document).on('click', '.options-icon-app', function (e) {
    let app_name = $(this).attr('data-app-name');
    let app_title = $(this).attr('data-app-title');
    let app_uid = $(this).attr('data-app-uid');

    e.preventDefault();
    e.stopPropagation();
    e.stopImmediatePropagation();
    app_context_menu(app_name, app_title, app_uid);
});

async function attempt_delete_app (app_name, app_title, app_uid) {
    // get app
    const app_data = await puter.apps.get(app_name, { icon_size: 16 });

    if ( app_data.metadata?.locked ) {
        puter.ui.alert(`<strong>${app_data.title}</strong> is locked and cannot be deleted.`, [
            {
                label: 'Ok',
            },
        ], {
            type: 'warning',
        });
        return;
    }

    // confirm delete
    const alert_resp = await puter.ui.alert(`Are you sure you want to premanently delete <strong>${html_encode(app_title)}</strong>?`,
                    [
                        {
                            label: 'Yes, delete permanently',
                            value: 'delete',
                            type: 'danger',
                        },
                        {
                            label: 'Cancel',
                        },
                    ]);

    if ( alert_resp === 'delete' ) {
        remove_app_card(app_uid);

        // delete app
        puter.apps.delete(app_name).then(async (app) => {
            // get app directory
            puter.fs.stat({
                path: `/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
                returnSubdomains: true,
            }).then(async (stat) => {
                // delete subdomain associated with the app dir
                puter.hosting.delete(stat.subdomains[0].subdomain);
                // delete app directory
                puter.fs.delete(`/${auth_username}/AppData/${dev_center_uid}/${app_uid}`,
                                { recursive: true });
            });
        }).catch(async (err) => {
            puter.ui.hideSpinner();
            puter.ui.alert(err?.message, [
                {
                    label: 'Ok',
                },
            ]);
        });
    }

}

export default init_apps;