AutoFiller Script Multi-profile

Multi-profile autofill with draggable panel, site binding, theme override, history/undo, favorites, and CSV export

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey to install this script.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

/* jshint esversion: 8 */
// ==UserScript==
// @name         AutoFiller Script Multi-profile
// @namespace    http://tampermonkey.net/
// @version      6.2
// @description  Multi-profile autofill with draggable panel, site binding, theme override, history/undo, favorites, and CSV export
// @match        *://*/*
// @grant        GM_getValue
// @grant        GM_setValue
// @license      MIT
// ==/UserScript==

/*
MIT License with No Liability
Copyright (c) 2026 Shariar

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The software is provided "as is", without warranty of any kind, express or
implied, including but not limited to the warranties of merchantability,
fitness for a particular purpose, or non-infringement. In no event shall
the authors or copyright holders be liable for any claim, damages, or other
liability, whether in an action of contract, tort, or otherwise, arising
from, out of, or in connection with the software or the use or other dealings
in the software.
*/

/*
⚠️ DISCLAIMER:
This script is for personal use to automate form-filling.
The author (Shariar) is not responsible for any misuse or damages
resulting from the use of this script.
Do not use for illegal activity.
*/

(async function () {
    'use strict';

    const STORAGE_KEY = 'TM_Global_AutoFill_Profiles';
    const LAST_PROFILE = 'TM_Last_Profile';
    const SITE_PROFILE_MAP_KEY = 'TM_Site_Profile_Map';
    const AUTO_FILL_ON_LOAD_KEY = 'TM_AutoFill_On_Load';
    const THEME_PREFERENCE_KEY = 'TM_Theme_Preference';
    const PROFILE_HISTORY_KEY = 'TM_Profile_History';
    const FAVORITE_PROFILES_KEY = 'TM_Favorite_Profiles';

    const defaultProfiles = { Default: {} };
    const hostname = window.location.hostname;
    const HISTORY_LIMIT = 20;

    function notify(msg) {
        const n = document.createElement('div');
        n.textContent = msg;
        n.style.position = 'fixed';
        n.style.bottom = '80px';
        n.style.right = '20px';
        n.style.background = '#333';
        n.style.color = '#fff';
        n.style.padding = '8px 12px';
        n.style.borderRadius = '6px';
        n.style.zIndex = '999999';
        n.style.fontSize = '13px';
        document.body.appendChild(n);
        setTimeout(() => n.remove(), 2200);
    }

    function resolveTheme(themePreference) {
        if (themePreference === 'dark') return 'dark';
        if (themePreference === 'light') return 'light';
        return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    }

    function applyTheme(panel, toggleButton, themePreference) {
        const theme = resolveTheme(themePreference);
        const inputs = panel.querySelectorAll('input, select, textarea');
        const buttons = panel.querySelectorAll('button');

        if (theme === 'dark') {
            panel.style.background = '#222';
            panel.style.color = '#fff';
            toggleButton.style.background = '#222';
            toggleButton.style.color = '#fff';
            toggleButton.style.border = '1px solid #555';
            inputs.forEach((el) => {
                el.style.background = '#1a1a1a';
                el.style.color = '#fff';
                el.style.border = '1px solid #555';
            });
            buttons.forEach((btn) => {
                btn.style.background = '#3a3a3a';
                btn.style.color = '#fff';
                btn.style.border = '1px solid #666';
            });
        } else {
            panel.style.background = '#fff';
            panel.style.color = '#000';
            toggleButton.style.background = '#fff';
            toggleButton.style.color = '#000';
            toggleButton.style.border = '1px solid #ccc';
            inputs.forEach((el) => {
                el.style.background = '#fff';
                el.style.color = '#000';
                el.style.border = '1px solid #ccc';
            });
            buttons.forEach((btn) => {
                btn.style.background = '#f4f4f4';
                btn.style.color = '#000';
                btn.style.border = '1px solid #ccc';
            });
        }
    }

    function safeParseObject(text, fallback) {
        try {
            const parsed = JSON.parse(text);
            if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
                return parsed;
            }
            return fallback;
        } catch (_err) {
            return fallback;
        }
    }

    async function loadProfiles() {
        const data = await GM_getValue(STORAGE_KEY, '{}');
        const parsed = safeParseObject(data, {});
        return Object.keys(parsed).length ? parsed : { ...defaultProfiles };
    }

    function saveProfiles(data) {
        GM_setValue(STORAGE_KEY, JSON.stringify(data));
    }

    async function loadLastProfile() {
        return await GM_getValue(LAST_PROFILE, null);
    }

    function saveLastProfile(name) {
        GM_setValue(LAST_PROFILE, name);
    }

    async function loadSiteProfileMap() {
        const data = await GM_getValue(SITE_PROFILE_MAP_KEY, '{}');
        return safeParseObject(data, {});
    }

    function saveSiteProfileMap(map) {
        GM_setValue(SITE_PROFILE_MAP_KEY, JSON.stringify(map));
    }

    async function loadHistoryMap() {
        const data = await GM_getValue(PROFILE_HISTORY_KEY, '{}');
        return safeParseObject(data, {});
    }

    function saveHistoryMap(map) {
        GM_setValue(PROFILE_HISTORY_KEY, JSON.stringify(map));
    }

    async function loadFavoritesMap() {
        const data = await GM_getValue(FAVORITE_PROFILES_KEY, '{}');
        return safeParseObject(data, {});
    }

    function saveFavoritesMap(map) {
        GM_setValue(FAVORITE_PROFILES_KEY, JSON.stringify(map));
    }

    function pushHistorySnapshot(historyMap, profileName, snapshot, reason) {
        if (!profileName) return;

        if (!historyMap[profileName] || !Array.isArray(historyMap[profileName])) {
            historyMap[profileName] = [];
        }

        historyMap[profileName].push({
            savedAt: new Date().toISOString(),
            reason,
            fields: JSON.parse(JSON.stringify(snapshot || {}))
        });

        if (historyMap[profileName].length > HISTORY_LIMIT) {
            historyMap[profileName] = historyMap[profileName].slice(-HISTORY_LIMIT);
        }
    }

    function csvEscape(value) {
        const str = String(value ?? '');
        if (str.includes('"') || str.includes(',') || str.includes('\n')) {
            return `"${str.replace(/"/g, '""')}"`;
        }
        return str;
    }

    function downloadTextFile(filename, mimeType, content) {
        const blob = new Blob([content], { type: mimeType });
        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = filename;
        link.click();
    }

    let profiles = await loadProfiles();
    let siteProfileMap = await loadSiteProfileMap();
    let profileHistoryMap = await loadHistoryMap();
    let favoriteProfilesMap = await loadFavoritesMap();
    const savedProfile = await loadLastProfile();
    let autoFillOnLoad = await GM_getValue(AUTO_FILL_ON_LOAD_KEY, false);
    let themePreference = await GM_getValue(THEME_PREFERENCE_KEY, 'system');

    let currentProfile = savedProfile && profiles[savedProfile]
    ? savedProfile
    : Object.keys(profiles)[0];

    if (siteProfileMap[hostname] && profiles[siteProfileMap[hostname]]) {
        currentProfile = siteProfileMap[hostname];
    }

    const formElements = () => document.querySelectorAll('input, textarea, select');

    function getKey(el) {
        return (
            el.name ||
            el.id ||
            el.getAttribute('aria-label') ||
            el.placeholder ||
            el.labels?.[0]?.innerText ||
            null
        );
    }

    function fillForm() {
        if (!currentProfile || !profiles[currentProfile]) {
            notify('No profile selected');
            return;
        }

        let filledCount = 0;

        formElements().forEach((el) => {
            const key = getKey(el);
            if (!key) return;

            const value = profiles[currentProfile][key];
            if (value === undefined) return;

            if (el.type === 'checkbox') {
                el.checked = !!value;
            } else if (el.type === 'radio') {
                if (value === el.value) el.checked = true;
            } else {
                el.value = value;
            }

            filledCount += 1;
            el.dispatchEvent(new Event('input', { bubbles: true }));
            el.dispatchEvent(new Event('change', { bubbles: true }));
        });

        notify(`Form filled (${filledCount} fields)`);
    }

    function saveFromPage() {
        if (!currentProfile || !profiles[currentProfile]) {
            notify('No profile selected');
            return;
        }

        pushHistorySnapshot(profileHistoryMap, currentProfile, profiles[currentProfile], 'saveFromPage');

        formElements().forEach((el) => {
            const key = getKey(el);
            if (!key) return;

            if (el.type === 'checkbox') {
                profiles[currentProfile][key] = el.checked;
            } else if (el.type === 'radio') {
                if (el.checked) profiles[currentProfile][key] = el.value;
            } else {
                profiles[currentProfile][key] = el.value;
            }
        });

        saveProfiles(profiles);
        saveHistoryMap(profileHistoryMap);
        notify(`Saved to profile: ${currentProfile}`);
    }

    const observer = new MutationObserver(() => {
        // reserved for future dynamic form enhancements
    });
    observer.observe(document.body, { childList: true, subtree: true });

    const toggleButton = document.createElement('button');
    toggleButton.innerHTML = '⚡ AutoFill';
    toggleButton.style.position = 'fixed';
    toggleButton.style.bottom = '20px';
    toggleButton.style.right = '20px';
    toggleButton.style.zIndex = '999999';
    toggleButton.style.padding = '6px 10px';
    toggleButton.style.borderRadius = '6px';
    toggleButton.style.border = '1px solid #555';
    toggleButton.style.outline = 'none';
    toggleButton.style.cursor = 'pointer';
    toggleButton.style.fontSize = '13px';
    toggleButton.onmouseenter = () => {
        toggleButton.style.opacity = '0.85';
    };
    toggleButton.onmouseleave = () => {
        toggleButton.style.opacity = '1';
    };

    const panel = document.createElement('div');
    panel.style.position = 'fixed';
    panel.style.bottom = '20px';
    panel.style.right = '20px';
    panel.style.padding = '14px';
    panel.style.borderRadius = '10px';
    panel.style.zIndex = '999999';
    panel.style.width = '310px';
    panel.style.maxWidth = '92vw';
    panel.style.fontSize = '14px';
    panel.style.boxShadow = '0 4px 12px rgba(0,0,0,0.4)';

    panel.innerHTML = `
<b>⚡ AutoFill</b><br><br>
<b>Profile:</b>
<input id="profileSearch" placeholder="Search profile..." style="width:100%; margin-bottom:4px;">
<select id="profileSelect" style="width:100%; margin-bottom:6px;"></select>
<div style="display:flex; gap:6px; margin-bottom:7px;">
  <button id="favoriteToggleBtn" style="flex:1;">☆ Favorite</button>
  <button id="undoBtn" style="flex:1;">Undo Last</button>
</div>
<div id="siteBindingHint" style="font-size:12px; opacity:0.85; margin-bottom:7px;"></div>
<button id="fillBtn" style="width:100%; margin-bottom:7px;">Fill Data</button>
<button id="saveBtn" style="width:100%; margin-bottom:7px;">Save From Page</button>
<button id="bindSiteBtn" style="width:100%; margin-bottom:7px;">Use Profile For This Site</button>
<button id="unbindSiteBtn" style="width:100%; margin-bottom:7px;">Clear Site Binding</button>
<label style="display:flex; align-items:center; gap:8px; margin-bottom:8px; font-size:13px;">
  <input type="checkbox" id="autoFillOnLoadToggle" /> Auto-fill on page load
</label>
<label style="display:block; margin-bottom:8px; font-size:13px;">
  Theme:
  <select id="themeSelect" style="width:100%; margin-top:4px;">
    <option value="system">System</option>
    <option value="dark">Dark</option>
    <option value="light">Light</option>
  </select>
</label>
<button id="addBtn" style="width:100%; margin-bottom:7px;">Create New Profile</button>
<button id="editBtn" style="width:100%; margin-bottom:7px;">Edit Profile</button>
<button id="deleteBtn" style="width:100%; margin-bottom:7px;">Delete Profile</button>
<button id="logBtn" style="width:100%; margin-bottom:7px;">Log Profiles</button>
<button id="exportBtn" style="width:100%; margin-bottom:7px;">Export JSON Backup</button>
<button id="exportCsvBtn" style="width:100%; margin-bottom:7px;">Export CSV (All Profiles)</button>
<button id="exportReadableBtn" style="width:100%; margin-bottom:7px;">Export Readable Text</button>
<button id="importBtn" style="width:100%;">Import Profile</button>
<input type="file" id="importFile" style="display:none;" accept=".json"/>
`;

    document.body.appendChild(panel);
    document.body.appendChild(toggleButton);

    panel.style.display = 'none';
    applyTheme(panel, toggleButton, themePreference);

    window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
        if (themePreference === 'system') {
            applyTheme(panel, toggleButton, themePreference);
        }
    });

    let isDragging = false;
    let offsetX = 0;
    let offsetY = 0;

    panel.addEventListener('mousedown', (e) => {
        if (!['BUTTON', 'SELECT', 'INPUT', 'TEXTAREA', 'LABEL', 'OPTION'].includes(e.target.tagName)) {
            isDragging = true;
            offsetX = e.clientX - panel.offsetLeft;
            offsetY = e.clientY - panel.offsetTop;
        }
    });

    document.addEventListener('mousemove', (e) => {
        if (isDragging) {
            panel.style.left = `${e.clientX - offsetX}px`;
            panel.style.top = `${e.clientY - offsetY}px`;
            panel.style.bottom = 'auto';
            panel.style.right = 'auto';
        }
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
    });

    panel.addEventListener('touchstart', (e) => {
        const touch = e.touches[0];
        isDragging = true;
        offsetX = touch.clientX - panel.offsetLeft;
        offsetY = touch.clientY - panel.offsetTop;
    });

    document.addEventListener('touchmove', (e) => {
        if (!isDragging) return;
        const touch = e.touches[0];
        panel.style.left = `${touch.clientX - offsetX}px`;
        panel.style.top = `${touch.clientY - offsetY}px`;
        panel.style.bottom = 'auto';
        panel.style.right = 'auto';
    });

    document.addEventListener('touchend', () => {
        isDragging = false;
    });

    toggleButton.onclick = () => {
        panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
    };

    document.addEventListener('keydown', (e) => {
        if (e.altKey && e.key === 'a') {
            panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
        }
    });

    document.addEventListener('click', (e) => {
        const path = e.composedPath();
        if (!path.includes(panel) && !path.includes(toggleButton)) {
            panel.style.display = 'none';
        }
    });

    const profileSelect = document.getElementById('profileSelect');
    const profileSearch = document.getElementById('profileSearch');
    const siteBindingHint = document.getElementById('siteBindingHint');
    const autoFillOnLoadToggle = document.getElementById('autoFillOnLoadToggle');
    const themeSelect = document.getElementById('themeSelect');
    const favoriteToggleBtn = document.getElementById('favoriteToggleBtn');

    function refreshFavoriteButton() {
        if (!currentProfile) {
            favoriteToggleBtn.textContent = '☆ Favorite';
            return;
        }
        const isFavorite = !!favoriteProfilesMap[currentProfile];
        favoriteToggleBtn.textContent = isFavorite ? '★ Favorited' : '☆ Favorite';
    }

    profileSearch.addEventListener('input', () => {
        const searchWords = profileSearch.value.toLowerCase().split(' ').filter((w) => w);
        let firstMatch = null;

        Array.from(profileSelect.options).forEach((option) => {
            const name = option.value;
            const text = name.toLowerCase();
            const matchesAll = searchWords.every((word) => text.includes(word));

            option.style.display = matchesAll ? '' : 'none';

            if (matchesAll && searchWords.length) {
                let highlighted = name;
                searchWords.forEach((word) => {
                    const regex = new RegExp(`(${word})`, 'gi');
                    highlighted = highlighted.replace(regex, (match) => `→${match}←`);
                });
                option.textContent = highlighted;
            } else {
                option.textContent = name;
            }

            if (matchesAll && !firstMatch) firstMatch = name;
        });

        if (firstMatch) {
            profileSelect.value = firstMatch;
            currentProfile = firstMatch;
            refreshFavoriteButton();
        } else {
            profileSelect.value = '';
            currentProfile = null;
            refreshFavoriteButton();
        }
    });

    profileSearch.addEventListener('keydown', (e) => {
        if (e.key === 'Enter') {
            e.preventDefault();
            const visibleOption = Array.from(profileSelect.options).find((opt) => opt.style.display !== 'none');
            if (visibleOption) {
                profileSelect.value = visibleOption.value;
                currentProfile = visibleOption.value;
                saveLastProfile(currentProfile);
                refreshFavoriteButton();
                notify(`Selected profile: ${currentProfile}`);
            }
        }
    });

    function refreshSiteBindingHint() {
        if (siteProfileMap[hostname]) {
            siteBindingHint.textContent = `Site binding: ${hostname} → ${siteProfileMap[hostname]}`;
        } else {
            siteBindingHint.textContent = `Site binding: none (${hostname})`;
        }
    }

    function refreshDropdown() {
        profileSearch.value = '';
        profileSelect.innerHTML = '';

        const sortedProfiles = Object.keys(profiles).sort((a, b) => {
            const af = !!favoriteProfilesMap[a];
            const bf = !!favoriteProfilesMap[b];
            if (af !== bf) return af ? -1 : 1;
            return a.localeCompare(b);
        });

        sortedProfiles.forEach((name) => {
            const opt = document.createElement('option');
            opt.value = name;
            opt.textContent = favoriteProfilesMap[name] ? `★ ${name}` : name;
            profileSelect.appendChild(opt);
        });

        if (!profiles[currentProfile]) {
            currentProfile = Object.keys(profiles)[0];
            saveLastProfile(currentProfile);
        }

        profileSelect.value = currentProfile;
        refreshSiteBindingHint();
        refreshFavoriteButton();
    }

    refreshDropdown();
    autoFillOnLoadToggle.checked = !!autoFillOnLoad;
    themeSelect.value = ['system', 'dark', 'light'].includes(themePreference) ? themePreference : 'system';

    profileSelect.onchange = function onProfileChange() {
        currentProfile = this.value;
        saveLastProfile(currentProfile);
        profileSearch.value = '';
        refreshFavoriteButton();
    };

    document.getElementById('fillBtn').onclick = fillForm;
    document.getElementById('saveBtn').onclick = saveFromPage;

    favoriteToggleBtn.onclick = () => {
        if (!currentProfile) {
            notify('No profile selected');
            return;
        }

        if (favoriteProfilesMap[currentProfile]) {
            delete favoriteProfilesMap[currentProfile];
            notify(`Removed favorite: ${currentProfile}`);
        } else {
            favoriteProfilesMap[currentProfile] = true;
            notify(`Favorited: ${currentProfile}`);
        }

        saveFavoritesMap(favoriteProfilesMap);
        refreshDropdown();
    };

    document.getElementById('undoBtn').onclick = () => {
        if (!currentProfile) {
            notify('No profile selected');
            return;
        }

        const history = profileHistoryMap[currentProfile] || [];
        if (!history.length) {
            notify('No history to undo');
            return;
        }

        const lastVersion = history.pop();
        profiles[currentProfile] = JSON.parse(JSON.stringify(lastVersion.fields || {}));

        saveProfiles(profiles);
        saveHistoryMap(profileHistoryMap);
        notify(`Undo complete (${lastVersion.reason || 'previous version'})`);
    };

    document.getElementById('bindSiteBtn').onclick = () => {
        siteProfileMap[hostname] = currentProfile;
        saveSiteProfileMap(siteProfileMap);
        refreshSiteBindingHint();
        notify(`Bound ${hostname} to profile: ${currentProfile}`);
    };

    document.getElementById('unbindSiteBtn').onclick = () => {
        delete siteProfileMap[hostname];
        saveSiteProfileMap(siteProfileMap);
        refreshSiteBindingHint();
        notify(`Cleared site binding for ${hostname}`);
    };

    autoFillOnLoadToggle.onchange = function onAutoFillToggle() {
        autoFillOnLoad = this.checked;
        GM_setValue(AUTO_FILL_ON_LOAD_KEY, autoFillOnLoad);
        notify(`Auto-fill on load: ${autoFillOnLoad ? 'ON' : 'OFF'}`);
    };

    themeSelect.onchange = function onThemeChange() {
        themePreference = this.value;
        GM_setValue(THEME_PREFERENCE_KEY, themePreference);
        applyTheme(panel, toggleButton, themePreference);
        notify(`Theme set to: ${themePreference}`);
    };

    document.getElementById('addBtn').onclick = () => {
        const name = prompt('Profile Name:');
        if (!name) return;

        if (profiles[name]) {
            notify('Profile already exists');
            return;
        }

        profiles[name] = {};
        saveProfiles(profiles);
        currentProfile = name;
        saveLastProfile(name);
        refreshDropdown();
    };

    document.getElementById('editBtn').onclick = () => {
        if (!currentProfile) return;

        const oldName = currentProfile;
        const newName = prompt('Rename profile (leave blank to keep same):', currentProfile);

        if (newName && newName !== currentProfile) {
            if (profiles[newName]) {
                notify('A profile with that name already exists');
                return;
            }

            profiles[newName] = profiles[currentProfile];
            delete profiles[currentProfile];

            if (favoriteProfilesMap[currentProfile]) {
                favoriteProfilesMap[newName] = true;
                delete favoriteProfilesMap[currentProfile];
            }

            if (profileHistoryMap[currentProfile]) {
                profileHistoryMap[newName] = profileHistoryMap[currentProfile];
                delete profileHistoryMap[currentProfile];
            }

            Object.keys(siteProfileMap).forEach((site) => {
                if (siteProfileMap[site] === currentProfile) {
                    siteProfileMap[site] = newName;
                }
            });
            saveSiteProfileMap(siteProfileMap);
            saveFavoritesMap(favoriteProfilesMap);
            saveHistoryMap(profileHistoryMap);

            currentProfile = newName;
        }

        pushHistorySnapshot(profileHistoryMap, currentProfile, profiles[currentProfile], 'editProfile');

        const fields = profiles[currentProfile] || {};
        const text = Object.entries(fields).map(([k, v]) => `${k}=${v}`).join('\n');
        const newText = prompt('Edit fields (key=value):', text);

        if (newText !== null) {
            const newFields = {};
            newText.split('\n').forEach((line) => {
                const [key, ...rest] = line.split('=');
                if (!key) return;

                let val = rest.join('=').trim();
                if (val === 'true') val = true;
                if (val === 'false') val = false;

                newFields[key.trim()] = val;
            });

            profiles[currentProfile] = newFields;
            saveProfiles(profiles);
            saveHistoryMap(profileHistoryMap);
            refreshDropdown();
            notify(`Profile updated${oldName !== currentProfile ? ` (renamed from ${oldName})` : ''}`);
        }
    };

    document.getElementById('deleteBtn').onclick = () => {
        if (Object.keys(profiles).length <= 1) {
            notify('At least one profile required');
            return;
        }

        if (confirm('Delete this profile?')) {
            const deletedProfile = currentProfile;
            delete profiles[currentProfile];
            delete favoriteProfilesMap[deletedProfile];
            delete profileHistoryMap[deletedProfile];
            currentProfile = Object.keys(profiles)[0];

            Object.keys(siteProfileMap).forEach((site) => {
                if (siteProfileMap[site] === deletedProfile) delete siteProfileMap[site];
            });

            saveProfiles(profiles);
            saveSiteProfileMap(siteProfileMap);
            saveFavoritesMap(favoriteProfilesMap);
            saveHistoryMap(profileHistoryMap);
            saveLastProfile(currentProfile);
            refreshDropdown();
        }
    };

    document.getElementById('logBtn').onclick = () => {
        // eslint-disable-next-line no-console
        console.log('Profiles:', profiles);
        // eslint-disable-next-line no-console
        console.log('Site bindings:', siteProfileMap);
        // eslint-disable-next-line no-console
        console.log('Favorite profiles:', favoriteProfilesMap);
        // eslint-disable-next-line no-console
        console.log('Profile history:', profileHistoryMap);
        notify('Profiles, favorites, history, and bindings logged in console');
    };

    document.getElementById('exportBtn').onclick = () => {
        const payload = {
            profiles,
            siteProfileMap,
            favoriteProfilesMap,
            profileHistoryMap,
            themePreference,
            exportedAt: new Date().toISOString(),
            version: '7.0'
        };

        downloadTextFile('autofill_profiles_backup.json', 'application/json', JSON.stringify(payload, null, 2));
        notify('JSON backup exported');
    };

    document.getElementById('exportCsvBtn').onclick = () => {
        const lines = ['profile,key,value'];
        Object.entries(profiles).forEach(([profileName, fields]) => {
            Object.entries(fields || {}).forEach(([key, value]) => {
                lines.push(`${csvEscape(profileName)},${csvEscape(key)},${csvEscape(value)}`);
            });
        });

        downloadTextFile('autofill_profiles.csv', 'text/csv', `${lines.join('\n')}\n`);
        notify('CSV exported');
    };

    document.getElementById('exportReadableBtn').onclick = () => {
        const readable = Object.entries(profiles).map(([profileName, fields]) => {
            const favoriteMark = favoriteProfilesMap[profileName] ? ' ★ favorite' : '';
            const lines = Object.entries(fields || {}).map(([k, v]) => `  - ${k}: ${v}`);
            return [`[${profileName}]${favoriteMark}`, ...lines, ''].join('\n');
        }).join('\n');

        downloadTextFile('autofill_profiles_readable.txt', 'text/plain', readable || '[No profiles]');
        notify('Readable text exported');
    };

    const importFile = document.getElementById('importFile');

    document.getElementById('importBtn').onclick = () => {
        importFile.click();
    };

    importFile.onchange = function onImportChange() {
        const file = this.files[0];
        if (!file) return;

        const reader = new FileReader();

        reader.onload = function onLoad(e) {
            try {
                const parsed = JSON.parse(e.target.result);
                const importedProfiles = parsed.profiles || parsed;
                const importedSiteMap = parsed.siteProfileMap || {};
                const importedFavorites = parsed.favoriteProfilesMap || {};
                const importedHistory = parsed.profileHistoryMap || {};
                const importedTheme = parsed.themePreference;

                if (typeof importedProfiles !== 'object' || importedProfiles === null || Array.isArray(importedProfiles)) {
                    notify('Invalid profile file');
                    return;
                }

                const mergeMode = confirm('Import mode: OK = merge, Cancel = replace all existing profiles. Continue?');

                if (mergeMode) {
                    Object.keys(importedProfiles).forEach((name) => {
                        profiles[name] = importedProfiles[name];
                    });
                    Object.keys(importedSiteMap).forEach((site) => {
                        siteProfileMap[site] = importedSiteMap[site];
                    });
                    Object.keys(importedFavorites).forEach((name) => {
                        favoriteProfilesMap[name] = !!importedFavorites[name];
                    });
                    Object.keys(importedHistory).forEach((name) => {
                        profileHistoryMap[name] = Array.isArray(importedHistory[name]) ? importedHistory[name].slice(-HISTORY_LIMIT) : [];
                    });
                } else {
                    profiles = Object.keys(importedProfiles).length ? importedProfiles : { ...defaultProfiles };
                    siteProfileMap = safeParseObject(JSON.stringify(importedSiteMap), {});
                    favoriteProfilesMap = safeParseObject(JSON.stringify(importedFavorites), {});
                    profileHistoryMap = safeParseObject(JSON.stringify(importedHistory), {});
                }

                if (['system', 'dark', 'light'].includes(importedTheme)) {
                    themePreference = importedTheme;
                    GM_setValue(THEME_PREFERENCE_KEY, themePreference);
                    themeSelect.value = themePreference;
                    applyTheme(panel, toggleButton, themePreference);
                }

                saveProfiles(profiles);
                saveSiteProfileMap(siteProfileMap);
                saveFavoritesMap(favoriteProfilesMap);
                saveHistoryMap(profileHistoryMap);
                currentProfile = Object.keys(profiles)[0];
                saveLastProfile(currentProfile);
                refreshDropdown();
                notify('Import successful');
            } catch (_err) {
                notify('Invalid JSON file');
            }
        };

        reader.readAsText(file);
    };

    if (autoFillOnLoad && Object.keys(profiles[currentProfile] || {}).length) {
        setTimeout(fillForm, 200);
    }
})();