• Analyzes skip patterns based on listening context

    Performs a detailed analysis of how skip behavior varies across different listening contexts, such as playlists, albums, and artist pages. This function identifies which contexts tend to produce higher skip rates and which lead to more engaged listening.

    The analysis includes:

    • Breakdown of skips by context type (playlist, album, artist, etc.)
    • Percentage distribution across different context types
    • Identification of specific high-skip contexts (e.g., particular playlists)
    • Ranking of contexts by skip frequency
    • Contextual metadata for each top-skipped context

    This analysis helps identify whether certain context types lead to more skipping behavior and can reveal patterns in content curation that may affect listening engagement.

    Returns Promise<
        {
            totalSkips: number;
            byContextType: Record<
                string,
                {
                    count: number;
                    percentage: number;
                    topContexts: {
                        id: string;
                        name: string;
                        count: number;
                        percentage: number;
                    }[];
                },
            >;
            mostSkippedContext: | null
            | { type: string; id: string; name: string; count: number };
        },
    >

    Promise resolving to a detailed context-based skip analysis

    // Analyze which contexts lead to most skips
    const contextPatterns = await analyzeListeningContextPatterns();
    if (contextPatterns.mostSkippedContext) {
    console.log(`Most skipped in: ${contextPatterns.mostSkippedContext.type} - ${contextPatterns.mostSkippedContext.name}`);
    }
    export async function analyzeListeningContextPatterns(): Promise<{
    totalSkips: number;
    byContextType: Record<
    string,
    {
    count: number;
    percentage: number;
    topContexts: Array<{
    id: string;
    name: string;
    count: number;
    percentage: number;
    }>;
    }
    >;
    mostSkippedContext: {
    type: string;
    id: string;
    name: string;
    count: number;
    } | null;
    }> {
    try {
    // Read skipped tracks from file
    const skippedTracksFilePath = join(
    getUserDataFolder(),
    "skipped_tracks.json",
    );

    if (!existsSync(skippedTracksFilePath)) {
    return {
    totalSkips: 0,
    byContextType: {},
    mostSkippedContext: null,
    };
    }

    const skippedTracks: SkippedTrack[] =
    readJsonSync(skippedTracksFilePath, { throws: false }) || [];

    // Default result structure
    const result = {
    totalSkips: 0,
    byContextType: {} as Record<
    string,
    {
    count: number;
    percentage: number;
    topContexts: Array<{
    id: string;
    name: string;
    count: number;
    percentage: number;
    }>;
    }
    >,
    mostSkippedContext: null as {
    type: string;
    id: string;
    name: string;
    count: number;
    } | null,
    };

    // Process each skipped track
    skippedTracks.forEach((track: SkippedTrack) => {
    if (!track.contextStats) return;

    // Create a typed version of contextStats that allows string indexing
    type ContextData = {
    type: string;
    name?: string;
    uri?: string;
    count: number;
    };

    type IndexableContextStats = {
    [key: string]: {
    count: number;
    contexts?: Record<string, ContextData>;
    };
    };

    // Cast to the indexable type to avoid TypeScript errors
    const indexableContextStats =
    track.contextStats as unknown as IndexableContextStats;

    // Count total skips that have context data
    let trackSkipsWithContext = 0;

    // Use for...in loop with the properly typed object
    for (const contextType in indexableContextStats) {
    if (contextType !== "total" && contextType !== "contexts") {
    // Access specific context types and their count values
    const contextCount = indexableContextStats[contextType]?.count;
    if (typeof contextCount === "number") {
    trackSkipsWithContext += contextCount;
    }
    }
    }

    result.totalSkips += trackSkipsWithContext;

    // Process each context type
    for (const contextType in indexableContextStats) {
    if (contextType !== "total" && contextType !== "contexts") {
    // Initialize context type in result if needed
    if (!result.byContextType[contextType]) {
    result.byContextType[contextType] = {
    count: 0,
    percentage: 0,
    topContexts: [],
    };
    }

    // Access the count and contexts safely using the typed object
    const contextCount = indexableContextStats[contextType]?.count;
    const contexts = indexableContextStats[contextType]?.contexts;

    // Add counts from this track
    if (typeof contextCount === "number") {
    result.byContextType[contextType].count += contextCount;
    }

    // Process individual contexts within this type
    if (contexts) {
    for (const contextId in contexts) {
    const contextData = contexts[contextId];
    if (!contextData) continue;

    // Find existing context or create new entry
    const existingContext = result.byContextType[
    contextType
    ].topContexts.find((c) => c.id === contextId);

    if (existingContext) {
    existingContext.count += contextData.count || 0;
    } else {
    result.byContextType[contextType].topContexts.push({
    id: contextId,
    name: contextData.name || "(Unknown)",
    count: contextData.count || 0,
    percentage: 0, // Will calculate later
    });
    }

    // Check if this is the most skipped context overall
    if (
    !result.mostSkippedContext ||
    (result.mostSkippedContext &&
    (contextData.count || 0) > result.mostSkippedContext.count)
    ) {
    result.mostSkippedContext = {
    type: contextType,
    id: contextId,
    name: contextData.name || "(Unknown)",
    count: contextData.count || 0,
    };
    }
    }
    }
    }
    }
    });

    // Calculate percentages
    if (result.totalSkips > 0) {
    Object.keys(result.byContextType).forEach((contextType) => {
    // Calculate percentage for this context type
    result.byContextType[contextType].percentage =
    (result.byContextType[contextType].count / result.totalSkips) * 100;

    // Sort top contexts by count (descending)
    result.byContextType[contextType].topContexts.sort(
    (a, b) => b.count - a.count,
    );

    // Limit to top 5 contexts
    result.byContextType[contextType].topContexts = result.byContextType[
    contextType
    ].topContexts.slice(0, 5);

    // Calculate percentages for each context
    result.byContextType[contextType].topContexts.forEach((context) => {
    context.percentage =
    (context.count / result.byContextType[contextType].count) * 100;
    });
    });
    }

    return result;
    } catch (error) {
    console.error("Error analyzing listening context patterns:", error);
    return {
    totalSkips: 0,
    byContextType: {},
    mostSkippedContext: null,
    };
    }
    }