• Records a track skip in the application's history

    Stores detailed information about a skipped track in the application's persistent storage, including comprehensive metadata about the skip event. This function supports two parameter styles:

    1. Complete SkipInfo object with all details
    2. Individual parameters for basic skip recording

    The function implements sophisticated storage logic:

    • Merges new skip events with existing skip history
    • Updates metadata with each skip occurrence
    • Tracks skip patterns over time
    • Maintains contextual information about skips
    • Handles storage errors gracefully

    Parameters

    • trackIdOrInfo: string | SkipInfo

      Track ID or detailed SkipInfo object

    • OptionaltrackName: string

      Track name (when using individual parameters)

    • OptionalartistName: string

      Artist name (when using individual parameters)

    • skippedAt: number = ...

      Timestamp when track was skipped (when using individual parameters)

    • Optionalprogress: number

      Progress percentage at which the track was skipped (when using individual parameters)

    Returns Promise<void>

    Promise that resolves when the skip has been recorded

    // Record a skip with detailed information
    const skipInfo = {
    id: 'spotify:track:1234567890',
    name: 'Track Name',
    artist: 'Artist Name',
    album: 'Album Name',
    skippedAt: Date.now(),
    playDuration: 45000,
    trackDuration: 180000,
    playPercentage: 0.25,
    deviceName: 'My Device',
    skipType: 'standard',
    isManualSkip: true,
    confidence: 0.85,
    reason: 'Track skipped at 25%, below threshold of 70%',
    isInLibrary: true,
    context: {
    type: 'playlist',
    name: 'My Playlist',
    id: 'playlist:1234567890'
    }
    };

    await recordSkippedTrack(skipInfo);
    // Record a skip with basic information
    await recordSkippedTrack(
    'spotify:track:1234567890', // Track ID
    'Track Name', // Track name
    'Artist Name', // Artist name
    Date.now(), // Current timestamp
    0.25 // 25% progress
    );
    export async function recordSkippedTrack(
    trackIdOrInfo: string | SkipInfo,
    trackName?: string,
    artistName?: string,
    skippedAt: number = Date.now(),
    progress?: number,
    ): Promise<void> {
    // Get existing skipped tracks data - placing outside of try/catch to handle this error differently
    let skippedTracks;
    try {
    skippedTracks = await getSkippedTracks();
    } catch (error) {
    // If getSkippedTracks fails, log the error and exit early without saving
    store.saveLog(`Failed to get skipped tracks: ${error}`, "ERROR");
    return; // Critical: Exit the function here to prevent proceeding with save
    }

    try {
    // Handle both parameter styles
    let trackId: string;
    let skipEvent: {
    timestamp: string;
    progress: number;
    skipType?: string;
    isManual?: boolean;
    context?: {
    type: string;
    uri?: string;
    name?: string;
    id?: string;
    };
    };
    let trackAlbum: string | undefined;
    let trackArtist: string;
    let trackNameToUse: string;
    let skippedAtToUse: number;
    let isInLibrary: boolean | undefined;

    // Check if first parameter is an object (enhanced skip info)
    if (typeof trackIdOrInfo === "object") {
    const skipInfo = trackIdOrInfo;
    trackId = skipInfo.id;
    trackNameToUse = skipInfo.name;
    trackArtist = skipInfo.artist;
    trackAlbum = skipInfo.album;
    skippedAtToUse = skipInfo.skippedAt;
    isInLibrary = skipInfo.isInLibrary;

    skipEvent = {
    timestamp: skipInfo.skippedAt.toString(),
    progress: skipInfo.playPercentage,
    skipType: skipInfo.skipType,
    isManual: skipInfo.isManualSkip,
    context: skipInfo.context,
    };
    } else {
    // Original parameter style
    trackId = trackIdOrInfo;
    trackNameToUse = trackName || "";
    trackArtist = artistName || "";
    skippedAtToUse = skippedAt;

    skipEvent = {
    timestamp: skippedAt.toString(),
    progress: progress || 0,
    };
    }

    // Check if track exists in the skipped tracks list
    const existingTrackIndex = skippedTracks.findIndex(
    (track) => track.id === trackId,
    );

    const skippedAtStr = skippedAtToUse.toString();

    if (existingTrackIndex >= 0) {
    // Update existing entry
    const track = skippedTracks[existingTrackIndex];
    track.skipCount = (track.skipCount || 0) + 1;
    track.lastSkipped = skippedAtStr;

    // Update isInLibrary status if it's provided
    if (isInLibrary !== undefined) {
    track.isInLibrary = isInLibrary;
    }

    // Add to skipTimestamps array if it exists
    const extendedTrack = track as ExtendedSkippedTrack;
    if (!extendedTrack.skipTimestamps) {
    extendedTrack.skipTimestamps = [];
    }
    extendedTrack.skipTimestamps.push(skippedAtStr);

    // Cast to extended type to handle skipEvents
    const trackWithEvents = track as SkippedTrackWithEvents;

    if (!Array.isArray(trackWithEvents.skipEvents)) {
    trackWithEvents.skipEvents = [];
    }

    trackWithEvents.skipEvents.push(skipEvent);

    // Update skip type counts
    if (!trackWithEvents.skipTypes) {
    trackWithEvents.skipTypes = {};
    }
    const skipTypeKey = skipEvent.skipType || "standard";
    trackWithEvents.skipTypes[skipTypeKey] =
    (trackWithEvents.skipTypes[skipTypeKey] || 0) + 1;

    // Update manual/automatic skip counts
    if (skipEvent.isManual !== undefined) {
    if (skipEvent.isManual) {
    trackWithEvents.manualSkipCount =
    (trackWithEvents.manualSkipCount || 0) + 1;
    } else {
    trackWithEvents.autoSkipCount =
    (trackWithEvents.autoSkipCount || 0) + 1;
    }
    }

    // Update time-of-day data
    if (!trackWithEvents.timeOfDayData) {
    trackWithEvents.timeOfDayData = Array(24).fill(0);
    }
    const skipHour = new Date(parseInt(skippedAtStr)).getHours();
    trackWithEvents.timeOfDayData[skipHour] += 1;

    // Update context data if available
    if (skipEvent.context) {
    // Store the last context
    trackWithEvents.lastContext = skipEvent.context;

    // Initialize context stats if needed
    if (!trackWithEvents.contextStats) {
    trackWithEvents.contextStats = {
    total: 0,
    contexts: {},
    };
    }

    const contextType = skipEvent.context.type;
    const contextId =
    skipEvent.context.id || skipEvent.context.uri || "unknown";
    const contextName = skipEvent.context.name || "Unknown";

    // Increment total count
    trackWithEvents.contextStats.total += 1;

    // Initialize and update specific context
    if (!trackWithEvents.contextStats.contexts[contextId]) {
    trackWithEvents.contextStats.contexts[contextId] = {
    type: contextType,
    name: contextName,
    count: 0,
    };
    }

    trackWithEvents.contextStats.contexts[contextId].count += 1;
    }
    } else {
    // Create new entry with the base SkippedTrack properties
    // and add the skipEvents array as an extension
    const newTrack: SkippedTrackWithEvents = {
    id: trackId,
    name: trackNameToUse,
    artist: trackArtist,
    skipCount: 1,
    notSkippedCount: 0, // Initialize to 0 for a new track
    lastSkipped: skippedAtStr,
    skipTimestamps: [skippedAtStr],
    skipEvents: [skipEvent],
    // Initialize skip type tracking
    skipTypes: {
    [skipEvent.skipType || "standard"]: 1,
    },
    // Initialize manual/automatic counts if available
    ...(skipEvent.isManual !== undefined && {
    manualSkipCount: skipEvent.isManual ? 1 : 0,
    autoSkipCount: skipEvent.isManual ? 0 : 1,
    }),
    // Initialize time-of-day data
    timeOfDayData: (() => {
    const hourArray = Array(24).fill(0);
    const skipHour = new Date(parseInt(skippedAtStr)).getHours();
    hourArray[skipHour] = 1;
    return hourArray;
    })(),
    // Set isInLibrary status if provided
    ...(isInLibrary !== undefined && { isInLibrary }),
    };

    // Add album if available (from enhanced info)
    if (trackAlbum) {
    newTrack.album = trackAlbum;
    }

    // Add context information if available
    if (skipEvent.context) {
    newTrack.lastContext = skipEvent.context;

    const contextType = skipEvent.context.type;
    const contextId =
    skipEvent.context.id || skipEvent.context.uri || "unknown";
    const contextName = skipEvent.context.name || "Unknown";

    newTrack.contextStats = {
    total: 1,
    contexts: {
    [contextId]: {
    type: contextType,
    name: contextName,
    count: 1,
    },
    },
    };
    }

    skippedTracks.push(newTrack);
    }

    // Save updated data
    await store.saveSkippedTracks(skippedTracks);

    store.saveLog(
    `Recorded skip for "${trackNameToUse}" by ${trackArtist} - Skip type: ${skipEvent.skipType || "standard"}`,
    "INFO",
    );
    } catch (error) {
    store.saveLog(`Failed to record skipped track: ${error}`, "ERROR");
    }
    }