import { cli, Strategy } from '@jackwener/opencli/registry';
import { AuthRequiredError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
import { TWITTER_BEARER_TOKEN } from './utils.js';
import { describeTwitterApiError } from './shared.js';

const LISTS_QUERY_ID = '78UbkyXwXBD98IgUWXOy9g';
const OPERATION_NAME = 'ListsManagementPageTimeline';

const FEATURES = {
    rweb_video_screen_enabled: false,
    profile_label_improvements_pcf_label_in_post_enabled: true,
    rweb_tipjar_consumption_enabled: true,
    verified_phone_label_enabled: false,
    creator_subscriptions_tweet_preview_api_enabled: true,
    responsive_web_graphql_timeline_navigation_enabled: true,
    responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
    premium_content_api_read_enabled: false,
    communities_web_enable_tweet_community_results_fetch: true,
    c9s_tweet_anatomy_moderator_badge_enabled: true,
    responsive_web_grok_analyze_button_fetch_trends_enabled: false,
    responsive_web_grok_analyze_post_followups_enabled: true,
    responsive_web_jetfuel_frame: false,
    responsive_web_grok_share_attachment_enabled: true,
    articles_preview_enabled: true,
    responsive_web_edit_tweet_api_enabled: true,
    graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
    view_counts_everywhere_api_enabled: true,
    longform_notetweets_consumption_enabled: true,
    responsive_web_twitter_article_tweet_consumption_enabled: true,
    tweet_awards_web_tipping_enabled: false,
    responsive_web_grok_show_grok_translated_post: false,
    responsive_web_grok_analysis_button_from_backend: false,
    creator_subscriptions_quote_tweet_preview_enabled: false,
    freedom_of_speech_not_reach_fetch_enabled: true,
    standardized_nudges_misinfo: true,
    tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
    longform_notetweets_rich_text_read_enabled: true,
    longform_notetweets_inline_media_enabled: true,
    responsive_web_grok_image_annotation_enabled: true,
    responsive_web_enhance_cards_enabled: false,
};

function buildUrl(queryId) {
    return `/i/api/graphql/${queryId}/${OPERATION_NAME}`
        + `?features=${encodeURIComponent(JSON.stringify(FEATURES))}`;
}

export function extractListEntry(entry, seen) {
    const list = entry?.content?.itemContent?.list
        || entry?.content?.list
        || entry?.item?.itemContent?.list;
    if (!list) return null;
    const id = list.id_str || list.id || '';
    if (!id || seen.has(id)) return null;
    seen.add(id);
    const mode = typeof list.mode === 'string' && /private/i.test(list.mode) ? 'private' : 'public';
    return {
        id: String(id),
        name: list.name || '',
        members: String(list.member_count ?? 0),
        followers: String(list.subscriber_count ?? 0),
        mode,
    };
}

// X 的 ListsManagementPageTimeline 把 /<user>/lists 整个页面的所有 section
// 都塞在同一个 TimelineAddEntries instruction 里，靠 entry.entryId 前缀区分：
//   - `owned-subscribed-list-module-*`  → 用户的 owned + subscribed list（要保留）
//   - `list-to-follow-module-*`         → "Discover new Lists" 算法推荐（要剔除）
//   - `cursor-*`                         → 分页游标（无 list 数据）
// 旧版 parser 忽略 entryId 一律下钻，导致推荐 list 被当成自建/订阅泄漏出来。
const OWNED_SUBSCRIBED_ENTRY_PREFIX = 'owned-subscribed-list-module-';

export function isOwnedSubscribedEntry(entry) {
    return typeof entry?.entryId === 'string'
        && entry.entryId.startsWith(OWNED_SUBSCRIBED_ENTRY_PREFIX);
}

export function getListsManagementInstructions(data) {
    const instructions = data?.data?.viewer?.list_management_timeline?.timeline?.instructions
        || data?.data?.viewer_v2?.user_results?.result?.list_management_timeline?.timeline?.instructions
        || data?.data?.list_management_timeline?.timeline?.instructions
        || data?.data?.data?.viewer?.list_management_timeline?.timeline?.instructions
        || data?.data?.data?.viewer_v2?.user_results?.result?.list_management_timeline?.timeline?.instructions
        || data?.data?.data?.list_management_timeline?.timeline?.instructions;
    return Array.isArray(instructions) ? instructions : null;
}

export function parseListsManagement(data, seen) {
    const lists = [];
    const instructions = getListsManagementInstructions(data) || [];
    for (const inst of instructions) {
        for (const entry of inst.entries || []) {
            if (!isOwnedSubscribedEntry(entry)) continue;
            const direct = extractListEntry(entry, seen);
            if (direct) {
                lists.push(direct);
                continue;
            }
            for (const item of entry?.content?.items || []) {
                const nested = extractListEntry(item, seen);
                if (nested) lists.push(nested);
            }
        }
    }
    return lists;
}

export const command = cli({
    site: 'twitter',
    name: 'lists',
    access: 'read',
    description: 'Get Twitter/X lists for the logged-in user (owned + subscribed)',
    domain: 'x.com',
    strategy: Strategy.COOKIE,
    browser: true,
    args: [
        { name: 'limit', type: 'int', default: 50, help: 'Maximum number of lists to return (default 50).' },
    ],
    columns: ['id', 'name', 'members', 'followers', 'mode'],
    func: async (page, kwargs) => {
        const limit = kwargs.limit || 50;
        const cookies = await page.getCookies({ url: 'https://x.com' });
        const ct0 = cookies.find((c) => c.name === 'ct0')?.value || null;
        if (!ct0)
            throw new AuthRequiredError('x.com', 'Not logged into x.com (no ct0 cookie)');
        // opencli >=1.7.x wraps primitive page.evaluate returns as { session, data: <value> }.
        const unwrap = (v) => (v && typeof v === 'object' && 'session' in v && 'data' in v ? v.data : v);
        const queryIdRaw = await page.evaluate(`async () => {
            try {
                const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
                if (ghResp.ok) {
                    const data = await ghResp.json();
                    const entry = data['${OPERATION_NAME}'];
                    if (entry && entry.queryId) return entry.queryId;
                }
            } catch {}
            try {
                const scripts = performance.getEntriesByType('resource')
                    .filter(r => r.name.includes('client-web') && r.name.endsWith('.js'))
                    .map(r => r.name);
                for (const scriptUrl of scripts.slice(0, 15)) {
                    try {
                        const text = await (await fetch(scriptUrl)).text();
                        const re = /queryId:"([A-Za-z0-9_-]+)"[^}]{0,200}operationName:"${OPERATION_NAME}"/;
                        const m = text.match(re);
                        if (m) return m[1];
                    } catch {}
                }
            } catch {}
            return null;
        }`);
        const queryId = unwrap(queryIdRaw) || LISTS_QUERY_ID;
        const headers = JSON.stringify({
            'Authorization': `Bearer ${decodeURIComponent(TWITTER_BEARER_TOKEN)}`,
            'X-Csrf-Token': ct0,
            'X-Twitter-Auth-Type': 'OAuth2Session',
            'X-Twitter-Active-User': 'yes',
        });
        const apiUrl = buildUrl(queryId);
        const data = await page.evaluate(`async () => {
            const r = await fetch(${JSON.stringify(apiUrl)}, { headers: ${headers}, credentials: 'include' });
            return r.ok ? await r.json() : { error: r.status };
        }`);
        if (data?.error) {
            throw new CommandExecutionError(describeTwitterApiError('ListsManagementPageTimeline', data.error));
        }
        const seen = new Set();
        if (!getListsManagementInstructions(data)) {
            throw new CommandExecutionError('Twitter lists returned an unexpected payload shape');
        }
        const lists = parseListsManagement(data, seen);
        if (lists.length === 0) {
            throw new EmptyResultError('twitter lists', 'No owned or subscribed lists found');
        }
        return lists.slice(0, limit);
    },
});
