amit / Bing Rewards

// ==UserScript==
// @name         Bing Rewards
// @version      V2.1.1
// @description  Automatically complete the Microsoft Rewards daily search task.
// @author       Amit
// @match        https://www.bing.com/*
// @match        https://rewards.bing.com/*
// @license      MIT
// @icon         https://www.bing.com/favicon.ico
// @connect      feeds.bbci.co.uk
// @run-at       document-end
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        window.close
// @copyright    2026, amit (https://openuserjs.org/users/amit)
// ==/UserScript==
/*=============================================*\
|*                CONFIGURATION               *|
\*=============================================*/
// Constants
var TIMEOUT_RANGE = GM_getValue("timeout-range", [5, 10]);	// Randomize the time to wait between searches
var COOLDOWN_TIMEOUT = GM_getValue("cooldown-timeout", 15); // Cooldown_Timeout between searches
var UNDER_COOLDOWN = GM_getValue("under-cooldown", false);	// Workaround for cooldown restriction
var OPEN_RANDOM_LINKS = GM_getValue("open-random-links", true);	// Simulate real human searcg by opening links
var QUEUE_OVERLAY_OPEN = GM_getValue("queue-overlay-open", false);	// Persist queue overlay open state

// Auto-close-tabs is read dynamically via getAutoCloseTabs() so settings changes take effect without reload.
function getAutoCloseTabs() { return GM_getValue("auto-close-tabs", true); }

// Blacklist: any reward card whose title STARTS WITH one of these strings is skipped
// during collection (case-insensitive). Use for noisy promo cards that we never want to
// auto-click — e.g. "No joke: Get 100 points/day" which has its own bonus mechanics.
const TASK_TITLE_BLACKLIST = [
	"No joke",
];

function isTaskBlacklisted(title) {
	if (!title) return false;
	const lower = title.toLowerCase();
	return TASK_TITLE_BLACKLIST.some(prefix => lower.startsWith(prefix.toLowerCase()));
}

console.log({TIMEOUT_RANGE, COOLDOWN_TIMEOUT, UNDER_COOLDOWN, OPEN_RANDOM_LINKS, AUTO_CLOSE_TABS: getAutoCloseTabs()});

var TIMEOUT = (Math.floor(Math.random() * (TIMEOUT_RANGE[1] - TIMEOUT_RANGE[0]) * 1000) + TIMEOUT_RANGE[0] * 1000);	// Randomize the timeout with given range

/**
 * Generate a random timeout within the configured TIMEOUT_RANGE
 * @returns {number} Random timeout in milliseconds
 */
function getRandomTimeout() {
	return Math.floor(Math.random() * (TIMEOUT_RANGE[1] - TIMEOUT_RANGE[0]) * 1000) + TIMEOUT_RANGE[0] * 1000;
}

class ExploreOnBing{
	keywordsMap = {
		flightSearch: ['getaway', 'flight', 'vacation', 'travel', 'trip', 'affordable flight'],
		latestNews: ['news', 'what\'s new', 'latest', 'breaking'],
		jobSearch: ['job', 'roles', 'career', 'hiring', 'employment'],
		bookDeals: ['book', 'read', 'deals', 'reviews', 'reading'],
		craftSupplies: ['creative', 'DIY', 'craft', 'supplies', 'kits'],
		shoppingDeals: ['deals', 'shopping', 'items', 'buy', 'purchase', 'shopping list'],
		insurancePlans: ['insurance', 'protect', 'coverage', 'plans'],
		carDeals: ['car', 'vehicle', 'auto', 'road', 'automotive', 'drive'],
		creditCards: ['credit card', 'swipe', 'rewards', 'rates'],
		internetPlans: ['internet', 'broadband', 'wifi'],
		homeUpgrades: ['home', 'upgrade', 'space', 'improvement', 'renovation'],
		flowerDelivery: ['flower', 'delivery', 'smile', 'bouquet'],
		concertTickets: ['concert', 'show', 'tickets', 'music', 'event'],
		cruiseDeals: ['cruise', 'sail', 'destinations', 'vacation'],
		weatherInCity: ['weather', 'temperature', 'climate', 'forecast', 'upcoming weather'],
		wordMeaning: ['meaning', 'definition', 'explain'],
		localRestaurants: ['restaurant', 'food', 'dining', 'eat'],
		loanDeals: ['loan', 'rates', 'mortgage', 'financing', 'future', 'personal', 'student loan'],
		movie: ['movie', 'film', 'cinema', 'watch'],
		sportsScores: ['sports', 'scores', 'match', 'team', 'won', 'league', 'standings'],
		lyricsSearch: ['lyrics', 'song', 'music'],
		famousPerson: ['person', 'biography', 'who is', 'facts'],
		famousQuote: ['quote', 'saying', 'wisdom', 'inspiration'],
		stockPrice: ['stock', 'price', 'share', 'market', 'economy', 'stock price'],
		eventsNearMe: ['events', 'near me', 'local', 'happening'],
		hotelInCity: ['hotel', 'stay', 'accommodation', 'resort'],
		healthSymptom: ['health', 'symptom', 'medical', 'treatment'],
		relalEstateNearMe: ['real estate', 'house', 'apartment', 'property'],
		currencyConversion: ['currency', 'conversion', 'exchange'],
		recipeSearch: ['recipe', 'cook', 'bake', 'food'],
		gamingSearch: ['gaming', 'game', 'video game', 'console', 'controller', 'xbox', 'playstation'],
		streamingSearch: ['streaming', 'stream', 'platforms', 'bundles', 'tv show', 'series', 'streaming platform'],
		phonePlans: ['cell phone', 'phone plan', 'mobile', 'talk', 'text', 'carrier', 'cell plan'],
		bankingSearch: ['bank', 'banking', 'checking', 'savings account', 'savings', 'account', 'financial'],
		locationExplore: ['spot', 'adventure', 'explore', 'new spot', 'new place', 'next adventure'],
		translateTasks: ['translate', 'translation', 'language'],
		creditScore: ['credit', 'score', 'credit report', 'credit score', 'raise your score'],
		couponCodes: ['coupon', 'discount', 'save', 'coupon codes', 'discounts', 'save more'],
		mattressDeals: ['sleep', 'mattress', 'mattresses', 'bed', 'bedding', 'sleep better'],
		petSupplies: ['pet', 'furry', 'dog', 'cat', 'pet food', 'pet toys', 'furry friend'],
		airportParking: ['park', 'parking', 'airport parking', 'airport', 'reserve parking'],
		jewelrySearch: ['jewelry', 'jewellery', 'shine', 'necklace', 'ring', 'earrings', 'bracelet', 'stunning jewelry'],
	};
	static translateTasks() {
		const words= ["hello", "world", "computer", "science", "artificial intelligence", "machine learning", "data science", "open source", "programming", "javascript", "python", "java", "ruby", "golang"];
		const verbs = ["translate", "language translate", "translation of" ];
		const languages = ["english", "hindi", "french", "spanish", "german", "japanese", "chinese", "arabic", "russian", "portuguese", "italian"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${words[Math.floor(Math.random() * words.length)]} in ${languages[Math.floor(Math.random() * languages.length)]}`;
	}
	static flightSearch() {
		const words= ["flight", "flights"];
		const verbs = ["search for", "find", "book", "cheap", "best", "towards", "near me", "to"];
		const places = ["new york", "london", "paris", "tokyo", "sydney", "mumbai", "delhi", "beijing", "moscow", "cairo"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${words[Math.floor(Math.random() * words.length)]} ${places[Math.floor(Math.random() * places.length)]}`;
	}
	static weatherInCity() {
		const cities = ["new york", "london", "paris", "tokyo", "sydney", "mumbai", "delhi", "beijing", "moscow", "cairo"];
		const verbs = ["weather in", "temperature in", "climate in", "forecast for"];	
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${cities[Math.floor(Math.random() * cities.length)]}`;
	}
	static wordMeaning() {
		const words = ["serendipity", "ephemeral", "eloquent", "resilient", "ubiquitous", "paradigm", "benevolent", "enigma", "nostalgia", "ambiguous"];
		const verbs = ["meaning of", "definition of", "what is", "explain"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${words[Math.floor(Math.random() * words.length)]}`;
	}
	static localRestaurants() {
		const cuisines = ["italian", "chinese", "mexican", "indian", "japanese", "thai", "french", "mediterranean", "korean", "vietnamese", "ethiopian", "greek", "brazilian", "turkish"];
		const verbs = ["restaurants near me", "food near me", "places to eat near me", "dining near me", "eateries near me"];
		const modifiers = ["best", "top rated", "authentic", "popular", "affordable", ""];
		const modifier = modifiers[Math.floor(Math.random() * modifiers.length)];
		return modifier ? `${modifier} ${cuisines[Math.floor(Math.random() * cuisines.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]}` : `${cuisines[Math.floor(Math.random() * cuisines.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]}`;
	}
	static loanDeals() {
		const loanTypes = ["home", "car", "personal", "student", "business", "mortgage", "auto", "refinance", "payday", "consolidation"];
		const verbs = ["loan rates", "loan deals", "loan offers", "loan comparison", "interest rates"];
		const modifiers = ["best", "lowest", "affordable", "competitive", "cheap"];
		return `${modifiers[Math.floor(Math.random() * modifiers.length)]} ${loanTypes[Math.floor(Math.random() * loanTypes.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]}`;
	}
	
	static movie() {
		const genres = ["action", "comedy", "thriller", "horror", "romance", "sci-fi", "drama", "fantasy", "mystery", "adventure"];
		const years = ["2024", "2023", "2022", "classic", "new"];
		return `best ${genres[Math.floor(Math.random() * genres.length)]} ${years[Math.floor(Math.random() * years.length)]} movies`;
	}
	static sportsScores() {
		const sports = ["NBA", "NFL", "MLB", "NHL", "Premier League", "La Liga", "Champions League", "cricket", "tennis", "Formula 1", "NCAA", "MLS", "Serie A", "Bundesliga"];
		const verbs = ["scores today", "results today", "live scores", "standings", "schedule today", "highlights"];
		return `${sports[Math.floor(Math.random() * sports.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]}`;
	}
	static lyricsSearch() {
		const songs = ["Shape of You", "Blinding Lights", "Bohemian Rhapsody", "Imagine", "Hotel California", "Stairway to Heaven", "Smells Like Teen Spirit", "Billie Jean", "Sweet Child O Mine", "Hey Jude", "Wonderwall", "Yesterday", "Hallelujah", "Rolling in the Deep"];
		const verbs = ["lyrics of", "song lyrics for", "words to", "lyrics to", "full lyrics of"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${songs[Math.floor(Math.random() * songs.length)]}`;
	}
	static famousPerson() {
		const people = ["Albert Einstein", "Marie Curie", "Nelson Mandela", "Leonardo da Vinci", "Elon Musk", "Oprah Winfrey", "Steve Jobs", "Barack Obama", "Bill Gates", "Malala Yousafzai"];
		const verbs = ["biography of", "facts about", "who is", "information about"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${people[Math.floor(Math.random() * people.length)]}`;
	}
	static famousQuote() {
		const topics = ["success", "life", "love", "motivation", "wisdom", "happiness", "courage", "leadership", "perseverance", "friendship", "family", "faith", "strength", "change"];
		const verbs = ["famous quotes about", "inspirational quotes about", "best quotes on", "quotes about", "sayings about"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${topics[Math.floor(Math.random() * topics.length)]}`;
	}
	static stockPrice() {
		const stocks = ["AAPL", "GOOGL", "MSFT", "AMZN", "TSLA", "META", "NVDA", "NFLX", "AMD", "INTC"];
		const verbs = ["current stock price of", "stock price of", "share price of", "stock quote for"];
		return `${stocks[Math.floor(Math.random() * stocks.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]}`;
	}
	static eventsNearMe() {
		const events = ["concerts", "festivals", "theater shows", "comedy shows", "sports events", "art exhibitions", "food festivals", "conferences", "workshops", "meetups", "farmers markets", "live music", "plays"];
		const verbs = ["near me", "this weekend", "tonight", "today", "coming up"];
		const modifiers = ["upcoming", "best", "popular", "free", "local", ""];
		const modifier = modifiers[Math.floor(Math.random() * modifiers.length)];
		return modifier ? `${modifier} ${events[Math.floor(Math.random() * events.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]}` : `${events[Math.floor(Math.random() * events.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]}`;
	}
	static hotelInCity() {
		const cities = ["New York", "London", "Paris", "Tokyo", "Dubai", "Barcelona", "Singapore", "Rome", "Amsterdam", "Sydney", "Bangkok", "Istanbul", "Miami", "Las Vegas"];
		const modifiers = ["best", "luxury", "cheap", "budget", "affordable", "top rated", "boutique"];
		const verbs = ["hotels in", "places to stay in", "accommodations in", "resorts in"];
		return `${modifiers[Math.floor(Math.random() * modifiers.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]} ${cities[Math.floor(Math.random() * cities.length)]}`;
	}
	static healthSymptom() {
		const symptoms = ["headache", "fever", "cough", "fatigue", "sore throat", "back pain", "stomach ache", "dizziness", "insomnia", "allergies"];
		const verbs = ["symptoms of", "treatment for", "causes of", "remedies for"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${symptoms[Math.floor(Math.random() * symptoms.length)]}`;
	}
	static relalEstateNearMe() {
		const types = ["houses", "apartments", "condos", "townhouses", "land", "commercial properties"];
		const actions = ["for sale", "for rent", "listings"];
		return `${types[Math.floor(Math.random() * types.length)]} ${actions[Math.floor(Math.random() * actions.length)]} near me`;
	}
	static currencyConversion() {
		const currencies = [["USD", "EUR"], ["EUR", "GBP"], ["GBP", "JPY"], ["USD", "INR"], ["EUR", "JPY"], ["USD", "CAD"], ["AUD", "USD"], ["CHF", "EUR"], ["CNY", "USD"], ["USD", "MXN"]];
		const pair = currencies[Math.floor(Math.random() * currencies.length)];
		return `${Math.floor(Math.random() * 1000)} ${pair[0]} to ${pair[1]}`;
	}
	static recipeSearch() {
		const dishes = ["chocolate cake", "pasta carbonara", "chicken curry", "pizza margherita", "sushi rolls", "beef stew", "apple pie", "lasagna", "pad thai", "tiramisu"];
		const verbs = ["how to make", "recipe for", "easy", "best"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${dishes[Math.floor(Math.random() * dishes.length)]}`;
	}
	static latestNews() {
		const topics = ["technology", "world", "politics", "business", "entertainment", "sports", "science", "health", "climate", "economy"];
		const verbs = ["latest news on", "breaking news", "news about", "today's news on", "recent news about"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${topics[Math.floor(Math.random() * topics.length)]}`;
	}
	static jobSearch() {
		const companies = ["Google", "Microsoft", "Apple", "Amazon", "Meta", "Tesla", "Netflix", "Adobe", "Intel", "IBM", "Oracle", "Salesforce"];
		const verbs = ["open roles at", "jobs at", "careers at", "hiring at", "positions at"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${companies[Math.floor(Math.random() * companies.length)]}`;
	}
	static bookDeals() {
		const genres = ["fiction", "mystery", "sci-fi", "romance", "thriller", "fantasy", "biography", "self-help", "history", "cookbook"];
		const verbs = ["best deals on", "reviews for", "find", "buy", "top rated"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${genres[Math.floor(Math.random() * genres.length)]} books`;
	}
	static craftSupplies() {
		const crafts = ["DIY kits", "knitting supplies", "painting supplies", "scrapbooking materials", "jewelry making kits", "woodworking tools", "sewing supplies", "paper crafts", "pottery kits", "embroidery supplies"];
		const verbs = ["find", "shop for", "buy", "deals on", "best"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${crafts[Math.floor(Math.random() * crafts.length)]}`;
	}
	static shoppingDeals() {
		const items = ["laptops", "headphones", "coffee maker", "running shoes", "smartwatch", "backpack", "desk chair", "blender", "vacuum cleaner", "gaming mouse"];
		const verbs = ["deals on", "best price for", "shop for", "buy", "find"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${items[Math.floor(Math.random() * items.length)]}`;
	}
	static insurancePlans() {
		const types = ["health", "life", "auto", "home", "dental", "vision", "pet", "travel", "disability", "renters"];
		const verbs = ["best", "affordable", "compare", "top rated", "cheap"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${types[Math.floor(Math.random() * types.length)]} insurance plans`;
	}
	static carDeals() {
		const brands = ["Toyota", "Honda", "Ford", "Tesla", "BMW", "Mercedes", "Hyundai", "Nissan", "Mazda", "Volkswagen"];
		const verbs = ["deals on", "used", "new", "certified pre-owned", "best price for"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${brands[Math.floor(Math.random() * brands.length)]} cars`;
	}
	static creditCards() {
		const types = ["travel rewards", "cash back", "no annual fee", "balance transfer", "student", "business", "secured", "low interest"];
		const verbs = ["best", "top rated", "compare", "find", "apply for"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${types[Math.floor(Math.random() * types.length)]} credit cards`;
	}
	static internetPlans() {
		const providers = ["Xfinity", "AT&T", "Verizon", "Spectrum", "Cox", "Optimum", "Frontier", "CenturyLink"];
		const verbs = ["plans from", "deals from", "compare", "best", "affordable"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} internet ${providers[Math.floor(Math.random() * providers.length)]}`;
	}
	static homeUpgrades() {
		const projects = ["kitchen remodel", "bathroom renovation", "flooring installation", "new roof", "solar panels", "new windows", "deck building", "basement finishing", "HVAC upgrade", "landscaping"];
		const verbs = ["tools for", "tips for", "deals on", "cost of", "best contractors for"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${projects[Math.floor(Math.random() * projects.length)]}`;
	}
	static flowerDelivery() {
		const occasions = ["birthday", "anniversary", "sympathy", "get well", "congratulations", "thank you", "romantic", "just because"];
		const types = ["roses", "lilies", "tulips", "orchids", "mixed bouquet", "flower arrangement"];
		const verbs = ["send", "delivery", "order", "buy", "same day"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${types[Math.floor(Math.random() * types.length)]} for ${occasions[Math.floor(Math.random() * occasions.length)]}`;
	}
	static concertTickets() {
		const artists = ["Taylor Swift", "Ed Sheeran", "Beyoncé", "Drake", "Coldplay", "The Weeknd", "Ariana Grande", "BTS", "Billie Eilish", "Harry Styles"];
		const verbs = ["tickets for", "concert tickets", "tour dates", "find tickets for"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${artists[Math.floor(Math.random() * artists.length)]}`;
	}
	static cruiseDeals() {
		const destinations = ["Caribbean", "Alaska", "Mediterranean", "Mexico", "Hawaii", "Bahamas", "Europe", "Asia", "Australia", "South America"];
		const lines = ["Royal Caribbean", "Carnival", "Norwegian", "Princess", "Disney", "MSC"];
		const verbs = ["cruise to", "deals on cruise to", "book cruise to", "best cruise to"];
		const includeLine = Math.random() > 0.5;
		return includeLine
			? `${lines[Math.floor(Math.random() * lines.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]} ${destinations[Math.floor(Math.random() * destinations.length)]}`
			: `${verbs[Math.floor(Math.random() * verbs.length)]} ${destinations[Math.floor(Math.random() * destinations.length)]}`;
	}

	static gamingSearch() {
		const games = ["Call of Duty", "Minecraft", "FIFA", "GTA 5", "Fortnite", "Elden Ring", "The Legend of Zelda", "Cyberpunk 2077", "God of War", "Spider-Man", "Hogwarts Legacy", "Baldur's Gate 3"];
		const verbs = ["best", "reviews for", "how to play", "tips for", "latest news about", "gameplay guide for"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${games[Math.floor(Math.random() * games.length)]}`;
	}
	static streamingSearch() {
		const services = ["Netflix", "Disney Plus", "Hulu", "HBO Max", "Apple TV Plus", "Amazon Prime Video", "Peacock", "Paramount Plus"];
		const verbs = ["best shows on", "new movies on", "new releases on", "what to watch on", "top series on"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${services[Math.floor(Math.random() * services.length)]}`;
	}
	static phonePlans() {
		const carriers = ["Verizon", "AT&T", "T-Mobile", "Mint Mobile", "Cricket Wireless", "Boost Mobile", "Google Fi"];
		const verbs = ["best", "affordable", "compare", "unlimited", "cheapest"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} cell phone plans ${carriers[Math.floor(Math.random() * carriers.length)]}`;
	}
	static bankingSearch() {
		const accountTypes = ["checking account", "savings account", "high yield savings", "money market account", "CD rates", "online bank account"];
		const verbs = ["best", "compare", "top rated", "no fee", "high interest"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${accountTypes[Math.floor(Math.random() * accountTypes.length)]}`;
	}
	static locationExplore() {
		const activities = ["bing maps"]
		const verbs = ["directions to", "map to"];	
		const locations = ["tokyo", "paris", "new york", "sydney", "london", "rome", "barcelona", "dubai", "singapore", "amsterdam"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${activities[Math.floor(Math.random() * activities.length)]} ${locations[Math.floor(Math.random() * locations.length)]}`;
	}

	static creditScore() {
		const verbs = ["check", "view", "monitor", "improve", "raise"];
		const terms = ["credit score", "credit report", "credit rating", "FICO score", "credit check"];
		const modifiers = ["free", "online", "best ways to", "how to", "tips to"];
		return `${modifiers[Math.floor(Math.random() * modifiers.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]} ${terms[Math.floor(Math.random() * terms.length)]}`;
	}
	static couponCodes() {
		const stores = ["Amazon", "Target", "Walmart", "Best Buy", "Macy's", "Nike", "Kohl's", "Home Depot", "Costco", "Nordstrom"];
		const verbs = ["coupon codes for", "discounts at", "promo codes for", "deals at", "coupons for"];
		const modifiers = ["latest", "best", "working", "today's", "exclusive"];
		return `${modifiers[Math.floor(Math.random() * modifiers.length)]} ${verbs[Math.floor(Math.random() * verbs.length)]} ${stores[Math.floor(Math.random() * stores.length)]}`;
	}
	static mattressDeals() {
		const brands = ["Casper", "Purple", "Tempur-Pedic", "Nectar", "Saatva", "Sleep Number", "Beautyrest", "Serta", "Tuft & Needle", "Helix"];
		const verbs = ["best", "top rated", "deals on", "reviews for", "compare"];
		const types = ["mattresses", "memory foam mattress", "hybrid mattress", "king mattress", "queen mattress"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${brands[Math.floor(Math.random() * brands.length)]} ${types[Math.floor(Math.random() * types.length)]}`;
	}
	static petSupplies() {
		const pets = ["dog", "cat", "puppy", "kitten", "pet"];
		const items = ["toys", "food", "treats", "beds", "collars", "grooming supplies", "gear", "accessories", "leashes", "bowls"];
		const verbs = ["best", "top rated", "buy", "find", "shop for"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${pets[Math.floor(Math.random() * pets.length)]} ${items[Math.floor(Math.random() * items.length)]}`;
	}
	static airportParking() {
		const airports = ["LAX", "JFK", "ORD", "ATL", "DFW", "SFO", "MIA", "SEA", "DEN", "BOS"];
		const verbs = ["reserve", "book", "find", "compare", "cheap"];
		const types = ["airport parking", "long term parking", "parking rates", "parking deals", "parking near"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${types[Math.floor(Math.random() * types.length)]} ${airports[Math.floor(Math.random() * airports.length)]}`;
	}
	static jewelrySearch() {
		const types = ["necklaces", "rings", "earrings", "bracelets", "pendants", "watches", "diamond jewelry", "gold jewelry", "silver jewelry", "pearl jewelry"];
		const verbs = ["find", "shop for", "buy", "deals on", "best"];
		const occasions = ["for anniversary", "for birthday", "for wedding", "for Valentine's Day", "for Mother's Day", "for any occasion"];
		return `${verbs[Math.floor(Math.random() * verbs.length)]} ${types[Math.floor(Math.random() * types.length)]} ${occasions[Math.floor(Math.random() * occasions.length)]}`;
	}

	/**
	 * Find the best matching search method based on keywords in title and description
	 * @param {string} title - The task title
	 * @param {string} description - The task description
	 * @returns {string} - The name of the matching method
	 */
	static findBestMatch(title, description) {
		const combinedText = (title + " " + description).toLowerCase();
		let bestMatch = null;
		let maxMatches = 0;

		// Create instance to access keywordsMap
		const instance = new ExploreOnBing();

		for (const [methodName, keywords] of Object.entries(instance.keywordsMap)) {
			let matches = 0;
			for (const keyword of keywords) {
				if (combinedText.includes(keyword.toLowerCase())) {
					matches++;
				}
			}

			if (matches > maxMatches) {
				maxMatches = matches;
				bestMatch = methodName;
			}
		}

		console.log(`[ExploreOnBing] Best match for "${title}": ${bestMatch} (${maxMatches} keyword matches)`);

		// If no good match found, use a generic search
		if (maxMatches === 0) {
			console.warn(`[ExploreOnBing] No keyword matches found for "${title}", using generic search`);
			return null;
		}

		return bestMatch;
	}

	/**
	 * Get search string for a given task
	 * @param {string} title - The task title
	 * @param {string} description - The task description
	 * @returns {string} - The search string to use
	 */
	static getSearchString(title, description) {
		const methodName = ExploreOnBing.findBestMatch(title, description);

		if (methodName && typeof ExploreOnBing[methodName] === 'function') {
			return { searchString: ExploreOnBing[methodName](), matchedFunction: methodName };
		}

		// Fallback: extract search intent from description
		// Pattern: "Search on Bing for/to TASK" or "Search using Bing for/to TASK"
		const match = description.match(/search\b.*?\b(?:for|to)\s+(.+?)(?:\.|$)/i);
		if (match) {
			const fallback = match[1].trim();
			pushQueueDebugLog(`Unmatched: "${title}" — using: "${fallback}"`, "unmatched");
			return { searchString: fallback, matchedFunction: "descriptionFallback" };
		}

		// Last resort: use title as search
		pushQueueDebugLog(`Unmatched: "${title}" — no description pattern, using title`, "unmatched");
		return { searchString: title, matchedFunction: "titleFallback" };
	}
}


// Configuration options for the user script
const configurations = [
	{
		id: "timeout-range",
		name: "Search Delay",
		type: "range",
		value: TIMEOUT_RANGE,
		range: [1, 60],
		description: "Random delay between searches (seconds). Keep above 5s to avoid rate limits.",
	},
	{
		id: "under-cooldown",
		name: "Cooldown Mode",
		type: "checkbox",
		value: UNDER_COOLDOWN,
		description: "Enable if experiencing 15-min cooldown restrictions. Adds 15-min wait after every 4 searches.",
	},
	{
		id: "cooldown-timeout",
		name: "Cooldown Wait",
		type: "slider",
		value: COOLDOWN_TIMEOUT,
		range: [3, 30],
        disabled: !UNDER_COOLDOWN,
		description: "Minutes to wait after every 4th search when Cooldown Mode is enabled.",
	},
	{
		id: "open-random-links",
		name: "Open Random Links",
		type: "checkbox",
		value: OPEN_RANDOM_LINKS,
		description: "Opens random search results in iframes to simulate human behavior and reduce restrictions.",
	},
	{
		id: "auto-close-tabs",
		name: "Auto-Close Tabs",
		type: "checkbox",
		value: getAutoCloseTabs(),
		description: "Automatically close all tabs/windows opened by the script after completion.",
	},
];

// Load previous searches from local storage or initialize an empty array
var searches = GM_getValue("searches", []);

// store the list of window handles or urls to close them later
var tabsToClose = GM_getValue("tabsToClose", []);

// Adjust timeout if under cooldown and within search limit
if (UNDER_COOLDOWN && searches.length % 4 == 0 && searches.length > 0) {
	TIMEOUT = COOLDOWN_TIMEOUT * 60000; // mins * 60 secs
}
// Check if the current page is Bing search page
const isSearchPage = window.location.href.startsWith("https://www.bing.com/search");
// Check if the current page is Bing rewards dashboard page
const isDashboardPage = window.location.href.startsWith("https://rewards.bing.com/dashboard");
// Check if the current page is Bing rewards earn page
const isEarnPage = window.location.href.startsWith("https://rewards.bing.com/earn");
// Check if the current page is any Bing rewards page
const isRewardPage = isDashboardPage || isEarnPage;
// Check whether current device is a mobile or not
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const pageUrlParams = new URLSearchParams(window.location.search);
// Automation markers live in the URL hash fragment so they're not sent to Bing's servers.
const pageHashParams = new URLSearchParams(window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash);
const IS_AUTOMATION_CHILD_TAB = pageHashParams.get("ag_child") === "1";
const AUTOMATION_TAB_KIND = pageHashParams.get("ag_kind") || "";
const ACTIVE_EXPLORE_TASK_KEY = "activeExploreTaskId";

/**
 * Persistent reward task queue and runner
 * Keeps tasks across page navigations and reloads
 */
const REWARD_TASK_QUEUE_KEY = "rewardTaskQueue";
const REWARD_RUNNER_KEY = "rewardTaskRunner";
const QUEUE_DEBUG_LOG_KEY = "queueDebugLog";
const RUNNER_LEASE_TTL_MS = 180000;
const runnerInstanceId = `runner_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
let rewardQueueRunnerStarted = false;

/*=============================================*\
|*        AUTOMATION STATE MACHINE             *|
\*=============================================*/

const AUTOMATION_STATE_KEY = "automationState";
const AUTOMATION_RUNNING_KEY = "automationRunning";
const PHASES = {
	IDLE: "idle",
	PLAN_DASHBOARD: "phase_plan_dashboard",
	PLAN_EARN: "phase_plan_earn",
	PLAN_EXPLORE: "phase_plan_explore",
	PLAN_POINTS: "phase_plan_points",
	PLAN_SEED_SEARCH: "phase_plan_seed_search",
	EXECUTE: "phase_execute",
	COMPLETE: "phase_complete"
};

function isAutomationRunning() {
	return GM_getValue(AUTOMATION_RUNNING_KEY, false);
}

function setAutomationRunning(value) {
	GM_setValue(AUTOMATION_RUNNING_KEY, !!value);
	if (typeof renderStartStopButton === "function") renderStartStopButton();
}

const DEFAULT_AUTOMATION_STATE = {
	phase: PHASES.IDLE,
	startedAt: null,
	searchesNeeded: null,
	searchesCompleted: 0,
	searchTerms: [],
	error: null
};

function getAutomationState() {
	const state = GM_getValue(AUTOMATION_STATE_KEY, DEFAULT_AUTOMATION_STATE);
	// Auto-reset if stuck for more than 30 minutes
	if (state.phase !== PHASES.IDLE && state.startedAt && (Date.now() - state.startedAt > 30 * 60 * 1000)) {
		console.warn("[AutoGrind] Automation state is stale (>30min). Resetting to idle.");
		resetAutomationState();
		return { ...DEFAULT_AUTOMATION_STATE };
	}
	return state;
}

function setAutomationState(updates) {
	const current = getAutomationState();
	const next = { ...current, ...updates };
	GM_setValue(AUTOMATION_STATE_KEY, next);
	if (updates.phase && updates.phase !== current.phase) {
		console.log(`[AutoGrind] Phase transition: ${current.phase} -> ${next.phase}`);
		pushQueueDebugLog(`Phase: ${current.phase} → ${next.phase}`);
	}
	return next;
}

function resetAutomationState() {
	GM_setValue(AUTOMATION_STATE_KEY, { ...DEFAULT_AUTOMATION_STATE });
}

/** Map each phase to the URL where it should execute */
function getPhaseUrl(phase) {
	switch (phase) {
		case PHASES.PLAN_DASHBOARD:   return "https://rewards.bing.com/dashboard";
		case PHASES.PLAN_EARN:        return "https://rewards.bing.com/earn";
		case PHASES.PLAN_EXPLORE:     return "https://rewards.bing.com/earn";
		case PHASES.PLAN_POINTS:      return "https://rewards.bing.com/earn";
		case PHASES.PLAN_SEED_SEARCH: return "https://rewards.bing.com/earn";
		case PHASES.EXECUTE:          return "https://rewards.bing.com/earn";
		default:                      return null;
	}
}

/** Navigate to the correct page for the current phase if not already there */
function navigateForPhase(phase) {
	const targetUrl = getPhaseUrl(phase);
	if (!targetUrl) return false;
	if (!window.location.href.startsWith(targetUrl)) {
		pushQueueDebugLog(`Navigating to ${targetUrl} for ${phase}`);
		window.location.href = targetUrl;
		return true; // navigated away
	}
	return false; // already on correct page
}

/** Wait for a DOM element to appear, returning a promise */
function waitForElementAsync(selector, timeoutMs = 10000) {
	return new Promise((resolve) => {
		const existing = document.querySelector(selector);
		if (existing) { resolve(existing); return; }
		let elapsed = 0;
		const interval = setInterval(() => {
			elapsed += 300;
			const el = document.querySelector(selector);
			if (el) {
				clearInterval(interval);
				resolve(el);
			} else if (elapsed >= timeoutMs) {
				clearInterval(interval);
				resolve(null);
			}
		}, 300);
	});
}

function sleep(ms) {
	return new Promise(resolve => setTimeout(resolve, ms));
}

// Cancellable timer registry. Every setTimeout/setInterval used by the automation
// pipeline must register here so stopAutomation() can cancel pending work, and so
// callbacks short-circuit when automation has been stopped between schedule and fire.
const timerHandles = new Set();

function scheduleTimeout(fn, ms) {
	const handle = setTimeout(() => {
		timerHandles.delete(handle);
		if (!isAutomationRunning()) return;
		fn();
	}, ms);
	timerHandles.add(handle);
	return handle;
}

function scheduleInterval(fn, ms) {
	const handle = setInterval(() => {
		if (!isAutomationRunning()) {
			clearInterval(handle);
			timerHandles.delete(handle);
			return;
		}
		fn();
	}, ms);
	timerHandles.add(handle);
	return handle;
}

function clearAllTimers() {
	timerHandles.forEach(h => {
		clearTimeout(h);
		clearInterval(h);
	});
	timerHandles.clear();
}

// Append automation marker params to a URL hash fragment. The hash is local-only
// (browsers never send it to servers) so Bing's tracking sees the unmodified URL,
// but the userscript can still identify its own tabs via location.hash.
function withHashParams(url, params = {}) {
	try {
		const parsed = new URL(url, window.location.origin);
		const hashParams = new URLSearchParams(parsed.hash.startsWith("#") ? parsed.hash.slice(1) : parsed.hash);
		Object.entries(params).forEach(([key, value]) => {
			if (value !== null && value !== undefined && value !== "") {
				hashParams.set(key, String(value));
			}
		});
		parsed.hash = hashParams.toString();
		return parsed.toString();
	} catch (error) {
		console.warn(`[AutoGrind] Could not append hash params to URL: ${url}`, error);
		return url;
	}
}

// Fetch headlines from the BBC World RSS feed (host allowlisted via @connect).
// Returns an array of strings on success, [] on any failure — caller falls back
// to generators.
function fetchNewsHeadlines() {
	return new Promise((resolve) => {
		GM_xmlhttpRequest({
			method: "GET",
			url: "https://feeds.bbci.co.uk/news/world/rss.xml",
			timeout: 8000,
			onload: (res) => {
				try {
					const doc = new DOMParser().parseFromString(res.responseText, "text/xml");
					const headlines = Array.from(doc.querySelectorAll("item > title"))
						.map(el => (el.textContent || "").trim())
						.filter(s => s.length > 0);
					resolve(headlines);
				} catch (e) {
					console.warn("[News] Parse failed:", e);
					resolve([]);
				}
			},
			onerror: () => resolve([]),
			ontimeout: () => resolve([]),
		});
	});
}

// Slice each headline into a few random 3-6 word snippets, shuffle, dedupe,
// then take the first `count`. Snippets are cleaned of trailing punctuation
// so they look like queries someone would actually type.
function snippetsFromHeadlines(headlines, count) {
	const snippets = new Set();
	const cleanWord = w => w.replace(/^[^\w]+|[^\w]+$/g, "");
	const shuffled = [...headlines].shuffle();

	for (const headline of shuffled) {
		if (snippets.size >= count) break;
		const words = headline.split(/\s+/).map(cleanWord).filter(w => w.length > 1);
		if (words.length < 3) continue;
		// Yield a few (1-3) snippets per headline to maximize variety from a small set.
		const yields = 1 + Math.floor(Math.random() * 3);
		for (let i = 0; i < yields && snippets.size < count; i++) {
			const len = 3 + Math.floor(Math.random() * 4); // 3..6
			const maxStart = Math.max(0, words.length - len);
			const start = Math.floor(Math.random() * (maxStart + 1));
			const snippet = words.slice(start, start + len).join(" ").trim();
			if (snippet && snippet.split(" ").length >= 3) snippets.add(snippet);
		}
	}
	return Array.from(snippets).slice(0, count);
}

function getRewardTaskQueue() {
	return GM_getValue(REWARD_TASK_QUEUE_KEY, []);
}

function saveRewardTaskQueue(queue) {
	GM_setValue(REWARD_TASK_QUEUE_KEY, queue);
}

function generateTaskId(prefix = "task") {
	return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
}

function enqueueRewardTask(task) {
	const queue = getRewardTaskQueue();
	const duplicate = queue.find(existing =>
		existing.status !== "complete" &&
		existing.type === task.type &&
		existing.url === task.url &&
		existing.searchString === task.searchString
	);

	if (duplicate) {
		return duplicate.id;
	}

	const normalizedTask = {
		id: generateTaskId("reward"),
		title: task.title || "Untitled task",
		type: task.type,
		url: task.url || null,
		searchString: task.searchString || null,
		source: task.source || "unknown",
		status: "pending",
		timestamp: Date.now(),
		updatedAt: Date.now(),
		error: null,
		exploreTaskId: null
	};

	queue.push(normalizedTask);
	saveRewardTaskQueue(queue);
	console.log(`[RewardQueue] Added ${normalizedTask.type} task ${normalizedTask.id}: "${normalizedTask.title}"`);
	pushQueueDebugLog(`Enqueued ${normalizedTask.type} task: ${normalizedTask.title}`);
	return normalizedTask.id;
}

function getNextPendingRewardTask() {
	const queue = getRewardTaskQueue();
	return queue.find(task => task.status === "pending") || null;
}

function updateRewardTask(taskId, updater) {
	const queue = getRewardTaskQueue();
	const taskIndex = queue.findIndex(task => task.id === taskId);
	if (taskIndex === -1) return null;

	const currentTask = queue[taskIndex];
	const updatedTask = updater(currentTask);
	queue[taskIndex] = {
		...currentTask,
		...updatedTask,
		updatedAt: Date.now()
	};

	saveRewardTaskQueue(queue);
	return queue[taskIndex];
}

function markRewardTaskStatus(taskId, status, extra = {}) {
	return updateRewardTask(taskId, task => ({ ...task, status, ...extra }));
}

function clearRewardTaskQueue() {
	saveRewardTaskQueue([]);
	GM_setValue("exploreTaskQueue", []);
	GM_setValue(REWARD_RUNNER_KEY, null);
	console.log("[RewardQueue] Cleared reward and explore queues");
	pushQueueDebugLog("Cleared reward and explore queues", "warn");
}

function cleanupRewardTaskQueue() {
	const queue = getRewardTaskQueue();
	const thirtyMinutesAgo = Date.now() - (30 * 60 * 1000);
	const cleanedQueue = queue.filter(task => !(task.status === "complete" && task.updatedAt < thirtyMinutesAgo));
	if (cleanedQueue.length !== queue.length) {
		saveRewardTaskQueue(cleanedQueue);
		console.log(`[RewardQueue] Cleaned ${queue.length - cleanedQueue.length} completed tasks`);
	}
}

function getQueueStats() {
	const rewardQueue = getRewardTaskQueue();
	const exploreQueue = getExploreTaskQueue();
	const rewards = {
		pending: rewardQueue.filter(task => task.status === "pending").length,
		inProgress: rewardQueue.filter(task => task.status === "in_progress").length,
		complete: rewardQueue.filter(task => task.status === "complete").length,
		failed: rewardQueue.filter(task => task.status === "failed").length,
		total: rewardQueue.length
	};
	const explore = {
		pending: exploreQueue.filter(task => task.status === "pending").length,
		inProgress: exploreQueue.filter(task => task.status === "in_progress").length,
		complete: exploreQueue.filter(task => task.status === "complete").length,
		total: exploreQueue.length
	};
	return { rewards, explore };
}

function getQueueDebugLogs() {
	return GM_getValue(QUEUE_DEBUG_LOG_KEY, []);
}

function pushQueueDebugLog(message, level = "info") {
	const logs = getQueueDebugLogs();
	logs.push({ ts: Date.now(), level, message });
	const maxLogs = 250;
	const nextLogs = logs.slice(-maxLogs);
	GM_setValue(QUEUE_DEBUG_LOG_KEY, nextLogs);
}

function clearQueueDebugLogs() {
	GM_setValue(QUEUE_DEBUG_LOG_KEY, []);
}

function claimRunnerLease() {
	const now = Date.now();
	const lease = GM_getValue(REWARD_RUNNER_KEY, null);
	const leaseExpired = !lease || (now - lease.ts > RUNNER_LEASE_TTL_MS);
	if (leaseExpired || lease.id === runnerInstanceId) {
		GM_setValue(REWARD_RUNNER_KEY, { id: runnerInstanceId, ts: now });
		return true;
	}
	return false;
}

function refreshRunnerLease() {
	const lease = GM_getValue(REWARD_RUNNER_KEY, null);
	if (lease && lease.id === runnerInstanceId) {
		GM_setValue(REWARD_RUNNER_KEY, { id: runnerInstanceId, ts: Date.now() });
	}
}

function isRunnerOwner() {
	const lease = GM_getValue(REWARD_RUNNER_KEY, null);
	return !!lease && lease.id === runnerInstanceId;
}

async function runRewardTask(task) {
	const closeTimeout = getRandomTimeout();

	if (task.type === "direct") {
		if (!task.url) {
			pushQueueDebugLog(`Direct task missing URL: ${task.title}`, "error");
			markRewardTaskStatus(task.id, "failed", { error: "Missing task URL" });
			return;
		}
		const taskUrl = withHashParams(task.url, { ag_child: "1", ag_kind: "direct", ag_task_id: task.id });
		console.log(`[RewardQueue] Running direct task ${task.id}: ${taskUrl}`);
		pushQueueDebugLog(`Opening direct task: ${task.title}`);
		GM_openInTab(taskUrl, { active: false });
		if (getAutoCloseTabs()) addTabToClose(taskUrl, closeTimeout);
		let directWaitElapsed = 0;
		const directWaitTotal = closeTimeout + 1200;
		while (directWaitElapsed < directWaitTotal && isRunnerOwner() && isAutomationRunning()) {
			await sleep(1000);
			directWaitElapsed += 1000;
			refreshRunnerLease();
		}
		if (!isAutomationRunning()) return;
		markRewardTaskStatus(task.id, "complete");
		pushQueueDebugLog(`Completed direct task: ${task.title}`);
		return;
	}

	if (task.type === "explore") {
		console.log(`[RewardQueue] Running explore task ${task.id}: "${task.searchString}"`);
		pushQueueDebugLog(`Running explore task: ${task.title}`);
		const exploreTaskId = addExploreTask(task.searchString, task.title);
		markRewardTaskStatus(task.id, "in_progress", { exploreTaskId });
		const exploreUrl = withHashParams(task.url || "https://www.bing.com/", {
			ag_child: "1",
			ag_kind: "explore",
			ag_explore_task_id: exploreTaskId,
			ag_parent_task_id: task.id
		});

		GM_openInTab(exploreUrl, { active: false });
		if (getAutoCloseTabs()) addTabToClose(exploreUrl, closeTimeout + 10000);

		const maxWait = 90000;
		const pollInterval = 500;
		let elapsed = 0;
		while (elapsed < maxWait && isAutomationRunning()) {
			await sleep(pollInterval);
			elapsed += pollInterval;
			refreshRunnerLease();
			if (isTaskComplete(exploreTaskId)) {
				markRewardTaskStatus(task.id, "complete");
				pushQueueDebugLog(`Completed explore task: ${task.title}`);
				return;
			}
		}

		if (!isAutomationRunning()) return;
		markRewardTaskStatus(task.id, "failed", { error: "Explore task timed out" });
		pushQueueDebugLog(`Explore task timed out: ${task.title}`, "warn");
		return;
	}

	if (task.type === "search") {
		if (!task.searchString) {
			pushQueueDebugLog(`Search task missing query: ${task.title}`, "error");
			markRewardTaskStatus(task.id, "failed", { error: "Missing search string" });
			return;
		}
		console.log(`[RewardQueue] Running search task ${task.id}: "${task.searchString}"`);
		pushQueueDebugLog(`Running search task: ${task.searchString}`);
		const searchChildId = addExploreTask(task.searchString, task.title);
		markRewardTaskStatus(task.id, "in_progress", { exploreTaskId: searchChildId });

		const searchUrl = withHashParams(generateSearchUrl(task.searchString), {
			ag_child: "1",
			ag_kind: "search",
			ag_explore_task_id: searchChildId
		});

		GM_openInTab(searchUrl, { active: false });
		if (getAutoCloseTabs()) addTabToClose(searchUrl, closeTimeout + 10000);

		const maxWait = 90000;
		const pollInterval = 500;
		let elapsed = 0;
		while (elapsed < maxWait && isAutomationRunning()) {
			await sleep(pollInterval);
			elapsed += pollInterval;
			refreshRunnerLease();
			if (isTaskComplete(searchChildId)) {
				markRewardTaskStatus(task.id, "complete");
				const currentState = getAutomationState();
				setAutomationState({
					searchesCompleted: (currentState.searchesCompleted || 0) + 1
				});
				pushQueueDebugLog(`Completed search task: "${task.searchString}"`);
				return;
			}
		}

		if (!isAutomationRunning()) return;
		markRewardTaskStatus(task.id, "failed", { error: "Search task timed out" });
		pushQueueDebugLog(`Search task timed out: "${task.searchString}"`, "warn");
		return;
	}

	markRewardTaskStatus(task.id, "failed", { error: `Unsupported task type: ${task.type}` });
	pushQueueDebugLog(`Unsupported task type encountered: ${task.type}`, "error");
}

async function startRewardQueueRunner() {
	if (rewardQueueRunnerStarted) return;
	if (!isAutomationRunning()) {
		console.log("[RewardQueue] Automation not running. Runner not started.");
		return;
	}
	if (!claimRunnerLease()) {
		console.log("[RewardQueue] Another tab is running the queue");
		pushQueueDebugLog("Runner start skipped: another tab owns the lease", "warn");
		return;
	}

	rewardQueueRunnerStarted = true;
	console.log("[RewardQueue] Runner started");
	pushQueueDebugLog(`Runner started in tab ${runnerInstanceId}`);

	while (isRunnerOwner() && isAutomationRunning()) {
		cleanupRewardTaskQueue();
		cleanupCompletedTasks();
		refreshRunnerLease();

		const nextTask = getNextPendingRewardTask();
		if (!nextTask) {
			break;
		}

		markRewardTaskStatus(nextTask.id, "in_progress", { error: null });
		try {
			await runRewardTask(nextTask);
		} catch (error) {
			console.error("[RewardQueue] Task failed", error);
			pushQueueDebugLog(`Task failed: ${nextTask.title} (${String(error?.message || error)})`, "error");
			markRewardTaskStatus(nextTask.id, "failed", { error: String(error?.message || error) });
		}

		refreshRunnerLease();
		await sleep(1000);
	}

	if (isRunnerOwner()) {
		GM_setValue(REWARD_RUNNER_KEY, null);
	}
	rewardQueueRunnerStarted = false;
	console.log("[RewardQueue] Runner stopped");
	pushQueueDebugLog("Runner stopped");

	// If the queue drained naturally while in EXECUTE phase, transition to COMPLETE.
	if (isAutomationRunning()) {
		const remaining = getRewardTaskQueue().filter(t => t.status === "pending" || t.status === "in_progress");
		if (remaining.length === 0 && getAutomationState().phase === PHASES.EXECUTE) {
			setAutomationState({ phase: PHASES.COMPLETE });
			await executeCompletePhase();
		}
	}
}

// Add shuffle method to Array prototype
Array.prototype.shuffle = function() {
	const array = [...this];
	for (let i = array.length - 1; i > 0; i--) {
		const j = Math.floor(Math.random() * (i + 1));
		[array[i], array[j]] = [array[j], array[i]];
	}
	return array;
};

/*=============================================*\
|*					MAIN UI					   *|
\*=============================================*/

/**
 * Create a container for the stop/enable, start, and settings buttons.
 * These buttons now appear on all pages and have improved styling.
 */
const autoSearchContainer = document.createElement("div");
autoSearchContainer.classList.add("auto-search-container");

const searchIcon = document.createElement("div");
searchIcon.classList.add("search-icon", "modern-button");

const PLAY_ICON_HTML = `
	<svg class="button-icon" viewBox="0 0 24 24" fill="currentColor">
		<path d="M8 5v14l11-7z"/>
	</svg>
	<span>Start</span>
`;
const STOP_ICON_HTML = `
	<svg class="button-icon" viewBox="0 0 24 24" fill="currentColor">
		<rect x="6" y="6" width="12" height="12" rx="2"/>
	</svg>
	<span>Stop</span>
`;

function renderStartStopButton() {
	const running = isAutomationRunning();
	searchIcon.innerHTML = running ? STOP_ICON_HTML : PLAY_ICON_HTML;
	searchIcon.title = running ? "Stop automation" : "Start Auto-Search!!";
	searchIcon.classList.toggle("running", running);
}

renderStartStopButton();
searchIcon.addEventListener("click", () => {
	if (isAutomationRunning()) {
		stopAutomation();
	} else {
		startSearch();
	}
});

// Pick up cross-tab state changes (Stop clicked in another tab) without needing GM_addValueChangeListener.
setInterval(renderStartStopButton, 1000);

const settingsIcon = document.createElement("div");
settingsIcon.classList.add("settings-icon", "modern-button");
settingsIcon.innerHTML = `
	<svg class="button-icon" viewBox="0 0 24 24" fill="currentColor">
		<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/>
	</svg>
	<span>Settings</span>
`;
settingsIcon.title = "Configure Auto-Search Settings";

const queueIcon = document.createElement("div");
queueIcon.classList.add("queue-icon", "modern-button");
queueIcon.innerHTML = `
	<svg class="button-icon" viewBox="0 0 24 24" fill="currentColor">
		<path d="M3 5h18v2H3V5zm0 6h18v2H3v-2zm0 6h12v2H3v-2z"/>
	</svg>
	<span>Queue</span>
`;
queueIcon.title = "Open Queue Monitor";

const rateIcon = document.createElement("div");
rateIcon.classList.add("rate-icon", "modern-button", "mini-button");
rateIcon.innerHTML = `
	<svg class="button-icon" viewBox="0 0 24 24" fill="currentColor">
		<path d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/>
	</svg>
`;
rateIcon.title = "Rate this script on OpenUserJS";

const reportIcon = document.createElement("div");
reportIcon.classList.add("report-icon", "modern-button", "mini-button");
reportIcon.innerHTML = `
	<svg class="button-icon" viewBox="0 0 24 24" fill="currentColor">
		<path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"/>
	</svg>
`;
reportIcon.title = "Report a problem";

// UI buttons only appear on reward pages (dashboard and earn)
if (isRewardPage) {
	autoSearchContainer.appendChild(searchIcon);
	autoSearchContainer.appendChild(settingsIcon);
	autoSearchContainer.appendChild(queueIcon);

	// Group the smaller secondary buttons in a row, slightly inset from the main column.
	const miniRow = document.createElement("div");
	miniRow.classList.add("mini-button-row");
	miniRow.appendChild(rateIcon);
	miniRow.appendChild(reportIcon);
	autoSearchContainer.appendChild(miniRow);
}

 // Add menu entries for Search and Settings
 GM_registerMenuCommand('Start / Stop Auto-Search', function() {
	 if (isAutomationRunning()) stopAutomation();
	 else startSearch();
 }, 's');
 GM_registerMenuCommand('Settings', function() {
	 settingsOverlay.style.display = 'flex';
 }, 'c');
 GM_registerMenuCommand('Queue Monitor', function() {
	 openQueueOverlay();
 }, 'q');


/**
 * Create a settings overlay to configure the user script.
 * The settings overlay contains a list of configuration options that can be adjusted by the user.
 * The settings are stored in the local storage and are used to update the script's behavior.
 */
const settingsOverlay = document.createElement("div");
settingsOverlay.classList.add("settings-overlay");
settingsOverlay.style = `position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: radial-gradient(ellipse at top, rgba(15, 23, 42, 0.55), rgba(0, 0, 0, 0.7)); backdrop-filter: blur(14px) saturate(140%); -webkit-backdrop-filter: blur(14px) saturate(140%); display: none; justify-content: center; align-items: center; z-index: 10000; animation: fadeIn 0.3s ease;`;

const settingsContent = document.createElement("div");
settingsContent.classList.add("settings-content");
settingsContent.style = `background: linear-gradient(180deg, #344361 0%, #2D3C59 50%, #1f2b42 100%); padding: 0; border-radius: 20px; display: flex; flex-direction: column; max-width: 680px; max-height: 80vh; box-shadow: 0 30px 70px rgba(0, 0, 0, 0.55), 0 0 0 1px rgba(255, 255, 255, 0.06); animation: slideUp 0.4s cubic-bezier(0.22, 1, 0.36, 1); overflow: hidden;`;

// Add header
const settingsHeader = document.createElement("div");
settingsHeader.classList.add("settings-header");
settingsHeader.innerHTML = `
	<h2 style="margin: 0; color: white; font-size: 17px; font-weight: 700; letter-spacing: 0.2px; display: flex; align-items: center; gap: 10px;">
		<svg style="width: 20px; height: 20px;" viewBox="0 0 24 24" fill="white">
			<path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/>
		</svg>
		Settings
	</h2>
	<button class="settings-close-btn" title="Close Settings">
		<svg viewBox="0 0 24 24" fill="white">
			<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
		</svg>
	</button>
`;

const settingsBody = document.createElement("div");
settingsBody.classList.add("settings-body");
settingsBody.style = `background-color: #f4f6fa; padding: 18px; overflow-y: auto; flex: 1;`;

const queueOverlay = document.createElement("div");
queueOverlay.classList.add("settings-overlay", "queue-overlay");
queueOverlay.style = `position: fixed; top: 90px; left: 190px; display: none; z-index: 10001; font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;`;

const queueOverlayContent = document.createElement("div");
queueOverlayContent.classList.add("settings-content", "queue-overlay-content");
queueOverlayContent.style = `background: linear-gradient(180deg, #344361 0%, #2D3C59 50%, #1f2b42 100%); padding: 0; border-radius: 16px; display: flex; flex-direction: column; width: 380px; max-height: 520px; box-shadow: 0 14px 40px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.06); animation: slideUp 0.35s cubic-bezier(0.22, 1, 0.36, 1); overflow: hidden;`;

const queueOverlayHeader = document.createElement("div");
queueOverlayHeader.classList.add("settings-header");
queueOverlayHeader.innerHTML = `
	<h2 style="margin: 0; color: white; font-size: 13.5px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase; display: flex; align-items: center; gap: 8px;">
		<svg style="width: 16px; height: 16px; opacity: 0.85;" viewBox="0 0 24 24" fill="white"><path d="M3 5h18v2H3V5zm0 6h18v2H3v-2zm0 6h12v2H3v-2z"/></svg>
		Queue Monitor
	</h2>
	<button class="settings-close-btn" title="Close Queue Monitor">
		<svg viewBox="0 0 24 24" fill="white">
			<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/>
		</svg>
	</button>
`;

const queueOverlayTabs = document.createElement("div");
queueOverlayTabs.classList.add("settings-tabs");
queueOverlayTabs.innerHTML = `
	<button class="settings-tab active" data-queue-tab="queue">Queue</button>
	<button class="settings-tab" data-queue-tab="logs">Debug Log</button>
`;

const queuePanelBody = document.createElement("div");
queuePanelBody.classList.add("settings-body", "queue-debug-body");
queuePanelBody.style = `background-color: #f4f6fa; padding: 12px; overflow-y: auto; flex: 1;`;

const queueLogBody = document.createElement("div");
queueLogBody.classList.add("settings-body", "queue-debug-body");
queueLogBody.style = `background-color: #f4f6fa; padding: 12px; overflow-y: auto; flex: 1; display: none;`;

let queueDebugInterval = null;
let activeQueueOverlayTab = "queue";

function getStatusTagClass(status) {
	if (status === "complete") return "status-complete";
	if (status === "in_progress") return "status-progress";
	if (status === "failed") return "status-failed";
	return "status-pending";
}

// Map phase to a high-level "stage" label for the queue overlay header.
const PLAN_PHASES = new Set([
	PHASES.PLAN_DASHBOARD, PHASES.PLAN_EARN, PHASES.PLAN_EXPLORE,
	PHASES.PLAN_POINTS, PHASES.PLAN_SEED_SEARCH
]);

function getStageLabel(state) {
	if (!isAutomationRunning()) return state.phase === PHASES.COMPLETE ? "Complete" : "Idle";
	if (PLAN_PHASES.has(state.phase)) return "Planning";
	if (state.phase === PHASES.EXECUTE) return "Executing";
	if (state.phase === PHASES.COMPLETE) return "Complete";
	return "Idle";
}

function renderQueueDebugPanel() {
	const rewardQueue = getRewardTaskQueue();
	const exploreQueue = getExploreTaskQueue();
	const stats = getQueueStats();
	const lease = GM_getValue(REWARD_RUNNER_KEY, null);
	const autoState = getAutomationState();
	const stageLabel = getStageLabel(autoState);
	const phaseLabel = autoState.phase === PHASES.IDLE ? "Idle" : autoState.phase.replace("phase_", "").replace(/_/g, " ");

	const inProgressTask = rewardQueue.find(t => t.status === "in_progress");
	const finishedCount = stats.rewards.complete + stats.rewards.failed;
	const totalCount = stats.rewards.total;

	let stageDetail = stageLabel;
	if (isAutomationRunning()) {
		const progressFragment = totalCount > 0 ? ` ${finishedCount}/${totalCount}` : "";
		const phaseFragment = stageLabel === "Planning" ? ` (${phaseLabel})` : "";
		const taskFragment = inProgressTask ? ` — ${inProgressTask.title}` : "";
		stageDetail = `${stageLabel}${progressFragment}${phaseFragment}${taskFragment}`;
	}

	const searchProgress = autoState.searchesNeeded != null
		? `${autoState.searchesCompleted || 0}/${autoState.searchesNeeded}`
		: "—";

	const rewardRows = rewardQueue.length
		? rewardQueue.map(task => `
			<div class="queue-row">
				<div class="queue-row-title">${task.title}</div>
				<div class="queue-row-meta">
					<span class="queue-type">${task.type}</span>
					<span class="queue-status ${getStatusTagClass(task.status)}">${task.status}</span>
				</div>
			</div>
		`).join("")
		: `<div class="queue-empty">No reward tasks queued.</div>`;

	const exploreRows = exploreQueue.length
		? exploreQueue.map(task => `
			<div class="queue-row small">
				<div class="queue-row-title">${task.title}</div>
				<div class="queue-row-meta">
					<span class="queue-status ${getStatusTagClass(task.status)}">${task.status}</span>
				</div>
			</div>
		`).join("")
		: `<div class="queue-empty">No explore child tasks.</div>`;

	queuePanelBody.innerHTML = `
		<div class="queue-stage-banner">${stageDetail}</div>
		<div class="queue-summary-grid">
			<div class="queue-summary-card"><strong>Reward Total</strong><span>${stats.rewards.total}</span></div>
			<div class="queue-summary-card"><strong>Pending</strong><span>${stats.rewards.pending}</span></div>
			<div class="queue-summary-card"><strong>In Progress</strong><span>${stats.rewards.inProgress}</span></div>
			<div class="queue-summary-card"><strong>Failed</strong><span>${stats.rewards.failed}</span></div>
		</div>
		<div class="queue-lease-row">Phase: <strong>${phaseLabel}</strong> | Searches: ${searchProgress}</div>
		<div class="queue-lease-row">Orchestrator: ${lease ? lease.id : "none"}</div>
		<div class="queue-section">
			<div class="queue-section-header">
				<h3>Reward Queue</h3>
				<button class="queue-clear-btn" id="clear-queue-btn">Clear Queue</button>
			</div>
			<div class="queue-list">${rewardRows}</div>
		</div>
		<div class="queue-section">
			<div class="queue-section-header">
				<h3>Explore Subtasks</h3>
				<span class="queue-count">${stats.explore.total} total</span>
			</div>
			<div class="queue-list">${exploreRows}</div>
		</div>
	`;

	const clearButton = queuePanelBody.querySelector("#clear-queue-btn");
	if (clearButton) {
		clearButton.addEventListener("click", () => {
			stopAutomation();
			clearQueueDebugLogs();
			renderQueueDebugPanel();
			renderQueueDebugLogs();
		});
	}
}

function renderQueueDebugLogs() {
	const logs = getQueueDebugLogs();
	const rows = logs.length
		? logs.slice().reverse().map(log => {
			const time = new Date(log.ts).toLocaleTimeString();
			return `<div class="queue-log-row ${log.level}"><span>[${time}]</span> ${log.message}</div>`;
		}).join("")
		: `<div class="queue-empty">No debug logs yet.</div>`;

	queueLogBody.innerHTML = `
		<div class="queue-section">
			<div class="queue-section-header">
				<h3>Debug Log</h3>
				<button class="queue-clear-btn" id="clear-log-btn">Clear Log</button>
			</div>
			<div class="queue-log-list">${rows}</div>
		</div>
	`;

	const clearLogButton = queueLogBody.querySelector("#clear-log-btn");
	if (clearLogButton) {
		clearLogButton.addEventListener("click", () => {
			clearQueueDebugLogs();
			renderQueueDebugLogs();
		});
	}
}

function setQueueOverlayTab(tabName) {
	activeQueueOverlayTab = tabName;
	queueOverlayTabs.querySelectorAll(".settings-tab").forEach(tab => {
		tab.classList.toggle("active", tab.dataset.queueTab === tabName);
	});
	queuePanelBody.style.display = tabName === "queue" ? "block" : "none";
	queueLogBody.style.display = tabName === "logs" ? "block" : "none";
	if (tabName === "queue") renderQueueDebugPanel();
	if (tabName === "logs") renderQueueDebugLogs();
}

function startQueueOverlayRefresh() {
	if (queueDebugInterval) clearInterval(queueDebugInterval);
	renderQueueDebugPanel();
	renderQueueDebugLogs();
	queueDebugInterval = setInterval(() => {
		renderQueueDebugPanel();
		renderQueueDebugLogs();
	}, 2000);
}

function openQueueOverlay() {
	QUEUE_OVERLAY_OPEN = true;
	GM_setValue("queue-overlay-open", true);
	setQueueOverlayTab(activeQueueOverlayTab || "queue");
	queueOverlay.style.display = "block";
	startQueueOverlayRefresh();
}

function closeQueueOverlay() {
	QUEUE_OVERLAY_OPEN = false;
	GM_setValue("queue-overlay-open", false);
	if (queueDebugInterval) {
		clearInterval(queueDebugInterval);
		queueDebugInterval = null;
	}
	queueOverlay.style.animation = "fadeOut 0.3s ease";
	queueOverlayContent.style.animation = "slideDown 0.3s ease";
	setTimeout(() => {
		queueOverlay.style.display = "none";
		queueOverlay.style.animation = "";
		queueOverlayContent.style.animation = "";
	}, 300);
}

queueOverlayTabs.querySelectorAll(".settings-tab").forEach(tab => {
	tab.addEventListener("click", () => setQueueOverlayTab(tab.dataset.queueTab));
});

configurations.forEach(config => {
	const settingItem = document.createElement("div");
	settingItem.classList.add("settings-item");

	const settingCard = document.createElement("div");
	settingCard.classList.add("setting-card");

	const settingHeader = document.createElement("div");
	settingHeader.classList.add("setting-card-header");

	const name = document.createElement("div");
	name.classList.add("settings-item-name");
	name.textContent = config.name;

	const currentValue = document.createElement("div");
	currentValue.classList.add("settings-item-value");

	settingHeader.appendChild(name);
	settingHeader.appendChild(currentValue);

	const inputContainer = document.createElement("div");
	inputContainer.classList.add("settings-item-input");
	let input;
	if (config.type == "slider") {
		input = document.createElement("input");
		input.type = "range";
		input.min = config.range[0];
		input.max = config.range[1];
		input.value = GM_getValue(config.id, config.value);
		input.disabled = config.disabled;
		input.classList.add("modern-slider");
	} else if (config.type == "range") {
		input = document.createElement("div"); input.classList.add("range-slider");
		input.value = GM_getValue(config.id, config.value);
		input.valueText = input.value.join("-");
		for (let i = 0; i < 2; i++) {
			let rangeInput = document.createElement("input");
			Object.assign(rangeInput, { type: "range", value: config.value[i], min: config.range[0], max: config.range[1], style: `height: 1px;` });
			rangeInput.classList.add("modern-slider");
			input.appendChild(rangeInput);
			rangeInput.addEventListener("input", () => {
				if (parseInt(input.children[0].value) > parseInt(input.children[1].value)) input.children[i].value = input.children[1 - i].value; // Ensure min <= max
				input.value = [input.children[0].value, input.children[1].value];
				input.valueText = input.value.join("-");
			});
		}
	} else if (config.type == "checkbox") {
		input = document.createElement("input");
		input.type = "checkbox";
		input.checked = GM_getValue(config.id, config.value);
		input.oninput = () => input.valueText = input.checked ? "Enabled" : "Disabled";
		input.classList.add("modern-checkbox");
	}
	input.id = config.id;
	input.dispatchEvent(new Event("input"));	// Trigger input event to initialize `input.valueText`

	currentValue.textContent = input.valueText??input.value;

	input.addEventListener("input", () => {
		GM_setValue(config.id, input.type == "checkbox" ? input.checked : input.value);
		currentValue.textContent = input.valueText??input.value;
		updateConfigVariable(config.id, input.type == "checkbox" ? input.checked : input.value);
	});
	inputContainer.appendChild(input);

	const description = document.createElement("div");
	description.classList.add("settings-item-description");
	description.innerHTML = config.description;

	settingCard.appendChild(settingHeader);
	settingCard.appendChild(inputContainer);
	settingCard.appendChild(description);
	settingItem.appendChild(settingCard);

	settingsBody.appendChild(settingItem);
});

settingsContent.appendChild(settingsHeader);
settingsContent.appendChild(settingsBody);
settingsOverlay.appendChild(settingsContent);

queueOverlayContent.appendChild(queueOverlayHeader);
queueOverlayContent.appendChild(queueOverlayTabs);
queueOverlayContent.appendChild(queuePanelBody);
queueOverlayContent.appendChild(queueLogBody);
queueOverlay.appendChild(queueOverlayContent);

// Function to close settings with animation
function closeSettings() {
	settingsOverlay.style.animation = "fadeOut 0.3s ease";
	settingsContent.style.animation = "slideDown 0.3s ease";
	setTimeout(() => {
		settingsOverlay.style.display = "none";
		settingsOverlay.style.animation = "";
		settingsContent.style.animation = "";
	}, 300);
}

// Close button event listener
settingsHeader.querySelector(".settings-close-btn").addEventListener("click", closeSettings);
queueOverlayHeader.querySelector(".settings-close-btn").addEventListener("click", closeQueueOverlay);

if (isRewardPage) {
	document.body.appendChild(settingsOverlay);
	document.body.appendChild(queueOverlay);

	// Restore queue overlay if it was previously open
	if (QUEUE_OVERLAY_OPEN) {
		openQueueOverlay();
	}
}

settingsIcon.addEventListener("click", () => {
	settingsOverlay.style.display = "flex";
});

queueIcon.addEventListener("click", () => {
	openQueueOverlay();
});

// Close settings dialog when clicking outside the popup
settingsOverlay.addEventListener("mousedown", function (event) {
	// Only close if clicking outside the settingsContent
	if (event.target === settingsOverlay) {
		closeSettings();
	}
});

// Feedback dialog (Rate / Report). A single overlay, repopulated per call.
const feedbackOverlay = document.createElement("div");
feedbackOverlay.classList.add("feedback-dialog-overlay");
const feedbackDialog = document.createElement("div");
feedbackDialog.classList.add("feedback-dialog");
feedbackOverlay.appendChild(feedbackDialog);
if (isRewardPage) document.body.appendChild(feedbackOverlay);

function closeFeedbackDialog() {
	feedbackOverlay.style.animation = "fadeOut 0.3s ease";
	feedbackDialog.style.animation = "slideDown 0.3s var(--ag-ease)";
	setTimeout(() => {
		feedbackOverlay.style.display = "none";
		feedbackOverlay.style.animation = "";
		feedbackDialog.style.animation = "";
	}, 300);
}

function showFeedbackDialog({ variant, iconSvg, title, body, primaryLabel, url }) {
	feedbackDialog.innerHTML = `
		<div class="feedback-dialog-icon ${variant}">${iconSvg}</div>
		<h3>${title}</h3>
		<p>${body}</p>
		<div class="feedback-dialog-actions">
			<button class="feedback-btn secondary" data-action="close">Maybe later</button>
			<button class="feedback-btn primary ${variant}" data-action="open">${primaryLabel}</button>
		</div>
	`;
	feedbackOverlay.style.display = "flex";
	feedbackDialog.querySelector('[data-action="close"]').addEventListener("click", closeFeedbackDialog);
	feedbackDialog.querySelector('[data-action="open"]').addEventListener("click", () => {
		try { GM_openInTab(url, { active: true, insert: true }); }
		catch { window.open(url, "_blank", "noopener,noreferrer"); }
		closeFeedbackDialog();
	});
}

feedbackOverlay.addEventListener("mousedown", (event) => {
	if (event.target === feedbackOverlay) closeFeedbackDialog();
});

const STAR_SVG = `<svg viewBox="0 0 24 24" fill="white"><path d="M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>`;
const REPORT_SVG = `<svg viewBox="0 0 24 24" fill="white"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-7 12h-2v-2h2v2zm0-4h-2V6h2v4z"/></svg>`;

rateIcon.addEventListener("click", () => {
	showFeedbackDialog({
		variant: "rate",
		iconSvg: STAR_SVG,
		title: "Enjoying the script?",
		body: "A quick vote on OpenUserJS keeps the project alive and helps me find time to keep building. It only takes a moment — thank you so much for the support!",
		primaryLabel: "Vote on OpenUserJS",
		url: "https://openuserjs.org/scripts/amit/Bing_Rewards"
	});
});

reportIcon.addEventListener("click", () => {
	showFeedbackDialog({
		variant: "report",
		iconSvg: REPORT_SVG,
		title: "Something not working?",
		body: "Bug reports help me find and fix issues much faster than I ever could on my own. Even a short note about what went wrong is hugely valuable — thank you for taking the time.",
		primaryLabel: "Open an issue",
		url: "https://openuserjs.org/scripts/amit/Bing_Rewards/issues"
	});
});

// Close settings with Escape key
document.addEventListener("keydown", function(event) {
	if (event.key === "Escape" && settingsOverlay.style.display === "flex") {
		closeSettings();
	}
	if (event.key === "Escape" && queueOverlay.style.display !== "none") {
		closeQueueOverlay();
	}
	if (event.key === "Escape" && feedbackOverlay.style.display === "flex") {
		closeFeedbackDialog();
	}
});

// Add logic to enable/disable the cooldown-timeout input
const underCooldownEl = settingsOverlay.querySelector("#under-cooldown");
if (underCooldownEl) {
    underCooldownEl.addEventListener("change", (event) => {
        const cooldownInput = settingsOverlay.querySelector("#cooldown-timeout");
        if (cooldownInput) cooldownInput.disabled = !event.target.checked;
    });
}


/**
 * This function updates the icon's appearance with the specified content and classlist.
 * @param {string} content - The content to display in the icon.
 * @param {string} classlist - The classlist to apply to the icon.
 */
function updateIcon(content, classlist="searching") {
	searchIcon.classList.add(classlist);
	settingsIcon.classList.add(classlist);
	searchIcon.querySelector("span").textContent = content;
}


/**
 * This function updates the configuration variables based on the user's input in the settings overlay.
 * This is required only for configurations that require immediate changes before reloading the tab like the `Max Searches` option.
 * @param {string} id - The id of the configuration variable to update.
 * @param {string} value - The new value of the configuration variable.
*/
function updateConfigVariable(id, value) {
	if (id === "cooldown-timeout") COOLDOWN_TIMEOUT = parseInt(value);
	else if (id === "under-cooldown") UNDER_COOLDOWN = !!value;
  }

/*=============================================*\
|* 				HELPER FUNCTIONS			   *|
\*=============================================*/
/**
 * Initiate the automation flow.
 * Resets state and begins the planning pipeline (no execution yet):
 * Plan Dashboard -> Plan Earn -> Plan Explore -> Plan Points -> Plan Seed Search
 * After planning completes, the queue runner drains all queued tasks one at a time.
 */
function startSearch() {
	// Reset all state
	resetAutomationState();
	clearRewardTaskQueue();
	searches = [];
	GM_setValue("searches", searches);
	tabsToClose = [];
	GM_setValue("tabsToClose", tabsToClose);

	setAutomationRunning(true);

	// Initialize state machine - start planning from the dashboard
	setAutomationState({
		phase: PHASES.PLAN_DASHBOARD,
		startedAt: Date.now()
	});

	pushQueueDebugLog("Start clicked. Beginning planning pipeline.");

	// Navigate to dashboard to begin planning
	if (isDashboardPage) {
		waitForRewardPageReady(() => dispatchPhase(getAutomationState()));
	} else {
		window.location.href = "https://rewards.bing.com/dashboard";
	}
}

/**
 * Halt all automation work. Flips the running flag (so any in-flight callbacks
 * short-circuit), cancels every pending timer, clears queues so future tab loads
 * don't try to auto-close, and releases the runner lease.
 */
function stopAutomation() {
	setAutomationRunning(false);
	clearAllTimers();
	resetAutomationState();
	GM_setValue("tabsToClose", []);
	tabsToClose = [];
	GM_setValue("searches", []);
	searches = [];
	clearRewardTaskQueue();
	GM_setValue(REWARD_RUNNER_KEY, null);
	rewardQueueRunnerStarted = false;
	pushQueueDebugLog("Automation stopped by user", "warn");
}

/*=============================================*\
|*       COLLECTION FUNCTIONS                  *|
\*=============================================*/

// Daily-set tasks live on /dashboard. We enqueue them so the queue UI shows the plan,
// then click each <a> in place — clicking the real DOM element preserves the user-gesture
// + referer + cookie context Bing's tracking relies on. GM_openInTab loses that.
async function collectDashboardActivities() {
	console.log("[Dashboard] Planning daily set activity cards...");

	const root = document.querySelector("section#dailyset")
		|| findSectionByHeading(/^(daily set|today'?s set)$/i);

	if (!root) {
		pushQueueDebugLog("[Dashboard] Daily set section NOT FOUND in DOM", "error");
		return 0;
	}

	const allCards = root.querySelectorAll('a[data-rac][href], a[href*="bing.com/search"]');
	if (allCards.length === 0) {
		pushQueueDebugLog("[Dashboard] No anchor cards inside section", "warn");
		return 0;
	}

	const taskEntries = [];
	let completed = 0, noChip = 0, locked = 0, enqueued = 0, skippedTitleless = 0, blacklisted = 0;
	for (const card of allCards) {
		if (isCardCompleted(card)) { completed++; continue; }
		if (isCardLocked(card))    { locked++;    continue; }
		const pts = getCardPoints(card);
		if (!pts) { noChip++; continue; }

		const title = extractCardTitle(card);
		if (!title) { logTitlelessCard("[Dashboard]", card); skippedTitleless++; continue; }
		if (isTaskBlacklisted(title)) {
			pushQueueDebugLog(`[Dashboard] Blacklisted card skipped: "${title}"`);
			blacklisted++;
			continue;
		}

		const taskId = enqueueRewardTask({
			type: "direct",
			title: `${title} (+${pts})`,
			url: card.href,
			source: "dashboard"
		});
		taskEntries.push({ card, title, taskId });
		enqueued++;
	}

	pushQueueDebugLog(`[Dashboard] cards=${allCards.length} completed=${completed} locked=${locked} noChip=${noChip} blacklisted=${blacklisted} enqueued=${enqueued} skippedTitleless=${skippedTitleless}`);

	for (const { card, title, taskId } of taskEntries) {
		if (!isAutomationRunning()) break;
		try {
			markRewardTaskStatus(taskId, "in_progress");
			// Marker goes in the hash so Bing's server-side tracking sees the unmodified URL.
			card.href = withHashParams(card.href, { ag_child: "1", ag_kind: "direct", ag_task_id: taskId });
			card.target = "_blank";
			card.click();
			pushQueueDebugLog(`Clicked dashboard card: ${title}`);

			const closeTimeout = getRandomTimeout();
			if (getAutoCloseTabs()) addTabToClose(card.href, closeTimeout);

			await sleep(closeTimeout + 1200);
			if (!isAutomationRunning()) break;
			markRewardTaskStatus(taskId, "complete");
		} catch (error) {
			console.error(`[Dashboard] Click failed for "${title}":`, error);
			pushQueueDebugLog(`Click failed for "${title}": ${error.message}`, "error");
			markRewardTaskStatus(taskId, "pending", { error: `Click failed: ${error.message}` });
		}
	}

	return enqueued;
}

// Card heuristics. Microsoft rotates React class hashes; these helpers center the logic
// in one place so a future schema change is a single-file edit.

// "+N" chip identifies cards with claimable points. The chip element lives in a div
// with bg-statusInformativeTintBg; the number is the inner <p>'s text. Fallback:
// any "+\d+" text inside the card.
function getCardPoints(card) {
	const chipEl = card.querySelector('.bg-statusInformativeTintBg');
	if (chipEl) {
		const m = (chipEl.textContent || '').match(/(\d+)/);
		return m ? parseInt(m[1], 10) : 1;
	}
	const m2 = (card.textContent || '').match(/\+\s*(\d{1,4})(?!\d)/);
	if (m2) return parseInt(m2[1], 10);
	// Older schema: explicit "X pts" badge
	if (/\d+\s*pts?\b/i.test(card.textContent || '')) return 1;
	return 0;
}

function isCardCompleted(card) {
	if (card.querySelector('.bg-statusSuccessRewardsBg, .bg-statusSuccessBg3')) return true;
	return /\bCompleted\b/.test(card.textContent || '');
}

function isCardLocked(card) {
	if (card.hasAttribute('data-disabled') || card.getAttribute('aria-disabled') === 'true') return true;
	return /Available on \w+|Unlocks (?:tomorrow|on \w+)/i.test(card.textContent || '');
}

// Microsoft's Explore-on-Bing prompts follow "Search on Bing for/to X" — the X is the
// exact query they want. Falls back to null if the pattern doesn't match, in which
// case the caller can derive a query some other way.
function deriveSearchFromPrompt(description) {
	if (!description) return null;
	const cleaned = description.replace(/​/g, '').trim();
	const m = cleaned.match(/^search on bing (?:for|to)\s+(.+?)[.\s]*$/i);
	return m ? m[1].trim() : null;
}

function extractCardTitle(card) {
	return card.querySelector('p.text-globalBody2Strong')?.textContent?.trim()
		|| card.querySelector('p[class*="globalBody2Strong"]')?.textContent?.trim()
		|| card.querySelector('p.text-body1Strong')?.textContent?.trim()
		|| card.querySelector('p[class*="body1Strong"]')?.textContent?.trim()
		|| card.getAttribute('aria-label')?.trim()
		|| null;
}

function extractCardDescription(card) {
	// Description is the second line-clamp paragraph (not the bold title).
	const ps = card.querySelectorAll('p.line-clamp-3, p[class*="line-clamp"]');
	for (const p of ps) {
		if (/globalBody2Strong|body1Strong/.test(p.className)) continue;
		const txt = p.textContent?.trim();
		if (txt) return txt;
	}
	return '';
}

// If the card's href is a Bing search URL, return the query — that's the exact
// search Microsoft expects, no need to derive one from title/description.
function extractBingSearchQuery(url) {
	try {
		const u = new URL(url);
		if (/(^|\.)bing\.com$/.test(u.host) && u.pathname === '/search' && u.searchParams.has('q')) {
			return u.searchParams.get('q');
		}
	} catch {}
	return null;
}

// Find a section by heading text, working with both old and new disclosure wrappers.
function findSectionByHeading(pattern) {
	const heading = Array.from(document.querySelectorAll('h2,h3'))
		.find(h => pattern.test((h.textContent || '').trim()));
	if (!heading) return null;
	return heading.closest('.react-aria-Disclosure')
		|| heading.closest('section, [role="region"], div');
}

// Diagnostic dump for cards our selector matches but that have no visible title.
// Logs href + classes + a snippet of text so the offending card (e.g. an
// IAP_INACTIVE_TEMP_NOTIFY placeholder) can be identified without enqueueing it.
function logTitlelessCard(prefix, card) {
	const href = card.getAttribute('href') || card.href || '';
	const ariaLabel = card.getAttribute('aria-label') || '';
	const className = card.getAttribute('class') || '';
	const text = (card.textContent || '').replace(/\s+/g, ' ').trim().slice(0, 160);
	const dataAttrs = Array.from(card.attributes)
		.filter(a => a.name.startsWith('data-'))
		.map(a => `${a.name}="${a.value}"`)
		.join(' ');
	console.warn(`${prefix} Skipping titleless card`, { href, ariaLabel, className, dataAttrs, text, card });
	pushQueueDebugLog(`Skipped titleless card: ${text || ariaLabel || href || '(empty)'}`, "warn");
}

// Click any "Show more" toggle inside a section until every card is rendered.
// Bing collapses the Keep earning list past a fixed count; without this we'd miss
// the hidden cards entirely.
async function expandShowMoreToggles(root, prefix) {
	let clicks = 0;
	const maxClicks = 8;
	while (clicks < maxClicks) {
		const btn = Array.from(root.querySelectorAll('button')).find(b => {
			if (b.disabled || b.getAttribute('aria-disabled') === 'true') return false;
			return /^show more\b/i.test((b.textContent || '').trim());
		});
		if (!btn) break;
		const beforeCount = root.querySelectorAll('a[data-rac][href]').length;
		btn.click();
		clicks++;
		await sleep(800);
		const afterCount = root.querySelectorAll('a[data-rac][href]').length;
		pushQueueDebugLog(`${prefix} Clicked "Show more" (${clicks}): ${beforeCount} → ${afterCount} cards`);
		if (afterCount === beforeCount) break;
	}
}

// Earn-page Keep-earning cards. Same pattern as dashboard: enqueue all, then click
// each <a> in place. Microsoft mixes search-prompt cards in here too — those get an
// exploreTaskId so the child tab can match itself to the parent task.
async function collectEarnActivities() {
	console.log("[Earn] Planning Keep earning cards...");

	const root = findSectionByHeading(/^keep earning$/i)
		|| document.querySelector("section#moreactivities");

	if (!root) {
		pushQueueDebugLog("[Earn] Keep earning section NOT FOUND in DOM", "error");
		return 0;
	}

	await expandShowMoreToggles(root, "[Earn]");

	const allCards = root.querySelectorAll('a[data-rac][href]');
	if (allCards.length === 0) {
		pushQueueDebugLog("[Earn] No anchor cards inside section", "warn");
		return 0;
	}

	const taskEntries = [];
	let completed = 0, noChip = 0, locked = 0, enqueued = 0, skippedTitleless = 0, blacklisted = 0;
	for (const card of allCards) {
		if (isCardCompleted(card)) { completed++; continue; }
		if (isCardLocked(card))    { locked++;    continue; }
		const pts = getCardPoints(card);
		if (!pts) { noChip++; continue; }

		const title = extractCardTitle(card);
		if (!title) { logTitlelessCard("[Earn]", card); skippedTitleless++; continue; }
		if (isTaskBlacklisted(title)) {
			pushQueueDebugLog(`[Earn] Blacklisted card skipped: "${title}"`);
			blacklisted++;
			continue;
		}

		const description = extractCardDescription(card);
		const urlQuery = extractBingSearchQuery(card.href);
		const promptQuery = deriveSearchFromPrompt(description);
		// Only treat as a search card when there's a real signal (URL ?q= or prompt). The
		// keyword generators below give varied queries, but they'd happily match any card
		// containing a keyword — we don't want to "search" for a non-search activity card.
		let searchQuery = null;
		if (urlQuery || promptQuery) {
			// Bing's new dashboard puts the card title into ?q=, so URL extraction yields the
			// literal title. Prefer a keyword-generated query for variety; fall back to the
			// literal URL/prompt text if no generator matches.
			const generated = ExploreOnBing.getSearchString(title, description);
			const isGenerated = generated.matchedFunction
				&& generated.matchedFunction !== "titleFallback"
				&& generated.matchedFunction !== "descriptionFallback";
			searchQuery = isGenerated ? generated.searchString : (urlQuery || promptQuery);
		}

		if (searchQuery) {
			const exploreTaskId = addExploreTask(searchQuery, title);
			const taskId = enqueueRewardTask({
				type: "explore",
				title: `${title} (+${pts})`,
				url: card.href,
				searchString: searchQuery,
				source: "earn"
			});
			taskEntries.push({ card, title, taskId, exploreTaskId, kind: "explore" });
		} else {
			const taskId = enqueueRewardTask({
				type: "direct",
				title: `${title} (+${pts})`,
				url: card.href,
				source: "earn"
			});
			taskEntries.push({ card, title, taskId, kind: "direct" });
		}
		enqueued++;
	}

	pushQueueDebugLog(`[Earn] cards=${allCards.length} completed=${completed} locked=${locked} noChip=${noChip} blacklisted=${blacklisted} enqueued=${enqueued} skippedTitleless=${skippedTitleless}`);

	for (const entry of taskEntries) {
		if (!isAutomationRunning()) break;
		await clickAndWaitCard(entry, "[Earn]");
	}

	return enqueued;
}

// Shared click-and-wait used by earn + explore collectors. Performs the user-gesture
// click on the actual <a> element so Bing's tracking sees a real navigation; falls
// back to a fixed wait for direct cards or polls the explore-task queue for search prompts.
async function clickAndWaitCard(entry, prefix) {
	const { card, title, taskId, exploreTaskId, kind } = entry;
	try {
		const hashParams = kind === "explore"
			? { ag_child: "1", ag_kind: "explore", ag_explore_task_id: exploreTaskId, ag_parent_task_id: taskId }
			: { ag_child: "1", ag_kind: "direct", ag_task_id: taskId };

		markRewardTaskStatus(taskId, "in_progress", kind === "explore" ? { exploreTaskId } : {});
		card.href = withHashParams(card.href, hashParams);
		card.target = "_blank";
		card.click();
		pushQueueDebugLog(`Clicked ${kind} card: ${title}`);

		const closeTimeout = getRandomTimeout();
		if (getAutoCloseTabs()) addTabToClose(card.href, closeTimeout + (kind === "explore" ? 10000 : 0));

		if (kind === "explore") {
			const maxWait = 90000, pollInterval = 500;
			let elapsed = 0, completed = false;
			while (elapsed < maxWait && isAutomationRunning()) {
				await sleep(pollInterval);
				elapsed += pollInterval;
				if (isTaskComplete(exploreTaskId)) { completed = true; break; }
			}
			if (!isAutomationRunning()) return;
			if (completed) {
				markRewardTaskStatus(taskId, "complete");
				pushQueueDebugLog(`Completed ${kind} card: ${title}`);
			} else {
				markRewardTaskStatus(taskId, "failed", { error: "Explore task timed out" });
				pushQueueDebugLog(`${kind} card timed out: ${title}`, "warn");
			}
		} else {
			await sleep(closeTimeout + 1200);
			if (!isAutomationRunning()) return;
			markRewardTaskStatus(taskId, "complete");
			pushQueueDebugLog(`Completed ${kind} card: ${title}`);
		}
	} catch (error) {
		console.error(`${prefix} Click failed for "${title}":`, error);
		pushQueueDebugLog(`Click failed for "${title}": ${error.message}`, "error");
		markRewardTaskStatus(taskId, "pending", { error: `Click failed: ${error.message}` });
	}
}

// Explore on Bing cards. Locked cards render as <span data-disabled="true">,
// so the a[data-rac][href] selector naturally excludes them; isCardLocked() also
// catches "Unlocks tomorrow"/aria-disabled in case Microsoft changes the markup.
async function collectExploreOnBingActivities() {
	const root = document.querySelector('section#exploreonbing')
		|| findSectionByHeading(/^explore on bing$/i);

	if (!root) {
		pushQueueDebugLog("[ExploreOnBing] No standalone section (now part of Keep earning)");
		return 0;
	}

	const allCards = root.querySelectorAll('a[data-rac][href]:not([aria-disabled="true"])');
	if (allCards.length === 0) {
		pushQueueDebugLog("[ExploreOnBing] No unlocked anchor cards in section");
		return 0;
	}

	cleanupCompletedTasks();

	const taskEntries = [];
	let completed = 0, noChip = 0, locked = 0, enqueued = 0, skippedTitleless = 0, blacklisted = 0;
	for (const card of allCards) {
		if (isCardCompleted(card)) { completed++; continue; }
		if (isCardLocked(card))    { locked++;    continue; }
		const pts = getCardPoints(card);
		if (!pts) { noChip++; continue; }

		const title = extractCardTitle(card);
		if (!title) { logTitlelessCard("[ExploreOnBing]", card); skippedTitleless++; continue; }
		if (isTaskBlacklisted(title)) {
			pushQueueDebugLog(`[ExploreOnBing] Blacklisted card skipped: "${title}"`);
			blacklisted++;
			continue;
		}

		const description = extractCardDescription(card);
		// Bing's new dashboard puts the card title into ?q=, so URL extraction yields the
		// literal title (looks like a useless "title search"). Prefer a keyword-generated
		// query for variety; fall back to URL ?q=, then "Search on Bing for X" in the
		// description, then getSearchString's own title/description fallback.
		const urlQuery = extractBingSearchQuery(card.href);
		const promptQuery = deriveSearchFromPrompt(description);
		const generated = ExploreOnBing.getSearchString(title, description);
		const isGenerated = generated.matchedFunction
			&& generated.matchedFunction !== "titleFallback"
			&& generated.matchedFunction !== "descriptionFallback";
		const searchString = isGenerated
			? generated.searchString
			: (urlQuery || promptQuery || generated.searchString);

		const exploreTaskId = addExploreTask(searchString, title);
		const taskId = enqueueRewardTask({
			type: "explore",
			title: `${title} (+${pts})`,
			url: card.href,
			searchString,
			source: "explore"
		});
		taskEntries.push({ card, title, taskId, exploreTaskId, kind: "explore" });
		enqueued++;
	}

	pushQueueDebugLog(`[ExploreOnBing] cards=${allCards.length} completed=${completed} locked=${locked} noChip=${noChip} blacklisted=${blacklisted} enqueued=${enqueued} skippedTitleless=${skippedTitleless}`);

	for (const entry of taskEntries) {
		if (!isAutomationRunning()) break;
		await clickAndWaitCard(entry, "[ExploreOnBing]");
	}

	return enqueued;
}

/*=============================================*\
|*       PHASE EXECUTORS                       *|
\*=============================================*/

async function executePlanDashboardPhase() {
	pushQueueDebugLog("Plan 1/5: Dashboard daily set");
	await collectDashboardActivities();
	setAutomationState({ phase: PHASES.PLAN_EARN });
	scheduleTimeout(() => {
		window.location.href = "https://rewards.bing.com/earn";
	}, 1500);
}

async function executePlanEarnPhase() {
	pushQueueDebugLog("Plan 2/5: Earn activities");
	await collectEarnActivities();
	setAutomationState({ phase: PHASES.PLAN_EXPLORE });
	scheduleTimeout(() => dispatchPhase(getAutomationState()), 1500);
}

async function executePlanExplorePhase() {
	pushQueueDebugLog("Plan 3/5: Explore on Bing");
	await collectExploreOnBingActivities();
	setAutomationState({ phase: PHASES.PLAN_POINTS });
	scheduleTimeout(() => dispatchPhase(getAutomationState()), 1500);
}

async function executePlanPointsPhase() {
	pushQueueDebugLog("Plan 4/5: Points breakdown");

	const breakdownButton = findPointsBreakdownButton();
	if (!breakdownButton) {
		console.warn("[Plan-Points] Points breakdown button not found. Skipping searches.");
		setAutomationState({ searchesNeeded: 0, phase: PHASES.EXECUTE });
		await dispatchPhase(getAutomationState());
		return;
	}

	breakdownButton.click();
	pushQueueDebugLog("Clicked Points breakdown button");

	// Wait for a dialog that actually contains breakdown content, not just any dialog
	const dialog = await new Promise((resolve) => {
		const searchLabels = ['bing search', 'pc search', 'mobile search'];
		let elapsed = 0;
		const interval = setInterval(() => {
			elapsed += 300;
			const candidates = document.querySelectorAll('[role="dialog"], section[role="dialog"]');
			for (const el of candidates) {
				const text = el.textContent.toLowerCase();
				if (searchLabels.some(label => text.includes(label))) {
					clearInterval(interval);
					resolve(el);
					return;
				}
			}
			if (elapsed >= 10000) { clearInterval(interval); resolve(null); }
		}, 300);
	});

	if (!dialog) {
		console.warn("[Plan-Points] Points breakdown dialog did not appear. Skipping searches.");
		setAutomationState({ searchesNeeded: 0, phase: PHASES.EXECUTE });
		await dispatchPhase(getAutomationState());
		return;
	}

	// Parse the dialog for the Bing-search row's X/Y. Fall back to 10 if the parser
	// throws, returns 0, or returns null — Microsoft's dialog markup churns and a
	// silently-zero parse would skip searches entirely, which we definitely don't want.
	const FALLBACK_SEARCHES = 10;
	let searchesNeeded;
	try {
		const parsed = parseBingSearchPoints(dialog);
		if (parsed && parsed > 0) {
			searchesNeeded = parsed;
			pushQueueDebugLog(`Parsed ${searchesNeeded} searches needed from points breakdown`);
		} else {
			searchesNeeded = FALLBACK_SEARCHES;
			pushQueueDebugLog(`Parser returned ${parsed} — falling back to ${FALLBACK_SEARCHES} searches`, "warn");
		}
	} catch (e) {
		console.warn("[Plan-Points] parseBingSearchPoints threw:", e);
		searchesNeeded = FALLBACK_SEARCHES;
		pushQueueDebugLog(`Points parser failed (${e.message}) — falling back to ${FALLBACK_SEARCHES} searches`, "warn");
	}

	const closeBtn = dialog.querySelector('button[aria-label="Close"]') ||
	                  dialog.querySelector('button[slot="close"]');
	if (closeBtn) closeBtn.click();

	pushQueueDebugLog(`Points breakdown: ${searchesNeeded} searches needed`);
	setAutomationState({ phase: PHASES.PLAN_SEED_SEARCH, searchesNeeded });
	scheduleTimeout(() => dispatchPhase(getAutomationState()), 1000);
}

function findPointsBreakdownButton() {
	const allButtons = document.querySelectorAll('button[data-rac]');
	for (const btn of allButtons) {
		if (btn.textContent.toLowerCase().includes('points breakdown') && btn.hasAttribute('aria-expanded')) {
			return btn;
		}
	}
	const allPs = document.querySelectorAll('p');
	for (const p of allPs) {
		if (p.textContent.trim().toLowerCase() === 'points breakdown') {
			const btn = p.closest('button');
			if (btn) return btn;
		}
	}
	return null;
}

function parseBingSearchPoints(dialog) {
	const searchLabels = ['bing search', 'pc search', 'mobile search'];
	const pointsPerSearch = 3;

	// Find all leaf-level elements whose text exactly matches a search label,
	// then walk up the DOM to find the label container and scan its next siblings
	// for an "X/Y" points pattern (e.g. "45/60").
	const allElements = dialog.querySelectorAll('*');
	for (const el of allElements) {
		if (el.children.length > 0) continue;
		const text = el.textContent.trim().toLowerCase();
		if (!searchLabels.includes(text)) continue;

		// Try the element's parent, grandparent, etc. as a potential label cell
		let container = el.parentElement;
		while (container && container !== dialog) {
			// Scan up to 4 next siblings (grid may have empty <span> cells between columns)
			let sibling = container.nextElementSibling;
			for (let i = 0; i < 4 && sibling; i++, sibling = sibling.nextElementSibling) {
				const sibText = sibling.textContent.trim();
				const match = sibText.match(/(\d+)\s*\/\s*(\d+)/);
				if (match && parseInt(match[2], 10) > 0) {
					const current = parseInt(match[1], 10);
					const max = parseInt(match[2], 10);
					const remaining = max - current;
					const searchesNeeded = Math.ceil(remaining / pointsPerSearch);
					console.log(`[PointsBreakdown] "${text}" ${current}/${max} → ${searchesNeeded} searches needed`);
					return Math.max(0, searchesNeeded);
				}
			}
			container = container.parentElement;
		}
	}

	// Fallback: text scan — find X/Y within 150 chars of any search label
	const dialogText = dialog.textContent;
	for (const label of searchLabels) {
		const idx = dialogText.toLowerCase().indexOf(label);
		if (idx === -1) continue;
		const nearby = dialogText.substring(idx, idx + 150);
		const match = nearby.match(/(\d+)\s*\/\s*(\d+)/);
		if (match) {
			const current = parseInt(match[1], 10);
			const max = parseInt(match[2], 10);
			const remaining = max - current;
			const searchesNeeded = Math.ceil(remaining / pointsPerSearch);
			console.log(`[PointsBreakdown] (text fallback) "${label}" ${current}/${max} → ${searchesNeeded} searches needed`);
			return Math.max(0, searchesNeeded);
		}
	}

	console.warn("[PointsBreakdown] Could not parse Bing search row. Returning 0.");
	return 0;
}

async function executePlanSeedSearchPhase() {
	pushQueueDebugLog("Plan 5/5: Fetching news headlines for search terms");

	const state = getAutomationState();
	const searchesNeeded = state.searchesNeeded ?? 0;

	if (searchesNeeded <= 0) {
		setAutomationState({ phase: PHASES.EXECUTE, searchTerms: [], searchesCompleted: 0 });
		await dispatchPhase(getAutomationState());
		return;
	}

	let terms = [];
	try {
		const headlines = await fetchNewsHeadlines();
		pushQueueDebugLog(`Fetched ${headlines.length} headlines from news API`);
		terms = snippetsFromHeadlines(headlines, searchesNeeded);
	} catch (e) {
		console.warn("[SeedSearch] News fetch failed:", e);
	}

	// Top up with generators if the API was thin or failed.
	if (terms.length < searchesNeeded) {
		pushQueueDebugLog(`News yielded ${terms.length}/${searchesNeeded} terms — topping up with generators`, "warn");
		const generators = [
			ExploreOnBing.latestNews, ExploreOnBing.weatherInCity,
			ExploreOnBing.sportsScores, ExploreOnBing.famousPerson,
			ExploreOnBing.stockPrice, ExploreOnBing.recipeSearch,
			ExploreOnBing.movie, ExploreOnBing.lyricsSearch
		];
		const seen = new Set(terms);
		let i = 0;
		while (terms.length < searchesNeeded && i < searchesNeeded * 3) {
			const candidate = generators[i % generators.length]();
			if (!seen.has(candidate)) { terms.push(candidate); seen.add(candidate); }
			i++;
		}
	}

	pushQueueDebugLog(`Prepared ${terms.length} search terms — enqueueing`);

	for (const term of terms) {
		enqueueRewardTask({
			type: "search",
			title: `Search: ${term}`,
			searchString: term,
			source: "seed_search"
		});
	}

	setAutomationState({
		phase: PHASES.EXECUTE,
		searchTerms: terms,
		searchesCompleted: 0
	});

	// No navigation needed — we never left the earn page. Just dispatch the next phase.
	await dispatchPhase(getAutomationState());
}

// EXECUTE phase: kick off the queue runner and idle. The runner drains all queued
// tasks (direct, explore, search) one at a time. When it's done, the runner exits
// and the next time we land on a reward page with the queue empty, we transition to COMPLETE.
async function executeExecutePhase() {
	const queue = getRewardTaskQueue();
	const remaining = queue.filter(task => task.status !== "complete" && task.status !== "failed");

	if (remaining.length === 0) {
		pushQueueDebugLog("Queue drained. Moving to complete.");
		setAutomationState({ phase: PHASES.COMPLETE });
		await executeCompletePhase();
		return;
	}

	// DEBUG: confirm the planned queue before executing. Re-enable when debugging.
	// if (!confirmPlannedQueueOnce(remaining)) {
	// 	pushQueueDebugLog("User declined planned queue — halting before execute. Queue preserved for review.", "warn");
	// 	// Halt without wiping the queue so the user can inspect what was planned.
	// 	setAutomationRunning(false);
	// 	clearAllTimers();
	// 	GM_setValue(REWARD_RUNNER_KEY, null);
	// 	rewardQueueRunnerStarted = false;
	// 	return;
	// }

	pushQueueDebugLog(`Execute: ${remaining.length} task(s) queued`);
	startRewardQueueRunner();
}

// DEBUG-ONLY: blocks before execution starts so you can review the planned queue.
// Uses a session flag so it only fires once per planning cycle (not on every tab reload).
function confirmPlannedQueueOnce(remaining) {
	const RUN_KEY = "executeConfirmedAt";
	const state = getAutomationState();
	const startedAt = state.startedAt || 0;
	const lastConfirmed = GM_getValue(RUN_KEY, 0);
	if (lastConfirmed && lastConfirmed >= startedAt) return true;

	const summary = remaining.map((t, i) => `  ${i + 1}. [${t.type}] ${t.title}`).join("\n");
	const ok = confirm(
		`Planning complete. About to execute ${remaining.length} task(s):\n\n${summary}\n\nProceed?`
	);
	if (ok) GM_setValue(RUN_KEY, Date.now());
	return ok;
}

async function executeCompletePhase() {
	const state = getAutomationState();
	const searchCount = state.searchesCompleted || 0;
	pushQueueDebugLog(`All phases complete! ${searchCount} searches performed.`);
	console.log("[AutoGrind] All phases complete!");

	resetAutomationState();
	setAutomationRunning(false);
	GM_setValue("searches", []);

	if (isRewardPage) {
		const notification = document.createElement("div");
		notification.classList.add("script-notification");
		notification.textContent = `All done! ${searchCount} searches completed.`;
		document.body.appendChild(notification);
		setTimeout(() => notification.remove(), 5000);
	}
}

/*=============================================*\
|*       PHASE DISPATCHER                      *|
\*=============================================*/

function waitForRewardPageReady(callback) {
	let attempts = 0;
	const maxAttempts = 15;
	const checkInterval = setInterval(() => {
		attempts++;
		const hasContent = document.querySelector("section#dailyset") ||
		                   document.querySelector("section#moreactivities") ||
		                   document.querySelector("h2");
		if (hasContent || attempts >= maxAttempts) {
			clearInterval(checkInterval);
			callback();
		}
	}, 1000);
}

async function dispatchPhase(state) {
	if (!isAutomationRunning()) {
		console.log("[Dispatcher] Automation not running. Skipping dispatch.");
		return;
	}
	console.log(`[Dispatcher] Phase: ${state.phase}, Page: ${isDashboardPage ? 'dashboard' : isEarnPage ? 'earn' : 'other'}`);

	switch (state.phase) {
		case PHASES.PLAN_DASHBOARD:
			if (navigateForPhase(PHASES.PLAN_DASHBOARD)) return;
			await executePlanDashboardPhase();
			break;
		case PHASES.PLAN_EARN:
			if (navigateForPhase(PHASES.PLAN_EARN)) return;
			await executePlanEarnPhase();
			break;
		case PHASES.PLAN_EXPLORE:
			if (navigateForPhase(PHASES.PLAN_EXPLORE)) return;
			await executePlanExplorePhase();
			break;
		case PHASES.PLAN_POINTS:
			if (navigateForPhase(PHASES.PLAN_POINTS)) return;
			await executePlanPointsPhase();
			break;
		case PHASES.PLAN_SEED_SEARCH:
			// Seed terms come from a news API (no navigation) — execute in place.
			if (navigateForPhase(PHASES.PLAN_SEED_SEARCH)) return;
			await executePlanSeedSearchPhase();
			break;
		case PHASES.EXECUTE:
			if (navigateForPhase(PHASES.EXECUTE)) return;
			await executeExecutePhase();
			break;
		case PHASES.COMPLETE:
			await executeCompletePhase();
			break;
		default:
			console.log(`[Dispatcher] Nothing to do for phase: ${state.phase}`);
	}
}

/**
 * Wait for elements to appear on the page and execute a callback function when they are found.
 * This function repeatedly checks for the presence of the specified selectors on the page.
 * Once any of the selectors are found, the callback function is called with the selector as a parameter.
 * @param {Array} selectors - The selectors to wait for.
 * @param {Function} callback - The callback function to execute when the selectors are found.
 */
function waitForElements(selectors, callback) {
	if (selectors == null) {
		callback(null);
		return;
	}
	for (let selector of selectors) {
		if (document.querySelector(selector)) {
			callback(selector);
			return;
		}
	}
	setTimeout(() => waitForElements(selectors, callback), 500);
}

/**
 * Add a tab to the list of tabs to close after a specified timeout.
 * This function adds the tab to the [tabsToClose] array and sets a timeout to close the tab.
 * The tabs to close are stored in the local storage and are closed after the specified timeout.
 * @param {Window} tab - The tab to close.
 * @param {number} timeout - The timeout in milliseconds to close the tab.
 * @example addTabToClose(window.open("https://rewards.bing.com/dashboard", "_blank"), 5000);
 */
function addTabToClose(tab, timeout=5000) {
	tabsToClose.push(
		{"url": tab, "timeout": timeout}
	);
	GM_setValue("tabsToClose", tabsToClose);
}

/**
 * Get the Bing search URL for a given search term.
 * This function constructs the Bing search URL with the specified search term and returns it.
 * @param {string} searchTerm - The search term to include in the URL.
 * @returns {string} - The Bing search URL for the given search term.
 */
function generateSearchUrl(searchTerm) {
	return `https://www.bing.com/search?FORM=U523DF&PC=U523&q=${encodeURI(searchTerm)}&FORM=ANNTA1&qs=ds`;
}

/*=============================================*\
|*          EXPLORE TASK QUEUE HELPERS        *|
\*=============================================*/

/**
 * Get the explore task queue from storage
 * @returns {Array} - Array of task objects
 */
function getExploreTaskQueue() {
	return GM_getValue('exploreTaskQueue', []);
}

/**
 * Save the explore task queue to storage
 * @param {Array} queue - Array of task objects
 */
function saveExploreTaskQueue(queue) {
	GM_setValue('exploreTaskQueue', queue);
}

/**
 * Add a new task to the explore task queue
 * @param {string} searchString - The search string to execute
 * @param {string} title - The task title for logging
 * @returns {string} - The unique task ID
 */
function addExploreTask(searchString, title) {
	const queue = getExploreTaskQueue();
	const taskId = `task_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;

	const task = {
		id: taskId,
		searchString: searchString,
		title: title,
		status: 'pending',
		timestamp: Date.now()
	};

	queue.push(task);
	saveExploreTaskQueue(queue);

	console.log(`[ExploreTaskQueue] Added task ${taskId}: "${title}"`);
	return taskId;
}

/**
 * Get the next pending task from the queue
 * @returns {Object|null} - The next pending task or null
 */
function getNextPendingTask() {
	const queue = getExploreTaskQueue();
	return queue.find(task => task.status === 'pending') || null;
}

function getExploreTaskById(taskId) {
	if (!taskId) return null;
	const queue = getExploreTaskQueue();
	return queue.find(task => task.id === taskId) || null;
}

/**
 * Mark a task as in progress
 * @param {string} taskId - The task ID
 */
function markTaskInProgress(taskId) {
	const queue = getExploreTaskQueue();
	const task = queue.find(t => t.id === taskId);
	if (task) {
		task.status = 'in_progress';
		saveExploreTaskQueue(queue);
		console.log(`[ExploreTaskQueue] Task ${taskId} marked as in_progress`);
	}
}

/**
 * Mark a task as complete
 * @param {string} taskId - The task ID
 */
function markTaskComplete(taskId) {
	const queue = getExploreTaskQueue();
	const task = queue.find(t => t.id === taskId);
	if (task) {
		task.status = 'complete';
		saveExploreTaskQueue(queue);
		console.log(`[ExploreTaskQueue] Task ${taskId} marked as complete`);
	}
}

/**
 * Find a task by search string
 * @param {string} searchString - The search string to match
 * @returns {Object|null} - The matching task or null
 */
function findTaskBySearchString(searchString) {
	const queue = getExploreTaskQueue();
	return queue.find(task =>
		task.searchString === searchString &&
		task.status === 'in_progress'
	) || null;
}

/**
 * Check if a specific task is complete
 * @param {string} taskId - The task ID
 * @returns {boolean} - True if task is complete
 */
function isTaskComplete(taskId) {
	const queue = getExploreTaskQueue();
	const task = queue.find(t => t.id === taskId);
	return task ? task.status === 'complete' : false;
}

/**
 * Clean up old completed tasks (older than 5 minutes)
 */
function cleanupCompletedTasks() {
	const queue = getExploreTaskQueue();
	const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
	const cleanedQueue = queue.filter(task =>
		!(task.status === 'complete' && task.timestamp < fiveMinutesAgo)
	);

	if (cleanedQueue.length !== queue.length) {
		saveExploreTaskQueue(cleanedQueue);
		console.log(`[ExploreTaskQueue] Cleaned up ${queue.length - cleanedQueue.length} old tasks`);
	}
}

/**
 * Debug helper to show current queue state
 */
function debugQueue() {
	const queue = getExploreTaskQueue();
	console.log(`[ExploreTaskQueue] Current queue state (${queue.length} tasks):`);
	queue.forEach((task, index) => {
		console.log(`  [${index + 1}] ${task.id} - "${task.title}" - ${task.status} - "${task.searchString}"`);
	});
	return queue;
}

// Make debug function globally available (call window.debugExploreQueue() from console)
window.debugExploreQueue = debugQueue;

/*=============================================*\
|*                MAIN SCRIPT                 *|
\*=============================================*/

/**
 * Handle "Explore on Bing" task search execution
 * When a new tab opens from an "Explore on Bing" task, fill the search form and submit
 */
if ((window.location.pathname === '/' && window.location.host === 'www.bing.com') && IS_AUTOMATION_CHILD_TAB && AUTOMATION_TAB_KIND === "explore") {
	const exploreTaskId = pageHashParams.get("ag_explore_task_id");
	const nextTask = getExploreTaskById(exploreTaskId);

	if (nextTask && (nextTask.status === "pending" || nextTask.status === "in_progress")) {
		console.log(`[ExploreOnBing] Detected explore task ${nextTask.id}: "${nextTask.title}"`);
		console.log(`[ExploreOnBing] Search string: "${nextTask.searchString}"`);
		console.log(`[ExploreOnBing] Current URL: ${window.location.href}`);

		// Mark task as in progress
		markTaskInProgress(nextTask.id);
		GM_setValue(ACTIVE_EXPLORE_TASK_KEY, nextTask.id);

		// Wait for the search form to load
		let formCheckAttempts = 0;
		const maxFormCheckAttempts = 20;

		const checkForForm = () => {
			if (!isAutomationRunning()) return;
			formCheckAttempts++;
			console.log(`[ExploreOnBing] Attempt ${formCheckAttempts}/${maxFormCheckAttempts} to find search form`);

			// Try multiple selectors for the search box
			const searchBox = document.querySelector('textarea#sb_form_q') ||
			                  document.querySelector('textarea.sb_form_q') ||
			                  document.querySelector('input.b_searchbox') ||
			                  document.querySelector('textarea.b_searchbox');

			if (searchBox) {
				console.log("[ExploreOnBing] Search box found, filling and submitting");
				searchBox.value = nextTask.searchString;

				const exploreSearchUrl = withHashParams(generateSearchUrl(nextTask.searchString), {
					ag_child: "1",
					ag_kind: "explore",
					ag_explore_task_id: nextTask.id
				});
				setTimeout(() => {
					if (isAutomationRunning()) window.location.href = exploreSearchUrl;
				}, 300);
			} else if (formCheckAttempts < maxFormCheckAttempts) {
				setTimeout(checkForForm, 500);
			} else {
				console.error(`[ExploreOnBing] ERROR: Search form not found after ${maxFormCheckAttempts} attempts!`);
				if (isAutomationRunning()) {
					window.location.href = withHashParams(generateSearchUrl(nextTask.searchString), {
						ag_child: "1",
						ag_kind: "explore",
						ag_explore_task_id: nextTask.id
					});
				}
			}
		};

		checkForForm();
	} else if (exploreTaskId) {
		console.warn(`[ExploreOnBing] Explore task ${exploreTaskId} not available. Closing tab.`);
		if (getAutoCloseTabs() && isAutomationRunning()) window.close();
	}
}

/**
 * Wait for the page to load the points element first.
 * For android, this step is skipped.
 * Bing seems to have 2 different selectors for the points element
 * based on which browser is being used, so the possible selectors are
 * put inside [pointsElem]
 * In case of mobile, the script skips searching for the element.
 */
try {
// Add the button container only on reward pages
if (isRewardPage) {
	document.body.appendChild(autoSearchContainer);
}

if (isSearchPage) {
	const urlParams = new URLSearchParams(window.location.search);
	const currentSearchQuery = urlParams.get('q');
	// Automation markers live in the hash fragment, not the query string.
	const requestedExploreTaskId = pageHashParams.get('ag_explore_task_id') || GM_getValue(ACTIVE_EXPLORE_TASK_KEY, null);

	console.log(`[ExploreOnBing] Search page loaded, query: "${currentSearchQuery}"`);

	// Handle explore child tabs
	if (IS_AUTOMATION_CHILD_TAB && AUTOMATION_TAB_KIND === "explore" && currentSearchQuery) {
		const task = getExploreTaskById(requestedExploreTaskId) || findTaskBySearchString(currentSearchQuery);

		console.log(`[ExploreOnBing] Task lookup result:`, task ? `Found ${task.id}` : 'Not found');

		if (task) {
			console.log(`[ExploreOnBing] This is explore task ${task.id}: "${task.title}"`);
			console.log(`[ExploreOnBing] Search query: "${currentSearchQuery}"`);

			if (OPEN_RANDOM_LINKS) {
				try {
					let searchLinks = isMobile
						? document.querySelectorAll(".b_algoheader > a")
						: document.querySelectorAll("li.b_algo h2 a");

					if (searchLinks.length > 0) {
						const excludeDomains = ["britannica.com", "sunshineseeker.com"];
						searchLinks = Array.from(searchLinks).filter(link => {
							const metaElement = link.closest(".b_algo")?.querySelector(".b_tpcn div.tpmeta");
							return !metaElement || !excludeDomains.some(domain => metaElement.innerText.includes(domain));
						});

						if (searchLinks.length > 0) {
							let randLink = searchLinks[Math.floor(Math.random() * searchLinks.length)];
							console.log(`[ExploreOnBing] Opening random link in iframe: ${randLink.href}`);
							let iframe = document.createElement("iframe");
							iframe.name = "randLinkFrame";
							iframe.style.width = "100%";
							iframe.style.height = "600px";
							randLink.parentElement.appendChild(iframe);
							randLink.target = "randLinkFrame";
							randLink.click();
						}
					}
				} catch (e) {
					console.error("[ExploreOnBing] Error opening random link:", e);
				}
			}

			const waitTime = getRandomTimeout();
			console.log(`[ExploreOnBing] Waiting ${waitTime}ms before closing`);

			if (getAutoCloseTabs() && isAutomationRunning()) showAutoCloseRing(waitTime);

			setTimeout(() => {
				if (!isAutomationRunning()) {
					console.log("[ExploreOnBing] Automation stopped, skipping completion + close");
					return;
				}
				console.log(`[ExploreOnBing] Marking task ${task.id} as complete`);
				markTaskComplete(task.id);
				if (GM_getValue(ACTIVE_EXPLORE_TASK_KEY, null) === task.id) {
					GM_setValue(ACTIVE_EXPLORE_TASK_KEY, null);
				}

				if (getAutoCloseTabs()) {
					console.log("[ExploreOnBing] Closing tab");
					window.close();
				}
			}, waitTime);

			throw new Error("ExploreTaskHandled");
		} else {
			console.log("[ExploreOnBing] Child explore tab query did not match a task");
		}
	}

	// Handle search child tabs (opened by the queue runner during EXECUTE phase)
	if (IS_AUTOMATION_CHILD_TAB && AUTOMATION_TAB_KIND === "search" && currentSearchQuery) {
		const searchTaskId = pageHashParams.get('ag_explore_task_id');
		const task = getExploreTaskById(searchTaskId);

		console.log(`[SearchChild] Task lookup:`, task ? `Found ${task.id}` : 'Not found');

		if (task) {
			console.log(`[SearchChild] Processing search task ${task.id}: "${currentSearchQuery}"`);
			markTaskInProgress(task.id);

			if (OPEN_RANDOM_LINKS) {
				try {
					let searchLinks = isMobile
						? document.querySelectorAll(".b_algoheader > a")
						: document.querySelectorAll("li.b_algo h2 a");

					if (searchLinks.length > 0) {
						const excludeDomains = ["britannica.com", "sunshineseeker.com"];
						searchLinks = Array.from(searchLinks).filter(link => {
							const metaElement = link.closest(".b_algo")?.querySelector(".b_tpcn div.tpmeta");
							return !metaElement || !excludeDomains.some(domain => metaElement.innerText.includes(domain));
						});

						if (searchLinks.length > 0) {
							let randLink = searchLinks[Math.floor(Math.random() * searchLinks.length)];
							console.log(`[SearchChild] Opening random link in iframe: ${randLink.href}`);
							let iframe = document.createElement("iframe");
							iframe.name = "randLinkFrame";
							iframe.style.width = "100%";
							iframe.style.height = "600px";
							randLink.parentElement.appendChild(iframe);
							randLink.target = "randLinkFrame";
							randLink.click();
						}
					}
				} catch (e) {
					console.error("[SearchChild] Error opening random link:", e);
				}
			}

			const waitTime = getRandomTimeout();
			console.log(`[SearchChild] Waiting ${waitTime}ms before marking complete`);

			if (getAutoCloseTabs() && isAutomationRunning()) showAutoCloseRing(waitTime);

			setTimeout(() => {
				if (!isAutomationRunning()) {
					console.log("[SearchChild] Automation stopped, skipping completion + close");
					return;
				}
				console.log(`[SearchChild] Marking task ${task.id} as complete`);
				markTaskComplete(task.id);

				if (getAutoCloseTabs()) {
					console.log("[SearchChild] Closing tab");
					window.close();
				}
			}, waitTime);

			throw new Error("SearchTaskHandled");
		} else {
			console.log("[SearchChild] Search task not found, closing");
			if (getAutoCloseTabs() && isAutomationRunning()) window.close();
		}
	}

	// PLAN_SEED_SEARCH no longer runs on search pages — it now fetches news via API
	// from the earn page directly. Nothing to do here.
}

if (isRewardPage) {
	if (IS_AUTOMATION_CHILD_TAB) {
		console.log(`[AutoGrind] Child tab (${AUTOMATION_TAB_KIND}). Skipping orchestration.`);
	} else {
		const autoState = getAutomationState();
		console.log(`[AutoGrind] Current phase: ${autoState.phase}, running: ${isAutomationRunning()}`);

		if (isAutomationRunning() && autoState.phase !== PHASES.IDLE && autoState.phase !== PHASES.COMPLETE) {
			// Active automation in progress - wait for DOM and dispatch
			waitForRewardPageReady(() => {
				dispatchPhase(getAutomationState());
			});
		} else {
			console.log("[AutoGrind] Idle. Waiting for Start button click.");
		}
	}
}


/**
 * Show a small ring countdown in the bottom-right corner that depletes over `timeoutMs`.
 * @param {number} timeoutMs - Duration matching the auto-close delay.
 */
function showAutoCloseRing(timeoutMs) {
	const size = 44;
	const r = 17;
	const stroke = 3.5;
	const circumference = 2 * Math.PI * r;

	const ring = document.createElement('div');
	ring.style.cssText = [
		'position:fixed', 'bottom:16px', 'right:16px', 'z-index:2147483647',
		'width:' + size + 'px', 'height:' + size + 'px',
		'background:rgba(15,15,15,0.82)', 'border-radius:50%',
		'display:flex', 'align-items:center', 'justify-content:center',
		'box-shadow:0 2px 8px rgba(0,0,0,0.5)'
	].join(';');

	ring.innerHTML = `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" style="transform:rotate(-90deg)">
		<circle cx="${size/2}" cy="${size/2}" r="${r}" fill="none" stroke="#333" stroke-width="${stroke}"/>
		<circle id="ag-close-ring" cx="${size/2}" cy="${size/2}" r="${r}" fill="none" stroke="#60a5fa" stroke-width="${stroke}"
			stroke-dasharray="${circumference}" stroke-dashoffset="0"
			style="transition:stroke-dashoffset ${timeoutMs}ms linear;"/>
	</svg>`;

	const attach = () => {
		document.body.appendChild(ring);
		requestAnimationFrame(() => requestAnimationFrame(() => {
			const arc = ring.querySelector('#ag-close-ring');
			if (arc) arc.style.strokeDashoffset = String(circumference);
		}));
	};

	if (document.body) attach();
	else document.addEventListener('DOMContentLoaded', attach);
}

/**
 * Close the current tab if it has been registered for auto-closing.
 * Waits for the specified timeout before closing the tab.
 * If the tab cant be closed, then use a workaround for modern browser's limitation in closing tabs that werent opened by the script.
 * Tip: This workaround still might not work, so you can use an external tool to automate closing windows
 * by checking for the title of the window (Close this window).
 */
// Auto-close logic: only close tabs marked by this script (ag_child=1 in hash) AND
// only while automation is actually running. If the user has clicked Stop, no tab
// closes — even ones with stale entries in tabsToClose. The marker is read from the
// URL hash so Bing's tracking servers never see it.
if (getAutoCloseTabs() && isAutomationRunning() && pageHashParams.get('ag_child') === '1') {
    const currentUrl = new URL(window.location.href);
    const tabToClose = tabsToClose.find(tab => {
        if (window.location.href === tab.url) return true;
        try {
            const stored = new URL(tab.url);
            return currentUrl.origin === stored.origin && currentUrl.pathname === stored.pathname;
        } catch {
            return false;
        }
    });
    if (tabToClose) {
        tabsToClose = tabsToClose.filter(tab => tab.url != tabToClose.url);
        GM_setValue("tabsToClose", tabsToClose);
        showAutoCloseRing(tabToClose.timeout);
        setTimeout(() => { if (isAutomationRunning()) window.close(); }, tabToClose.timeout);
    }
}

/*=============================================*\
|*					CSS STYLES				   *|
\*=============================================*/

const stylesheet = Object.assign(document.createElement("style"), {textContent: `
	/******** Design tokens *********/
	:root {
		--ag-font: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, system-ui, sans-serif;
		--ag-accent: #E5BA41;
		--ag-accent-soft: #f4cf6b;
		--ag-navy: #2D3C59;
		--ag-navy-deep: #1f2b42;
		--ag-blue: #4C6FA7;
		--ag-green: #94A378;
		--ag-red: #D14C4C;
		--ag-orange: #D1855C;
		--ag-radius: 14px;
		--ag-radius-lg: 18px;
		--ag-radius-pill: 999px;
		--ag-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 3px rgba(15, 23, 42, 0.08);
		--ag-shadow-md: 0 4px 10px rgba(15, 23, 42, 0.08), 0 10px 24px rgba(15, 23, 42, 0.10);
		--ag-shadow-lg: 0 12px 28px rgba(15, 23, 42, 0.18), 0 24px 56px rgba(15, 23, 42, 0.22);
		--ag-ease: cubic-bezier(0.22, 1, 0.36, 1);
	}

	/******** Modern Button Container *********/
    .auto-search-container {
        position: fixed;
        top: 90px;
        left: 20px;
		display: flex;
		flex-direction: column;
		gap: 10px;
		align-items: flex-start;
		z-index: 9998;
		font-family: var(--ag-font);
    }

	.modern-button {
        display: flex;
        align-items: center;
		gap: 10px;
		overflow: hidden;
        cursor: pointer;
        border-radius: var(--ag-radius);
        background: linear-gradient(135deg, var(--ag-accent-soft) 0%, var(--ag-accent) 100%);
        padding: 10px 18px;
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.35) inset,
			0 -1px 0 rgba(0, 0, 0, 0.08) inset,
			0 6px 16px rgba(229, 186, 65, 0.28),
			0 2px 4px rgba(15, 23, 42, 0.08);
        transition: transform 0.35s var(--ag-ease), box-shadow 0.35s var(--ag-ease), filter 0.35s var(--ag-ease);
		color: #fff;
		font-family: var(--ag-font);
		font-weight: 600;
		font-size: 13.5px;
		letter-spacing: 0.1px;
		min-width: 48px;
		position: relative;
		will-change: transform;
    }

	.modern-button:hover {
		transform: translateX(6px);
		filter: brightness(1.04);
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.4) inset,
			0 -1px 0 rgba(0, 0, 0, 0.08) inset,
			0 10px 22px rgba(229, 186, 65, 0.38),
			0 4px 8px rgba(15, 23, 42, 0.12);
	}

	.modern-button:active {
		transform: translateX(6px) scale(0.98);
		filter: brightness(0.98);
	}

	.button-icon {
		width: 18px;
		height: 18px;
		flex-shrink: 0;
		transition: transform 0.35s var(--ag-ease);
		filter: drop-shadow(0 1px 0 rgba(0, 0, 0, 0.15));
	}

	.modern-button:hover .button-icon {
		transform: scale(1.08) rotate(4deg);
	}

    .modern-button span {
		white-space: nowrap;
		transition: opacity 0.3s ease;
    }

	/* Settings button styling */
	.settings-icon.modern-button {
		background: linear-gradient(135deg, #3a4d70 0%, var(--ag-navy) 100%);
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.12) inset,
			0 -1px 0 rgba(0, 0, 0, 0.2) inset,
			0 6px 16px rgba(45, 60, 89, 0.32),
			0 2px 4px rgba(15, 23, 42, 0.1);
	}

	.settings-icon.modern-button:hover {
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.14) inset,
			0 -1px 0 rgba(0, 0, 0, 0.22) inset,
			0 10px 22px rgba(45, 60, 89, 0.42),
			0 4px 8px rgba(15, 23, 42, 0.14);
	}

	/* Queue button styling */
	.queue-icon.modern-button {
		background: linear-gradient(135deg, #5d83c3 0%, var(--ag-blue) 100%);
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.18) inset,
			0 -1px 0 rgba(0, 0, 0, 0.16) inset,
			0 6px 16px rgba(76, 111, 167, 0.32),
			0 2px 4px rgba(15, 23, 42, 0.1);
	}

	.queue-icon.modern-button:hover {
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.2) inset,
			0 -1px 0 rgba(0, 0, 0, 0.18) inset,
			0 10px 22px rgba(76, 111, 167, 0.45),
			0 4px 8px rgba(15, 23, 42, 0.14);
	}

	/* Mini button row + size override for low-emphasis actions */
	.mini-button-row {
		display: flex;
		gap: 6px;
		margin-top: 2px;
		padding-left: 6px;
	}

	.mini-button.modern-button {
		min-width: 0;
		padding: 7px;
		border-radius: 10px;
		opacity: 0.7;
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.18) inset,
			0 -1px 0 rgba(0, 0, 0, 0.1) inset,
			0 3px 8px rgba(15, 23, 42, 0.12);
	}

	.mini-button.modern-button:hover {
		opacity: 1;
		transform: translateX(3px);
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.22) inset,
			0 -1px 0 rgba(0, 0, 0, 0.12) inset,
			0 6px 14px rgba(15, 23, 42, 0.18);
	}

	.mini-button.modern-button:active {
		transform: translateX(3px) scale(0.96);
	}

	.mini-button .button-icon {
		width: 14px;
		height: 14px;
	}

	.mini-button.modern-button:hover .button-icon {
		transform: scale(1.08) rotate(4deg);
	}

	/* Rate / Report button variants — quieter, neutral surface */
	.rate-icon.modern-button,
	.report-icon.modern-button {
		background: rgba(45, 60, 89, 0.55);
		color: #fff;
		backdrop-filter: blur(6px);
		-webkit-backdrop-filter: blur(6px);
	}

	.rate-icon.modern-button:hover {
		background: linear-gradient(135deg, #b1bf94 0%, var(--ag-green) 100%);
	}

	.report-icon.modern-button:hover {
		background: linear-gradient(135deg, #e3a47e 0%, var(--ag-orange) 100%);
	}

	/* Feedback dialog (Rate / Report) */
	.feedback-dialog-overlay {
		position: fixed;
		inset: 0;
		background: radial-gradient(ellipse at top, rgba(15, 23, 42, 0.55), rgba(0, 0, 0, 0.7));
		backdrop-filter: blur(14px) saturate(140%);
		-webkit-backdrop-filter: blur(14px) saturate(140%);
		display: none;
		justify-content: center;
		align-items: center;
		z-index: 10003;
		animation: fadeIn 0.3s ease;
		font-family: var(--ag-font);
	}

	.feedback-dialog {
		width: 90%;
		max-width: 420px;
		background: linear-gradient(180deg, #344361 0%, #2D3C59 55%, #1f2b42 100%);
		border-radius: 22px;
		padding: 28px 26px 22px;
		box-shadow:
			0 30px 70px rgba(0, 0, 0, 0.55),
			0 0 0 1px rgba(255, 255, 255, 0.06);
		animation: slideUp 0.4s var(--ag-ease);
		text-align: center;
		color: #fff;
	}

	.feedback-dialog-icon {
		width: 60px;
		height: 60px;
		margin: 0 auto 18px;
		border-radius: 50%;
		display: flex;
		align-items: center;
		justify-content: center;
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.22) inset,
			0 -1px 0 rgba(0, 0, 0, 0.18) inset,
			0 12px 28px rgba(0, 0, 0, 0.3);
	}

	.feedback-dialog-icon svg {
		width: 30px;
		height: 30px;
	}

	.feedback-dialog-icon.rate {
		background: linear-gradient(135deg, var(--ag-accent-soft), var(--ag-accent));
	}

	.feedback-dialog-icon.report {
		background: linear-gradient(135deg, #e3a47e, var(--ag-orange));
	}

	.feedback-dialog h3 {
		margin: 0 0 10px;
		font-family: var(--ag-font);
		font-size: 19px;
		font-weight: 700;
		letter-spacing: 0.1px;
		color: #fff;
	}

	.feedback-dialog p {
		margin: 0 0 22px;
		font-family: var(--ag-font);
		font-size: 13.5px;
		line-height: 1.6;
		color: rgba(255, 255, 255, 0.78);
	}

	.feedback-dialog-actions {
		display: flex;
		gap: 10px;
		justify-content: center;
		flex-wrap: wrap;
	}

	.feedback-btn {
		border: none;
		cursor: pointer;
		padding: 10px 20px;
		border-radius: var(--ag-radius-pill);
		font-family: var(--ag-font);
		font-size: 13px;
		font-weight: 600;
		letter-spacing: 0.2px;
		transition: transform 0.2s var(--ag-ease), filter 0.2s var(--ag-ease), box-shadow 0.2s var(--ag-ease), background 0.2s var(--ag-ease);
	}

	.feedback-btn.secondary {
		background: rgba(255, 255, 255, 0.06);
		color: rgba(255, 255, 255, 0.85);
		border: 1px solid rgba(255, 255, 255, 0.12);
	}

	.feedback-btn.secondary:hover {
		background: rgba(255, 255, 255, 0.12);
		border-color: rgba(255, 255, 255, 0.2);
	}

	.feedback-btn.primary {
		color: #1f2b42;
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.3) inset,
			0 6px 16px rgba(0, 0, 0, 0.25);
	}

	.feedback-btn.primary.rate {
		background: linear-gradient(135deg, var(--ag-accent-soft), var(--ag-accent));
	}

	.feedback-btn.primary.report {
		background: linear-gradient(135deg, #e3a47e, var(--ag-orange));
		color: #fff;
	}

	.feedback-btn.primary:hover {
		transform: translateY(-1px);
		filter: brightness(1.05);
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.32) inset,
			0 10px 22px rgba(0, 0, 0, 0.3);
	}

	.feedback-btn.primary:active {
		transform: translateY(0);
		filter: brightness(0.98);
	}

	/* Script notification */
	.script-notification {
		position: fixed;
		top: 20px;
		right: 20px;
		background: linear-gradient(135deg, #3a4d70 0%, var(--ag-navy) 100%);
		color: white;
		padding: 14px 22px;
		border-radius: var(--ag-radius);
		box-shadow: var(--ag-shadow-lg);
		font-family: var(--ag-font);
		font-weight: 600;
		font-size: 13.5px;
		letter-spacing: 0.1px;
		z-index: 10001;
		animation: slideInRight 0.35s var(--ag-ease), fadeOut 0.3s ease 2.7s;
	}

	@keyframes slideInRight {
		from {
			transform: translateX(100%);
			opacity: 0;
		}
		to {
			transform: translateX(0);
			opacity: 1;
		}
	}

	/* Dark mode support */
    .b_drk .modern-button,
    .b_dark .modern-button {
        background: #E5BA41;
		box-shadow: 0 4px 12px rgba(229, 186, 65, 0.4);
    }

	.b_drk .settings-icon.modern-button,
    .b_dark .settings-icon.modern-button {
        background: #2D3C59;
		box-shadow: 0 4px 12px rgba(45, 60, 89, 0.4);
    }

	.b_drk .queue-icon.modern-button,
    .b_dark .queue-icon.modern-button {
		background: #4C6FA7;
		box-shadow: 0 4px 12px rgba(76, 111, 167, 0.45);
	}

	/* Start/Stop toggle: when automation is running, recolor to a stop-red */
	.search-icon.running {
		background: linear-gradient(135deg, #e26a6a 0%, var(--ag-red) 100%) !important;
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.22) inset,
			0 -1px 0 rgba(0, 0, 0, 0.16) inset,
			0 6px 16px rgba(209, 76, 76, 0.36),
			0 2px 4px rgba(15, 23, 42, 0.1);
	}

	.search-icon.running:hover {
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.24) inset,
			0 -1px 0 rgba(0, 0, 0, 0.18) inset,
			0 10px 22px rgba(209, 76, 76, 0.5),
			0 4px 8px rgba(15, 23, 42, 0.14);
	}

	/* Queue Monitor stage banner shown while planning/executing */
	.queue-stage-banner {
		font-size: 11.5px;
		font-weight: 700;
		color: #fff;
		background: linear-gradient(135deg, #5d83c3 0%, var(--ag-blue) 100%);
		padding: 8px 12px;
		border-radius: 10px;
		margin-bottom: 10px;
		text-align: center;
		letter-spacing: 0.4px;
		text-transform: uppercase;
		box-shadow:
			0 1px 0 rgba(255, 255, 255, 0.15) inset,
			0 -1px 0 rgba(0, 0, 0, 0.12) inset,
			0 4px 10px rgba(76, 111, 167, 0.28);
	}

	/* Search states */
    .search-icon.searching {
        background: linear-gradient(135deg, var(--ag-accent-soft) 0%, var(--ag-accent) 100%) !important;
		animation: shimmer 2.2s infinite ease-in-out;
    }

	@keyframes shimmer {
		0%, 100% {
			box-shadow:
				0 1px 0 rgba(255, 255, 255, 0.35) inset,
				0 6px 16px rgba(229, 186, 65, 0.32);
		}
		50% {
			box-shadow:
				0 1px 0 rgba(255, 255, 255, 0.45) inset,
				0 10px 26px rgba(229, 186, 65, 0.55);
		}
	}

    .search-icon.counting {
        background: linear-gradient(135deg, #b1bf94 0%, var(--ag-green) 100%) !important;
		animation: pulse-counting 1.2s infinite ease-in-out;
    }

	@keyframes pulse-counting {
		0%, 100% {
			transform: translateX(6px) scale(1);
		}
		50% {
			transform: translateX(6px) scale(1.04);
		}
	}
	

	/******** Settings Overlay *********/

	@keyframes fadeIn {
		from { opacity: 0; }
		to { opacity: 1; }
	}

	@keyframes slideUp {
		from {
			transform: translateY(24px) scale(0.98);
			opacity: 0;
		}
		to {
			transform: translateY(0) scale(1);
			opacity: 1;
		}
	}

	@keyframes fadeOut {
		from { opacity: 1; }
		to { opacity: 0; }
	}

	@keyframes slideDown {
		from {
			transform: translateY(0) scale(1);
			opacity: 1;
		}
		to {
			transform: translateY(24px) scale(0.98);
			opacity: 0;
		}
	}

	.settings-overlay {
		transition: opacity 0.3s ease;
		font-family: var(--ag-font);
	}

	.settings-content {
		width: 90%;
		max-width: 680px;
		font-family: var(--ag-font);
	}

	.settings-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		padding: 16px 22px;
		border-bottom: 1px solid rgba(255, 255, 255, 0.08);
		background: linear-gradient(180deg, rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0));
	}

	.settings-tabs {
		display: flex;
		gap: 6px;
		padding: 10px 14px;
		background: var(--ag-navy-deep);
		border-bottom: 1px solid rgba(255, 255, 255, 0.06);
	}

	.settings-tab {
		border: 1px solid rgba(255, 255, 255, 0.12);
		background: rgba(255, 255, 255, 0.06);
		color: rgba(255, 255, 255, 0.85);
		padding: 6px 14px;
		border-radius: var(--ag-radius-pill);
		font-family: var(--ag-font);
		font-size: 11.5px;
		font-weight: 600;
		letter-spacing: 0.2px;
		cursor: pointer;
		transition: background 0.25s var(--ag-ease), color 0.25s var(--ag-ease), border-color 0.25s var(--ag-ease);
	}

	.settings-tab:hover {
		background: rgba(255, 255, 255, 0.1);
		color: #fff;
	}

	.settings-tab.active {
		background: linear-gradient(135deg, var(--ag-accent-soft) 0%, var(--ag-accent) 100%);
		border-color: transparent;
		color: var(--ag-navy);
		box-shadow: 0 4px 10px rgba(229, 186, 65, 0.28);
	}

	.settings-close-btn {
		background: rgba(255, 255, 255, 0.08);
		border: 1px solid rgba(255, 255, 255, 0.12);
		width: 32px;
		height: 32px;
		border-radius: 50%;
		cursor: pointer;
		display: flex;
		align-items: center;
		justify-content: center;
		transition: background 0.3s var(--ag-ease), transform 0.3s var(--ag-ease), border-color 0.3s var(--ag-ease);
	}

	.settings-close-btn:hover {
		background: rgba(255, 255, 255, 0.18);
		border-color: rgba(255, 255, 255, 0.22);
		transform: rotate(90deg);
	}

	.settings-close-btn svg {
		width: 18px;
		height: 18px;
		opacity: 0.9;
	}

	.settings-body {
		scrollbar-width: thin;
		scrollbar-color: rgba(45, 60, 89, 0.25) transparent;
	}

	.settings-body::-webkit-scrollbar {
		width: 8px;
	}

	.settings-body::-webkit-scrollbar-track {
		background: transparent;
	}

	.settings-body::-webkit-scrollbar-thumb {
		background: rgba(45, 60, 89, 0.22);
		border-radius: 10px;
		border: 2px solid transparent;
		background-clip: padding-box;
	}

	.settings-body::-webkit-scrollbar-thumb:hover {
		background: rgba(45, 60, 89, 0.45);
		background-clip: padding-box;
	}

	.settings-item {
		margin-bottom: 10px;
	}

	.queue-summary-grid {
		display: grid;
		grid-template-columns: repeat(2, minmax(0, 1fr));
		gap: 6px;
		margin-bottom: 10px;
	}

	.queue-summary-card {
		background: #fff;
		border: 1px solid rgba(45, 60, 89, 0.08);
		border-radius: 10px;
		padding: 7px 10px;
		display: flex;
		justify-content: space-between;
		font-family: var(--ag-font);
		font-size: 11px;
		font-weight: 600;
		color: var(--ag-navy);
		box-shadow: var(--ag-shadow-sm);
	}

	.queue-summary-card span {
		color: var(--ag-blue);
		font-variant-numeric: tabular-nums;
	}

	.queue-section {
		margin-bottom: 10px;
	}

	.queue-section-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 6px;
	}

	.queue-section-header h3 {
		margin: 0;
		font-family: var(--ag-font);
		font-size: 10.5px;
		font-weight: 700;
		letter-spacing: 0.6px;
		text-transform: uppercase;
		color: rgba(45, 60, 89, 0.65);
	}

	.queue-count {
		font-size: 10.5px;
		font-weight: 600;
		color: #94a3b8;
		font-variant-numeric: tabular-nums;
	}

	.queue-list {
		display: flex;
		flex-direction: column;
		gap: 5px;
	}

	.queue-row {
		background: #fff;
		border: 1px solid rgba(45, 60, 89, 0.08);
		border-radius: 10px;
		padding: 7px 10px;
		box-shadow: var(--ag-shadow-sm);
		transition: transform 0.2s var(--ag-ease), box-shadow 0.2s var(--ag-ease);
	}

	.queue-row:hover {
		transform: translateY(-1px);
		box-shadow: var(--ag-shadow-md);
	}

	.queue-row.small {
		padding: 6px 10px;
	}

	.queue-row-title {
		font-family: var(--ag-font);
		font-size: 11.5px;
		font-weight: 600;
		color: #1f2937;
		margin-bottom: 4px;
	}

	.queue-row-meta {
		display: flex;
		gap: 6px;
		align-items: center;
	}

	.queue-type,
	.queue-status {
		font-family: var(--ag-font);
		font-size: 9.5px;
		font-weight: 700;
		text-transform: uppercase;
		letter-spacing: 0.4px;
		padding: 2px 8px;
		border-radius: var(--ag-radius-pill);
		display: inline-flex;
		align-items: center;
		gap: 4px;
	}

	.queue-type {
		background: rgba(76, 111, 167, 0.1);
		color: var(--ag-blue);
	}

	.queue-status.status-pending {
		background: rgba(229, 186, 65, 0.18);
		color: #a06d00;
	}

	.queue-status.status-progress {
		background: rgba(76, 111, 167, 0.16);
		color: #1d4ed8;
	}

	.queue-status.status-progress::before {
		content: '';
		width: 6px; height: 6px; border-radius: 50%;
		background: #1d4ed8;
		animation: ag-pulse-dot 1.2s infinite ease-in-out;
	}

	@keyframes ag-pulse-dot {
		0%, 100% { opacity: 1; transform: scale(1); }
		50% { opacity: 0.45; transform: scale(0.85); }
	}

	.queue-status.status-complete {
		background: rgba(148, 163, 120, 0.2);
		color: #2f6f31;
	}

	.queue-status.status-failed {
		background: rgba(209, 76, 76, 0.16);
		color: #b91c1c;
	}

	.queue-clear-btn {
		background: linear-gradient(135deg, #e3a47e 0%, var(--ag-orange) 100%);
		color: #fff;
		border: none;
		border-radius: var(--ag-radius-pill);
		padding: 5px 12px;
		font-family: var(--ag-font);
		font-size: 10.5px;
		font-weight: 700;
		letter-spacing: 0.3px;
		text-transform: uppercase;
		cursor: pointer;
		box-shadow: 0 4px 10px rgba(209, 133, 92, 0.32);
		transition: transform 0.2s var(--ag-ease), box-shadow 0.2s var(--ag-ease), filter 0.2s var(--ag-ease);
	}

	.queue-clear-btn:hover {
		transform: translateY(-1px);
		filter: brightness(1.05);
		box-shadow: 0 6px 14px rgba(209, 133, 92, 0.4);
	}

	.queue-empty {
		font-family: var(--ag-font);
		font-size: 11px;
		color: #94a3b8;
		background: rgba(148, 163, 184, 0.06);
		border: 1px dashed rgba(148, 163, 184, 0.4);
		border-radius: 10px;
		padding: 10px 12px;
		text-align: center;
	}

	.queue-lease-row {
		background: rgba(76, 111, 167, 0.07);
		border: 1px solid rgba(76, 111, 167, 0.15);
		border-radius: 10px;
		padding: 6px 10px;
		font-family: var(--ag-font);
		font-size: 11px;
		font-weight: 500;
		color: var(--ag-navy);
		margin-bottom: 6px;
	}

	.queue-lease-row strong {
		color: var(--ag-blue);
		font-weight: 700;
	}

	.queue-log-list {
		display: flex;
		flex-direction: column;
		gap: 4px;
	}

	.queue-log-row {
		background: #fff;
		border: 1px solid rgba(45, 60, 89, 0.08);
		border-radius: 8px;
		padding: 5px 9px;
		font-family: var(--ag-font);
		font-size: 11px;
		color: #334155;
		line-height: 1.4;
		box-shadow: var(--ag-shadow-sm);
	}

	.queue-log-row span {
		font-weight: 600;
		color: #94a3b8;
		margin-right: 6px;
		font-variant-numeric: tabular-nums;
	}

	.queue-log-row.warn {
		border-left: 3px solid var(--ag-orange);
	}

	.queue-log-row.error {
		border-left: 3px solid var(--ag-red);
	}

	.queue-log-row.unmatched {
		border-left: 3px solid #7c3aed;
		background: linear-gradient(90deg, rgba(124, 58, 237, 0.06), rgba(124, 58, 237, 0));
		color: #4c1d95;
	}

	.setting-card {
		background: #fff;
		border-radius: var(--ag-radius);
		padding: 14px 16px;
		box-shadow: var(--ag-shadow-sm);
		transition: transform 0.3s var(--ag-ease), box-shadow 0.3s var(--ag-ease), border-color 0.3s var(--ag-ease);
		border: 1px solid rgba(45, 60, 89, 0.06);
	}

	.setting-card:hover {
		box-shadow: var(--ag-shadow-md);
		border-color: rgba(229, 186, 65, 0.35);
		transform: translateY(-1px);
	}

	.setting-card-header {
		display: flex;
		justify-content: space-between;
		align-items: center;
		margin-bottom: 12px;
	}

	.settings-item-name {
		font-family: var(--ag-font);
		font-size: 13.5px;
		font-weight: 600;
		color: #1f2937;
		letter-spacing: 0.1px;
	}

	.settings-item-value {
		font-family: var(--ag-font);
		font-size: 11.5px;
		font-weight: 700;
		color: var(--ag-navy);
		background: linear-gradient(135deg, rgba(229, 186, 65, 0.22), rgba(229, 186, 65, 0.12));
		padding: 4px 12px;
		border-radius: var(--ag-radius-pill);
		min-width: 50px;
		text-align: center;
		letter-spacing: 0.3px;
		font-variant-numeric: tabular-nums;
		box-shadow: inset 0 0 0 1px rgba(229, 186, 65, 0.25);
	}

	.settings-item-input {
		margin-bottom: 10px;
	}

	.settings-item-description {
		font-family: var(--ag-font);
		font-size: 11.5px;
		color: #64748b;
		line-height: 1.55;
		padding-top: 10px;
		border-top: 1px solid rgba(45, 60, 89, 0.08);
	}

	/* Modern Slider */
	.modern-slider {
		width: 100%;
		height: 6px;
		border-radius: 999px;
		background: linear-gradient(90deg, var(--ag-green) 0%, var(--ag-green) 100%);
		background-color: rgba(45, 60, 89, 0.08);
		outline: none;
		-webkit-appearance: none;
		cursor: pointer;
	}

	.modern-slider::-webkit-slider-thumb {
		-webkit-appearance: none;
		appearance: none;
		width: 18px;
		height: 18px;
		border-radius: 50%;
		background: #fff;
		box-shadow:
			0 0 0 1px rgba(45, 60, 89, 0.12),
			0 4px 10px rgba(148, 163, 120, 0.45);
		cursor: pointer;
		transition: transform 0.25s var(--ag-ease), box-shadow 0.25s var(--ag-ease);
	}

	.modern-slider::-webkit-slider-thumb:hover {
		transform: scale(1.12);
		box-shadow:
			0 0 0 1px rgba(45, 60, 89, 0.15),
			0 6px 14px rgba(148, 163, 120, 0.6);
	}

	.modern-slider::-moz-range-thumb {
		width: 18px;
		height: 18px;
		border-radius: 50%;
		background: #fff;
		box-shadow:
			0 0 0 1px rgba(45, 60, 89, 0.12),
			0 4px 10px rgba(148, 163, 120, 0.45);
		cursor: pointer;
		border: none;
		transition: transform 0.25s var(--ag-ease), box-shadow 0.25s var(--ag-ease);
	}

	.modern-slider::-moz-range-thumb:hover {
		transform: scale(1.12);
		box-shadow:
			0 0 0 1px rgba(45, 60, 89, 0.15),
			0 6px 14px rgba(148, 163, 120, 0.6);
	}

	.modern-slider:disabled {
		opacity: 0.45;
		cursor: not-allowed;
	}

	/* Modern Checkbox */
	.modern-checkbox {
		width: 44px;
		height: 24px;
		position: relative;
		-webkit-appearance: none;
		background: rgba(45, 60, 89, 0.18);
		outline: none;
		border-radius: 999px;
		cursor: pointer;
		transition: background 0.3s var(--ag-ease);
		box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.08);
	}

	.modern-checkbox:checked {
		background: linear-gradient(135deg, #b1bf94 0%, var(--ag-green) 100%);
		box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
	}

	.modern-checkbox::before {
		content: '';
		position: absolute;
		width: 18px;
		height: 18px;
		border-radius: 50%;
		top: 3px;
		left: 3px;
		background: #fff;
		box-shadow:
			0 2px 4px rgba(0, 0, 0, 0.2),
			0 0 0 1px rgba(0, 0, 0, 0.04);
		transition: left 0.3s var(--ag-ease);
	}

	.modern-checkbox:checked::before {
		left: 23px;
	}

	/* Range Slider Container */
	.range-slider {
		display: flex;
		flex-direction: column;
		gap: 8px;
	}

	/* Dark mode support for settings */
	.b_drk .setting-card,
	.b_dark .setting-card {
		background: #1f2937;
		border-color: rgba(255, 255, 255, 0.05);
	}

	.b_drk .setting-card:hover,
	.b_dark .setting-card:hover {
		border-color: rgba(229, 186, 65, 0.35);
		box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
	}

	.b_drk .settings-item-name,
	.b_dark .settings-item-name {
		color: #f1f5f9;
	}

	.b_drk .settings-item-description,
	.b_dark .settings-item-description {
		color: #94a3b8;
		border-top-color: rgba(255, 255, 255, 0.08);
	}

	.b_drk .settings-item-value,
	.b_dark .settings-item-value {
		color: #f8fafc;
		background: linear-gradient(135deg, rgba(229, 186, 65, 0.28), rgba(229, 186, 65, 0.16));
		box-shadow: inset 0 0 0 1px rgba(229, 186, 65, 0.3);
	}

	.b_drk .settings-body,
	.b_dark .settings-body {
		background-color: #0f172a !important;
	}

	.b_drk .queue-row,
	.b_drk .queue-summary-card,
	.b_drk .queue-log-row,
	.b_dark .queue-row,
	.b_dark .queue-summary-card,
	.b_dark .queue-log-row {
		background: #1f2937;
		border-color: rgba(255, 255, 255, 0.06);
		color: #e2e8f0;
	}

	.b_drk .queue-summary-card,
	.b_dark .queue-summary-card {
		color: #cbd5e1;
	}

	.b_drk .queue-summary-card span,
	.b_dark .queue-summary-card span {
		color: var(--ag-accent-soft);
	}

	.b_drk .queue-row-title,
	.b_dark .queue-row-title {
		color: #f1f5f9;
	}

	.b_drk .queue-lease-row,
	.b_dark .queue-lease-row {
		background: rgba(76, 111, 167, 0.14);
		border-color: rgba(76, 111, 167, 0.28);
		color: #cbd5e1;
	}

	.b_drk .queue-lease-row strong,
	.b_dark .queue-lease-row strong {
		color: var(--ag-accent-soft);
	}

	.b_drk .queue-empty,
	.b_dark .queue-empty {
		background: rgba(148, 163, 184, 0.06);
		border-color: rgba(148, 163, 184, 0.18);
		color: #94a3b8;
	}

	.b_drk .queue-section-header h3,
	.b_dark .queue-section-header h3 {
		color: rgba(241, 245, 249, 0.65);
	}

	.b_drk .queue-log-row,
	.b_dark .queue-log-row {
		color: #cbd5e1;
	}

	.b_drk .queue-log-row span,
	.b_dark .queue-log-row span {
		color: #64748b;
	}

	.b_drk .queue-log-row.unmatched,
	.b_dark .queue-log-row.unmatched {
		background: linear-gradient(90deg, rgba(124, 58, 237, 0.18), rgba(124, 58, 237, 0));
		border-color: rgba(159, 122, 234, 0.35);
		color: #e9d8fd;
	}
`})
// Append styles to all pages
document.head.appendChild(stylesheet);
} catch (e) {
	console.error("[AutoGrind] ERROR:", e);
	console.error("[AutoGrind] Stack trace:", e.stack);
}