function label(entity) {
    const method = entity.data.method ?? '';
    const path = entity.data.path ?? '';
    return `${method} ${path}`.trim();
}
function href(entity) {
    return `/endpoints/${entity.slug}`;
}
function baseSingle(entity, ctx, includeDtos = true) {
    const dtos = includeDtos ? ctx.reader.findEndpointDtos(entity.id) : undefined;
    return {
        type: 'endpoint',
        slug: entity.slug,
        method: entity.data.method,
        path: entity.data.path,
        summary: entity.data.summary ?? '',
        description: entity.data.description ?? null,
        tags: entity.tags,
        ...(dtos !== undefined ? { dtos: formatDtos(dtos) } : {}),
    };
}
function formatDtos(dtos) {
    return dtos.map((d) => ({
        dtoSlug: d.dtoSlug,
        dtoName: d.dtoName,
        relation: d.relation,
        statusCode: d.statusCode,
    }));
}
function buildSnapshot(entity, ctx) {
    const dtos = ctx.reader.findEndpointDtos(entity.id);
    return {
        slug: entity.slug,
        method: entity.data.method,
        path: entity.data.path,
        summary: (entity.data.summary ?? '') || null,
        description: entity.data.description ?? null,
        linked_dtos: dtos
            .map((d) => ({
            dto_slug: d.dtoSlug,
            relation: d.relation,
            status_code: d.statusCode,
        }))
            .sort((a, b) => `${a.relation}:${a.dto_slug}:${a.status_code ?? ''}`.localeCompare(`${b.relation}:${b.dto_slug}:${b.status_code ?? ''}`)),
        tags: [...entity.tags].sort(),
    };
}
/** Defensive coercion of pre-M17 legacy rows (Endpoint domain object) to the
 *  EndpointSnapshot shape. Post-M17 rows already match the shape. */
function coerceEndpoint(raw) {
    const r = (raw ?? {});
    // Legacy: { dtos: [{ dtoSlug, relation, statusCode }] } → linked_dtos
    let linked_dtos = r.linked_dtos;
    if (!linked_dtos && Array.isArray(r.dtos)) {
        linked_dtos = r.dtos.map((d) => ({
            dto_slug: String(d.dtoSlug ?? d.dto_slug ?? ''),
            relation: String(d.relation ?? ''),
            status_code: (d.statusCode ?? d.status_code ?? null),
        }));
    }
    return {
        slug: String(r.slug ?? ''),
        method: String(r.method ?? ''),
        path: String(r.path ?? ''),
        summary: r.summary ?? null,
        description: r.description ?? null,
        linked_dtos: linked_dtos ?? [],
        tags: Array.isArray(r.tags) ? r.tags : [],
    };
}
function endpointDiff(a, b, slug) {
    if (a == null && b == null)
        return { type: 'endpoint', slug, op: 'noop' };
    if (a == null)
        return { type: 'endpoint', slug, op: 'created' };
    if (b == null)
        return { type: 'endpoint', slug, op: 'deleted' };
    const sa = coerceEndpoint(a);
    const sb = coerceEndpoint(b);
    const changes = {};
    const fieldChanges = [];
    for (const field of ['method', 'path', 'summary', 'description']) {
        if (sa[field] !== sb[field])
            fieldChanges.push({ field, from: sa[field], to: sb[field] });
    }
    if (fieldChanges.length)
        changes.field_changes = fieldChanges;
    // Junction diff by (relation, dto_slug)
    const keyOf = (l) => `${l.relation}|${l.dto_slug}`;
    const aMap = new Map(sa.linked_dtos.map((l) => [keyOf(l), l]));
    const bMap = new Map(sb.linked_dtos.map((l) => [keyOf(l), l]));
    const dtoAdded = [];
    const dtoRemoved = [];
    const statusChanged = [];
    for (const [k, link] of bMap) {
        if (!aMap.has(k))
            dtoAdded.push(link);
    }
    for (const [k, link] of aMap) {
        const other = bMap.get(k);
        if (!other) {
            dtoRemoved.push(link);
        }
        else if (other.status_code !== link.status_code) {
            statusChanged.push({
                dto_slug: link.dto_slug,
                relation: link.relation,
                from: link.status_code,
                to: other.status_code,
            });
        }
    }
    if (dtoAdded.length)
        changes.dto_added = dtoAdded;
    if (dtoRemoved.length)
        changes.dto_removed = dtoRemoved;
    if (statusChanged.length)
        changes.status_code_changed = statusChanged;
    // Tag diff (set semantics)
    const tagAdded = sb.tags.filter((t) => !sa.tags.includes(t));
    const tagRemoved = sa.tags.filter((t) => !sb.tags.includes(t));
    if (tagAdded.length)
        changes.tag_added = tagAdded;
    if (tagRemoved.length)
        changes.tag_removed = tagRemoved;
    if (Object.keys(changes).length === 0)
        return { type: 'endpoint', slug, op: 'noop' };
    return { type: 'endpoint', slug, op: 'modified', changes };
}
function endpointRestore(data, ctx) {
    const snap = data;
    const upsertResult = ctx.writer.upsertEndpoint(snap.slug, {
        method: snap.method,
        path: snap.path,
        summary: snap.summary ?? '',
        description: snap.description ?? undefined,
    }, ctx.actor);
    // Sync tags + junction (idempotent)
    ctx.writer.syncTags('endpoint', snap.slug, snap.tags);
    const junctionResult = ctx.writer.syncEndpointDtos(snap.slug, snap.linked_dtos.map((l) => ({
        dtoSlug: l.dto_slug,
        relation: l.relation,
        statusCode: l.status_code,
    })));
    const warnings = junctionResult.warnings.length ? junctionResult.warnings : undefined;
    return {
        op: upsertResult.op,
        entity: upsertResult.entity,
        ...(warnings ? { warnings } : {}),
    };
}
export const endpointSerializer = {
    type: 'endpoint',
    version: '1.0.0',
    inlineMention: (entity) => ({
        type: 'endpoint',
        slug: entity.slug,
        label: label(entity),
        href: href(entity),
    }),
    singleElement: (entity, ctx) => baseSingle(entity, ctx, true),
    elementListItem: (entity, ctx) => {
        const base = baseSingle(entity, ctx, true);
        const description = base.description ? base.description.split('\n')[0] : null;
        return { ...base, description };
    },
    taggedListItem: (entity, ctx) => {
        const base = baseSingle(entity, ctx, true);
        const description = base.description ? base.description.split('\n')[0] : null;
        return { ...base, description };
    },
    detail: (entity, ctx) => {
        const base = baseSingle(entity, ctx, true);
        const brokenRefs = [];
        const dtos = ctx.reader.findEndpointDtos(entity.id);
        const dtoObjects = dtos.map((link) => {
            const dto = ctx.reader.getEntity('dto', link.dtoSlug);
            if (!dto) {
                brokenRefs.push(`dto:${link.dtoSlug}`);
                return { ...link, dto: null };
            }
            if (ctx.depth >= ctx.maxDepth) {
                return { ...link, dto: { slug: dto.slug, name: dto.data.name, _truncated: true } };
            }
            return {
                ...link,
                dto: {
                    slug: dto.slug,
                    name: dto.data.name,
                    description: dto.data.description ?? null,
                    fields: dto.data.fields,
                    tags: dto.tags,
                },
            };
        });
        const references = ctx.reader.findSectionReferences('endpoint', entity.id);
        return {
            ...base,
            dtos: dtoObjects,
            _references: formatReferences(references),
            ...(brokenRefs.length ? { _brokenRefs: brokenRefs } : {}),
        };
    },
    // ─── M17 ───
    snapshot: (entity, ctx) => buildSnapshot(entity, ctx),
    restore: endpointRestore,
    diff: endpointDiff,
};
function formatReferences(refs) {
    return refs.map((r) => ({
        anchor: r.anchor,
        pagePath: r.pagePath,
        headingText: r.headingText,
        relation: r.relation,
    }));
}
//# sourceMappingURL=serializer.js.map