#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Kindle Key Finder V2 - Main Entry Point
A modular refactor of the original key_finder.py

Orchestrates 4 phases:
- Phase 1: Key Extraction
- Phase 2: DeDRM Plugin Configuration  
- Phase 3: Calibre Auto-Import
- Phase 4: KFX to EPUB Conversion
"""

import os
import sys
import platform
import shutil
import subprocess
import time
import json
from datetime import datetime

# Add modules to path (check both root and code/ subfolder)
script_dir = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, script_dir)

# Check if code folder exists (for reorganized structure)
code_dir = os.path.join(script_dir, "code")
if os.path.exists(code_dir):
    sys.path.insert(0, code_dir)

from modules.utils import ( # type: ignore
    print_banner_and_version, print_step, print_ok, print_error, print_warn,
    check_extractor_exists, check_alt_extractor_exists, check_write_permissions,
    clear_screen_between_phases, display_phase_summary, auto_launch_kindle,
    prevent_kindle_auto_update, write_startup_log, append_to_startup_log,
    is_running_as_admin, check_python_installation, print_phase_banner,
    generate_run_id, timed_input, format_elapsed, find_alt_extractor_for_version
)
from modules.config import load_config, save_config, prompt_config_action_with_timer, delete_config # type: ignore
from ui.simple_ui import run_gui_wizard # type: ignore
from modules.config import configure_pre_flight_wizard # type: ignore


# ============================================================================
# PRE-FLIGHT VALIDATION FUNCTIONS
# ============================================================================

def check_windows():
    """Verify Windows OS"""
    if platform.system() != 'Windows':
        print("This script only works on Windows!")
        return False
    return True


def discover_paths():
    """Discover script and working paths"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    user_home = os.path.expanduser("~")
    
    can_write, error = check_write_permissions(script_dir)
    if can_write:
        working_dir = script_dir
    else:
        working_dir = os.path.join(user_home, "AppData", "Local", "Kindle_Key_Finder")
        os.makedirs(working_dir, exist_ok=True)
    
    return script_dir, user_home, working_dir


def discover_config_location(script_dir, user_home):
    """Discover which location has the config file and which is writable"""
    fallback_base = os.path.join(user_home, "AppData", "Local", "Kindle_Key_Finder")
    fallback_config = os.path.join(fallback_base, "key_finder_config.json")
    script_config = os.path.join(script_dir, "key_finder_config.json")
    
    can_write, error = check_write_permissions(script_dir)
    
    if can_write:
        if os.path.exists(script_config):
            return script_config, script_dir, False, False
        if os.path.exists(fallback_config):
            return fallback_config, script_dir, True, False
        return script_config, script_dir, False, False
    else:
        if os.path.exists(fallback_config):
            return fallback_config, fallback_base, False, True
        if os.path.exists(script_config):
            return script_config, fallback_base, True, True
        return fallback_config, fallback_base, False, True


def find_kindle_exe():
    """Search for Kindle.exe in the two known installation locations"""
    user_home = os.path.expanduser("~")
    
    appdata_kindle = os.path.join(user_home, "AppData", "Local", "Amazon", "Kindle", "application")
    appdata_exe = os.path.join(appdata_kindle, "Kindle.exe")
    temp_marker = os.path.join(appdata_kindle, "TEMP.txt")
    
    program_files_kindle = r"C:\Program Files (x86)\Amazon\Kindle"
    program_files_exe = os.path.join(program_files_kindle, "Kindle.exe")
    
    appdata_exists = os.path.exists(appdata_exe)
    program_files_exists = os.path.exists(program_files_exe)
    
    if appdata_exists and program_files_exists:
        return appdata_kindle, False
    elif appdata_exists:
        if not os.path.exists(temp_marker):
            return appdata_kindle, False
        else:
            try:
                shutil.rmtree(appdata_kindle)
            except:
                pass
            if program_files_exists:
                return program_files_kindle, True
            return None, False
    elif program_files_exists:
        return program_files_kindle, True
    else:
        return None, False


def cleanup_temp_kindle():
    """Check for and cleanup any leftover temporary Kindle installation
    
    NOTE: This only cleans up if TEMP.txt marker exists (created by this script).
    This prevents accidentally deleting the user's real Kindle installation.
    """
    from modules.utils import rmtree_with_retry # type: ignore
    
    user_home = os.path.expanduser("~")
    temp_kindle_dir = os.path.join(user_home, "AppData", "Local", "Amazon", "Kindle", "application")
    temp_marker = os.path.join(temp_kindle_dir, "TEMP.txt")
    
    # Only cleanup if the TEMP.txt marker exists (proves it was created by this script)
    if os.path.exists(temp_marker):
        print_warn("Found leftover temporary Kindle installation from previous run")
        print_step("Cleaning up...")
        try:
            shutil.rmtree(temp_kindle_dir)
            print_ok("Cleanup completed successfully")
        except Exception as e:
            print_error(f"Failed to cleanup temporary files: {e}")
        print()
    # If no TEMP.txt marker, don't touch it - it's the user's real Kindle installation


def cleanup_temp_extraction(silent=False, working_dir=None):
    """Check for and cleanup any leftover temp_extraction folder"""
    from modules.utils import rmtree_with_retry # type: ignore
    
    if working_dir is None:
        script_dir = os.path.dirname(os.path.abspath(__file__))
        user_home = os.path.expanduser("~")
        can_write, _ = check_write_permissions(script_dir)
        working_dir = script_dir if can_write else os.path.join(user_home, "AppData", "Local", "Kindle_Key_Finder")
    
    temp_extraction_dir = os.path.join(working_dir, "temp_extraction")
    
    if os.path.exists(temp_extraction_dir):
        if not silent:
            print_warn("Found leftover temp_extraction folder from previous run")
            print_step("Cleaning up...")
        
        success = rmtree_with_retry(temp_extraction_dir)
        
        if success:
            if not silent:
                print_ok("Cleanup completed successfully")
        else:
            if not silent:
                print_error("Failed to cleanup temp_extraction folder")
        
        if not silent:
            print()


def cleanup_temp_staging(silent=False, working_dir=None):
    """Check for and cleanup any leftover temp_kindle_content staging folder"""
    from modules.utils import rmtree_with_retry # type: ignore
    
    if working_dir is None:
        script_dir = os.path.dirname(os.path.abspath(__file__))
        user_home = os.path.expanduser("~")
        can_write, _ = check_write_permissions(script_dir)
        working_dir = script_dir if can_write else os.path.join(user_home, "AppData", "Local", "Kindle_Key_Finder")
    
    staging_dir = os.path.join(working_dir, "temp_kindle_content")
    
    if os.path.exists(staging_dir):
        if not silent:
            print_warn("Found leftover staging folder from previous run")
            print_step("Cleaning up...")
        
        success = rmtree_with_retry(staging_dir)
        
        if success:
            if not silent:
                print_ok("Cleanup completed successfully")
        else:
            if not silent:
                print_error("Failed to cleanup staging folder")
        
        if not silent:
            print()


def validate_all_requirements(script_dir, user_home):
    """Comprehensive validation of all requirements"""
    from modules.config import load_config  # type: ignore
    from modules.utils import check_calibre_installed, check_plugin_installed  # type: ignore

    # Check early if Calibre import is disabled - affects which checks are critical
    _existing_cfg = load_config()
    _calibre_import_raw = _existing_cfg.get('calibre_import', {}) if _existing_cfg else {}
    _calibre_import_enabled = _calibre_import_raw.get('enabled', True) if isinstance(_calibre_import_raw, dict) else True

    # Admin rights check - done first so every other check can reference it.
    # NOT marked CRITICAL (script can still run without elevation in many cases),
    # but logged to startup.log so users and support can immediately see the
    # privilege level when diagnosing [Errno 13] / permission-denied failures.
    running_as_admin = is_running_as_admin()

    report = {
        'is_admin': running_as_admin,
        'write_permissions': False,
        'working_dir': None,
        'is_fallback': False,
        'fallback_reason': None,
        'extractor_installed': False,
        'extractor_path': None,
        'alt_extractor_installed': False,
        'alt_extractor_path': None,
        'use_alt_method': False,
        'kindle_installed': False,
        'kindle_path': None,
        'kindle_needs_temp_copy': False,
        'calibre_installed': False,
        'kfx_plugin_installed': False,
        'dedrm_plugin_installed': False,
        'all_critical_met': False,
        'warnings': []
    }

    if not running_as_admin:
        report['warnings'].append({
            'component': 'Administrator Rights',
            'severity': 'WARNING',
            'impact': (
                'Script is NOT running as Administrator. '
                'This can cause [Errno 13] Permission denied on dedrm.json, '
                'Calibre plugin folders, and Kindle in Program Files. '
                'Re-run using Run_keyfinder_admin.vbs for full access.'
            ),
            'install_url': 'N/A - Right-click Run_keyfinder_admin.vbs and run as Administrator'
        })
    
    existing_config = load_config()
    force_appdata = existing_config.get('always_use_appdata', False) if existing_config else False
    
    if force_appdata:
        fallback_base = os.path.join(user_home, "AppData", "Local", "Kindle_Key_Finder")
        working_dir = fallback_base
        is_fallback = True
        os.makedirs(working_dir, exist_ok=True)
        report['fallback_reason'] = "User configured: Always use APPDATA"
    else:
        config_path, working_dir, needs_migration, is_fallback = discover_config_location(script_dir, user_home)
        if is_fallback:
            can_write, error = check_write_permissions(script_dir)
            report['fallback_reason'] = f"Script directory not writable: {error}"
            os.makedirs(working_dir, exist_ok=True)
    
    report['write_permissions'] = True
    report['working_dir'] = working_dir
    report['is_fallback'] = is_fallback
    
    # Check for KFXKeyExtractor28.exe (standard method)
    extractor_ok, extractor_path = check_extractor_exists(script_dir)
    report['extractor_installed'] = extractor_ok
    report['extractor_path'] = extractor_path
    extractor_display = os.path.basename(extractor_path) if extractor_path else 'KFXKeyExtractor (not found)'
    if not extractor_ok:
        report['warnings'].append({
            'component': extractor_display,
            'severity': 'CRITICAL',
            'impact': 'Cannot extract keys (Phase 1 will fail immediately)',
            'install_url': 'https://github.com/Satsuoni/DeDRM_tools'
        })

    # Check for KFXArchiver282.exe (alternate method)
    alt_extractor_ok, alt_extractor_path = check_alt_extractor_exists(script_dir)
    report['alt_extractor_installed'] = alt_extractor_ok
    report['alt_extractor_path'] = alt_extractor_path
    
    # Check Kindle for PC
    kindle_path, needs_temp_copy = find_kindle_exe()
    report['kindle_installed'] = (kindle_path is not None)
    report['kindle_path'] = kindle_path
    report['kindle_needs_temp_copy'] = needs_temp_copy
    if not report['kindle_installed']:
        report['warnings'].append({
            'component': 'Kindle for PC',
            'severity': 'CRITICAL',
            'impact': 'Cannot extract keys (Phase 1 will fail)',
            'install_url': 'https://www.amazon.com/kindle-dbs/fd/kcp'
        })

    # Check Kindle version compatibility with both tools.
    # KFXKeyExtractor supports: 2.8.0 / 2.8.1 / 2.8.2
    # KFXArchiver supports:     2.8.0 / 2.8.1 / 2.8.2 / 2.8.3
    #
    # Kindle 2.8.3 -> primary tool crashes, but KFXArchiver works.
    #   Action: auto-activate alt method (use_alt_method = True).
    # Kindle 2.8.4+ -> both tools crash.
    #   Action: CRITICAL error, nothing can proceed.
    from modules.utils import check_kindle_version_supported  # type: ignore
    kindle_primary_ok = True
    kindle_alt_ok = True
    kindle_version_str = None
    if kindle_path:
        kindle_primary_ok, kindle_alt_ok, kindle_version_str = check_kindle_version_supported(kindle_path)
    report['kindle_version'] = kindle_version_str
    report['kindle_version_supported'] = kindle_primary_ok
    report['kindle_version_alt_ok'] = kindle_alt_ok

    if kindle_version_str and not kindle_primary_ok and kindle_alt_ok:
        # Kindle 2.8.3: primary fails but alt works — auto-switch to alt method
        report['use_alt_method'] = True
        report['kindle_version_auto_alt'] = True
        report['warnings'].append({
            'component': 'Kindle Version (Auto-Alt)',
            'severity': 'WARNING',
            'impact': (
                f"Kindle {kindle_version_str} is not supported by KFXKeyExtractor "
                f"(requires 2.8.0-2.8.2), but IS supported by KFXArchiver. "
                f"Script automatically switched to Alternate Method (KFXArchiver)."
            ),
            'install_url': 'N/A - KFXArchiver handles this version automatically'
        })
    elif kindle_version_str and not kindle_primary_ok and not kindle_alt_ok:
        # Kindle is newer than any supported version — both tools will crash
        report['kindle_version_auto_alt'] = False
        report['warnings'].append({
            'component': 'Kindle Version',
            'severity': 'CRITICAL',
            'impact': (
                f"Kindle {kindle_version_str} is not supported by either extraction tool. "
                f"KFXKeyExtractor supports 2.8.0-2.8.2, "
                f"KFXArchiver283 supports 2.8.3, "
                f"KFXArchiver291 supports 2.9.1. "
                f"Downgrade Kindle for PC to a supported version to continue."
            ),
            'install_url': 'https://www.amazon.com/kindle-dbs/fd/kcp'
        })
    else:
        report['kindle_version_auto_alt'] = False

    # ----------------------------------------------------------------
    # Version-specific KFXArchiver selection.
    # Now that the K4PC version is known, pick the exact KFXArchiver
    # tool for it (e.g. KFXArchiver283 for 2.8.3, KFXArchiver291 for
    # 2.9.1) rather than accepting whichever exe was found first by
    # check_alt_extractor_exists().
    #
    # This only runs when the version IS supported by some KFXArchiver
    # (kindle_alt_ok=True). If the right exe is missing, it was almost
    # certainly quarantined by antivirus - flag as CRITICAL so the user
    # knows to re-download the script package.
    # ----------------------------------------------------------------
    if kindle_version_str and kindle_alt_ok:
        versioned_ok, versioned_path = find_alt_extractor_for_version(script_dir, kindle_version_str)
        if versioned_path:
            # Correct version-specific archiver found - use it
            report['alt_extractor_path'] = versioned_path
            report['alt_extractor_installed'] = True
            alt_extractor_ok = True       # keep local var in sync
            alt_extractor_path = versioned_path
        else:
            # Version IS supported (kindle_alt_ok=True) but the matching exe
            # is not on disk.  Almost always caused by antivirus quarantine.
            report['alt_extractor_installed'] = False
            report['alt_extractor_path'] = None
            alt_extractor_ok = False      # keep local var in sync
            report['warnings'].append({
                'component': 'KFXArchiver (Version-Specific Tool Missing)',
                'severity': 'CRITICAL',
                'impact': (
                    f"The correct KFXArchiver for Kindle {kindle_version_str} is missing from "
                    f"code/tools/. It may have been quarantined by antivirus software. "
                    f"Re-download the script package and add code/tools/ to your AV exclusions."
                ),
                'install_url': 'N/A - Re-download the script package to restore missing tools'
            })
    
    # ----------------------------------------------------------------
    # Calibre & Plugin checks - these are NOT critical (no hard stop).
    # If import is enabled in config but components are missing, we
    # auto-disable import/convert and inform the user.
    # ----------------------------------------------------------------
    auto_disabled_reasons = []

    calibre_ok = check_calibre_installed()
    report['calibre_installed'] = calibre_ok
    if not calibre_ok:
        if _calibre_import_enabled:
            auto_disabled_reasons.append('Calibre is not installed')
            report['warnings'].append({
                'component': 'Calibre',
                'severity': 'WARNING',
                'impact': 'Import & Convert auto-disabled (Calibre not installed)',
                'install_url': 'https://calibre-ebook.com/download'
            })
        # If import already disabled, no warning needed

    # Check plugins only if Calibre is present
    if calibre_ok:
        kfx_ok = check_plugin_installed('KFX Input')
        dedrm_ok = check_plugin_installed('DeDRM')

        report['kfx_plugin_installed'] = kfx_ok
        report['dedrm_plugin_installed'] = dedrm_ok

        # DeDRM is now required in ALL modes when import is enabled.
        # Standard mode: Phase 2 writes k4i keys to dedrm.json for DeDRM to use.
        # ALT mode:      Phase 2 writes k4i keys so DeDRM can handle KF8/MOBI books
        #                that KFXArchiver cannot decrypt.
        if not dedrm_ok and _calibre_import_enabled:
            auto_disabled_reasons.append('DeDRM Plugin is not installed in Calibre')
            report['warnings'].append({
                'component': 'DeDRM Plugin',
                'severity': 'WARNING',
                'impact': 'Import & Convert auto-disabled (DeDRM Plugin not installed)',
                'install_url': 'https://github.com/noDRM/DeDRM_tools'
            })

        if not kfx_ok and _calibre_import_enabled:
            auto_disabled_reasons.append('KFX Input Plugin is not installed in Calibre')
            report['warnings'].append({
                'component': 'KFX Input Plugin',
                'severity': 'WARNING',
                'impact': 'Import & Convert auto-disabled (KFX Input Plugin not installed)',
                'install_url': 'https://www.mobileread.com/forums/showthread.php?t=283371'
            })
    else:
        # Calibre not installed - plugin state unknown
        report['kfx_plugin_installed'] = False
        report['dedrm_plugin_installed'] = False

    # Record auto-disable reasons in the report so main() can act on them
    report['auto_disabled_import'] = auto_disabled_reasons

    # ----------------------------------------------------------------
    # DEBUG/TEST: Force primary ALT method without needing DeDRM missing.
    # Set "debug_force_alt_method": true in key_finder_config.json to
    # bypass the standard Phase 1 path and use KFXArchiver282.exe directly.
    # Remove or set to false when done testing.
    # ----------------------------------------------------------------
    if existing_config and existing_config.get('debug_force_alt_method', False):
        # Always print immediate diagnostic output so the flag's resolution is visible
        # in the console regardless of what the validation table shows.
        print_warn("[DEBUG] debug_force_alt_method=true is set in config")
        print_warn(f"[DEBUG]   KFXArchiver found:    {alt_extractor_ok}")
        print_warn(f"[DEBUG]   KFXArchiver path:     {alt_extractor_path if alt_extractor_path else 'NOT FOUND'}")
        print_warn(f"[DEBUG]   Search location:      {os.path.join(script_dir, 'code', 'tools')}")

        if alt_extractor_ok:
            report['use_alt_method'] = True
            print_warn("[DEBUG]   -> ALT METHOD ACTIVATED - Phase 1 will use KFXArchiver")
            print()
            report['warnings'].append({
                'component': 'Debug Flag Active',
                'severity': 'WARNING',
                'impact': '[DEBUG] debug_force_alt_method=true: Forcing primary ALT method (KFXArchive)',
                'install_url': 'N/A - Remove this flag when done testing'
            })
        else:
            # Flag set but KFXArchiver not found - this is a hard error, not a silent fallback.
            # Raise it as CRITICAL so it shows in the warnings section and all_critical_met=False.
            print_error("[DEBUG]   -> FLAG CANNOT BE HONORED: KFXArchiver282.exe not found!")
            print_error(f"[DEBUG]      Expected location: {os.path.join(script_dir, 'code', 'tools')}")
            print_error("[DEBUG]      Place KFXArchiver282.exe in code/tools/ and re-run.")
            print()
            report['warnings'].append({
                'component': 'debug_force_alt_method: KFXArchiver Missing',
                'severity': 'CRITICAL',
                'impact': 'debug_force_alt_method=true but KFXArchiver282.exe not found in code/tools/',
                'install_url': f"Place KFXArchiver282.exe in: {os.path.join(script_dir, 'code', 'tools')}"
            })

    # Critical components: Kindle for PC + KFXKeyExtractor28.exe + KFXArchiver282.exe
    # Calibre, DeDRM, and KFX Plugin are NOT critical - their absence only auto-disables import.
    #
    # When debug_force_alt_method=true has activated use_alt_method, KFXKeyExtractor is not
    # needed for this run - only KFXArchiver + Kindle are required.
    # kindle_version_alt_ok=False means Kindle 2.8.4+ - both tools will crash.
    # This must block execution regardless of which method is active.
    _version_critical_ok = report.get('kindle_version_alt_ok', True)

    if report.get('use_alt_method', False):
        report['all_critical_met'] = (
            alt_extractor_ok
            and report['kindle_installed']
            and _version_critical_ok
        )
    else:
        report['all_critical_met'] = (
            extractor_ok
            and alt_extractor_ok
            and report['kindle_installed']
            and _version_critical_ok
        )

    report['calibre_import_enabled'] = _calibre_import_enabled
    
    return report


def display_validation_results(report):
    """Display validation results with color-coded warnings"""
    from modules.utils import print_colored # type: ignore
    
    print_step("Pre-Flight System Validation")
    print("=" * 70)
    print()
    
    print("┌─────────────────────────────┬────────────┬────────────────────────────────────────┐")
    print("│ Component                   │ Status     │ Details                                │")
    print("├─────────────────────────────┼────────────┼────────────────────────────────────────┤")

    # Admin rights row - always shown, warns when not elevated
    is_admin = report.get('is_admin', True)
    if is_admin:
        admin_status = "✓ Admin"
        admin_details = "Running as Administrator"
    else:
        admin_status = "! No Admin"
        admin_details = "Not elevated - may cause errors"
    print(f"│ Admin Rights                │ {admin_status:<10} │ {admin_details:<38} │")

    if report['is_fallback']:
        status = "⚠ Fallback"
        details = "Using AppData location"
    else:
        status = "✓ OK"
        details = "Script directory writable"
    print(f"│ Write Permissions           │ {status:<10} │ {details:<38} │")
    
    use_alt_method = report.get('use_alt_method', False)
    calibre_import_enabled = report.get('calibre_import_enabled', True)

    # Derive display names from the actual discovered paths
    extractor_name = os.path.basename(report['extractor_path']) if report.get('extractor_path') else 'KFXKeyExtractor'
    alt_extractor_name = os.path.basename(report['alt_extractor_path']) if report.get('alt_extractor_path') else 'KFXArchiver'

    if report['extractor_installed']:
        status = "✓ Found"
        details = extractor_name
    else:
        if use_alt_method:
            status = "- N/A"
            details = "Not needed (alt method active)"
        else:
            status = "✗ Missing"
            details = "CRITICAL - place in code/tools/"
    extractor_label = extractor_name if len(extractor_name) <= 27 else extractor_name[:24] + "..."
    print(f"│ {extractor_label:<27} │ {status:<10} │ {details:<38} │")

    if report.get('alt_extractor_installed'):
        status = "✓ Found"
        details = alt_extractor_name
    else:
        status = "✗ Missing"
        details = "CRITICAL - place in code/tools/"
    alt_label = alt_extractor_name if len(alt_extractor_name) <= 27 else alt_extractor_name[:24] + "..."
    print(f"│ {alt_label:<27} │ {status:<10} │ {details:<38} │")

    if report['kindle_installed']:
        status = "✓ Found"
        path_display = os.path.basename(report['kindle_path']) if report['kindle_path'] else "Installed"
        if len(path_display) > 38:
            path_display = path_display[:35] + "..."
        details = path_display
    else:
        status = "✗ Missing"
        details = "CRITICAL - Cannot extract keys"
    print(f"│ Kindle for PC               │ {status:<10} │ {details:<38} │")

    # Kindle version row - only show when version was readable
    kindle_ver = report.get('kindle_version')
    kindle_primary_ok = report.get('kindle_version_supported', True)
    kindle_alt_ok = report.get('kindle_version_alt_ok', True)
    kindle_auto_alt = report.get('kindle_version_auto_alt', False)

    if kindle_ver:
        if not kindle_primary_ok and kindle_alt_ok:
            # 2.8.3 - works with alt, auto-switched
            ver_status = "! Auto-Alt"
            ver_details = f"{kindle_ver} - KFXArchiver OK"
        elif not kindle_primary_ok and not kindle_alt_ok:
            # 2.8.4+ - both fail
            ver_status = "✗ Bad Ver"
            ver_details = f"{kindle_ver} - UNSUPPORTED"
        else:
            # Fully supported
            ver_status = "✓ OK"
            ver_details = f"{kindle_ver} - Supported"
        print(f"│ Kindle Version              │ {ver_status:<10} │ {ver_details:<38} │")

    auto_disabled_reasons = report.get('auto_disabled_import', [])
    import_auto_disabled = bool(auto_disabled_reasons)

    if not calibre_import_enabled:
        status = "- N/A"
        details = "Import disabled in config"
    elif import_auto_disabled and not report['calibre_installed']:
        status = "✗ Missing"
        details = "Import auto-disabled"
    elif report['calibre_installed']:
        status = "✓ Found"
        details = "Required to use keys"
    else:
        status = "✗ Missing"
        details = "Import auto-disabled"
    print(f"│ Calibre                     │ {status:<10} │ {details:<38} │")

    if not calibre_import_enabled:
        status = "- N/A"
        details = "Import disabled in config"
    elif import_auto_disabled and not report.get('kfx_plugin_installed'):
        status = "✗ Missing"
        details = "Import auto-disabled"
    elif report.get('kfx_plugin_installed'):
        status = "✓ Found"
        details = "Converts KFX books"
    else:
        status = "✗ Missing"
        details = "Import auto-disabled"
    print(f"│ KFX Input Plugin            │ {status:<10} │ {details:<38} │")

    if not calibre_import_enabled:
        status = "- N/A"
        details = "Import disabled in config"
    elif report.get('dedrm_plugin_installed'):
        status = "✓ Found"
        details = "Removes DRM protection"
    elif import_auto_disabled:
        status = "✗ Missing"
        details = "Import auto-disabled"
    else:
        status = "✗ Missing"
        details = "Import auto-disabled"
    print(f"│ DeDRM Plugin                │ {status:<10} │ {details:<38} │")

    print("└─────────────────────────────┴────────────┴────────────────────────────────────────┘")
    print()

    if not is_admin:
        print_colored("=" * 70, 'yellow')
        print_colored("[!] NOT RUNNING AS ADMINISTRATOR", 'yellow')
        print_colored("    This is the most common cause of permission errors, including:", 'yellow')
        print_colored("      - [Errno 13] Permission denied on dedrm.json", 'yellow')
        print_colored("      - Cannot write to Calibre plugin folders", 'yellow')
        print_colored("      - Access denied reading Kindle in Program Files", 'yellow')
        print_colored("    Re-run using Run_keyfinder_admin.vbs for full access.", 'yellow')
        print_colored("=" * 70, 'yellow')
        print()

    if report['is_fallback']:
        print_warn("IMPORTANT: Using fallback location for file operations")
        print(f"   Reason: {report['fallback_reason']}")
        print(f"   Working directory: {report['working_dir']}")
        print()

    if not calibre_import_enabled:
        print_colored("=" * 70, 'cyan')
        print_colored("[INFO] Calibre import is DISABLED in config", 'cyan')
        print_colored("    Phases 3 & 4 will be skipped.", 'cyan')
        print_colored("    Calibre, KFX Plugin, and DeDRM Plugin are not required.", 'cyan')
        print_colored("=" * 70, 'cyan')
        print()
    elif import_auto_disabled:
        # One or more Calibre/plugin components are missing - auto-disable import
        print_colored("=" * 70, 'yellow')
        print_colored("[!] IMPORT & CONVERT AUTO-DISABLED", 'yellow')
        print_colored("    The following required components are missing:", 'yellow')
        for reason in auto_disabled_reasons:
            print_colored(f"      - {reason}", 'yellow')
        print_colored("    Phases 3 & 4 will be skipped automatically.", 'yellow')
        print_colored("    Key extraction (Phases 1 & 2) will still run normally.", 'yellow')
        print_colored("    Install the missing components and re-run to enable import.", 'yellow')
        print_colored("=" * 70, 'yellow')
        print()

    if use_alt_method:
        print_colored("=" * 70, 'yellow')
        if report.get('kindle_version_auto_alt', False):
            kindle_ver_display = report.get('kindle_version', 'Unknown')
            print_colored("[!] ALT METHOD (KFXArchiver) AUTO-ACTIVATED", 'yellow')
            print_colored(
                f"    Kindle {kindle_ver_display} is not supported by KFXKeyExtractor "
                f"(requires 2.8.0-2.8.2).",
                'yellow'
            )
            print_colored(
                f"    KFXArchiver supports this version - switched automatically.",
                'yellow'
            )
        else:
            print_colored("[!] ALT METHOD (KFXArchiver) ACTIVE (debug_force_alt_method=true)", 'yellow')
        print_colored(f"    {alt_extractor_name} will decrypt KFX books directly to kfx-zip.", 'yellow')
        print_colored("    Phase 2 will configure DeDRM plugin for KF8/MOBI book fallback.", 'yellow')
        print_colored("    KF8/MOBI books not decryptable by KFXArchiver will be imported", 'yellow')
        print_colored("    via Calibre's DeDRM plugin with the keys from the .k4i file.", 'yellow')
        print_colored("=" * 70, 'yellow')
        print()

    if report['warnings']:
        # Only show CRITICAL warnings (for the 3 truly critical components)
        # WARNING-level entries (Calibre/plugins) are already explained in the
        # auto-disable info box above, so we skip them here to avoid duplication.
        display_warnings = [w for w in report['warnings'] if w['severity'] == 'CRITICAL']

        if display_warnings:
            print("=" * 70)
            print_warn(f"VALIDATION WARNINGS ({len(display_warnings)})")
            print("=" * 70)
            print()

            for warning in display_warnings:
                print_colored(f"[{warning['severity']}] {warning['component']}", 'red')
                print(f"   Impact: {warning['impact']}")
                print(f"   Install from: {warning['install_url']}")
                print()

    if not report['all_critical_met']:
        print_error("CRITICAL COMPONENTS MISSING!")

        missing_critical = []
        # Only flag KFXKeyExtractor as missing when NOT in alt-only mode
        if not report['extractor_installed'] and not use_alt_method:
            missing_critical.append(extractor_name)
        if not report.get('alt_extractor_installed'):
            missing_critical.append(alt_extractor_name)
        if not report['kindle_installed']:
            missing_critical.append("Kindle for PC")
        # Kindle version not supported by either tool (2.8.4+)
        if (not report.get('kindle_version_alt_ok', True)
                and report.get('kindle_version')):
            missing_critical.append(
                f"Supported Kindle version "
                f"(detected: {report['kindle_version']}, "
                f"supported: 2.8.0-2.8.2 / 2.8.3 (KFXArchiver283) / 2.9.1 (KFXArchiver291))"
            )

        if len(missing_critical) == 1:
            print(f"The script cannot proceed without: {missing_critical[0]}.")
        elif len(missing_critical) > 1:
            print("The script cannot proceed. Issues found:")
            for item in missing_critical:
                print(f"  - {item}")
        print()
        return False

    print_ok("All critical components validated successfully!")
    print()
    return True


def check_paths_configured(config):
    """Check if required paths are configured"""
    from modules.utils import check_calibre_installed, check_plugin_installed, test_kindle_folder_accessibility # type: ignore
    
    if not config:
        return False, "No configuration found - please run configuration wizard"
    
    kindle_path = config.get('kindle_content_path')
    if not kindle_path:
        return False, "Kindle content path not configured - please run configuration wizard"
    
    if not os.path.exists(kindle_path):
        return False, f"Kindle path does not exist: {kindle_path}"
    
    has_books = False
    has_db = False
    try:
        for root, dirs, files in os.walk(kindle_path):
            for f in files:
                if f.lower().endswith('.azw'):
                    has_books = True
                if f.lower() == 'book_asset.db':
                    has_db = True
            if has_books and has_db:
                break
    except Exception:
        pass
    
    if not has_books and not has_db:
        return False, f"Kindle path exists but contains no books or database: {kindle_path}"
    
    # Test Kindle folder accessibility (copy/paste/delete test)
    # This helps detect access violations (0xc0000005) before attempting extraction.
    # Only runs when books are actually present - without a book file to copy the
    # test cannot do anything meaningful, and Phase 1 will surface the empty-folder
    # situation itself with a clear error message.
    script_dir = os.path.dirname(os.path.abspath(__file__))
    user_home = os.path.expanduser("~")
    can_write, _ = check_write_permissions(script_dir)
    working_dir = script_dir if can_write else os.path.join(user_home, "AppData", "Local", "Kindle_Key_Finder")
    
    # Only test accessibility if NOT running from AppData (as per requirements)
    # AND there are actually book files present to test with.
    if has_books and not working_dir.lower().startswith(os.path.join(user_home, "AppData").lower()):
        print_step("Testing Kindle folder accessibility...")
        success, error_msg, test_file = test_kindle_folder_accessibility(kindle_path, working_dir)
        
        if not success:
            return False, f"Kindle folder accessibility test failed: {error_msg}"
        
        if test_file:
            print_ok(f"Accessibility test passed using: {os.path.basename(test_file)}")
        print()
    
    # Check calibre_import setting
    calibre_import = config.get('calibre_import', {})
    if not isinstance(calibre_import, dict):
        calibre_import = {}
    calibre_import_enabled = calibre_import.get('enabled', False)

    # If Calibre import is disabled, skip all Calibre-related checks
    if not calibre_import_enabled:
        return True, "Configuration valid (Calibre import disabled - skipping Calibre checks)"

    # --- Calibre import is enabled: validate Calibre requirements ---
    # NOTE: Missing Calibre or plugins are handled gracefully by validate_all_requirements()
    # which auto-disables import with a user notification. We only fail here for things
    # the user CAN fix through the configuration wizard (paths, etc.), not for software
    # that isn't installed (Calibre, DeDRM plugin) - those are surfaced at validation time.

    if not check_calibre_installed():
        # Calibre not installed - validate_all_requirements() will auto-disable import.
        # Don't block here; let the script proceed to give the user that notification.
        return True, "Configuration valid (Calibre missing - import will be auto-disabled at runtime)"

    calibre_path = calibre_import.get('library_path')
    if not calibre_path:
        return False, "Calibre library path not configured - please run configuration wizard"

    if not os.path.exists(calibre_path):
        return False, f"Calibre library path does not exist: {calibre_path}"

    metadata_db = os.path.join(calibre_path, "metadata.db")
    if not os.path.exists(metadata_db):
        return False, f"Calibre library path is not valid (metadata.db not found): {calibre_path}"

    # DeDRM plugin check - also auto-disabled gracefully; don't block wizard launch for it
    alt_ok, _ = check_alt_extractor_exists(script_dir)
    if not alt_ok and not check_plugin_installed('DeDRM'):
        # DeDRM missing - validate_all_requirements() will auto-disable import.
        # Don't block here; let the script proceed to give the user that notification.
        return True, "Configuration valid (DeDRM missing - import will be auto-disabled at runtime)"

    # If alternate extractor is present, ensure extracted_books_path is configured in the JSON
    if alt_ok:
        extracted_books_path = config.get('extracted_books_path')
        if not extracted_books_path:
            return False, "Extracted Books output path not configured - please run configuration wizard to set it"

    return True, "Configuration valid"


def launch_configuration_wizard(user_home, existing_config=None):
    """Launch the GUI configuration wizard
    
    Args:
        user_home: Path to user's home directory
        existing_config: Optional dict of existing configuration to pre-populate wizard
    """
    print_step("Launching configuration wizard...")
    print()
    
    try:
        config = run_gui_wizard(existing_config=existing_config)
        if config:
            save_config(config)
            print_ok("Configuration saved")
            return config
        return None
    except Exception as e:
        print_warn(f"GUI wizard not available: {e}")
        print("Falling back to console wizard...")
        TOTAL_STEPS = 8
        return configure_pre_flight_wizard(user_home, TOTAL_STEPS)


# ============================================================================
# MAIN ENTRY POINT
# ============================================================================

def main():
    """Main entry point"""
    
    # Check Windows
    if not check_windows():
        return 1
    
    # Clear screen
    os.system('cls')

    # =========================================================================
    # TIMING: Record script start and initialise user-wait accumulator.
    # script_start_time - wall-clock when main() began (before any output)
    # user_wait_seconds - total seconds the script was idle waiting for user
    #                     input. Subtracted from total to get processing time.
    # =========================================================================
    script_start_time = time.time()
    user_wait_seconds = 0.0

    # Show banner
    print_banner_and_version()
    print()
    print("=" * 70)
    print("Phase 1: Key Extraction")
    print("Phase 2: DeDRM Plugin Configuration")
    print("Phase 3: Calibre Auto-Import")
    print("Phase 4: KFX to EPUB Conversion")
    print("=" * 70)
    print()
    
    # =========================================================================
    # MICROSOFT STORE PYTHON CHECK
    # Detect sandboxed Python early - before any filesystem operations.
    # The MS Store sandbox silently redirects AppData writes and prevents
    # native Win32 subprocesses (KFXKeyExtractor) from accessing Kindle.exe
    # memory, causing 100% extraction failure with STATUS_ACCESS_VIOLATION.
    # =========================================================================
    _py_is_store, _py_exe, _py_redirect = check_python_installation()
    if _py_is_store:
        from modules.utils import print_colored  # type: ignore
        print()
        print_colored("=" * 70, 'red')
        print_colored("[!] MICROSOFT STORE PYTHON DETECTED - SCRIPT WILL LIKELY FAIL", 'red')
        print_colored("=" * 70, 'red')
        print()
        print(f"  Python executable: {_py_exe}")
        if _py_redirect:
            print(f"  Files redirected to: {_py_redirect}")
        print()
        print_colored("  WHY THIS IS A PROBLEM:", 'yellow')
        print("  The Microsoft Store version of Python runs inside a sandboxed")
        print("  environment that silently redirects file system writes and")
        print("  restricts cross-process memory access.")
        print()
        print("  This causes two specific failures:")
        print("    1. The KFXKeyExtractor cannot read Kindle.exe memory")
        print("       (crashes with STATUS_ACCESS_VIOLATION - all books fail)")
        print("    2. Temp folder cleanup fails because the extractor leaves")
        print("       locked file handles behind after crashing")
        print()
        print_colored("  HOW TO FIX IT (Recommended):", 'yellow')
        print("    1. Open Settings -> Apps and uninstall Python 3.x (Microsoft Store)")
        print("    2. Download the official installer from https://www.python.org/downloads/")
        print("    3. Run installer - check 'Add Python to PATH' and 'Install for all users'")
        print("    4. Re-run this script using Run_keyfinder_admin.vbs")
        print()
        print_colored("=" * 70, 'red')
        print()
        print("Options:")
        print("  [Q] Quit   - Exit now and reinstall Python (STRONGLY RECOMMENDED)")
        print("  [C] Continue - Proceed knowing the script will almost certainly fail")
        print()

        while True:
            _store_choice, _w = timed_input("Your choice (Q/C) [Q]: ")
            _store_choice = _store_choice.strip().upper()
            user_wait_seconds += _w
            if _store_choice == '' or _store_choice == 'Q':
                print()
                print_warn("Exiting - please reinstall Python from https://www.python.org/downloads/")
                print()
                return 0
            elif _store_choice == 'C':
                print()
                print_warn("Continuing with Microsoft Store Python - extraction will very likely fail.")
                print_warn("All errors encountered are expected consequences of the sandbox.")
                print()
                break
            else:
                print_error("Invalid choice. Please enter Q or C.")

    # Discover paths
    script_dir, user_home, working_dir = discover_paths()
    
    # Cleanup any leftover temp folders
    print_step("Cleaning up any leftover temporary files...")
    cleanup_temp_kindle()  # Only cleans up if TEMP.txt marker exists
    cleanup_temp_extraction()
    cleanup_temp_staging()
    
    # Validate all requirements
    print_step("Validating system requirements...")
    print()
    
    validation_report = validate_all_requirements(script_dir, user_home)
    
    if not display_validation_results(validation_report):
        print_warn("Script cancelled - missing critical components")
        return 1
    
    # Use the validated working directory
    working_dir = validation_report['working_dir']
    extractor_path = validation_report['extractor_path']
    use_alt_method = validation_report.get('use_alt_method', False)
    alt_extractor_path = validation_report.get('alt_extractor_path')
    # Ensure directories exist
    os.makedirs(working_dir, exist_ok=True)
    os.makedirs(os.path.join(working_dir, "Keys"), exist_ok=True)
    os.makedirs(os.path.join(working_dir, "Logs"), exist_ok=True)
    os.makedirs(os.path.join(working_dir, "backups"), exist_ok=True)
    
    # Debug: Show where working directory and history file are located
    print_step(f"Working directory: {working_dir}")
    history_file = os.path.join(working_dir, "history.txt")
    if os.path.exists(history_file):
        print_ok(f"History file found: {history_file}")
    else:
        print(f"History file (will be created): {history_file}")
    
    # Check for existing config
    config = load_config()
    
    # Show which config file is actually being used (helps diagnose path confusion)
    from modules.config import _last_loaded_path as _config_loaded_from  # type: ignore
    if _config_loaded_from:
        print_step(f"Config loaded from: {_config_loaded_from}")
    else:
        print_warn("No config file found - will prompt for configuration")

    # Write startup log - captures every pre-flight check result for this run.
    # Written here so the config path and working_dir are both known.
    # The log is always overwritten (not appended) so Logs/startup.log always
    # reflects the most recent execution.
    write_startup_log(
        working_dir,
        validation_report,
        config=config,
        config_path=_config_loaded_from
    )
    print_ok(f"Startup log: {os.path.join(working_dir, 'Logs', 'startup.log')}")
    print()
    
    # Check if configuration version matches current script version
    if config:
        from modules.config import check_config_version # type: ignore
        is_valid, config_version, current_version = check_config_version(config)
        
        if not is_valid:
            # Version mismatch detected - force reconfiguration
            print_error("=" * 70)
            print_error("CONFIGURATION VERSION MISMATCH DETECTED!")
            print_error("=" * 70)
            print()
            print_warn(f"Saved Configuration Version: {config_version}")
            print_warn(f"Current Script Version:      {current_version}")
            print()
            print("The configuration format has changed and requires reconfiguration.")
            print("This ensures compatibility with new features and settings.")
            print()
            _, _w = timed_input("Press Enter to start the configuration wizard...")
            user_wait_seconds += _w
            print()
            
            user_home = os.path.expanduser("~")
            config = launch_configuration_wizard(user_home)
            if not config:
                print("Exiting...")
                return 0
    
    # Check if configuration is valid
    is_configured, config_message = check_paths_configured(config)
    
    if not is_configured:
        print_warn(f"Configuration issue: {config_message}")
        print()
        print_step("Launching configuration wizard to set up paths...")
        print()
        
        config = launch_configuration_wizard(user_home)
        if not config:
            print("Exiting...")
            return 0
        
        # After wizard, verify the new config is valid
        is_configured, config_message = check_paths_configured(config)
        if not is_configured:
            print_error(f"Configuration still invalid: {config_message}")
            print("Exiting...")
            return 0
    
    # Config is valid - show config with countdown timer to allow reconfiguration
    # This gives users the option to reconfigure without deleting the config file
    print()
    _t_cfg = time.time()
    action = prompt_config_action_with_timer(config)
    user_wait_seconds += time.time() - _t_cfg
    
    if action == 'quit':
        print_warn("Script cancelled by user")
        return 0
    elif action == 'reconfigure':
        print_step("Starting reconfiguration...")
        # Pass existing config to wizard so it pre-populates with current values
        config = launch_configuration_wizard(user_home, existing_config=config)
        if not config:
            print("Exiting...")
            return 0
    elif action == 'delete':
        # User chose to delete config and reconfigure
        delete_config()
        print_step("Starting reconfiguration...")
        config = launch_configuration_wizard(user_home)
        if not config:
            print("Exiting...")
            return 0
    
    # -------------------------------------------------------------------------
    # FINAL SYNC: Re-evaluate use_alt_method against the CURRENT config.
    #
    # validate_all_requirements() runs early - before any wizard can run and
    # change debug_force_alt_method.  If the wizard (initial setup or
    # reconfigure) enabled or disabled Mode B after validation completed, the
    # use_alt_method flag must be updated here before Phase 1 starts.
    # -------------------------------------------------------------------------
    _force_alt_now = bool(config.get('debug_force_alt_method', False)) if config else False
    _alt_available = validation_report.get('alt_extractor_installed', False)
    _alt_path_from_report = validation_report.get('alt_extractor_path')

    if _force_alt_now and not use_alt_method:
        # Flag was set (e.g. by wizard) AFTER validation already ran - activate now.
        if _alt_available:
            use_alt_method = True
            alt_extractor_path = _alt_path_from_report
            print_warn("[DEBUG] debug_force_alt_method=true applied post-validation -> ALT METHOD NOW ACTIVE")
            print()
        else:
            print_error("[DEBUG] debug_force_alt_method=true but KFXArchiver not found in code/tools/ -> standard method")
            print()
    elif not _force_alt_now and use_alt_method and not validation_report.get('kindle_version_auto_alt', False):
        # debug_force_alt_method was cleared in the wizard after validation already activated
        # alt method via that flag - deactivate now.
        # IMPORTANT: do NOT deactivate when use_alt_method was set by Kindle version
        # auto-detection (kindle_version_auto_alt=True). That auto-switch must persist
        # regardless of whether the debug flag is present.
        use_alt_method = False
        print_warn("[DEBUG] debug_force_alt_method cleared post-validation -> switching to standard method")
        print()

    # -------------------------------------------------------------------------
    # RUNTIME FIELDS: Write Kindle version and extraction mode to config (disk).
    #
    # These fields are prefixed with '_runtime_' to distinguish them from
    # user-configured values.  They are updated on every run so the JSON always
    # reflects the most recent execution - useful for diagnosing issues without
    # needing the user to locate startup.log.
    #
    # Fallback values are used when detection failed so log headers always
    # contain something meaningful rather than a blank line.
    #
    # Written AFTER the wizard and use_alt_method final-sync block so the
    # mode value is always the definitive one used for the actual run.
    # -------------------------------------------------------------------------
    if config:
        _runtime_ver = validation_report.get('kindle_version') or "Unknown (version check unavailable - PowerShell failed or Kindle.exe not found)"
        _runtime_mode = (
            "Mode B - KFXArchiver Only (Alternate Method)"
            if use_alt_method else
            "Mode A - KFXKeyExtractor (Standard)"
        )
        config['_runtime_kindle_version'] = _runtime_ver
        config['_runtime_extraction_mode'] = _runtime_mode
        # Generate a unique Run ID for this execution.
        # Saved to config so get_raw_log_path() can embed it in every raw log
        # filename, linking extraction / import / conversion logs together.
        _run_id = generate_run_id(config)
        print_ok(f"Runtime info saved: Kindle {_runtime_ver} | {_runtime_mode}")
        print_ok(f"Run ID: {_run_id}")
        # Append Run ID to startup.log so all 4 log files share the same identifier
        append_to_startup_log(working_dir, "RUN ID", [
            f"Run ID : {_run_id}",
            "This ID is embedded in every raw debug log filename for this run:",
            f"  raw_extraction_{_run_id}.log",
            f"  raw_import_{_run_id}.log",
            f"  raw_conversion_{_run_id}.log",
        ])
        print()

    # Get content directory from config
    content_dir = config.get('kindle_content_path') if config else None
    
    if not content_dir or not os.path.exists(content_dir):
        print_error("Invalid Kindle content path")
        return 1
    
    print_ok(f"Using Kindle content path: {content_dir}")
    
    # If validation auto-disabled import due to missing Calibre/plugins, honour that here.
    # This overrides the config value so Phases 3 & 4 are skipped naturally by existing logic.
    auto_disabled_reasons = validation_report.get('auto_disabled_import', [])
    if auto_disabled_reasons:
        # Already notified the user in display_validation_results() - just enforce it
        if config and isinstance(config.get('calibre_import'), dict):
            config['calibre_import']['enabled'] = False

    # Get Calibre config - simplified approach
    calibre_import: dict = {}
    calibre_import_raw = config.get('calibre_import') # type: ignore
    if calibre_import_raw is not None:
        if isinstance(calibre_import_raw, dict):
            calibre_import = calibre_import_raw

    # Determine if Calibre import is actually enabled
    calibre_import_enabled = calibre_import.get('enabled', False)

    calibre_path = None
    if calibre_import_enabled and calibre_import:
        lib_path = calibre_import.get('library_path')
        if lib_path:
            calibre_path = lib_path

    if calibre_path:
        # Get and display book count - skip if Calibre is running (DB locked)
        from modules.utils import is_calibre_running, get_library_book_count # type: ignore

        if is_calibre_running():
            print_ok(f"Using Calibre library path: {calibre_path} (books: N/A - Calibre is running)")
        else:
            book_count = get_library_book_count(calibre_path)
            if book_count is not None:
                print_ok(f"Using Calibre library path: {calibre_path} ({book_count} books)")
            else:
                print_ok(f"Using Calibre library path: {calibre_path}")
    elif not calibre_import_enabled:
        print_ok("Calibre import is DISABLED - Phases 3 & 4 will be skipped")
    print()
    
    # Define paths early for use in pre-extraction check
    keys_dir = os.path.join(working_dir, "Keys")
    output_key = os.path.join(keys_dir, "kindlekey.txt")
    output_k4i = os.path.join(keys_dir, "kindlekey.k4i")

    # Determine Extracted_Books output folder for alternate method
    # Use config value if set, otherwise default to working_dir\Extracted_Books
    extracted_books_path = config.get('extracted_books_path') if config else None
    if not extracted_books_path:
        extracted_books_path = os.path.join(working_dir, "Extracted_Books")
    if use_alt_method:
        os.makedirs(extracted_books_path, exist_ok=True)
        print_ok(f"Extracted Books output folder ready: {extracted_books_path}")
        print()

    # Create backups directory with timestamp
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    backups_dir = os.path.join(working_dir, "backups")
    
    # =========================================================================
    # EARLY CALIBRE CHECK - Before any phases (only when import is enabled)
    # =========================================================================
    from modules.utils import wait_for_calibre_to_close, remove_file_with_retry # type: ignore

    # calibre_skip_phases_34: set to True when user chooses to continue with
    # Calibre still running (avoids DB corruption by skipping import phases).
    calibre_skip_phases_34 = False

    # Check if Calibre is running before any phases that need it closed.
    # Standard method: needs Calibre closed for Phase 2 (DeDRM config file writes).
    # ALT method:      needs Calibre closed for Phase 3 (database import).
    # In both cases, if user skips the warning, calibre_skip_phases_34 is set True
    # and Phase 3 & 4 are skipped to prevent Calibre database corruption.
    if calibre_import_enabled:
        print()
        print_step("Checking if Calibre is running...")
        _startup_calibre_result = wait_for_calibre_to_close(
            "Calibre must be closed before import (Phase 3) to prevent database corruption.",
            calibre_import_enabled=True
        )
        # wait_for_calibre_to_close() returns a dict - use 'proceed' key for skip logic
        if not _startup_calibre_result.get('proceed', True):
            calibre_skip_phases_34 = True

        # Unpack status fields for logging
        _calibre_was_running = _startup_calibre_result.get('calibre_was_running', False)
        _calibre_action      = _startup_calibre_result.get('action', 'not_running')
        _calibre_wait_secs   = _startup_calibre_result.get('wait_seconds', 0)
        _calibre_procs       = ', '.join(_startup_calibre_result.get('processes_found', [])) or 'none'
        _cw_hr, _cw_rem = divmod(_calibre_wait_secs, 3600)
        _cw_min, _cw_sec = divmod(_cw_rem, 60)

        # Log Calibre running state to startup.log so it appears in the
        # diagnostic record even when the user didn't enable raw logs.
        _calibre_log_lines = [
            f"Calibre was running at startup  : {'YES' if _calibre_was_running else 'NO'}",
            f"Detected process(es)            : {_calibre_procs}",
        ]
        if _calibre_was_running:
            if _calibre_action == 'waited':
                _calibre_log_lines.append(
                    f"Action taken                    : [W] Waited - Calibre closed before phases began"
                )
                _calibre_log_lines.append(
                    f"Time waited                     : {_cw_hr:02d}:{_cw_min:02d}:{_cw_sec:02d}  ({_calibre_wait_secs}s)"
                )
            elif _calibre_action == 'killed':
                _calibre_log_lines.append(
                    f"Action taken                    : [K] Force-terminated Calibre process(es)"
                )
            elif _calibre_action == 'continued':
                _calibre_log_lines.append(
                    "Action taken                    : [C] User continued with Calibre still running"
                )
                _calibre_log_lines.append(
                    "Result                          : Phases 3 & 4 will be SKIPPED to prevent database corruption"
                )
        else:
            _calibre_log_lines.append("Calibre was not running - no action required.")
        append_to_startup_log(working_dir, "CALIBRE RUNNING CHECK", _calibre_log_lines)
        # Accumulate any time the script spent waiting for Calibre to close
        user_wait_seconds += _startup_calibre_result.get('wait_seconds', 0)
    
    # =========================================================================
    # PRE-EXTRACTION: REMOVE LOCKED DEDRM.JSON (Standard method only)
    # =========================================================================
    if not use_alt_method:
        # Phase 2 requires read/write access to dedrm.json.
        # Test that access here (before any extraction work) so we can give
        # the user a clear choice rather than a cryptic failure deep in Phase 2.
        # In Standard mode the extractor also accesses dedrm.json directly,
        # so we remove it preemptively to prevent lock conflicts.
        print()
        print_step("Pre-extraction check for locked configuration files...")
        dedrm_json = os.path.join(user_home, "AppData", "Roaming", "calibre", "plugins", "dedrm.json")
        _dedrm_log_lines = [f"dedrm.json path: {dedrm_json}"]
        _dedrm_access_ok = True  # Will be set False on any permission failure

        if os.path.exists(dedrm_json):
            print_step("Found existing dedrm.json - removing to prevent extractor lock issues...")
            _dedrm_log_lines.append("dedrm.json existed: YES")

            backup_path = os.path.join(backups_dir, f"pre_extraction_dedrm_backup_{timestamp}.json")

            try:
                shutil.copy2(dedrm_json, backup_path)
                print_ok(f"Created backup: {os.path.basename(backup_path)}")
                _dedrm_log_lines.append(f"Backup created: {os.path.basename(backup_path)}")

                success, error_msg = remove_file_with_retry(dedrm_json, max_retries=3)

                if success:
                    print_ok("Configuration file removed successfully")
                    print("   (This prevents the extractor from encountering file locks)")
                    _dedrm_log_lines.append("File removed: YES - extractor will create fresh config")
                    _dedrm_log_lines.append("ACCESS STATUS: OK - file removed, Phase 2 will write fresh config")
                else:
                    print_warn(f"Could not remove configuration file: {error_msg}")
                    _dedrm_log_lines.append(f"File removal FAILED: {error_msg}")
                    _dedrm_log_lines.append(f"ACCESS STATUS: *** FAILED *** - cannot remove file: {error_msg}")
                    _dedrm_access_ok = False
            except Exception as e:
                print_warn(f"Could not backup/remove dedrm.json: {e}")
                _dedrm_log_lines.append(f"Backup/remove FAILED with exception: {e}")
                _dedrm_log_lines.append(f"ACCESS STATUS: *** FAILED *** - exception: {e}")
                _dedrm_access_ok = False

            if not _dedrm_access_ok:
                admin_note = (
                    "Admin rights: NO - this is the most likely cause. "
                    "Re-run using Run_keyfinder_admin.vbs."
                ) if not is_running_as_admin() else (
                    "Admin rights: YES (elevated) - cause is likely a Calibre process lock."
                )
                _dedrm_log_lines.append(admin_note)
                _dedrm_log_lines.append("Action taken: User shown [Q]uit / [C]ontinue prompt.")
        else:
            print_ok("No existing dedrm.json found - extractor will create fresh configuration")
            _dedrm_log_lines.append("dedrm.json existed: NO")
            _dedrm_log_lines.append("ACCESS STATUS: OK - file absent, extractor will create fresh config")

        append_to_startup_log(working_dir, "DEDRM.JSON PRE-EXTRACTION CHECK", _dedrm_log_lines)

        # ----------------------------------------------------------------
        # GATE: if dedrm.json is not accessible, Phase 2 will fail.
        # Give the user a chance to quit and re-launch with admin rights
        # before any Phase 1 work has been done.
        # ----------------------------------------------------------------
        if not _dedrm_access_ok:
            from modules.utils import print_colored  # type: ignore
            print()
            print_colored("=" * 70, 'yellow')
            print_colored("[!] DEDRM.JSON IS NOT ACCESSIBLE - PHASE 2 WILL FAIL", 'yellow')
            print_colored("=" * 70, 'yellow')
            print()
            print("The script was unable to read or modify:")
            print(f"  {dedrm_json}")
            print()
            if not is_running_as_admin():
                print_colored("Most likely cause: Script is NOT running as Administrator.", 'yellow')
                print_colored("Windows is blocking access to the Calibre plugin folder.", 'yellow')
                print()
            print_step("Your options:")
            print("  [Q] Quit      - Close and re-launch using Run_keyfinder_admin.vbs")
            print("                  (RECOMMENDED - fixes the permission issue)")
            print()
            print("  [C] Continue  - Phase 1 will still run and generate:")
            print("                    kindlekey.txt  (voucher keys)")
            print("                    kindlekey.k4i  (account data)")
            print("                  Phase 2 will likely fail, but you can manually")
            print("                  pass those files to the DeDRM plugin in Calibre.")
            print()

            while True:
                _choice, _w = timed_input("Your choice (Q/C) [Q]: ")
                _choice = _choice.strip().upper()
                user_wait_seconds += _w
                if _choice == '' or _choice == 'Q':
                    print()
                    print_warn("Exiting - please re-launch using Run_keyfinder_admin.vbs")
                    if not is_running_as_admin():
                        print("  Option 1: Double-click  Run_keyfinder_admin.vbs")
                        print("            (requests UAC elevation automatically)")
                        print()
                        print("  Option 2: Right-click   Run_keyfinder.bat")
                        print("            Choose         'Run as administrator'")
                    print()
                    return 0
                elif _choice == 'C':
                    print()
                    print_warn("Continuing at user's own risk - Phase 2 will likely fail.")
                    print_warn("Phase 1 will generate kindlekey.txt and kindlekey.k4i.")
                    print_warn("Use those files manually with the DeDRM plugin if Phase 2 fails.")
                    print()
                    break
                else:
                    print_error("Invalid choice. Please enter Q or C.")

        print()

    # =========================================================================
    # AUTO-LAUNCH KINDLE (if enabled in config)
    # =========================================================================
    auto_launch_kindle(config)

    # Clear screen before Phase 1 if configured
    clear_screen_between_phases(config)

    # =========================================================================
    # PHASE 1: KEY EXTRACTION  /  PHASE 1 ALT: DECRYPTION & EXTRACTION
    # =========================================================================
    print()
    if use_alt_method:
        print_phase_banner("1", "DECRYPTION & EXTRACTION (ALTERNATE METHOD - KFXArchive)")
    else:
        print_phase_banner("1", "KEY EXTRACTION")
    print()

    if use_alt_method:
        # ----------------------------------------------------------------
        # ALT PATH: Decrypt books directly to kfx-zip via KFXArchiver282
        # ----------------------------------------------------------------
        from phases import run_phase_1_alt  # type: ignore

        print_step("Decrypting books using Alternate Method - KFXArchive...")
        print("--------------------------------------------------")
        print()

        phase1_result = run_phase_1_alt(
            alt_extractor_path,
            content_dir,
            extracted_books_path,
            output_k4i,
            working_dir,
            config
        )

        print("--------------------------------------------------")
        print()

        success = phase1_result['success']
        extraction_stats = phase1_result.get('stats', {})

        if not success:
            if extraction_stats.get('total', 0) > 0 and extraction_stats.get('success', 0) == 0 and extraction_stats.get('failed', 0) == 0:
                print("Decryption cancelled by user.")
            elif extraction_stats.get('total', 0) == 0:
                print_error("No books found in content directory!")
                return 1
            elif extraction_stats.get('success', 0) == 0:
                print_error("Decryption failed! No kfx-zip files were generated.")
                print_error(f"Failed: {extraction_stats.get('failed', 0)} book(s)")
                return 1

        if success and extraction_stats.get('success', 0) == 0 and extraction_stats.get('skipped', 0) > 0:
            print_ok("All books have been previously processed - script completed successfully")
            print()
            import msvcrt
            print("Press any key to continue...")
            msvcrt.getch()
            os.system('cls')
            from modules.utils import display_credits_and_links  # type: ignore
            display_credits_and_links()
            return 0

        # Display decryption results
        kfx_zip_files = phase1_result.get('kfx_zip_files', [])
        print_ok(f"Decrypted {len(kfx_zip_files)} book(s) to kfx-zip format")
        print(f"   Output folder: {extracted_books_path}")
        print()

        if extraction_stats.get('total', 0) > 0:
            print_step("Decryption Statistics:")
            print(f"   Total books found:       {extraction_stats.get('total', 0)}")
            print(f"   Successfully decrypted:  {extraction_stats.get('success', 0)}")
            if extraction_stats.get('failed', 0) > 0:
                print_warn(f"   Failed:                  {extraction_stats.get('failed', 0)}")
            if extraction_stats.get('skipped', 0) > 0:
                print_ok(f"   Skipped:                 {extraction_stats.get('skipped', 0)} (previously processed)")
        print()

        prevent_kindle_auto_update()

        phase1_summary = [
            f"Processed {extraction_stats.get('total', 0)} book(s) for decryption",
            f"Decrypted {extraction_stats.get('success', 0)} book(s) to kfx-zip format",
            f"Output folder: {extracted_books_path}",
            "Kindle auto-update prevention configured"
        ]
        display_phase_summary(1, "Decryption & Extraction (KFXArchive)", phase1_summary, config)

    else:
        # ----------------------------------------------------------------
        # STANDARD PATH: Extract keys via KFXKeyExtractor28
        # ----------------------------------------------------------------
        # Backup existing key files before extraction
        print_step("Checking for existing key files to preserve...")

        key_files_to_backup = [
            (output_key, os.path.join(backups_dir, f"kindlekey_backup_{timestamp}.txt")),
            (output_k4i, os.path.join(backups_dir, f"kindlekey_backup_{timestamp}.k4i"))
        ]

        for source_file, backup_file in key_files_to_backup:
            if os.path.exists(source_file):
                shutil.copy2(source_file, backup_file)
                print_ok(f"Backed up {os.path.basename(source_file)} to backups folder")

        print()

        from phases import run_phase_1  # type: ignore

        print_step("Extracting Kindle keys...")
        print("--------------------------------------------------")
        print()

        phase1_result = run_phase_1(
            extractor_path,
            content_dir,
            output_key,
            output_k4i,
            working_dir,
            config
        )

        print("--------------------------------------------------")
        print()

        success = phase1_result['success']
        extraction_stats = phase1_result.get('stats', {})

        if not success:
            if extraction_stats.get('total', 0) > 0 and extraction_stats.get('success', 0) == 0 and extraction_stats.get('failed', 0) == 0:
                print("Key extraction cancelled by user.")
                print()
                import msvcrt
                print("Press any key to continue...")
                msvcrt.getch()
                os.system('cls')
                from modules.utils import display_credits_and_links  # type: ignore
                display_credits_and_links()
                return 1
            if extraction_stats.get('total', 0) == 0:
                print_error("No books found in content directory!")
                return 1
            elif extraction_stats.get('success', 0) == 0:
                print_error("Key extraction failed! No keys were extracted from any books.")
                print_error(f"Failed: {extraction_stats.get('failed', 0)} book(s)")
                return 1

        if success and extraction_stats.get('success', 0) == 0 and extraction_stats.get('skipped', 0) > 0:
            print_ok("All books have been previously processed - script completed successfully")
            print()
            import msvcrt
            print("Press any key to continue...")
            msvcrt.getch()
            os.system('cls')
            from modules.utils import display_credits_and_links  # type: ignore
            display_credits_and_links()
            return 0

        print_ok("Keys successfully extracted:")
        print(f"   - {output_key}")
        print(f"   - {output_k4i}")
        print()

        # ----------------------------------------------------------------
        # DEBUG/TEST: Force alt retry path without needing a real failure.
        # Set "debug_force_alt_retry": true in key_finder_config.json to
        # inject the first successfully extracted book as a fake Phase 1
        # failure, triggering Phase 1b/3b retry for testing purposes.
        # Remove or set to false when done testing.
        # ----------------------------------------------------------------
        if config and config.get('debug_force_alt_retry', False):
            _first_success_asin = None
            # Find the first ASIN that succeeded (not already in failed_books)
            from phases.phase_01_key_extraction import scan_kindle_content_directory  # type: ignore
            _all_folders = scan_kindle_content_directory(content_dir)
            _failed_set = {a for a, _, _ in extraction_stats.get('failed_books', [])}
            for _asin, _path, _title in _all_folders:
                if _asin not in _failed_set:
                    _first_success_asin = _asin
                    break
            if _first_success_asin:
                extraction_stats.setdefault('failed_books', []).append(
                    (_first_success_asin, _first_success_asin, 'DEBUG: Forced failure for alt retry test')
                )
                extraction_stats['failed'] = extraction_stats.get('failed', 0) + 1
                print_warn(f"[DEBUG] Injected forced Phase 1 failure for ASIN: {_first_success_asin}")
                print_warn("[DEBUG] debug_force_alt_retry is enabled - alt retry path will run")
                print()
            else:
                print_warn("[DEBUG] debug_force_alt_retry enabled but no successful book found to inject")
                print()

        if extraction_stats.get('total', 0) > 0:
            print_step("Extraction Statistics:")
            print(f"   Total books found: {extraction_stats.get('total', 0)}")
            print(f"   Successfully extracted: {extraction_stats.get('success', 0)}")
            if extraction_stats.get('failed', 0) > 0:
                print_warn(f"   Failed: {extraction_stats.get('failed', 0)}")
            if extraction_stats.get('skipped', 0) > 0:
                print_ok(f"   Skipped: {extraction_stats.get('skipped', 0)} (previously processed)")

        print()

        prevent_kindle_auto_update()

        phase1_summary = [
            f"Processed {extraction_stats.get('total', 0)} book(s) for key extraction",
            f"Successfully extracted keys from {extraction_stats.get('success', 0)} book(s)",
            "Generated kindlekey.txt",
            "Generated kindlekey.k4i",
            "Kindle auto-update prevention configured"
        ]
        display_phase_summary(1, "Key Extraction", phase1_summary, config)

    # Clear screen before Phase 2 if configured
    clear_screen_between_phases(config)

    # =========================================================================
    # PHASE 2: DEDRM PLUGIN CONFIGURATION
    # =========================================================================
    # Phase 2 runs in BOTH modes:
    #
    # Standard Mode: KFXKeyExtractor generates kindlekey.k4i AND kindlekey.txt.
    #                Phase 2 reads the .k4i and writes DSN/tokens to dedrm.json.
    #
    # ALT Mode:      KFXArchiver writes DSN/tokens to kindlekey.k4i from Kindle.exe
    #                memory.  Phase 2 reads that .k4i and writes the data to
    #                dedrm.json so the DeDRM plugin can decrypt KF8/MOBI books that
    #                KFXArchiver cannot process (format-mismatch books).
    #
    # When ALT mode is active and no format_failed_books exist, Phase 2 is not
    # needed - but it still runs harmlessly to keep the dedrm.json in sync.
    # =========================================================================
    if use_alt_method:
        alt_name = os.path.basename(alt_extractor_path) if alt_extractor_path else 'KFXArchiver'
        print()
        print_phase_banner("2", "DEDRM PLUGIN CONFIGURATION (for KF8/MOBI compatibility)")
        print()
        print(f"   {alt_name} already decrypted KFX books directly.")
        print("   Phase 2 will configure the DeDRM plugin with your Kindle keys")
        print("   so DeDRM can handle KF8/MOBI books that KFXArchiver cannot process.")
        print()

        from phases import run_phase_2  # type: ignore

        phase2_result = run_phase_2(output_k4i, output_key, working_dir, script_dir, config)

        if not phase2_result['success']:
            print_error("Failed to update DeDRM configuration for KF8/MOBI fallback!")
            print_error("KF8/MOBI books will need manual DeDRM configuration.")
            # Don't return 1 - KFX books were already successfully decrypted
        else:
            print_ok("DeDRM plugin configured for KF8/MOBI book decryption.")
        print()
    else:
        print()
        print_phase_banner("2", "DEDLM PLUGIN CONFIGURATION")
        print()

        from phases import run_phase_2  # type: ignore

        phase2_result = run_phase_2(output_k4i, output_key, working_dir, script_dir, config)

        # If the user chose [W]ait during Phase 2's Calibre check and Calibre is now
        # confirmed closed, reset calibre_skip_phases_34 so Phases 3 & 4 can run.
        # This handles the case where user pressed [C] at the first (pre-Phase 1) check
        # but then actually waited and closed Calibre when prompted again in Phase 2.
        if phase2_result.get('calibre_closed_in_phase2', False) and calibre_skip_phases_34:
            print_ok("Calibre was closed during Phase 2 - Phase 3 & 4 will now proceed.")
            calibre_skip_phases_34 = False

        if not phase2_result['success']:
            print_error("Failed to update DeDRM configuration!")
            return 1

        phase2_summary = [
            "Processed kindlekey.k4i and created Kindle key data",
            "Updated DeDRM plugin configuration automatically",
            "Set extra key file path in plugin",
            "Added account keys to plugin database"
        ]
        display_phase_summary(2, "DeDRM Plugin Configuration", phase2_summary, config)

    # Clear screen before Phase 3 if configured
    clear_screen_between_phases(config)

    # =========================================================================
    # PHASE 3: CALIBRE AUTO-IMPORT
    # =========================================================================
    imported_count = 0
    book_ids = []
    alt_retry_book_ids = []

    if not calibre_path:
        if not calibre_import_enabled:
            print()
            print(3, "CALIBRE AUTO-IMPORT - SKIPPED (Disabled in Config)")
            print()
            print_ok("Calibre import is set to NO in your configuration.")
            print("   Keys were extracted successfully - you can use them manually.")
            print()
            print("=" * 70)
            print("PHASE 4: KFX TO EPUB CONVERSION - SKIPPED (Import Disabled)")
            print("=" * 70)
            print()
            print_ok("Phase 4 requires Phase 3 - both skipped.")
            print()
        else:
            print_warn("Calibre import not configured - skipping Phase 3")
    elif calibre_skip_phases_34:
        # User chose to continue with Calibre still running - skip import phases
        print()
        print("=" * 70)
        print("PHASE 3: CALIBRE AUTO-IMPORT - SKIPPED (Calibre was running)")
        print("=" * 70)
        print()
        print_warn("You chose to continue while Calibre was running.")
        print_warn("Phase 3 has been skipped to prevent Calibre database corruption.")
        print("   To import your books, close Calibre and re-run this script.")
        print()
        print("=" * 70)
        print("PHASE 4: KFX TO EPUB CONVERSION - SKIPPED (Phase 3 was skipped)")
        print("=" * 70)
        print()
        print_warn("Phase 4 requires Phase 3 - both skipped.")
        print()

    else:
        print()
        if use_alt_method:
            print_phase_banner("3", "CALIBRE AUTO-IMPORT (KFXArchive)")
        else:
            print_phase_banner("3", "CALIBRE AUTO-IMPORT")
        print()

        if use_alt_method:
            from phases import run_phase_3_alt  # type: ignore

            phase3_result = run_phase_3_alt(
                extracted_books_path,
                calibre_path,
                working_dir,
                extraction_stats,
                config
            )
        else:
            from phases import run_phase_3  # type: ignore

            phase3_result = run_phase_3(content_dir, calibre_path, working_dir, extraction_stats, config)

        imported_count = phase3_result.get('imported_count', 0)
        book_ids = phase3_result.get('book_ids', [])

        if imported_count > 0 or book_ids:
            if use_alt_method:
                phase3_summary = [
                    f"Imported {imported_count} kfx-zip file(s) to Calibre",
                    f"Books added with IDs: {', '.join(book_ids) if book_ids else 'None'}",
                    "KFX books decrypted by KFXArchive (ALT method)",
                    "DeDRM plugin configured for KF8/MOBI via Phase 2 (kindlekey.k4i)",
                    "Books are now available in Calibre",
                    "Extracted_Books folder cleared after import"
                ]
                display_phase_summary(3, "Calibre Auto-Import (KFXArchive)", phase3_summary, config)
            else:
                phase3_summary = [
                    f"Imported {imported_count} ebook(s) to Calibre",
                    f"Books added with IDs: {', '.join(book_ids) if book_ids else 'None'}",
                    "DeDRM plugin automatically processed all imports",
                    "Books are now available in Calibre"
                ]
                display_phase_summary(3, "Calibre Auto-Import", phase3_summary, config)

    # =====================================================================
    # PHASE 3c: IMPORT KF8/MOBI FORMAT-FAILED BOOKS (ALT mode only)
    #
    # When running in ALT mode, KFXArchiver cannot decrypt KF8/MOBI books
    # (they fail with "Invalid book format, maybe KF8/MOBI?").  However,
    # Phase 2 just configured the DeDRM plugin with the DSN/tokens from the
    # k4i file.  These KF8/MOBI books can now be imported via standard
    # calibredb add, and the DeDRM plugin will decrypt them during import.
    # =====================================================================
    extraction_stats = phase1_result.get('stats', {})
    format_failed_books = extraction_stats.get('format_failed_books', [])
    if (use_alt_method
            and format_failed_books
            and calibre_path
            and not calibre_skip_phases_34):
        clear_screen_between_phases(config)

        print()
        print_phase_banner("3c", "CALIBRE IMPORT - KF8/MOBI BOOKS (via DeDRM Plugin)")
        print()
        print_warn(f"{len(format_failed_books)} KF8/MOBI book(s) were not decryptable by KFXArchiver.")
        print("   The DeDRM plugin (configured in Phase 2) will handle them during import.")
        print()

        from phases import run_phase_3  # type: ignore

        # Build a dummy extraction_stats that tells run_phase_3 which ASINs
        # to import (the format_failed ones) and which to exclude (already
        # handled by ALT Phase 3 = successful kfx-zip imports).
        kf8_import_stats = {
            'total': len(format_failed_books),
            'success': 0,
            'failed': 0,
            'skipped': 0,
            'failed_books': [],      # No tool failures to exclude
            'skipped_books': [],     # No skipped to exclude
        }

        phase3c_result = run_phase_3(
            content_dir,
            calibre_path,
            working_dir,
            kf8_import_stats,
            config
        )

        kf8_imported = phase3c_result.get('imported_count', 0)
        kf8_book_ids = phase3c_result.get('book_ids', [])

        if kf8_imported > 0:
            print_ok(f"Imported {kf8_imported} KF8/MOBI book(s) via DeDRM plugin")
            # Merge into main book_ids list for Phase 4
            book_ids = list(dict.fromkeys(book_ids + kf8_book_ids))
            imported_count = len(book_ids)
        else:
            print_warn("No KF8/MOBI books were imported through the DeDRM plugin.")
            print_warn("They may already be in the Calibre library.")
        print()

    # =====================================================================
    # PHASE 1b / 3b: ALT METHOD RETRY FOR FAILED BOOKS (Standard path only)
    #
    # Phase 1b (decryption via KFXArchiver) runs regardless of whether
    # Calibre is configured - it only writes kfx-zip files to Extracted_Books
    # and does NOT need Calibre at all.
    #
    # Phase 3b (Calibre import) is a separate sub-gate inside Phase 1b that
    # checks calibre_path before attempting import.  When Calibre is absent
    # the user is shown a guidance message and the kfx-zip files are left in
    # Extracted_Books for manual import.
    #
    # Phase 3b is also gated on not calibre_skip_phases_34 to protect the
    # Calibre database when the user continued with Calibre still running.
    # If skipped, files stay in Extracted_Books for the next run.
    # =====================================================================
    if (not use_alt_method
            and extraction_stats.get('failed', 0) > 0
            and alt_extractor_path
            and os.path.exists(alt_extractor_path)):

        failed_asins = [asin for asin, _, _ in extraction_stats.get('failed_books', [])]

        if failed_asins:
            clear_screen_between_phases(config)

            print()
            print_phase_banner("1b", "ALT METHOD RETRY FOR FAILED BOOKS")
            print()
            extractor_name_main = os.path.basename(extractor_path) if extractor_path else 'KFXKeyExtractor'
            alt_extractor_name_main = os.path.basename(alt_extractor_path) if alt_extractor_path else 'KFXArchiver'
            print_warn(f"{len(failed_asins)} book(s) failed Phase 1 ({extractor_name_main}) - retrying with {alt_extractor_name_main}...")
            print()

            from phases import run_phase_1_alt_retry  # type: ignore

            alt_retry_result = run_phase_1_alt_retry(
                alt_extractor_path,
                content_dir,
                failed_asins,
                extracted_books_path,
                output_k4i,
                working_dir,
                config
            )

            alt_retry_stats = alt_retry_result.get('stats', {})
            alt_kfx_zip_files = alt_retry_result.get('kfx_zip_files', [])

            print_step("Alt Retry Statistics:")
            print(f"   Attempted:    {alt_retry_stats.get('total', 0)}")
            print_ok(f"   Recovered:    {alt_retry_stats.get('success', 0)}")
            if alt_retry_stats.get('failed', 0) > 0:
                print_warn(f"   Still failed: {alt_retry_stats.get('failed', 0)}")
            print()

            if alt_kfx_zip_files:
                if calibre_path and not calibre_skip_phases_34:
                    # Phase 3b: Import the recovered kfx-zip files into Calibre
                    clear_screen_between_phases(config)

                    print()
                    print_phase_banner("3b", "CALIBRE IMPORT - ALT METHOD RECOVERED BOOKS")
                    print()

                    from phases import run_phase_3_alt  # type: ignore

                    phase3b_result = run_phase_3_alt(
                        extracted_books_path,
                        calibre_path,
                        working_dir,
                        alt_retry_stats,
                        config
                    )

                    alt_retry_book_ids = phase3b_result.get('book_ids', [])
                    alt_imported = phase3b_result.get('imported_count', 0)

                    if alt_imported > 0:
                        print_ok(f"Alt method recovered and imported {alt_imported} book(s)")
                        phase3b_summary = [
                            f"Retried {len(failed_asins)} book(s) that failed Phase 1 ({extractor_name_main})",
                            f"{alt_extractor_name_main} recovered {alt_retry_stats.get('success', 0)} book(s)",
                            f"Imported {alt_imported} recovered book(s) to Calibre",
                            f"Book IDs: {', '.join(alt_retry_book_ids) if alt_retry_book_ids else 'None'}"
                        ]
                        display_phase_summary(3, "Calibre Import - Alt Retry", phase3b_summary, config)
                    else:
                        print_warn("No recovered books were imported in alt retry")
                        print()
                elif calibre_path and calibre_skip_phases_34:
                    # Calibre was running - Phase 3b skipped, files saved for next run
                    print_warn("Phase 3b skipped - Calibre was running when Phase 3 was skipped.")
                    print(f"   Recovered kfx-zip files saved to: {extracted_books_path}")
                    print("   Close Calibre and re-run the script to import these books.")
                    print()
                else:
                    # Calibre is not configured - kfx-zip files are ready but not imported
                    from modules.utils import print_colored  # type: ignore
                    print()
                    print_colored("=" * 70, 'yellow')
                    print_colored("[!] CALIBRE NOT CONFIGURED - BOOKS DECRYPTED BUT NOT IMPORTED", 'yellow')
                    print_colored("=" * 70, 'yellow')
                    print()
                    print_ok(f"  {alt_retry_stats.get('success', 0)} book(s) successfully decrypted to kfx-zip format.")
                    print(f"   Saved to: {extracted_books_path}")
                    print()
                    print_warn("  Calibre is not configured, so books were not automatically imported.")
                    print_warn("  To get fully DeDRM'd EPUBs automatically on the next run, consider")
                    print_warn("  switching to 'Mode B: KFXArchiver Only' in the config wizard")
                    print_warn("  (Key Extraction Mode step in the wizard).")
                    print()
                    print("  Alternatively, you can import the kfx-zip files manually into Calibre")
                    print("  using the KFX Input Plugin.")
                    print()
                    print_colored("=" * 70, 'yellow')
                    print()
            else:
                print_warn("Alt retry could not recover any failed books - they remain in Failed-Books.txt")
                print()

    # Merge alt-retry IDs into the main book_ids list for Phase 4
    if alt_retry_book_ids:
        # Use dict.fromkeys to deduplicate while preserving order
        book_ids = list(dict.fromkeys(book_ids + alt_retry_book_ids))
        imported_count = len(book_ids)

    # Clear screen before Phase 4 if configured
    clear_screen_between_phases(config)

    # =====================================================================
    # PHASE 4: KFX TO EPUB CONVERSION
    # =====================================================================
    if (not calibre_skip_phases_34
            and calibre_path
            and calibre_import.get('convert_to_epub', False)
            and book_ids):
        print()
        print_phase_banner("4", "KFX TO EPUB CONVERSION")
        print()

        from phases import run_phase_4, process_phase_4b_retries  # type: ignore

        generate_pdf_for_comics = calibre_import.get('generate_pdf_for_comics', False)

        phase4_result = run_phase_4(
            calibre_path,
            book_ids,
            calibre_import,
            working_dir,
            generate_pdf_for_comics=generate_pdf_for_comics
        )

        print_ok("Conversion complete!")
        print(f"   Converted: {phase4_result.get('converted', 0)}")
        print(f"   Merged:    {phase4_result.get('merged', 0)}")
        print(f"   Failed:    {phase4_result.get('failed', 0)}")
        print()

        # Phase 4B: Retry timed-out books with alternate method
        timeout_books = phase4_result.get('stats', {}).get('timeout_books', [])
        if timeout_books:
            book_info_map = phase4_result.get('stats', {}).get('book_info_map', {})
            print()
            print_warn(f"{len(timeout_books)} book(s) timed out during conversion and will be retried.")
            print_warn("Phase 4B uses calibre-debug (KFX Input plugin) with a 5-minute timeout.")
            print()
            print("  [Y] Retry now with alternate method (Recommended)")
            print("  [N] Skip retry - leave these books unconverted")
            print()
            retry_choice, _w = timed_input("Retry timed-out books? (Y/N) [Y]: ")
            retry_choice = retry_choice.strip().upper()
            user_wait_seconds += _w
            if retry_choice in ('', 'Y'):
                print()
                print_phase_banner("4B", "KFX TO EPUB CONVERSION - RETRY (ALTERNATE METHOD)")
                print()
                phase4b_result = process_phase_4b_retries(
                    calibre_path,
                    timeout_books,
                    book_info_map,
                    working_dir=working_dir
                )
                print_ok("Phase 4B complete!")
                print(f"   Retried:   {phase4b_result.get('total', 0)}")
                print(f"   Converted: {phase4b_result.get('converted', 0)}")
                print(f"   Merged:    {phase4b_result.get('merged', 0)}")
                print(f"   Failed:    {phase4b_result.get('failed', 0)}")
                print()
            else:
                print_warn("Phase 4B skipped - timed-out books were not retried")
                print()

    # Final summary - calculate elapsed times before printing the banner
    _total_elapsed   = time.time() - script_start_time
    _proc_elapsed    = max(0.0, _total_elapsed - user_wait_seconds)
    print()
    print("=" * 70)
    print("SUCCESS! Complete automation finished!")
    print("=" * 70)
    print()
    print_ok("What was accomplished:")

    if use_alt_method:
        alt_summary_name = os.path.basename(alt_extractor_path) if alt_extractor_path else 'KFXArchiver'
        print(f"  + Decrypted KFX books using {alt_summary_name} (Alternate Method - KFXArchive)")
        print("  + Generated decrypted kfx-zip files")
        print(f"  + Decrypted books saved to: {extracted_books_path}")
        print("  + Configured DeDRM plugin via kindlekey.k4i (DSN + Tokens from KFXArchiver)")
        print("  + Imported KF8/MOBI books via Calibre DeDRM plugin (if any were present)")
    else:
        print("  + Extracted Kindle keys using plugin-compatible method")
        print("  + Generated kindlekey.txt (voucher keys)")
        print("  + Generated kindlekey.k4i (account data)")
        print("  + Updated DeDRM plugin configuration automatically")
        print("  + Set extra key file path in plugin")
        print("  + Added account keys to plugin database")

    if imported_count > 0:
        print(f"  + Imported {imported_count} ebook(s) to Calibre")

    print()
    print("--------------------------------------------------")
    print_ok(f"  Elapsed processing time : {format_elapsed(_proc_elapsed)}  (active run time, excludes waiting for user input)")
    print_ok(f"  Total elapsed time      : {format_elapsed(_total_elapsed)}  (wall-clock including all user input pauses)")
    print("--------------------------------------------------")
    print()
    print()
    print("Press any key to continue...")
    
    # Wait for any key press
    import msvcrt
    msvcrt.getch()
    
    # Clear screen and show credits page
    os.system('cls')
    
    # Display credits and links
    from modules.utils import display_credits_and_links # type: ignore
    display_credits_and_links()
    
    return 0


if __name__ == "__main__":
    sys.exit(main())
