import { validateOrThrow } from './validation.js';
import { ExternalIssueError, } from './types.js';
function asRecord(value) {
    if (typeof value !== 'object' || value === null || Array.isArray(value)) {
        return {};
    }
    return value;
}
function asString(value) {
    if (typeof value !== 'string') {
        return undefined;
    }
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : undefined;
}
function asNullableString(value) {
    if (value === null || value === undefined) {
        return null;
    }
    return asString(value) ?? null;
}
function deriveProjectKeyFromExternalKey(externalKey) {
    if (!externalKey) {
        return undefined;
    }
    const [prefix] = externalKey.split('-');
    return prefix && prefix.trim().length > 0 ? prefix : undefined;
}
function ensureNormalized(source, candidate) {
    try {
        return validateOrThrow(candidate);
    }
    catch (error) {
        if (error instanceof ExternalIssueError && error.code === 'VALIDATION_FAILED') {
            throw new ExternalIssueError('NORMALIZE_FAILED', `Failed to normalize ${source} issue: ${error.message}`, source, error.validationErrors);
        }
        throw error;
    }
}
function getFetchByKeyOrThrow(source, fetchByKey) {
    if (fetchByKey) {
        return fetchByKey;
    }
    throw new ExternalIssueError('FETCH_FAILED', `No ${source} fetchByKey implementation configured`, source);
}
function getFetchByQueryOrThrow(source, fetchByQuery) {
    if (fetchByQuery) {
        return fetchByQuery;
    }
    throw new ExternalIssueError('FETCH_FAILED', `No ${source} fetchByQuery implementation configured`, source);
}
function normalizeLinearPriority(rawPriority) {
    if (rawPriority === null || rawPriority === undefined) {
        return null;
    }
    if (typeof rawPriority === 'number') {
        switch (rawPriority) {
            case 1:
                return 'P0';
            case 2:
                return 'P1';
            case 3:
                return 'P2';
            case 4:
                return 'P3';
            default:
                return null;
        }
    }
    if (typeof rawPriority === 'string') {
        const normalized = rawPriority.trim().toUpperCase();
        return normalized.length > 0 ? normalized : null;
    }
    return null;
}
function normalizeJiraPriority(rawPriority) {
    const name = typeof rawPriority === 'string'
        ? rawPriority
        : asString(asRecord(rawPriority).name);
    if (!name) {
        return null;
    }
    const normalized = name.trim().toLowerCase();
    if (normalized === 'highest' || normalized === 'blocker') {
        return 'P0';
    }
    if (normalized === 'high' || normalized === 'critical') {
        return 'P1';
    }
    if (normalized === 'medium') {
        return 'P2';
    }
    if (normalized === 'low' || normalized === 'lowest') {
        return 'P3';
    }
    return name.trim();
}
function normalizeLinearLabels(rawLabels) {
    if (!Array.isArray(rawLabels)) {
        return [];
    }
    return rawLabels
        .map((label) => {
        if (typeof label === 'string') {
            return asString(label);
        }
        if (typeof label === 'object' && label !== null) {
            return asString(label.name);
        }
        return undefined;
    })
        .filter((label) => typeof label === 'string');
}
function normalizeJiraLabels(rawLabels) {
    if (!Array.isArray(rawLabels)) {
        return [];
    }
    return rawLabels.filter((label) => typeof label === 'string' && label.trim().length > 0);
}
function extractAdfText(node) {
    if (typeof node === 'string') {
        return node;
    }
    if (!node || typeof node !== 'object') {
        return '';
    }
    const record = node;
    const parts = [];
    if (typeof record.text === 'string') {
        parts.push(record.text);
    }
    if (Array.isArray(record.content)) {
        for (const child of record.content) {
            const text = extractAdfText(child);
            if (text) {
                parts.push(text);
            }
        }
    }
    return parts.join(' ').trim();
}
function normalizeJiraDescription(rawDescription) {
    if (typeof rawDescription === 'string') {
        return rawDescription;
    }
    if (rawDescription === null || rawDescription === undefined) {
        return '';
    }
    return extractAdfText(rawDescription);
}
function deriveJiraUrl(raw, key) {
    const directUrl = asString(raw.url);
    if (directUrl) {
        return directUrl;
    }
    const selfUrl = asString(raw.self);
    if (!selfUrl || !key) {
        return undefined;
    }
    try {
        const parsed = new URL(selfUrl);
        return `${parsed.origin}/browse/${key}`;
    }
    catch {
        return undefined;
    }
}
export class LinearIssueAdapter {
    source = 'linear';
    fetchByKeyImpl;
    fetchByQueryImpl;
    constructor(fetchers = {}) {
        this.fetchByKeyImpl = fetchers.fetchByKey;
        this.fetchByQueryImpl = fetchers.fetchByQuery;
    }
    normalize(raw) {
        const data = asRecord(raw);
        const externalKey = asString(data.identifier);
        const envelope = {
            source: this.source,
            external_id: asString(data.id),
            external_key: externalKey,
            title: asString(data.title),
            description: typeof data.description === 'string' ? data.description : '',
            labels: normalizeLinearLabels(data.labels),
            priority: normalizeLinearPriority(data.priority),
            status: asString(asRecord(data.state).name) ?? asString(data.state),
            url: asString(data.url),
            project_key: asString(asRecord(data.team).key) ??
                asString(asRecord(data.project).key) ??
                deriveProjectKeyFromExternalKey(externalKey),
            assignee: asNullableString(asRecord(data.assignee).displayName) ??
                asNullableString(asRecord(data.assignee).name) ??
                asNullableString(asRecord(data.assignee).email),
            raw: data,
        };
        return ensureNormalized(this.source, envelope);
    }
    async fetchByKey(key) {
        const fetchIssueByKey = getFetchByKeyOrThrow(this.source, this.fetchByKeyImpl);
        const raw = await fetchIssueByKey(key);
        return this.normalize(raw);
    }
    async fetchByQuery(query) {
        const fetchIssuesByQuery = getFetchByQueryOrThrow(this.source, this.fetchByQueryImpl);
        const rawIssues = await fetchIssuesByQuery(query);
        return rawIssues.map((raw) => this.normalize(raw));
    }
    persistMapping(store, envelope, params) {
        return store.upsertMapping({
            provider: this.source,
            externalId: envelope.external_id,
            externalKey: envelope.external_key,
            canonicalUrl: envelope.url,
            latestStateSnapshot: {
                status: envelope.status,
                priority: envelope.priority,
                assignee: envelope.assignee,
                projectKey: envelope.project_key,
            },
            executionId: params?.executionId,
            prUrl: params?.prUrl,
            lastSyncedAt: params?.lastSyncedAt,
            lastSpawnedAt: params?.lastSpawnedAt,
        });
    }
    readMappingByExternalId(store, externalId) {
        return store.getByExternalId(this.source, externalId);
    }
    readMappingsByExecutionId(store, executionId) {
        return store.findByExecutionId(executionId).filter((mapping) => mapping.provider === this.source);
    }
}
export class JiraIssueAdapter {
    source = 'jira';
    fetchByKeyImpl;
    fetchByQueryImpl;
    constructor(fetchers = {}) {
        this.fetchByKeyImpl = fetchers.fetchByKey;
        this.fetchByQueryImpl = fetchers.fetchByQuery;
    }
    normalize(raw) {
        const data = asRecord(raw);
        const fields = asRecord(data.fields);
        const key = asString(data.key);
        const envelope = {
            source: this.source,
            external_id: asString(data.id),
            external_key: key,
            title: asString(fields.summary),
            description: normalizeJiraDescription(fields.description),
            labels: normalizeJiraLabels(fields.labels),
            priority: normalizeJiraPriority(fields.priority),
            status: asString(asRecord(fields.status).name),
            url: deriveJiraUrl(data, key),
            project_key: asString(asRecord(fields.project).key) ?? deriveProjectKeyFromExternalKey(key),
            assignee: asNullableString(asRecord(fields.assignee).displayName) ??
                asNullableString(asRecord(fields.assignee).emailAddress) ??
                asNullableString(asRecord(fields.assignee).accountId),
            item_type: asNullableString(asRecord(fields.issuetype).name),
            raw: data,
        };
        return ensureNormalized(this.source, envelope);
    }
    async fetchByKey(key) {
        const fetchIssueByKey = getFetchByKeyOrThrow(this.source, this.fetchByKeyImpl);
        const raw = await fetchIssueByKey(key);
        return this.normalize(raw);
    }
    async fetchByQuery(query) {
        const fetchIssuesByQuery = getFetchByQueryOrThrow(this.source, this.fetchByQueryImpl);
        const rawIssues = await fetchIssuesByQuery(query);
        return rawIssues.map((raw) => this.normalize(raw));
    }
    persistMapping(store, envelope, params) {
        return store.upsertMapping({
            provider: this.source,
            externalId: envelope.external_id,
            externalKey: envelope.external_key,
            canonicalUrl: envelope.url,
            latestStateSnapshot: {
                status: envelope.status,
                priority: envelope.priority,
                assignee: envelope.assignee,
                projectKey: envelope.project_key,
            },
            executionId: params?.executionId,
            prUrl: params?.prUrl,
            lastSyncedAt: params?.lastSyncedAt,
            lastSpawnedAt: params?.lastSpawnedAt,
        });
    }
    readMappingByExternalId(store, externalId) {
        return store.getByExternalId(this.source, externalId);
    }
    readMappingsByExecutionId(store, executionId) {
        return store.findByExecutionId(executionId).filter((mapping) => mapping.provider === this.source);
    }
}
//# sourceMappingURL=adapters.js.map