import { SETTINGS_NAME_REGEX, SETTINGS_SCREEN_NAME_REGEX, SETTINGS_USER_ID_REGEX, TWITTER_API_BASE, } from './twitter-client-constants.js';
import { buildFollowingFeatures } from './twitter-client-features.js';
import { extractCursorFromInstructions, parseUsersFromInstructions } from './twitter-client-utils.js';
export function withUsers(Base) {
    class TwitterClientUsers extends Base {
        // biome-ignore lint/complexity/noUselessConstructor lint/suspicious/noExplicitAny: TS mixin constructor requirement.
        constructor(...args) {
            super(...args);
        }
        async getFollowingQueryIds() {
            const primary = await this.getQueryId('Following');
            return Array.from(new Set([primary, 'BEkNpEt5pNETESoqMsTEGA']));
        }
        async getFollowersQueryIds() {
            const primary = await this.getQueryId('Followers');
            return Array.from(new Set([primary, 'kuFUYP9eV1FPoEy4N-pi7w']));
        }
        parseUsersFromRestResponse(users) {
            return (users ?? [])
                .map((u) => {
                const id = typeof u.id_str === 'string' ? u.id_str : typeof u.id === 'number' ? String(u.id) : null;
                const username = typeof u.screen_name === 'string' ? u.screen_name : null;
                if (!id || !username) {
                    return null;
                }
                return {
                    id,
                    username,
                    name: typeof u.name === 'string' && u.name.length > 0 ? u.name : username,
                    description: typeof u.description === 'string' ? u.description : undefined,
                    followersCount: typeof u.followers_count === 'number' ? u.followers_count : undefined,
                    followingCount: typeof u.friends_count === 'number' ? u.friends_count : undefined,
                    isBlueVerified: typeof u.verified === 'boolean' ? u.verified : undefined,
                    profileImageUrl: typeof u.profile_image_url_https === 'string' ? u.profile_image_url_https : undefined,
                    createdAt: typeof u.created_at === 'string' ? u.created_at : undefined,
                };
            })
                .filter((u) => u !== null);
        }
        async getFollowersViaRest(userId, count, cursor) {
            const params = new URLSearchParams({
                user_id: userId,
                count: String(count),
                skip_status: 'true',
                include_user_entities: 'false',
            });
            if (cursor) {
                params.set('cursor', cursor);
            }
            const urls = [
                `https://x.com/i/api/1.1/followers/list.json?${params.toString()}`,
                `https://api.twitter.com/1.1/followers/list.json?${params.toString()}`,
            ];
            let lastError;
            for (const url of urls) {
                try {
                    const response = await this.fetchWithTimeout(url, {
                        method: 'GET',
                        headers: this.getHeaders(),
                    });
                    if (!response.ok) {
                        const text = await response.text();
                        lastError = `HTTP ${response.status}: ${text.slice(0, 200)}`;
                        continue;
                    }
                    const data = (await response.json());
                    const users = this.parseUsersFromRestResponse(data.users);
                    const nextCursor = data.next_cursor_str && data.next_cursor_str !== '0' ? data.next_cursor_str : undefined;
                    return { success: true, users, nextCursor };
                }
                catch (error) {
                    lastError = error instanceof Error ? error.message : String(error);
                }
            }
            return { success: false, error: lastError ?? 'Unknown error fetching followers' };
        }
        async getFollowingViaRest(userId, count, cursor) {
            const params = new URLSearchParams({
                user_id: userId,
                count: String(count),
                skip_status: 'true',
                include_user_entities: 'false',
            });
            if (cursor) {
                params.set('cursor', cursor);
            }
            const urls = [
                `https://x.com/i/api/1.1/friends/list.json?${params.toString()}`,
                `https://api.twitter.com/1.1/friends/list.json?${params.toString()}`,
            ];
            let lastError;
            for (const url of urls) {
                try {
                    const response = await this.fetchWithTimeout(url, {
                        method: 'GET',
                        headers: this.getHeaders(),
                    });
                    if (!response.ok) {
                        const text = await response.text();
                        lastError = `HTTP ${response.status}: ${text.slice(0, 200)}`;
                        continue;
                    }
                    const data = (await response.json());
                    const users = this.parseUsersFromRestResponse(data.users);
                    const nextCursor = data.next_cursor_str && data.next_cursor_str !== '0' ? data.next_cursor_str : undefined;
                    return { success: true, users, nextCursor };
                }
                catch (error) {
                    lastError = error instanceof Error ? error.message : String(error);
                }
            }
            return { success: false, error: lastError ?? 'Unknown error fetching following' };
        }
        /**
         * Fetch the account associated with the current cookies
         */
        async getCurrentUser() {
            const candidateUrls = [
                'https://x.com/i/api/account/settings.json',
                'https://api.twitter.com/1.1/account/settings.json',
                'https://x.com/i/api/account/verify_credentials.json?skip_status=true&include_entities=false',
                'https://api.twitter.com/1.1/account/verify_credentials.json?skip_status=true&include_entities=false',
            ];
            let lastError;
            for (const url of candidateUrls) {
                try {
                    const response = await this.fetchWithTimeout(url, {
                        method: 'GET',
                        headers: this.getHeaders(),
                    });
                    if (!response.ok) {
                        const text = await response.text();
                        lastError = `HTTP ${response.status}: ${text.slice(0, 200)}`;
                        continue;
                    }
                    // biome-ignore lint/suspicious/noExplicitAny: Twitter API response is dynamic here
                    let data;
                    try {
                        data = await response.json();
                    }
                    catch (error) {
                        lastError = error instanceof Error ? error.message : String(error);
                        continue;
                    }
                    const username = typeof data?.screen_name === 'string'
                        ? data.screen_name
                        : typeof data?.user?.screen_name === 'string'
                            ? data.user.screen_name
                            : null;
                    const name = typeof data?.name === 'string'
                        ? data.name
                        : typeof data?.user?.name === 'string'
                            ? data.user.name
                            : (username ?? '');
                    const userId = typeof data?.user_id === 'string'
                        ? data.user_id
                        : typeof data?.user_id_str === 'string'
                            ? data.user_id_str
                            : typeof data?.user?.id_str === 'string'
                                ? data.user.id_str
                                : typeof data?.user?.id === 'string'
                                    ? data.user.id
                                    : null;
                    if (username && userId) {
                        this.clientUserId = userId;
                        return {
                            success: true,
                            user: {
                                id: userId,
                                username,
                                name: name || username,
                            },
                        };
                    }
                    lastError = 'Could not determine current user from response';
                }
                catch (error) {
                    lastError = error instanceof Error ? error.message : String(error);
                }
            }
            // Fallback: scrape the authenticated settings page (HTML) for screen_name/user_id
            const profilePages = ['https://x.com/settings/account', 'https://twitter.com/settings/account'];
            for (const page of profilePages) {
                try {
                    const response = await this.fetchWithTimeout(page, {
                        headers: {
                            cookie: this.cookieHeader,
                            'user-agent': this.userAgent,
                        },
                    });
                    if (!response.ok) {
                        lastError = `HTTP ${response.status} (settings page)`;
                        continue;
                    }
                    const html = await response.text();
                    const usernameMatch = SETTINGS_SCREEN_NAME_REGEX.exec(html);
                    const idMatch = SETTINGS_USER_ID_REGEX.exec(html);
                    const nameMatch = SETTINGS_NAME_REGEX.exec(html);
                    const username = usernameMatch?.[1];
                    const userId = idMatch?.[1];
                    const name = nameMatch?.[1]?.replace(/\\"/g, '"');
                    if (username && userId) {
                        return {
                            success: true,
                            user: {
                                id: userId,
                                username,
                                name: name || username,
                            },
                        };
                    }
                    lastError = 'Could not parse settings page for user info';
                }
                catch (error) {
                    lastError = error instanceof Error ? error.message : String(error);
                }
            }
            return {
                success: false,
                error: lastError ?? 'Unknown error fetching current user',
            };
        }
        /**
         * Get users that a user is following
         */
        async getFollowing(userId, count = 20, cursor) {
            const variables = {
                userId,
                count,
                includePromotedContent: false,
            };
            if (cursor) {
                variables.cursor = cursor;
            }
            const features = buildFollowingFeatures();
            const params = new URLSearchParams({
                variables: JSON.stringify(variables),
                features: JSON.stringify(features),
            });
            const tryOnce = async () => {
                let lastError;
                let had404 = false;
                const queryIds = await this.getFollowingQueryIds();
                for (const queryId of queryIds) {
                    const url = `${TWITTER_API_BASE}/${queryId}/Following?${params.toString()}`;
                    try {
                        const response = await this.fetchWithTimeout(url, {
                            method: 'GET',
                            headers: this.getHeaders(),
                        });
                        if (response.status === 404) {
                            had404 = true;
                            lastError = `HTTP ${response.status}`;
                            continue;
                        }
                        if (!response.ok) {
                            const text = await response.text();
                            return { success: false, error: `HTTP ${response.status}: ${text.slice(0, 200)}`, had404 };
                        }
                        const data = (await response.json());
                        if (data.errors && data.errors.length > 0) {
                            return { success: false, error: data.errors.map((e) => e.message).join(', '), had404 };
                        }
                        const instructions = data.data?.user?.result?.timeline?.timeline?.instructions;
                        const users = parseUsersFromInstructions(instructions);
                        const nextCursor = extractCursorFromInstructions(instructions);
                        return { success: true, users, nextCursor, had404 };
                    }
                    catch (error) {
                        lastError = error instanceof Error ? error.message : String(error);
                    }
                }
                return { success: false, error: lastError ?? 'Unknown error fetching following', had404 };
            };
            const { result, refreshed } = await this.withRefreshedQueryIdsOn404(tryOnce);
            if (result.success) {
                return { success: true, users: result.users, nextCursor: result.nextCursor };
            }
            if (refreshed) {
                // GraphQL Following can also return 404 (queryId churn / endpoint flakiness).
                // Fallback to the internal v1.1 REST endpoint used by the web client (cookie-auth; no dev API key).
                const restAttempt = await this.getFollowingViaRest(userId, count, cursor);
                if (restAttempt.success) {
                    return restAttempt;
                }
            }
            return { success: false, error: result.error };
        }
        /**
         * Get users that follow a user
         */
        async getFollowers(userId, count = 20, cursor) {
            const variables = {
                userId,
                count,
                includePromotedContent: false,
            };
            if (cursor) {
                variables.cursor = cursor;
            }
            const features = buildFollowingFeatures();
            const params = new URLSearchParams({
                variables: JSON.stringify(variables),
                features: JSON.stringify(features),
            });
            const tryOnce = async () => {
                let lastError;
                let had404 = false;
                const queryIds = await this.getFollowersQueryIds();
                for (const queryId of queryIds) {
                    const url = `${TWITTER_API_BASE}/${queryId}/Followers?${params.toString()}`;
                    try {
                        const response = await this.fetchWithTimeout(url, {
                            method: 'GET',
                            headers: this.getHeaders(),
                        });
                        if (response.status === 404) {
                            had404 = true;
                            lastError = `HTTP ${response.status}`;
                            continue;
                        }
                        if (!response.ok) {
                            const text = await response.text();
                            return { success: false, error: `HTTP ${response.status}: ${text.slice(0, 200)}`, had404 };
                        }
                        const data = (await response.json());
                        if (data.errors && data.errors.length > 0) {
                            return { success: false, error: data.errors.map((e) => e.message).join(', '), had404 };
                        }
                        const instructions = data.data?.user?.result?.timeline?.timeline?.instructions;
                        const users = parseUsersFromInstructions(instructions);
                        const nextCursor = extractCursorFromInstructions(instructions);
                        return { success: true, users, nextCursor, had404 };
                    }
                    catch (error) {
                        lastError = error instanceof Error ? error.message : String(error);
                    }
                }
                return { success: false, error: lastError ?? 'Unknown error fetching followers', had404 };
            };
            const { result, refreshed } = await this.withRefreshedQueryIdsOn404(tryOnce);
            if (result.success) {
                return { success: true, users: result.users, nextCursor: result.nextCursor };
            }
            if (refreshed) {
                // GraphQL Followers regularly returns 404 (queryId churn / endpoint flakiness).
                // Fallback to the internal v1.1 REST endpoint used by the web client (cookie-auth; no dev API key).
                const restAttempt = await this.getFollowersViaRest(userId, count, cursor);
                if (restAttempt.success) {
                    return restAttempt;
                }
            }
            return { success: false, error: result.error };
        }
    }
    return TwitterClientUsers;
}
//# sourceMappingURL=twitter-client-users.js.map