import { ExternalIssueAdapterError, toNormalizedEnvelope, } from './types.js';
function ensureTrelloConfig(config) {
    const apiKey = config.apiKey || process.env.PRLT_TRELLO_API_KEY || process.env.TRELLO_API_KEY;
    const apiToken = config.apiToken || process.env.PRLT_TRELLO_API_TOKEN || process.env.TRELLO_API_TOKEN;
    const boardId = config.boardId || process.env.PRLT_TRELLO_BOARD_ID;
    if (!apiKey) {
        throw new ExternalIssueAdapterError('MISSING_CONFIG', 'Missing Trello API key. Set TRELLO_API_KEY or PRLT_TRELLO_API_KEY, or run "prlt trello configure".');
    }
    if (!apiToken) {
        throw new ExternalIssueAdapterError('MISSING_CONFIG', 'Missing Trello API token. Set TRELLO_API_TOKEN or PRLT_TRELLO_API_TOKEN, or run "prlt trello configure".');
    }
    return { apiKey, apiToken, boardId: boardId || undefined };
}
function ensureCardShape(card) {
    if (!card.id || !card.name || !card.url) {
        throw new ExternalIssueAdapterError('BAD_PAYLOAD', 'Trello card payload is missing required fields (id, name, url).', card);
    }
}
function deriveTrelloLabels(card) {
    if (!Array.isArray(card.labels)) {
        return [];
    }
    return card.labels
        .map(label => label.name?.trim())
        .filter((name) => Boolean(name));
}
function deriveTrelloPriority(labels) {
    const priorityLabel = labels.find(l => /^P[0-3]$/i.test(l));
    return priorityLabel ? priorityLabel.toUpperCase() : null;
}
function deriveTrelloAssignee(card) {
    const members = card.members;
    if (Array.isArray(members) && members.length > 0) {
        return members[0]?.fullName || members[0]?.username || null;
    }
    return null;
}
function deriveCategory(_card) {
    return 'feature';
}
/**
 * Normalize a raw Trello API card into a canonical IssueEnvelope.
 */
export function normalizeTrelloCard(rawCard, listName) {
    if (!rawCard || typeof rawCard !== 'object') {
        throw new ExternalIssueAdapterError('BAD_PAYLOAD', 'Trello card payload is invalid.', rawCard);
    }
    const card = rawCard;
    ensureCardShape(card);
    const labels = deriveTrelloLabels(card);
    return {
        source: 'trello',
        external_id: card.id,
        external_key: card.id,
        title: card.name,
        description: card.desc || '',
        labels,
        priority: deriveTrelloPriority(labels),
        status: listName ?? (card.closed ? 'Closed' : 'Open'),
        url: card.url,
        project_key: card.idBoard || 'DEFAULT',
        assignee: deriveTrelloAssignee(card),
        item_type: 'card',
        raw: rawCard,
    };
}
/**
 * Normalize a raw Trello card into a PMO-ready NormalizedIssueEnvelope.
 */
export function normalizeTrelloCardToEnvelope(rawCard, listName) {
    const envelope = normalizeTrelloCard(rawCard, listName);
    const card = rawCard;
    return toNormalizedEnvelope(envelope, deriveCategory(card));
}
/**
 * Build a PMO ticket description from a NormalizedIssueEnvelope.
 */
export function buildTrelloTicketDescription(envelope) {
    const body = envelope.description.trim();
    const metadataLines = [
        `- Source: ${envelope.source.name}`,
        `- External key: ${envelope.source.externalKey}`,
        `- External id: ${envelope.source.externalId}`,
        `- URL: ${envelope.source.url}`,
        `- Status: ${envelope.status}`,
        `- Priority: ${envelope.priority || 'Unset'}`,
        `- Labels: ${envelope.labels.length > 0 ? envelope.labels.join(', ') : 'None'}`,
    ];
    const parts = [
        body,
        '## External Issue Context',
        metadataLines.join('\n'),
    ].filter(Boolean);
    return parts.join('\n\n');
}
/**
 * Build ticket metadata from a NormalizedIssueEnvelope for traceability.
 */
export function buildTrelloMetadata(envelope) {
    return {
        external_source: envelope.source.name,
        external_key: envelope.source.externalKey,
        external_id: envelope.source.externalId,
        external_url: envelope.source.url,
        external_raw: JSON.stringify(envelope.source.raw),
    };
}
/**
 * Build a spawn context message from a NormalizedIssueEnvelope.
 */
export function buildTrelloSpawnContextMessage(envelope, additionalMessage) {
    const lines = [
        `External issue source: ${envelope.source.name}`,
        `External issue key: ${envelope.source.externalKey}`,
        `External issue id: ${envelope.source.externalId}`,
        `External issue URL: ${envelope.source.url}`,
    ];
    if (additionalMessage?.trim()) {
        lines.push('', additionalMessage.trim());
    }
    return lines.join('\n');
}
/**
 * Build a CLI command string for selecting a specific Trello card.
 */
export function buildTrelloCardChoiceCommand(cardId, projectId) {
    let command = `prlt work start --from trello:${cardId} --json`;
    if (projectId) {
        command += ` -P ${projectId}`;
    }
    return command;
}
/**
 * Fetch the list name for a card's idList.
 */
async function fetchListName(config, listId, options) {
    const fetchImpl = options?.fetchImpl || fetch;
    try {
        const url = `https://api.trello.com/1/lists/${listId}?key=${encodeURIComponent(config.apiKey)}&token=${encodeURIComponent(config.apiToken)}&fields=name`;
        const response = await fetchImpl(url);
        if (!response.ok)
            return undefined;
        const list = await response.json();
        return list.name ?? undefined;
    }
    catch {
        return undefined;
    }
}
/**
 * Fetch a single Trello card by its ID and normalize it.
 */
export async function getTrelloCardById(configInput, cardId, options) {
    const config = ensureTrelloConfig(configInput);
    const fetchImpl = options?.fetchImpl || fetch;
    const url = `https://api.trello.com/1/cards/${encodeURIComponent(cardId)}?key=${encodeURIComponent(config.apiKey)}&token=${encodeURIComponent(config.apiToken)}&fields=id,name,desc,url,shortUrl,idBoard,idList,idMembers,closed,due,dueComplete&members=true&member_fields=fullName,username&labels=all`;
    const response = await fetchImpl(url);
    if (response.status === 401 || response.status === 403) {
        throw new ExternalIssueAdapterError('AUTH_FAILED', 'Trello authentication failed. Verify your API key and token.');
    }
    if (response.status === 429) {
        throw new ExternalIssueAdapterError('RATE_LIMITED', 'Trello API rate limit exceeded. Wait a moment and try again.');
    }
    if (response.status === 404) {
        return null;
    }
    if (!response.ok) {
        throw new ExternalIssueAdapterError('REQUEST_FAILED', `Trello request failed with status ${response.status}.`);
    }
    const payload = await response.json();
    // Resolve list name for status
    const listName = payload.idList
        ? await fetchListName(config, payload.idList, { fetchImpl })
        : undefined;
    return normalizeTrelloCardToEnvelope(payload, listName);
}
/**
 * Check if Trello is configured via environment variables.
 */
export function isTrelloConfiguredFromEnv(config) {
    const apiKey = config?.apiKey
        || process.env.PRLT_TRELLO_API_KEY
        || process.env.TRELLO_API_KEY;
    const apiToken = config?.apiToken
        || process.env.PRLT_TRELLO_API_TOKEN
        || process.env.TRELLO_API_TOKEN;
    return Boolean(apiKey && apiToken);
}
/**
 * Fetch and normalize Trello cards from a board into NormalizedIssueEnvelopes.
 */
export async function listTrelloCards(configInput, options) {
    const config = ensureTrelloConfig(configInput);
    const fetchImpl = options?.fetchImpl || fetch;
    const limit = Math.max(1, Math.min(options?.limit ?? 20, 100));
    if (!config.boardId && !options?.query) {
        throw new ExternalIssueAdapterError('MISSING_CONFIG', 'Missing Trello board ID. Run "prlt trello configure" and select a board, or set PRLT_TRELLO_BOARD_ID.');
    }
    // If query, use search endpoint
    if (options?.query) {
        const url = `https://api.trello.com/1/search?key=${encodeURIComponent(config.apiKey)}&token=${encodeURIComponent(config.apiToken)}&query=${encodeURIComponent(options.query)}&modelTypes=cards&cards_limit=${limit}&card_fields=id,name,desc,url,shortUrl,idBoard,idList,idMembers,closed,due,dueComplete&card_members=true&card_member_fields=fullName,username&card_labels=all`;
        const response = await fetchImpl(url);
        if (response.status === 401 || response.status === 403) {
            throw new ExternalIssueAdapterError('AUTH_FAILED', 'Trello authentication failed. Verify your API key and token.');
        }
        if (!response.ok) {
            throw new ExternalIssueAdapterError('REQUEST_FAILED', `Trello request failed with status ${response.status}.`);
        }
        const payload = await response.json();
        const cards = payload.cards ?? [];
        return cards.slice(0, limit).map(card => normalizeTrelloCardToEnvelope(card));
    }
    // Otherwise, list from board
    const url = `https://api.trello.com/1/boards/${encodeURIComponent(config.boardId)}/cards/open?key=${encodeURIComponent(config.apiKey)}&token=${encodeURIComponent(config.apiToken)}&fields=id,name,desc,url,shortUrl,idBoard,idList,idMembers,closed,due,dueComplete&members=true&member_fields=fullName,username&labels=all`;
    const response = await fetchImpl(url);
    if (response.status === 401 || response.status === 403) {
        throw new ExternalIssueAdapterError('AUTH_FAILED', 'Trello authentication failed. Verify your API key and token.');
    }
    if (!response.ok) {
        throw new ExternalIssueAdapterError('REQUEST_FAILED', `Trello request failed with status ${response.status}.`);
    }
    const cards = await response.json();
    if (!Array.isArray(cards)) {
        throw new ExternalIssueAdapterError('BAD_PAYLOAD', 'Trello response payload was missing cards array.', cards);
    }
    return cards.slice(0, limit).map(card => normalizeTrelloCardToEnvelope(card));
}
/**
 * Import a Trello card into PMO as a linked ticket.
 */
export async function importTrelloCardToPmo(storage, projectId, envelope) {
    const tickets = await storage.listTickets(projectId);
    const existing = tickets.find((ticket) => {
        const source = ticket.metadata?.external_source;
        const key = ticket.metadata?.external_key;
        const id = ticket.metadata?.external_id;
        return source === 'trello'
            && (key === envelope.source.externalKey || id === envelope.source.externalId);
    });
    const description = buildTrelloTicketDescription(envelope);
    const metadata = buildTrelloMetadata(envelope);
    if (existing) {
        await storage.updateTicket(existing.id, {
            title: envelope.title,
            description,
            priority: envelope.priority ?? undefined,
            category: envelope.category ?? undefined,
            labels: envelope.labels,
            metadata: {
                ...existing.metadata,
                ...metadata,
            },
        });
        return { ticketId: existing.id, created: false };
    }
    const ticket = await storage.createTicket(projectId, {
        title: envelope.title,
        description,
        priority: envelope.priority ?? undefined,
        category: envelope.category ?? undefined,
        labels: envelope.labels,
        metadata,
    });
    return { ticketId: ticket.id, created: true };
}
//# sourceMappingURL=trello.js.map