import { z } from 'zod';
import { ToolBubble } from '../../types/tool-bubble-class.js';
import { CredentialType } from '@bubblelab/shared-schemas';
import { ApifyBubble } from '../service-bubble/apify/apify.js';
// Unified LinkedIn data types (service-agnostic)
const LinkedInAuthorSchema = z.object({
    firstName: z.string().nullable().describe('Author first name'),
    lastName: z.string().nullable().describe('Author last name'),
    headline: z.string().nullable().describe('Author headline/title'),
    username: z.string().nullable().describe('Author username'),
    profileUrl: z.string().nullable().describe('Author profile URL'),
    profilePicture: z.string().nullable().describe('Author profile picture URL'),
});
const LinkedInStatsSchema = z.object({
    totalReactions: z.number().nullable().describe('Total number of reactions'),
    like: z.number().nullable().describe('Number of likes'),
    support: z.number().nullable().describe('Number of support reactions'),
    love: z.number().nullable().describe('Number of love reactions'),
    insight: z.number().nullable().describe('Number of insight reactions'),
    celebrate: z.number().nullable().describe('Number of celebrate reactions'),
    funny: z.number().nullable().describe('Number of funny reactions'),
    comments: z.number().nullable().describe('Number of comments'),
    reposts: z.number().nullable().describe('Number of reposts'),
});
const LinkedInMediaSchema = z.object({
    type: z.string().nullable().describe('Media type (image, video, images)'),
    url: z.string().nullable().describe('Media URL'),
    thumbnail: z.string().nullable().describe('Media thumbnail URL'),
    images: z
        .array(z.object({
        url: z.string().nullable(),
        width: z.number().nullable(),
        height: z.number().nullable(),
    }))
        .nullable()
        .describe('Array of images for multi-image posts'),
});
const LinkedInPostedAtSchema = z.object({
    date: z.string().nullable().describe('Post date (formatted string)'),
    relative: z
        .string()
        .nullable()
        .describe('Relative time (e.g., "2 days ago")'),
    timestamp: z.number().nullable().describe('Unix timestamp in milliseconds'),
});
const LinkedInPostSchema = z.object({
    urn: z.string().nullable().describe('Post URN'),
    fullUrn: z.string().nullable().describe('Full URN with prefix'),
    postedAt: LinkedInPostedAtSchema.nullable().describe('When post was created'),
    text: z.string().nullable().describe('Post text content'),
    url: z.string().nullable().describe('Post URL'),
    postType: z.string().nullable().describe('Post type (regular, quote, etc)'),
    author: LinkedInAuthorSchema.nullable().describe('Post author information'),
    stats: LinkedInStatsSchema.nullable().describe('Post engagement statistics'),
    media: LinkedInMediaSchema.nullable().describe('Post media content'),
    article: z
        .object({
        url: z.string().nullable(),
        title: z.string().nullable(),
        subtitle: z.string().nullable(),
        thumbnail: z.string().nullable(),
    })
        .nullable()
        .describe('Shared article information'),
    document: z
        .object({
        title: z.string().nullable(),
        pageCount: z.number().nullable(),
        url: z.string().nullable(),
        thumbnail: z.string().nullable(),
    })
        .nullable()
        .describe('Shared document information'),
    resharedPost: z
        .object({
        urn: z.string().nullable(),
        postedAt: LinkedInPostedAtSchema.nullable(),
        text: z.string().nullable(),
        url: z.string().nullable(),
        postType: z.string().nullable(),
        author: LinkedInAuthorSchema.nullable(),
        stats: LinkedInStatsSchema.nullable(),
        media: LinkedInMediaSchema.nullable(),
    })
        .nullable()
        .describe('Original post that was reshared'),
});
const LinkedInJobSchema = z.object({
    id: z.string().nullable().describe('Job ID'),
    title: z.string().nullable().describe('Job title'),
    company: z
        .object({
        name: z.string().nullable(),
        url: z.string().nullable(),
        logo: z.string().nullable(),
    })
        .nullable()
        .describe('Company info'),
    location: z.string().nullable().describe('Job location'),
    description: z.string().nullable().describe('Job description'),
    employmentType: z.string().nullable().describe('Employment type'),
    seniorityLevel: z.string().nullable().describe('Seniority level'),
    postedAt: z.string().nullable().describe('Posted date'),
    url: z.string().nullable().describe('Job URL'),
    applyUrl: z.string().nullable().describe('Apply URL'),
    salary: z
        .object({
        from: z.number().nullable(),
        to: z.number().nullable(),
        currency: z.string().nullable(),
        period: z.string().nullable(),
    })
        .nullable()
        .describe('Salary info'),
    skills: z.array(z.string()).nullable().describe('Required skills'),
});
const LinkedInProfileDateSchema = z.object({
    month: z.string().nullable().describe('Month name'),
    year: z.number().nullable().describe('Year'),
    text: z.string().nullable().describe('Formatted date text'),
});
const LinkedInProfileExperienceSchema = z.object({
    position: z.string().nullable().describe('Job title/position'),
    location: z.string().nullable().describe('Job location'),
    employmentType: z.string().nullable().describe('Employment type'),
    workplaceType: z.string().nullable().describe('Workplace type'),
    companyName: z.string().nullable().describe('Company name'),
    companyLinkedinUrl: z.string().nullable().describe('Company LinkedIn URL'),
    duration: z.string().nullable().describe('Duration text'),
    description: z.string().nullable().describe('Role description'),
    skills: z.array(z.string()).nullable().describe('Skills for this role'),
    startDate: LinkedInProfileDateSchema.nullable().describe('Start date'),
    endDate: LinkedInProfileDateSchema.nullable().describe('End date'),
});
const LinkedInProfileEducationSchema = z.object({
    schoolName: z.string().nullable().describe('School name'),
    schoolLinkedinUrl: z.string().nullable().describe('School LinkedIn URL'),
    degree: z.string().nullable().describe('Degree type'),
    fieldOfStudy: z.string().nullable().describe('Field of study'),
    startDate: LinkedInProfileDateSchema.nullable().describe('Start date'),
    endDate: LinkedInProfileDateSchema.nullable().describe('End date'),
    period: z.string().nullable().describe('Period text'),
});
const LinkedInProfileSchema = z.object({
    id: z.string().nullable().describe('LinkedIn member ID'),
    publicIdentifier: z
        .string()
        .nullable()
        .describe('Profile slug (e.g., "williamhgates")'),
    linkedinUrl: z.string().nullable().describe('Full LinkedIn profile URL'),
    firstName: z.string().nullable().describe('First name'),
    lastName: z.string().nullable().describe('Last name'),
    headline: z.string().nullable().describe('Profile headline'),
    about: z.string().nullable().describe('About/summary section'),
    openToWork: z.boolean().nullable().describe('Whether open to work'),
    hiring: z.boolean().nullable().describe('Whether actively hiring'),
    photo: z.string().nullable().describe('Profile photo URL'),
    premium: z.boolean().nullable().describe('Whether premium subscriber'),
    influencer: z.boolean().nullable().describe('Whether LinkedIn influencer'),
    location: z
        .object({
        text: z.string().nullable().describe('Location text'),
        countryCode: z.string().nullable().describe('Country code'),
        country: z.string().nullable().describe('Country'),
        state: z.string().nullable().describe('State/region'),
        city: z.string().nullable().describe('City'),
    })
        .nullable()
        .describe('Location information'),
    verified: z.boolean().nullable().describe('Whether profile is verified'),
    topSkills: z.string().nullable().describe('Top skills summary'),
    connectionsCount: z.number().nullable().describe('Number of connections'),
    followerCount: z.number().nullable().describe('Number of followers'),
    currentPosition: z
        .array(z.object({ companyName: z.string().nullable() }))
        .nullable()
        .describe('Current company/position'),
    experience: z
        .array(LinkedInProfileExperienceSchema)
        .nullable()
        .describe('Work experience history'),
    education: z
        .array(LinkedInProfileEducationSchema)
        .nullable()
        .describe('Education history'),
    certifications: z
        .array(z.object({
        title: z.string().nullable(),
        issuedAt: z.string().nullable(),
        issuedBy: z.string().nullable(),
    }))
        .nullable()
        .describe('Certifications'),
    languages: z
        .array(z.object({
        name: z.string().nullable(),
        proficiency: z.string().nullable(),
    }))
        .nullable()
        .describe('Languages'),
    skills: z
        .array(z.object({ name: z.string().nullable() }))
        .nullable()
        .describe('All skills'),
});
// Gemini-compatible single object schema with optional fields
const LinkedInToolParamsSchema = z.object({
    operation: z
        .enum(['scrapeProfile', 'scrapePosts', 'searchPosts', 'scrapeJobs'])
        .describe('Operation to perform: scrapeProfile (get profile info from LinkedIn URL), scrapePosts (get posts from a profile), searchPosts (search posts by keyword), or scrapeJobs (search jobs)'),
    // Profile lookup fields (optional)
    profileUrl: z
        .string()
        .optional()
        .describe('LinkedIn profile URL or username (for scrapeProfile operation). Examples: "https://www.linkedin.com/in/williamhgates", "williamhgates"'),
    // Profile scraping fields (optional)
    username: z
        .string()
        .optional()
        .describe('LinkedIn username (for scrapePosts operation). Examples: "satyanadella", "billgates"'),
    // Search fields (optional)
    keyword: z
        .string()
        .optional()
        .describe('Keyword or phrase to search for (for searchPosts/scrapeJobs). Examples: "AI", "hiring", "Software Engineer"'),
    location: z
        .string()
        .optional()
        .describe('Location for job search (e.g. "San Francisco", "Remote") (scrapeJobs only)'),
    jobType: z
        .array(z.enum(['full-time', 'part-time', 'contract', 'temporary', 'internship']))
        .optional()
        .describe('Filter by job type (scrapeJobs only)'),
    workplaceType: z
        .array(z.enum(['on-site', 'remote', 'hybrid']))
        .optional()
        .describe('Filter by workplace type (scrapeJobs only)'),
    experienceLevel: z
        .array(z.enum([
        'internship',
        'entry-level',
        'associate',
        'mid-senior',
        'director',
        'executive',
    ]))
        .optional()
        .describe('Filter by experience level (scrapeJobs only)'),
    sortBy: z
        .enum(['relevance', 'date_posted'])
        .default('relevance')
        .optional()
        .describe('Sort results by relevance or date posted (for searchPosts operation, default: relevance)'),
    dateFilter: z
        .enum(['past-24h', 'past-week', 'past-month'])
        .optional()
        .describe('Filter posts/jobs by date range (searchPosts/scrapeJobs). Options: past-24h, past-week, past-month. Leave empty for no date filter.'),
    // Common fields
    limit: z
        .number()
        .max(1000)
        .default(50)
        .optional()
        .describe('Maximum number of items to fetch (default: 50)'),
    pageNumber: z
        .number()
        .min(1)
        .default(1)
        .optional()
        .describe('Page number for pagination (default: 1)'),
    credentials: z
        .record(z.nativeEnum(CredentialType), z.string())
        .optional()
        .describe('Required credentials (auto-injected)'),
});
// Gemini-compatible single result schema
const LinkedInToolResultSchema = z.object({
    operation: z
        .enum(['scrapeProfile', 'scrapePosts', 'searchPosts', 'scrapeJobs'])
        .describe('Operation that was performed'),
    // Profile data (only for scrapeProfile)
    profile: LinkedInProfileSchema.nullable()
        .optional()
        .describe('LinkedIn profile data (only for scrapeProfile operation)'),
    // Jobs data (only for scrapeJobs)
    jobs: z
        .array(LinkedInJobSchema)
        .optional()
        .describe('Array of LinkedIn jobs'),
    // Posts data (always present)
    posts: z.array(LinkedInPostSchema).describe('Array of LinkedIn posts'),
    // Profile data (only for scrapePosts operation)
    username: z
        .string()
        .optional()
        .describe('LinkedIn username that was scraped (only for scrapePosts operation)'),
    paginationToken: z
        .string()
        .nullable()
        .optional()
        .describe('Token for fetching next page of results (only for scrapePosts operation)'),
    // Search data (only for searchPosts operation)
    keyword: z
        .string()
        .optional()
        .describe('Search keyword that was used (only for searchPosts operation)'),
    totalResults: z
        .number()
        .nullable()
        .optional()
        .describe('Total results available (only for searchPosts operation)'),
    hasNextPage: z
        .boolean()
        .nullable()
        .optional()
        .describe('Whether there are more results (only for searchPosts operation)'),
    // Common fields
    totalPosts: z.number().describe('Total number of posts found'),
    success: z.boolean().describe('Whether the operation was successful'),
    error: z.string().describe('Error message if operation failed'),
});
/**
 * LinkedIn scraping tool with multiple operations
 *
 * This tool provides a simple interface for scraping LinkedIn data.
 *
 * Operations:
 * 1. scrapePosts - Scrape posts from a specific LinkedIn profile
 * 2. searchPosts - Search for LinkedIn posts by keyword
 *
 * Features:
 * - Get complete post metadata (text, engagement stats, media, etc.)
 * - Support for all post types (regular, quotes, articles, documents)
 * - Pagination support
 * - Date filtering for search
 */
export class LinkedInTool extends ToolBubble {
    // Required static metadata
    static bubbleName = 'linkedin-tool';
    static schema = LinkedInToolParamsSchema;
    static resultSchema = LinkedInToolResultSchema;
    static shortDescription = 'Look up LinkedIn profiles by URL, scrape posts by profile, or search posts/jobs by keyword.';
    static longDescription = `
    Universal LinkedIn tool for profile lookup, post scraping, and job search.

    **DO NOT USE research-agent-tool or web-scrape-tool for LinkedIn** - This tool is specifically optimized for LinkedIn.

    **OPERATIONS:**

    1. **scrapeProfile**: Get full profile info from a LinkedIn URL
       - **USE THIS when you have a LinkedIn URL or username and need to know who someone is**
       - Returns: name, headline, about, current company, work experience, education, skills, location, certifications, languages, and more
       - Accepts full URLs (https://www.linkedin.com/in/williamhgates) or just usernames ("williamhgates")
       - This is the RIGHT tool for "look up this LinkedIn profile" or "who is this person on LinkedIn?"

    2. **scrapePosts**: Scrape posts from a LinkedIn profile
       - Get posts from specific users by username
       - Extract post text, engagement stats, media, articles, documents
       - **DO NOT use scrapePosts to get profile info** - use scrapeProfile instead

    3. **searchPosts**: Search LinkedIn posts by keyword
       - Find posts across all of LinkedIn by keyword
       - Filter by date (past 24h, week, month)
       - Sort by relevance or date

    4. **scrapeJobs**: Search LinkedIn job postings
       - Search jobs by keyword and location
       - Filter by job type, workplace type, experience level

    **CHOOSING THE RIGHT OPERATION:**
    - "Look up this LinkedIn profile" → **scrapeProfile**
    - "Who is this person?" (with LinkedIn URL) → **scrapeProfile**
    - "Get their name, company, experience" → **scrapeProfile**
    - "What has this person been posting?" → **scrapePosts**
    - "Find posts about AI on LinkedIn" → **searchPosts**
    - "Find software engineer jobs" → **scrapeJobs**
  `;
    static alias = 'li';
    static type = 'tool';
    constructor(params = {
        operation: 'scrapePosts',
        username: 'satyanadella',
        limit: 100,
    }, context) {
        super(params, context);
    }
    async performAction() {
        const credentials = this.params?.credentials;
        if (!credentials || !credentials[CredentialType.APIFY_CRED]) {
            return this.createErrorResult('LinkedIn scraping requires authentication. Please configure APIFY_CRED.');
        }
        try {
            const { operation } = this.params;
            if (operation === 'scrapeJobs' &&
                this.params?.limit &&
                this.params.limit < 100) {
                this.params.limit = 100;
            }
            // Validate required fields based on operation
            if (operation === 'scrapeProfile' &&
                (!this.params.profileUrl || this.params.profileUrl.length === 0)) {
                return this.createErrorResult('profileUrl is required for scrapeProfile operation. Provide a LinkedIn URL (e.g., "https://www.linkedin.com/in/williamhgates") or username.');
            }
            if (operation === 'scrapePosts' &&
                (!this.params.username || this.params.username.length === 0)) {
                return this.createErrorResult('Username is required for scrapePosts operation');
            }
            if (operation === 'searchPosts' &&
                (!this.params.keyword || this.params.keyword.length === 0)) {
                return this.createErrorResult('Keyword is required for searchPosts operation');
            }
            const result = await (async () => {
                switch (operation) {
                    case 'scrapeProfile':
                        return await this.handleScrapeProfile(this.params);
                    case 'scrapePosts':
                        return await this.handleScrapePosts(this.params);
                    case 'searchPosts':
                        return await this.handleSearchPosts(this.params);
                    case 'scrapeJobs':
                        return await this.handleScrapeJobs(this.params);
                    default:
                        throw new Error(`Unsupported operation: ${operation}`);
                }
            })();
            return result;
        }
        catch (error) {
            return this.createErrorResult(error instanceof Error ? error.message : 'Unknown error occurred');
        }
    }
    /**
     * Create an error result
     */
    createErrorResult(errorMessage) {
        const { operation } = this.params;
        return {
            operation: operation || 'scrapePosts',
            profile: operation === 'scrapeProfile' ? null : undefined,
            posts: [],
            username: operation === 'scrapePosts' ? this.params.username || '' : undefined,
            paginationToken: operation === 'scrapePosts' ? null : undefined,
            keyword: operation === 'searchPosts' ? this.params.keyword || '' : undefined,
            totalResults: operation === 'searchPosts' ? null : undefined,
            hasNextPage: operation === 'searchPosts' ? null : undefined,
            jobs: [],
            totalPosts: 0,
            success: false,
            error: errorMessage,
        };
    }
    /**
     * Normalize profileUrl input to a full LinkedIn URL.
     * Accepts either a full URL or just a username.
     */
    normalizeProfileUrl(profileUrl) {
        const trimmed = profileUrl.trim().replace(/\/+$/, '');
        if (trimmed.includes('linkedin.com/in/')) {
            return trimmed;
        }
        // Treat as username
        return `https://www.linkedin.com/in/${trimmed}`;
    }
    /**
     * Handle scrapeProfile operation - get full profile details from a LinkedIn URL
     */
    async handleScrapeProfile(params) {
        const profileUrl = this.normalizeProfileUrl(params.profileUrl);
        const profileScraper = new ApifyBubble({
            actorId: 'harvestapi/linkedin-profile-scraper',
            input: {
                profileScraperMode: 'Profile details no email ($4 per 1k)',
                queries: [profileUrl],
            },
            waitForFinish: true,
            limit: 1,
            timeout: 180000,
            credentials: params.credentials,
        }, this.context, 'linkedinProfileScraper');
        const apifyResult = await profileScraper.action();
        if (!apifyResult.data.success) {
            return {
                operation: 'scrapeProfile',
                profile: null,
                posts: [],
                totalPosts: 0,
                success: false,
                error: apifyResult.data.error ||
                    'Failed to scrape LinkedIn profile. Please try again.',
            };
        }
        const items = apifyResult.data.items || [];
        if (items.length === 0) {
            return {
                operation: 'scrapeProfile',
                profile: null,
                posts: [],
                totalPosts: 0,
                success: false,
                error: 'No profile found. The profile may be private or the URL may be invalid.',
            };
        }
        const raw = items[0];
        const profile = {
            id: raw.id || null,
            publicIdentifier: raw.publicIdentifier || null,
            linkedinUrl: raw.linkedinUrl || null,
            firstName: raw.firstName || null,
            lastName: raw.lastName || null,
            headline: raw.headline || null,
            about: raw.about || null,
            openToWork: raw.openToWork ?? null,
            hiring: raw.hiring ?? null,
            photo: raw.photo || null,
            premium: raw.premium ?? null,
            influencer: raw.influencer ?? null,
            location: raw.location
                ? {
                    text: raw.location.parsed?.text || raw.location.linkedinText || null,
                    countryCode: raw.location.parsed?.countryCode ||
                        raw.location.countryCode ||
                        null,
                    country: raw.location.parsed?.country || null,
                    state: raw.location.parsed?.state || null,
                    city: raw.location.parsed?.city || null,
                }
                : null,
            verified: raw.verified ?? null,
            topSkills: raw.topSkills || null,
            connectionsCount: raw.connectionsCount ?? null,
            followerCount: raw.followerCount ?? null,
            currentPosition: raw.currentPosition
                ? raw.currentPosition.map((p) => ({
                    companyName: p.companyName || null,
                }))
                : null,
            experience: raw.experience
                ? raw.experience.map((exp) => ({
                    position: exp.position || null,
                    location: exp.location || null,
                    employmentType: exp.employmentType || null,
                    workplaceType: exp.workplaceType || null,
                    companyName: exp.companyName || null,
                    companyLinkedinUrl: exp.companyLinkedinUrl || null,
                    duration: exp.duration || null,
                    description: exp.description || null,
                    skills: exp.skills || null,
                    startDate: exp.startDate
                        ? {
                            month: exp.startDate.month || null,
                            year: exp.startDate.year ?? null,
                            text: exp.startDate.text || null,
                        }
                        : null,
                    endDate: exp.endDate
                        ? {
                            month: exp.endDate.month || null,
                            year: exp.endDate.year ?? null,
                            text: exp.endDate.text || null,
                        }
                        : null,
                }))
                : null,
            education: raw.education
                ? raw.education.map((edu) => ({
                    schoolName: edu.schoolName || null,
                    schoolLinkedinUrl: edu.schoolLinkedinUrl || null,
                    degree: edu.degree || null,
                    fieldOfStudy: edu.fieldOfStudy || null,
                    startDate: edu.startDate
                        ? {
                            month: edu.startDate.month || null,
                            year: edu.startDate.year ?? null,
                            text: edu.startDate.text || null,
                        }
                        : null,
                    endDate: edu.endDate
                        ? {
                            month: edu.endDate.month || null,
                            year: edu.endDate.year ?? null,
                            text: edu.endDate.text || null,
                        }
                        : null,
                    period: edu.period || null,
                }))
                : null,
            certifications: raw.certifications
                ? raw.certifications.map((cert) => ({
                    title: cert.title || null,
                    issuedAt: cert.issuedAt || null,
                    issuedBy: cert.issuedBy || null,
                }))
                : null,
            languages: raw.languages
                ? raw.languages.map((lang) => ({
                    name: lang.name || null,
                    proficiency: lang.proficiency || null,
                }))
                : null,
            skills: raw.skills
                ? raw.skills.map((skill) => ({
                    name: skill.name || null,
                }))
                : null,
        };
        return {
            operation: 'scrapeProfile',
            profile,
            posts: [],
            totalPosts: 0,
            success: true,
            error: '',
        };
    }
    /**
     * Handle scrapePosts operation
     */
    async handleScrapePosts(params) {
        // Use Apify service to scrape LinkedIn posts
        const linkedinPostScraper = new ApifyBubble({
            actorId: 'apimaestro/linkedin-profile-posts',
            input: {
                username: params.username,
                limit: params.limit || 100,
                page_number: params.pageNumber || 1,
            },
            waitForFinish: true,
            limit: params.limit || 100,
            timeout: 180000, // 3 minutes
            credentials: params.credentials,
        }, this.context, 'linkedinPostScraper');
        const apifyResult = await linkedinPostScraper.action();
        if (!apifyResult.data.success) {
            return {
                operation: 'scrapePosts',
                posts: [],
                username: params.username,
                paginationToken: null,
                totalPosts: 0,
                success: false,
                error: apifyResult.data.error ||
                    'Failed to scrape LinkedIn posts. Please try again.',
            };
        }
        const items = apifyResult.data.items || [];
        // The actor returns posts directly in the items array
        if (items.length === 0) {
            return {
                operation: 'scrapePosts',
                posts: [],
                username: params.username,
                paginationToken: null,
                totalPosts: 0,
                success: false,
                error: 'No posts found. The profile may be private or have no public posts.',
            };
        }
        // Transform posts to unified format - items ARE the posts
        const posts = this.transformPosts(items);
        return {
            operation: 'scrapePosts',
            posts,
            username: params.username,
            paginationToken: null,
            totalPosts: posts.length,
            success: true,
            error: '',
        };
    }
    /**
     * Transform LinkedIn posts from Apify format to unified format
     */
    transformPosts(posts) {
        return posts.map((post) => ({
            urn: post.urn?.activity_urn ||
                post.urn?.share_urn ||
                post.urn?.ugcPost_urn ||
                null,
            fullUrn: post.full_urn || null,
            postedAt: post.posted_at
                ? {
                    date: post.posted_at.date || null,
                    relative: post.posted_at.relative || null,
                    timestamp: post.posted_at.timestamp || null,
                }
                : null,
            text: post.text || null,
            url: post.url || null,
            postType: post.post_type || null,
            author: post.author
                ? {
                    firstName: post.author.first_name || null,
                    lastName: post.author.last_name || null,
                    headline: post.author.headline || null,
                    username: post.author.username || null,
                    profileUrl: post.author.profile_url || null,
                    profilePicture: post.author.profile_picture || null,
                }
                : null,
            stats: post.stats
                ? {
                    totalReactions: post.stats.total_reactions || null,
                    like: post.stats.like || null,
                    support: post.stats.support || null,
                    love: post.stats.love || null,
                    insight: post.stats.insight || null,
                    celebrate: post.stats.celebrate || null,
                    funny: post.stats.funny || null,
                    comments: post.stats.comments || null,
                    reposts: post.stats.reposts || null,
                }
                : null,
            media: post.media
                ? {
                    type: post.media.type || null,
                    url: post.media.url || null,
                    thumbnail: post.media.thumbnail || null,
                    images: post.media.images
                        ? post.media.images.map((img) => ({
                            url: img.url || null,
                            width: img.width || null,
                            height: img.height || null,
                        }))
                        : null,
                }
                : null,
            article: post.article
                ? {
                    url: post.article.url || null,
                    title: post.article.title || null,
                    subtitle: post.article.subtitle || null,
                    thumbnail: post.article.thumbnail || null,
                }
                : null,
            document: post.document
                ? {
                    title: post.document.title || null,
                    pageCount: post.document.page_count || null,
                    url: post.document.url || null,
                    thumbnail: post.document.thumbnail || null,
                }
                : null,
            resharedPost: post.reshared_post
                ? {
                    urn: typeof post.reshared_post.urn === 'object'
                        ? post.reshared_post.urn?.activity_urn ||
                            post.reshared_post.urn?.ugcPost_urn ||
                            null
                        : post.reshared_post.urn || null,
                    postedAt: post.reshared_post.posted_at
                        ? {
                            date: post.reshared_post.posted_at.date || null,
                            relative: post.reshared_post.posted_at.relative || null,
                            timestamp: post.reshared_post.posted_at.timestamp || null,
                        }
                        : null,
                    text: post.reshared_post.text || null,
                    url: post.reshared_post.url || null,
                    postType: post.reshared_post.post_type || null,
                    author: post.reshared_post.author
                        ? {
                            firstName: post.reshared_post.author.first_name || null,
                            lastName: post.reshared_post.author.last_name || null,
                            headline: post.reshared_post.author.headline || null,
                            username: post.reshared_post.author.username || null,
                            profileUrl: post.reshared_post.author.profile_url || null,
                            profilePicture: post.reshared_post.author.profile_picture || null,
                        }
                        : null,
                    stats: post.reshared_post.stats
                        ? {
                            totalReactions: post.reshared_post.stats.total_reactions || null,
                            like: post.reshared_post.stats.like || null,
                            support: post.reshared_post.stats.support || null,
                            love: post.reshared_post.stats.love || null,
                            insight: post.reshared_post.stats.insight || null,
                            celebrate: post.reshared_post.stats.celebrate || null,
                            funny: post.reshared_post.stats.funny || null,
                            comments: post.reshared_post.stats.comments || null,
                            reposts: post.reshared_post.stats.reposts || null,
                        }
                        : null,
                    media: post.reshared_post.media
                        ? {
                            type: post.reshared_post.media.type || null,
                            url: post.reshared_post.media.url || null,
                            thumbnail: post.reshared_post.media.thumbnail || null,
                            images: null, // Reshared posts don't include multi-image data
                        }
                        : null,
                }
                : null,
        }));
    }
    /**
     * Handle searchPosts operation
     */
    async handleSearchPosts(params) {
        // Use Apify service to search LinkedIn posts
        const linkedinPostSearcher = new ApifyBubble({
            actorId: 'apimaestro/linkedin-posts-search-scraper-no-cookies',
            input: {
                keyword: params.keyword,
                sort_type: params.sortBy || 'relevance',
                date_filter: params.dateFilter || '',
                page_number: params.pageNumber || 1,
                limit: params.limit || 50,
            },
            waitForFinish: true,
            limit: params.limit || 50,
            timeout: 180000,
            credentials: params.credentials,
        }, this.context, 'linkedinPostSearcher');
        const apifyResult = await linkedinPostSearcher.action();
        if (!apifyResult.data.success) {
            return {
                operation: 'searchPosts',
                posts: [],
                keyword: params.keyword,
                totalResults: null,
                hasNextPage: null,
                totalPosts: 0,
                success: false,
                error: apifyResult.data.error ||
                    'Failed to search LinkedIn posts. Please try again.',
            };
        }
        const items = apifyResult.data.items || [];
        if (items.length === 0) {
            return {
                operation: 'searchPosts',
                posts: [],
                keyword: params.keyword,
                totalResults: 0,
                hasNextPage: false,
                totalPosts: 0,
                success: true,
                error: '',
            };
        }
        // Transform search results to unified format
        const posts = this.transformSearchResults(items);
        // Get metadata from first item (all items have the same metadata)
        const metadata = items[0].metadata;
        return {
            operation: 'searchPosts',
            posts,
            keyword: params.keyword,
            totalResults: metadata?.total_count || null,
            hasNextPage: metadata?.has_next_page || null,
            totalPosts: posts.length,
            success: true,
            error: '',
        };
    }
    /**
     * Transform search results to unified post format
     */
    transformSearchResults(items) {
        return items.map((item) => ({
            urn: item.activity_id || null,
            fullUrn: item.full_urn || null,
            postedAt: item.posted_at
                ? {
                    date: item.posted_at.date || null,
                    relative: item.posted_at.display_text || null,
                    timestamp: item.posted_at.timestamp || null,
                }
                : null,
            text: item.text || null,
            url: item.post_url || null,
            postType: item.is_reshare ? 'repost' : 'regular',
            author: item.author
                ? {
                    firstName: item.author.name?.split(' ')[0] || null,
                    lastName: item.author.name?.split(' ')[1] || null,
                    headline: item.author.headline || null,
                    username: item.author.profile_id || null,
                    profileUrl: item.author.profile_url || null,
                    profilePicture: item.author.image_url || null,
                }
                : null,
            stats: item.stats
                ? {
                    totalReactions: item.stats.total_reactions || null,
                    like: this.getReactionCount(item.stats.reactions, 'LIKE'),
                    support: this.getReactionCount(item.stats.reactions, 'EMPATHY'),
                    love: this.getReactionCount(item.stats.reactions || [], 'LOVE'),
                    insight: this.getReactionCount(item.stats.reactions || [], 'INTEREST'),
                    celebrate: this.getReactionCount(item.stats.reactions || [], 'PRAISE'),
                    funny: this.getReactionCount(item.stats.reactions || [], 'FUNNY'),
                    comments: item.stats.comments || null,
                    reposts: item.stats.shares || null,
                }
                : null,
            media: null, // Search results don't include detailed media info
            article: item.content?.type === 'article' && item.content.article
                ? {
                    url: item.content.article.url || null,
                    title: item.content.article.title || null,
                    subtitle: item.content.article.subtitle || null,
                    thumbnail: item.content.article.thumbnail || null,
                }
                : null,
            document: null, // Search results don't include document info
            resharedPost: null, // Search results don't include nested reshare details
        }));
    }
    /**
     * Helper to get reaction count by type from reactions array
     */
    getReactionCount(reactions, type) {
        if (!reactions || !reactions.length)
            return null;
        const reaction = reactions.find((r) => r.type === type);
        return reaction ? reaction.count || null : null;
    }
    /**
     * Handle scrapeJobs operation
     */
    async handleScrapeJobs(params) {
        if (!params.keyword) {
            return this.createErrorResult('Keyword is required for scrapeJobs');
        }
        // Construct LinkedIn jobs search URL
        const searchParams = new URLSearchParams();
        searchParams.set('keywords', params.keyword);
        if (params.location) {
            searchParams.set('location', params.location);
        }
        if (params.dateFilter) {
            // Map dateFilter to LinkedIn's format
            const dateMap = {
                'past-24h': 'r86400',
                'past-week': 'r604800',
                'past-month': 'r2592000',
            };
            if (dateMap[params.dateFilter]) {
                searchParams.set('f_TPR', dateMap[params.dateFilter]);
            }
        }
        if (params.experienceLevel && params.experienceLevel.length > 0) {
            const experienceMap = {
                internship: '1',
                'entry-level': '2',
                associate: '3',
                'mid-senior': '4',
                director: '6',
                executive: '7',
            };
            const experienceValues = params.experienceLevel
                .map((level) => experienceMap[level])
                .filter((v) => v !== undefined);
            if (experienceValues.length > 0) {
                searchParams.set('f_E', experienceValues.join(','));
            }
        }
        if (params.jobType && params.jobType.length > 0) {
            const jobTypeMap = {
                'full-time': 'F',
                'part-time': 'P',
                contract: 'C',
                temporary: 'T',
                internship: 'I',
            };
            const jobTypeValues = params.jobType
                .map((type) => jobTypeMap[type])
                .filter((v) => v !== undefined);
            if (jobTypeValues.length > 0) {
                searchParams.set('f_JT', jobTypeValues.join(','));
            }
        }
        if (params.workplaceType && params.workplaceType.length > 0) {
            const workplaceMap = {
                remote: '2',
                'on-site': '1',
                hybrid: '3',
            };
            const workplaceValues = params.workplaceType
                .map((type) => workplaceMap[type])
                .filter((v) => v !== undefined);
            if (workplaceValues.length > 0) {
                searchParams.set('f_WT', workplaceValues.join(','));
            }
        }
        searchParams.set('sort_by', 'date_posted');
        const searchUrl = `https://www.linkedin.com/jobs/search?${searchParams.toString()}`;
        const jobScraper = new ApifyBubble({
            actorId: 'curious_coder/linkedin-jobs-scraper',
            input: {
                urls: [searchUrl],
                count: params.limit || 100,
                scrapeCompany: true,
            },
            waitForFinish: true,
            limit: params.limit || 100,
            timeout: 240000,
            credentials: params.credentials,
        }, this.context, 'jobScraper');
        const apifyResult = await jobScraper.action();
        if (!apifyResult.data.success) {
            return {
                operation: 'scrapeJobs',
                posts: [],
                jobs: [],
                totalPosts: 0,
                success: false,
                error: apifyResult.data.error || 'Failed to scrape LinkedIn jobs',
            };
        }
        const items = apifyResult.data.items || [];
        const jobs = this.transformJobs(items);
        return {
            operation: 'scrapeJobs',
            posts: [],
            jobs,
            totalPosts: 0,
            success: true,
            error: '',
        };
    }
    transformJobs(items) {
        return items.map((item) => {
            // Parse salary from salaryInfo array (e.g., ["$105,910.00", "$178,000.00"])
            let salary = null;
            if (item.salaryInfo && item.salaryInfo.length > 0) {
                const salaryValues = item.salaryInfo
                    .map((s) => {
                    // Extract numeric value and currency from strings like "$105,910.00"
                    const match = s.match(/^([$€£¥]?)([\d,]+\.?\d*)/);
                    if (match) {
                        const currency = match[1] || 'USD';
                        const value = parseFloat(match[2].replace(/,/g, ''));
                        return { currency, value };
                    }
                    return null;
                })
                    .filter((v) => v !== null);
                if (salaryValues.length > 0) {
                    const from = salaryValues[0]?.value || null;
                    const to = salaryValues.length > 1 ? salaryValues[1]?.value : from;
                    const currency = salaryValues[0]?.currency || null;
                    salary = {
                        from,
                        to,
                        currency,
                        period: null, // Period not available in salaryInfo
                    };
                }
            }
            return {
                id: item.id || null,
                title: item.title || null,
                company: item.companyName || item.companyLinkedinUrl || item.companyLogo
                    ? {
                        name: item.companyName || null,
                        url: item.companyLinkedinUrl || null,
                        logo: item.companyLogo || null,
                    }
                    : null,
                location: item.location || null,
                description: item.descriptionText || item.descriptionHtml || null,
                employmentType: item.employmentType || null,
                seniorityLevel: item.seniorityLevel || null,
                postedAt: item.postedAt || null,
                url: item.link || null,
                applyUrl: item.applyUrl || null,
                salary,
                skills: null, // Skills not available in actual actor output
            };
        });
    }
}
//# sourceMappingURL=linkedin-tool.js.map