/**
 * X API plugin for OpenWriter.
 * Registers routes for checking X connection status and posting tweets.
 * Uses @xdevplatform/xdk with OAuth1 credentials from plugin config.
 */
import { Client, OAuth1 } from '@xdevplatform/xdk';
import { join, extname } from 'path';
import { readFileSync, existsSync } from 'fs';
import sharp from 'sharp';
import twitter from 'twitter-text';
const { parseTweet } = twitter;
function createXClient(config) {
    const apiKey = config['api-key'] || process.env.X_API_KEY || '';
    const apiSecret = config['api-secret'] || process.env.X_API_SECRET || '';
    const accessToken = config['access-token'] || process.env.X_ACCESS_TOKEN || '';
    const accessTokenSecret = config['access-token-secret'] || process.env.X_ACCESS_TOKEN_SECRET || '';
    if (!apiKey || !apiSecret || !accessToken || !accessTokenSecret)
        return null;
    const oauth1 = new OAuth1({
        apiKey,
        apiSecret,
        callback: 'oob',
        accessToken,
        accessTokenSecret,
    });
    return new Client({ oauth1 });
}
const plugin = {
    name: '@openwriter/plugin-x-api',
    version: '0.1.0',
    description: 'Post tweets from OpenWriter',
    category: 'social-media',
    configSchema: {
        'api-key': { type: 'string', env: 'X_API_KEY', description: 'X API Key' },
        'api-secret': { type: 'string', env: 'X_API_SECRET', description: 'X API Secret' },
        'access-token': { type: 'string', env: 'X_ACCESS_TOKEN', description: 'X Access Token' },
        'access-token-secret': { type: 'string', env: 'X_ACCESS_TOKEN_SECRET', description: 'X Access Token Secret' },
    },
    registerRoutes(ctx) {
        // GET /api/x/status — check if plugin is configured + authenticated
        ctx.app.get('/api/x/status', async (_req, res) => {
            try {
                const client = createXClient(ctx.config);
                if (!client) {
                    res.json({ connected: false });
                    return;
                }
                const me = await client.users.getMe();
                const username = me?.data?.username;
                res.json({ connected: true, username: username || undefined });
            }
            catch (err) {
                console.error('[X Plugin] Status check failed:', err.message);
                res.json({ connected: false, error: err.message });
            }
        });
        // POST /api/x/post — post a tweet (with optional media)
        ctx.app.post('/api/x/post', async (req, res) => {
            try {
                const { text, replyTo, quoteTweetId, mediaIds } = req.body;
                if ((!text || typeof text !== 'string') && (!Array.isArray(mediaIds) || mediaIds.length === 0)) {
                    res.status(400).json({ success: false, error: 'text or mediaIds is required' });
                    return;
                }
                if (mediaIds && (!Array.isArray(mediaIds) || mediaIds.length > 4)) {
                    res.status(400).json({ success: false, error: 'mediaIds must be an array of 1-4 IDs' });
                    return;
                }
                const client = createXClient(ctx.config);
                if (!client) {
                    res.status(400).json({ success: false, error: 'X API credentials not configured' });
                    return;
                }
                const body = {};
                if (text)
                    body.text = text;
                if (replyTo) {
                    body.reply = { inReplyToTweetId: replyTo };
                }
                if (quoteTweetId) {
                    body.quoteTweetId = quoteTweetId;
                }
                if (mediaIds && mediaIds.length > 0) {
                    body.media = { media_ids: mediaIds };
                }
                const result = await client.posts.create(body);
                const tweetId = result?.data?.id;
                const tweetUrl = tweetId ? `https://x.com/i/status/${tweetId}` : undefined;
                res.json({ success: true, tweetId, tweetUrl });
            }
            catch (err) {
                const detail = err.data ? JSON.stringify(err.data) : err.message;
                console.error('[X Plugin] Post failed:', detail);
                res.status(500).json({ success: false, error: detail });
            }
        });
        // POST /api/x/post-thread — post a full thread as a reply chain (with optional media per tweet)
        ctx.app.post('/api/x/post-thread', async (req, res) => {
            try {
                const { tweets, replyTo } = req.body;
                if (!Array.isArray(tweets) || tweets.length === 0) {
                    res.status(400).json({ success: false, error: 'tweets must be a non-empty array' });
                    return;
                }
                // Normalize: accept string[] or { text, mediaIds? }[]
                const normalized = tweets.map((t) => typeof t === 'string' ? { text: t, mediaIds: undefined } : t);
                // Validate character limits using X's weighted counting (emojis=2, URLs=23, CJK=2)
                const CHAR_LIMIT = 25000;
                const overLimit = normalized.map((t, i) => ({ i, len: parseTweet(t.text).weightedLength })).filter(x => x.len > CHAR_LIMIT);
                if (overLimit.length > 0) {
                    res.status(400).json({
                        success: false,
                        error: `${overLimit.length} tweet(s) exceed ${CHAR_LIMIT} chars: ${overLimit.map(x => `#${x.i + 1} (${x.len})`).join(', ')}`,
                    });
                    return;
                }
                // Validate mediaIds per tweet
                for (let i = 0; i < normalized.length; i++) {
                    const ids = normalized[i].mediaIds;
                    if (ids && (!Array.isArray(ids) || ids.length > 4)) {
                        res.status(400).json({ success: false, error: `Tweet ${i + 1}: mediaIds must be an array of 1-4 IDs` });
                        return;
                    }
                }
                const client = createXClient(ctx.config);
                if (!client) {
                    res.status(400).json({ success: false, error: 'X API credentials not configured' });
                    return;
                }
                const postedTweets = [];
                let previousTweetId = replyTo;
                for (let i = 0; i < normalized.length; i++) {
                    const { text, mediaIds } = normalized[i];
                    const body = {};
                    if (text)
                        body.text = text;
                    if (previousTweetId) {
                        body.reply = { inReplyToTweetId: previousTweetId };
                    }
                    if (mediaIds && mediaIds.length > 0) {
                        body.media = { media_ids: mediaIds };
                    }
                    const result = await client.posts.create(body);
                    const tweetId = result?.data?.id;
                    if (!tweetId) {
                        res.status(500).json({
                            success: false,
                            postedTweets,
                            failedAt: i,
                            error: `Tweet ${i + 1} posted but no ID returned`,
                        });
                        return;
                    }
                    postedTweets.push({ index: i, tweetId, text });
                    previousTweetId = tweetId;
                }
                // Build thread URL from first tweet
                const firstTweetId = postedTweets[0]?.tweetId;
                const threadUrl = firstTweetId ? `https://x.com/i/status/${firstTweetId}` : undefined;
                console.log(`[X Plugin] Thread posted: ${postedTweets.length} tweets, ${threadUrl}`);
                res.json({ success: true, postedTweets, threadUrl });
            }
            catch (err) {
                console.error('[X Plugin] Post thread failed:', err.message);
                res.status(500).json({ success: false, error: err.message });
            }
        });
        // POST /api/x/upload-media — upload a local /_images/ file for tweet attachment
        ctx.app.post('/api/x/upload-media', async (req, res) => {
            try {
                const { src } = req.body;
                if (!src || typeof src !== 'string') {
                    res.status(400).json({ success: false, error: 'src is required' });
                    return;
                }
                // Security: only allow /_images/ paths, no traversal
                if (!/^\/_images\/[^/\\]+$/.test(src)) {
                    res.status(400).json({ success: false, error: 'Invalid image path — must be /_images/<filename>' });
                    return;
                }
                const filename = src.replace('/_images/', '');
                const filePath = join(ctx.dataDir, '_images', filename);
                if (!existsSync(filePath)) {
                    res.status(404).json({ success: false, error: `Image not found: ${filename}` });
                    return;
                }
                const ext = extname(filename).toLowerCase();
                const mimeMap = {
                    '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg',
                    '.png': 'image/png', '.webp': 'image/webp',
                    '.gif': 'image/jpeg', '.bmp': 'image/bmp',
                    '.tiff': 'image/tiff', '.tif': 'image/tiff',
                };
                const mediaType = mimeMap[ext] || 'image/jpeg';
                const client = createXClient(ctx.config);
                if (!client) {
                    res.status(400).json({ success: false, error: 'X API credentials not configured' });
                    return;
                }
                let fileBuffer = readFileSync(filePath);
                let uploadType = mediaType;
                const origSize = fileBuffer.length;
                // Compress large images or PNGs to JPEG to stay under X API limits
                if (fileBuffer.length > 3 * 1024 * 1024 || ext === '.png') {
                    fileBuffer = Buffer.from(await sharp(fileBuffer).jpeg({ quality: 85 }).toBuffer());
                    uploadType = 'image/jpeg';
                    console.log(`[X Plugin] Compressed ${filename}: ${(origSize / 1024 / 1024).toFixed(2)}MB → ${(fileBuffer.length / 1024 / 1024).toFixed(2)}MB`);
                }
                console.log(`[X Plugin] Uploading ${filename}: ${(fileBuffer.length / 1024 / 1024).toFixed(2)}MB, type: ${uploadType}`);
                const mediaBase64 = fileBuffer.toString('base64');
                const uploadResult = await client.media.upload({
                    body: { media: mediaBase64, mediaCategory: 'tweet_image', mediaType: uploadType },
                });
                const mediaId = uploadResult?.data?.id
                    || uploadResult?.media_id_string;
                if (!mediaId) {
                    res.status(500).json({ success: false, error: 'Upload succeeded but no media ID returned' });
                    return;
                }
                console.log(`[X Plugin] Media uploaded: ${filename} → ${mediaId}`);
                res.json({ success: true, mediaId });
            }
            catch (err) {
                console.error('[X Plugin] Media upload failed:', err.message);
                if (err.response) {
                    try {
                        const body = await err.response.text();
                        console.error('[X Plugin] X API response:', err.response.status, body);
                    }
                    catch { /* ignore */ }
                }
                if (err.data)
                    console.error('[X Plugin] Error data:', JSON.stringify(err.data));
                console.error('[X Plugin] Full error:', JSON.stringify(err, Object.getOwnPropertyNames(err)));
                res.status(500).json({ success: false, error: err.message });
            }
        });
    },
};
export default plugin;
