import pyautogui
import pyperclip
import time
import subprocess

# --- MAIN CONFIGURATION ---
BROWSER_NAME = "Google Chrome"  # IMPORTANT: Change this to your browser's exact name
                                # Examples: "Safari", "Brave Browser", "Microsoft Edge"
NUMBER_OF_PAGES_TO_SOLVE = 9   # How many pages to loop through

# --- TIMING CONFIGURATION ---
URL_CHANGE_TIMEOUT_SECONDS = 120 # Max time to wait for the URL to change
PAGE_LOAD_DELAY_SECONDS = 5    # Extra time to wait after a new page loads

# --- COORDINATES ---
E_BUTTON_COORDS = (1506, 243)
CONSOLE_INPUT_COORDS = (1234, 998)


# --- JavaScript Code (V9 - Concurrent 10-Second Rule - UNABRIDGED) ---
javascript_code = """
// =======================================================================================
// == COMPLETE JAVASCRIPT PAYLOAD (V9 - CONCURRENT 10-SECOND RULE)                      ==
// =======================================================================================
//
// This version re-introduces concurrency as requested.
// It uses the EXACT SAME "10-Second Rule" logic from the previous version,
// but applies it to all animations on the page simultaneously.
//
// =======================================================================================


// --- PART 1: DATA LOADER ---
(function() {
  if (typeof $E === 'undefined') {
    console.error("'$E' is not defined. Please select a component in the Ember Inspector first.");
    return;
  }
  let owner;
  try { owner = require('@ember/application').getOwner($E); } 
  catch (e) { console.log("Could not use `require`. Trying private `_owner` property."); owner = $E._owner; }
  if (!owner) { console.error("Could not get the application owner from the selected component."); return; }
  console.log("Successfully retrieved the Application Owner!", owner);
  const pageController = owner.lookup('controller:zybook.chapter.section');
  if (pageController && pageController.model) {
    if (pageController.model.contentResources) {
      window.allQuestions = pageController.model.contentResources;
      console.log(`Success! Found ${window.allQuestions.length} content resource objects.`);
    } else { console.error("Found the model, but it does not have the 'contentResources' property."); }
  } else { console.error("Could not find the 'zybook.chapter.section' controller or its model."); }
})();


// --- PART 2: MAIN AUTOMATION LOGIC ---
(async function() {
    // --- Helper Functions ---
    function simulateRealClick(element) {
        if (!element) return;
        const event = new MouseEvent('click', { bubbles: true, cancelable: true, view: window });
        element.dispatchEvent(event);
    }
    const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
    // --- End of Helper Functions ---


    // --- Animation Handler (REVISED - V9 - Concurrent 10-Second Rule) ---
    async function handleAnimations() {
        console.log("%c--- SCRIPT V9: Handling Animations (Concurrent 10-Second Rule) ---", "font-weight: bold; font-size: 14px; color: red;");
        const animationQuestions = window.allQuestions.filter(r => (r.type === 'custom' || r.type === 'animation-player') && r.payload?.tool === 'zyAnimator');
        if (animationQuestions.length === 0) { console.warn("No animations found on this page."); return; }
        
        console.log(`[INFO] Found ${animationQuestions.length} animations. Starting them all concurrently.`);

        /**
         * This function contains the logic to play a SINGLE animation.
         * It follows the strict "10-Second Rule".
         */
        async function playOneAnimation(resource) {
            console.log(`%c--- Starting Animation ID: ${resource.id} ---`, "font-weight: bold;");
            
            function countAnimationSteps(res) {
                const xmlString = res.payload.options?.animXMLToLoad;
                if (!xmlString) return 1;
                const matches = xmlString.match(/instrType="step/g);
                return (matches ? matches.length : 1);
            }

            try {
                const resourceContainer = document.querySelector(`div[content_resource_id="${resource.id}"]`);
                if (!resourceContainer) { console.error(`Could not find container for animation ${resource.id}.`); return; }

                const startButton = resourceContainer.querySelector('.start-button');
                const speedCheckboxLabel = resourceContainer.querySelector('.speed-control label');
                
                if (speedCheckboxLabel) {
                    console.log(`[${resource.id}] [ACTION] Clicking 2x speed.`);
                    simulateRealClick(speedCheckboxLabel);
                }
                
                // On the first step, we use the START button.
                if (startButton && getComputedStyle(startButton).display !== 'none') {
                    console.log(`[${resource.id}] -> Executing START step...`);
                    simulateRealClick(startButton);
                    // THE UNBREAKABLE, UNCONDITIONAL 10-SECOND WAIT.
                    console.log(`[${resource.id}]    - WAITING 10 SECONDS...`);
                    await sleep(10000);
                }

                const totalSteps = countAnimationSteps(resource);
                console.log(`[${resource.id}] [INFO] Forcing PLAY loop to run ${totalSteps} times.`);

                for (let i = 0; i < totalSteps; i++) {
                    console.log(`[${resource.id}] -> Executing PLAY step ${i + 1}/${totalSteps}...`);
                    try {
                       const stepButtonDiv = resourceContainer.querySelector('.play-button');
                       simulateRealClick(stepButtonDiv.parentElement);
                    } catch (e) {
                       console.warn(`[${resource.id}]    - Could not find PLAY button. Waiting anyway.`);
                    }

                    // THE UNBREAKABLE, UNCONDITIONAL 10-SECOND WAIT.
                    console.log(`[${resource.id}]    - WAITING 10 SECONDS...`);
                    await sleep(10000);
                }

                console.log(`%c[${resource.id}] Forced loop has finished.`, 'color: green;');
            } catch (error) { console.error(`[${resource.id}] An error occurred during animation:`, error); }
        }

        // Create an array of promises by calling playOneAnimation for each resource.
        // This starts them all without waiting.
        const animationPromises = animationQuestions.map(resource => playOneAnimation(resource));

        // Now, wait here until ALL of the promises in the array have resolved.
        await Promise.all(animationPromises);

        console.log("%c--- All Concurrent Animations Processed ---", "font-weight: bold; color: blue;");
    }

    // --- Question Solver ---
    async function solveQuestions() {
        console.log("%c--- SCRIPT PHASE: Solving Questions ---", "font-weight: bold; color: blue;");
        const solvableQuestions = window.allQuestions.filter(resource => (resource.type === 'multiple_choice' || resource.type === 'short_answer') && resource.payload.questions);
        for (const resource of solvableQuestions) {
            const resourceContainer = document.querySelector(`div[content_resource_id="${resource.id}"]`);
            if (!resourceContainer || resourceContainer.querySelector('.activity-title-bar .zb-chevron.check')) continue;
            console.log(`%c--- Processing Question ID: ${resource.id} | Type: ${resource.type} ---`, "font-weight: bold;");
            if (resource.type === 'short_answer') {
                const questionParts = resourceContainer.querySelectorAll('.question-set-question');
                for (let i = 0; i < questionParts.length; i++) {
                    if (questionParts[i].querySelector('.correct')) continue;
                    const answer = resource.payload.questions[i]?.answers[0];
                    const inputField = questionParts[i].querySelector('input[type="text"], textarea');
                    if (inputField && answer) {
                        inputField.value = answer;
                        inputField.dispatchEvent(new Event('input', { bubbles: true }));
                        const checkButton = questionParts[i].querySelector('button.check-button');
                        if (checkButton && !checkButton.disabled) { simulateRealClick(checkButton); await sleep(2000); }
                    }
                }
            } else if (resource.type === 'multiple_choice') {
                const questionParts = resourceContainer.querySelectorAll('.question-set-question');
                const solutions = resource.payload.questions.map(q => q.choices.findIndex(choice => choice.correct === true));
                for (let i = 0; i < solutions.length; i++) {
                    const correctChoiceIndex = solutions[i];
                    if (questionParts[i] && correctChoiceIndex > -1) {
                        const choices = questionParts[i].querySelectorAll('input[type="radio"]');
                        if (choices[correctChoiceIndex] && !choices[correctChoiceIndex].checked) { simulateRealClick(choices[correctChoiceIndex].nextElementSibling); }
                    }
                }
                const mainCheckButton = resourceContainer.querySelector('button.check-button');
                if (mainCheckButton && !mainCheckButton.disabled) { simulateRealClick(mainCheckButton); await sleep(2000); }
            }
        }
    }

    // --- Page Navigation ---
    async function goToNextPage() {
        console.log("%c--- SCRIPT PHASE: Navigating to Next Page ---", "font-weight: bold; color: blue;");
        const nextButton = document.querySelector('.nav-text.next');
        if (nextButton) {
            console.log("Waiting 5 seconds before navigating...");
            await sleep(5000);
            console.log("Found 'Next Page' button. Clicking to navigate...");
            simulateRealClick(nextButton);
        } else {
            console.log("Could not find a 'Next Page' button. This is likely the end of the chapter.");
        }
    }

    // --- Main Execution ---
    console.log("--- AUTOMATION SCRIPT STARTED ---");
    if (typeof window.allQuestions === 'undefined') {
        console.error("FATAL: 'window.allQuestions' not found. Please run the initial setup script again.");
    } else {
        await handleAnimations();
        await solveQuestions();
        await goToNextPage();
        console.log("--- AUTOMATION SCRIPT FINISHED ---");
    }
})();
"""

def get_browser_url(browser_name):
    """Uses AppleScript to get the URL from the frontmost tab of the specified browser."""
    try:
        script = f'tell application "{browser_name}" to get URL of active tab of front window'
        result = subprocess.run(
            ['osascript', '-e', script],
            capture_output=True,
            text=True,
            check=True,
            timeout=5
        )
        return result.stdout.strip()
    except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
        print(f"\n[Error] Could not get URL from {browser_name}. Is it open and the frontmost app?")
        print(f"Details: {e}")
        return None

def main():
    """Main function to run the full automation loop."""
    pyautogui.FAILSAFE = True

    print("--- Zybooks Full Automation Script ---")
    print(f"Will loop for a maximum of {NUMBER_OF_PAGES_TO_SOLVE} pages.")
    print("Starting in 5 seconds. Please switch to your browser window.")
    print("!!! To stop, move mouse to a corner or press Ctrl+C in terminal !!!")
    time.sleep(5)

    for i in range(NUMBER_OF_PAGES_TO_SOLVE):
        print(f"\n--- Starting Page {i + 1} of {NUMBER_OF_PAGES_TO_SOLVE} ---")
        
        # Get the URL before we do anything
        initial_url = get_browser_url(BROWSER_NAME)
        if initial_url is None:
            print("Stopping script because browser URL could not be retrieved.")
            break
        print(f"Initial URL: {initial_url[:70]}...")

        # Run the automation sequence
        pyautogui.click(E_BUTTON_COORDS)
        time.sleep(0.5)
        pyautogui.click(CONSOLE_INPUT_COORDS)
        time.sleep(0.5)
        pyperclip.copy(javascript_code)
        time.sleep(0.2)
        pyautogui.hotkey('command', 'v')
        time.sleep(0.5)
        pyautogui.press('enter')
        print("JavaScript payload executed in console.")

        # Wait for the URL to change
        print(f"Waiting up to {URL_CHANGE_TIMEOUT_SECONDS}s for the URL to change...")
        start_time = time.time()
        url_has_changed = False
        while time.time() - start_time < URL_CHANGE_TIMEOUT_SECONDS:
            current_url = get_browser_url(BROWSER_NAME)
            if current_url and current_url != initial_url:
                print("\nURL changed successfully!")
                print(f"New URL: {current_url[:70]}...")
                url_has_changed = True
                break
            time.sleep(1)

        # Handle results of the wait
        if not url_has_changed:
            print("\nTimeout reached. URL did not change.")
            print("Assuming this is the last page or an error occurred. Stopping script.")
            break
        
        # Wait for the new page to finish loading before the next loop
        print(f"Waiting {PAGE_LOAD_DELAY_SECONDS}s for new page to settle...")
        time.sleep(PAGE_LOAD_DELAY_SECONDS)

    print("\n--- Automation script has completed its run. ---")

if __name__ == "__main__":
    main()