• SettingsPage component

    Returns Element

    export default function SettingsPage() {
    // State for application restart confirmation dialog
    const [showRestartDialog, setShowRestartDialog] = useState(false);
    // State for tracking whether settings have been changed
    const [settingsChanged, setSettingsChanged] = useState(false);
    // State for skip progress percentage slider
    const [skipProgress, setSkipProgress] = useState(70);
    // State for storing the complete current settings
    const [currentSettings, setCurrentSettings] = useState<SettingsSchema | null>(
    null,
    );

    /**
    * Initialize form with React Hook Form and Zod validation
    */
    const form = useForm<z.infer<typeof settingsFormSchema>>({
    resolver: zodResolver(settingsFormSchema),
    defaultValues: {
    clientId: "",
    clientSecret: "",
    redirectUri: "http://localhost:8888/callback",
    fileLogLevel: "INFO",
    logLineCount: 1000,
    maxLogFiles: 10,
    logRetentionDays: 30,
    skipThreshold: 3,
    timeframeInDays: 30,
    autoStartMonitoring: true,
    autoUnlike: true,
    pollingInterval: currentSettings?.pollingInterval || 1000,
    },
    });

    /**
    * Loads saved settings from storage on component mount
    */
    useEffect(() => {
    const loadSettings = async () => {
    try {
    const settings = await window.spotify.getSettings();

    // Store complete settings for import/export
    setCurrentSettings(settings);

    // Handle legacy settings format
    const fileLogLevel =
    settings.fileLogLevel || settings.logLevel || "INFO";

    form.reset({
    clientId: settings.clientId || "",
    clientSecret: settings.clientSecret || "",
    redirectUri: settings.redirectUri || "http://localhost:8888/callback",
    fileLogLevel: fileLogLevel,
    logLineCount: settings.logLineCount || 1000,
    maxLogFiles: settings.maxLogFiles || 10,
    logRetentionDays: settings.logRetentionDays || 30,
    skipThreshold: settings.skipThreshold || 5,
    timeframeInDays: settings.timeframeInDays || 30,
    autoStartMonitoring: settings.autoStartMonitoring ?? true,
    autoUnlike: settings.autoUnlike ?? true,
    pollingInterval: settings.pollingInterval || 1000,
    });

    setSkipProgress(settings.skipProgress || 70);
    setSettingsChanged(false);
    } catch (error) {
    console.error("Failed to load settings:", error);
    toast.error("Failed to load settings", {
    description: "Could not load saved settings. Using defaults.",
    });
    }
    };

    loadSettings();
    }, [form]);

    /**
    * Analyzes settings changes to determine restart requirements
    *
    * Evaluates critical configuration changes that require application
    * restart to take effect properly. Specifically identifies changes to
    * authentication parameters that affect the Spotify API connection
    * and cannot be applied dynamically at runtime.
    *
    * This ensures proper notification to users when their changes will
    * require an application restart to fully apply, providing transparency
    * in the configuration process.
    *
    * @param newSettings - The modified settings object to evaluate
    * @returns Boolean indicating whether restart is required
    */
    const requiresRestart = (newSettings: SettingsSchema): boolean => {
    const currentValues = form.getValues();

    return (
    currentValues.clientId !== newSettings.clientId ||
    currentValues.clientSecret !== newSettings.clientSecret ||
    currentValues.redirectUri !== newSettings.redirectUri
    );
    };

    /**
    * Initiates application restart process
    */
    const handleRestart = async () => {
    try {
    setShowRestartDialog(false);

    toast.info("Restarting application...", {
    description: "The application will restart now to apply changes.",
    });

    await window.spotify.restartApp();
    } catch (error) {
    console.error("Failed to restart application:", error);
    toast.error("Failed to restart", {
    description:
    "Could not restart the application. Please restart manually.",
    });
    }
    };

    /**
    * Processes and persists form submission with validation
    *
    * Central handler for settings form submission that:
    * 1. Merges form values with existing settings
    * 2. Persists changes to secure storage via IPC
    * 3. Evaluates restart requirements based on changes
    * 4. Provides appropriate success/failure feedback
    * 5. Initiates restart confirmation dialog when needed
    *
    * Implements proper error handling with user-friendly notifications
    * and detailed error logging to help troubleshoot configuration issues.
    *
    * @param values - Validated form values from React Hook Form
    */
    async function onSubmit(values: z.infer<typeof settingsFormSchema>) {
    try {
    const currentSettingsData = await window.spotify.getSettings();

    const settings = {
    ...currentSettingsData,
    ...values,
    skipProgress,
    autoUnlike: values.autoUnlike,
    };

    const success = await window.spotify.saveSettings(settings);

    if (success) {
    // Update current settings reference for import/export
    setCurrentSettings(settings);

    toast.success("Settings saved", {
    description: "Your settings have been saved successfully.",
    });

    if (requiresRestart(settings as SettingsSchema)) {
    setShowRestartDialog(true);
    }

    setSettingsChanged(false);
    } else {
    toast.error("Failed to save settings", {
    description: "Could not save settings. Please try again.",
    });
    }
    } catch (error) {
    console.error("Error saving settings:", error);
    toast.error("Error", {
    description: "An error occurred while saving settings.",
    });
    }
    }

    /**
    * Processes externally imported settings configuration
    *
    * Handles the integration of settings imported from JSON files by:
    * 1. Validating imported data structure against schema
    * 2. Extracting relevant configuration parameters
    * 3. Updating form state with imported values
    * 4. Persisting changes to application storage
    * 5. Providing clear success/failure feedback
    *
    * Includes validation safeguards to prevent invalid configuration
    * from corrupting application settings.
    *
    * @param importedSettings - Settings object from external import
    */
    const handleImportSettings = async (importedSettings: SettingsSchema) => {
    try {
    // Extract form values from imported settings
    const {
    clientId = "",
    clientSecret = "",
    redirectUri = "http://localhost:8888/callback",
    fileLogLevel = "INFO",
    logLevel = "INFO",
    logLineCount = 1000,
    maxLogFiles = 10,
    logRetentionDays = 30,
    skipThreshold = 3,
    timeframeInDays = 30,
    skipProgress: importedSkipProgress = 70,
    autoStartMonitoring = true,
    autoUnlike = true,
    } = importedSettings;

    // Update form with imported values
    form.reset({
    clientId,
    clientSecret,
    redirectUri,
    fileLogLevel: fileLogLevel || logLevel,
    logLineCount,
    maxLogFiles,
    logRetentionDays,
    skipThreshold,
    timeframeInDays,
    autoStartMonitoring,
    autoUnlike,
    pollingInterval: importedSettings.pollingInterval || 1000,
    });

    // Update other state values
    setSkipProgress(importedSkipProgress);

    // Save the imported settings
    const success = await window.spotify.saveSettings(importedSettings);

    if (success) {
    setCurrentSettings(importedSettings);
    setSettingsChanged(false);

    // Check if restart is needed
    if (requiresRestart(importedSettings as SettingsSchema)) {
    setShowRestartDialog(true);
    }
    } else {
    throw new Error("Failed to save imported settings");
    }
    } catch (error) {
    console.error("Error importing settings:", error);
    toast.error("Import failed", {
    description: "Failed to apply imported settings. Please try again.",
    });
    }
    };

    /**
    * Resets all settings to default values
    */
    const handleResetSettings = async () => {
    try {
    // Get default settings from the main process
    const success = await window.spotify.resetSettings();

    if (success) {
    // Reload settings after reset
    const defaultSettings = await window.spotify.getSettings();

    // Update the form with default values
    form.reset({
    clientId: defaultSettings.clientId || "",
    clientSecret: defaultSettings.clientSecret || "",
    redirectUri:
    defaultSettings.redirectUri || "http://localhost:8888/callback",
    fileLogLevel: defaultSettings.fileLogLevel || "INFO",
    logLineCount: defaultSettings.logLineCount || 1000,
    maxLogFiles: defaultSettings.maxLogFiles || 10,
    logRetentionDays: defaultSettings.logRetentionDays || 30,
    skipThreshold: defaultSettings.skipThreshold || 3,
    timeframeInDays: defaultSettings.timeframeInDays || 30,
    autoStartMonitoring: defaultSettings.autoStartMonitoring ?? true,
    autoUnlike: defaultSettings.autoUnlike ?? true,
    pollingInterval: defaultSettings.pollingInterval || 1000,
    });

    setSkipProgress(defaultSettings.skipProgress || 70);
    setCurrentSettings(defaultSettings);
    setSettingsChanged(false);

    toast.success("Settings reset", {
    description: "All settings have been reset to their default values.",
    });
    } else {
    throw new Error("Failed to reset settings");
    }
    } catch (error) {
    console.error("Error resetting settings:", error);
    toast.error("Reset failed", {
    description: "Failed to reset settings to defaults. Please try again.",
    });
    }
    };

    /**
    * Tracks form changes to enable/disable save button
    */
    useEffect(() => {
    const subscription = form.watch(() => setSettingsChanged(true));
    return () => subscription.unsubscribe();
    }, [form]);

    return (
    <div className="container mx-auto max-w-4xl py-8">
    <div className="mb-8 border-b pb-4">
    <h1 className="text-3xl font-bold tracking-tight">Settings</h1>
    <p className="text-muted-foreground mt-2">
    Configure application settings and Spotify credentials
    </p>
    </div>

    <CardContent className="p-0 sm:p-0">
    <Form {...form}>
    <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-10">
    {/* Spotify API Settings */}
    <ApiCredentialsForm
    form={form}
    setSettingsChanged={setSettingsChanged}
    />

    {/* Skip Detection Settings */}
    <SkipDetectionForm
    form={form}
    skipProgress={skipProgress}
    setSkipProgress={setSkipProgress}
    setSettingsChanged={setSettingsChanged}
    />

    {/* Application Settings */}
    <ApplicationSettingsForm
    form={form}
    setSettingsChanged={setSettingsChanged}
    />

    {/* Import/Export and Reset Settings */}
    {currentSettings && (
    <ImportExportSettings
    currentSettings={currentSettings}
    onImport={handleImportSettings}
    />
    )}

    <div className="mt-8 flex flex-col gap-4 sm:flex-row sm:justify-between">
    <div className="sm:w-1/3">
    <ResetSettingsDialog onReset={handleResetSettings} />
    </div>

    <div className="flex justify-end sm:w-2/3">
    <Button
    type="submit"
    disabled={!settingsChanged}
    className="w-full px-6 transition-all duration-200 sm:w-auto"
    size="lg"
    >
    <Save className="mr-2 h-4 w-4" />
    Save Settings
    </Button>
    </div>
    </div>
    </form>
    </Form>
    </CardContent>

    {/* Restart Dialog */}
    {showRestartDialog && (
    <RestartDialog
    showRestartDialog={showRestartDialog}
    setShowRestartDialog={setShowRestartDialog}
    onRestart={handleRestart}
    />
    )}
    </div>
    );
    }