import { apiFetch, type ApiSettings } from "@govoplan/core-webui"; export type AddressBookScope = "user" | "group" | "tenant" | "system"; export type AddressBook = { id: string; tenant_id?: string | null; scope_type: AddressBookScope; scope_id?: string | null; name: string; description?: string | null; source_kind: string; source_ref?: string | null; read_only: boolean; sync_status?: string | null; sync_error?: string | null; contact_count: number; deleted_at?: string | null; created_at: string; updated_at: string; }; export type AddressList = { id: string; tenant_id?: string | null; address_book_id: string; name: string; description?: string | null; source_kind: string; source_ref?: string | null; read_only: boolean; entry_count: number; deleted_at?: string | null; created_at: string; updated_at: string; }; export type ContactEmail = { id?: string; label?: string | null; email: string; original_email?: string; normalized_email?: string; provenance?: Record; quality_state?: ContactPointQualityState; quality_reason_code?: string | null; is_primary: boolean; }; export type ContactPhone = { id?: string; label?: string | null; phone: string; original_phone?: string; normalized_phone?: string; provenance?: Record; quality_state?: ContactPointQualityState; quality_reason_code?: string | null; is_primary: boolean; }; export type ContactPostalAddress = { id?: string; label?: string | null; street?: string | null; postal_code?: string | null; locality?: string | null; region?: string | null; country?: string | null; original_value?: Record; normalized_value?: Record; provenance?: Record; quality_state?: ContactPointQualityState; quality_reason_code?: string | null; is_primary: boolean; }; export type ContactPointQualityState = "valid" | "invalid" | "returned" | "stale" | "undeliverable"; export type ContactFieldProvenance = { id: string; contact_id: string; field_path: string; value?: unknown; source_kind: string; source_ref?: string | null; source_revision?: string | null; precedence: number; selected: boolean; reason_code: string; explanation?: string | null; visibility: "inherit" | "private" | "restricted" | "public"; merge_record_id?: string | null; created_by_account_id?: string | null; metadata: Record; created_at: string; }; export type Contact = { id: string; tenant_id?: string | null; address_book_id: string; display_name: string; given_name?: string | null; family_name?: string | null; organization?: string | null; role_title?: string | null; note?: string | null; tags: string[]; source_kind: string; source_ref?: string | null; source_payload_kind?: string | null; source_revision?: string | null; provenance: Record; emails: ContactEmail[]; phones: ContactPhone[]; postal_addresses: ContactPostalAddress[]; field_provenance?: ContactFieldProvenance[]; deleted_at?: string | null; created_at: string; updated_at: string; }; export type ContactPointQualityDecision = { id: string; tenant_id?: string | null; contact_id: string; channel: "email" | "phone" | "postal" | "internal_mail" | "portal"; contact_point_id?: string | null; state: ContactPointQualityState; reason_code: string; reason?: string | null; evidence_ref?: string | null; effective_from: string; effective_until?: string | null; created_by_account_id?: string | null; metadata: Record; created_at: string; updated_at: string; }; export type ContactDuplicateFeature = { code: string; label: string; weight: number; value: string; }; export type ContactDuplicateSuggestion = { left: Contact; right: Contact; score: number; confidence: "possible" | "likely" | "strong"; features: ContactDuplicateFeature[]; }; export type ContactDuplicateSuggestionList = { suggestions: ContactDuplicateSuggestion[]; scanned_contacts: number; candidate_pairs: number; truncated: boolean; }; export type ContactMergeRecord = { id: string; tenant_id?: string | null; address_book_id: string; winner_contact_id: string; loser_contact_ids: string[]; status: string; reason: string; survivorship: Record; decisions: Array>; before_hash: string; after_hash: string; created_by_account_id?: string | null; recovered_at?: string | null; recovered_by_account_id?: string | null; recovery_action?: string | null; recovery_reason?: string | null; provenance: Record; created_at: string; updated_at: string; }; export type AddressQualityCorrection = { contact_id: string; display_name: string; channel: "email" | "phone" | "postal" | "internal_mail" | "portal"; contact_point_id?: string | null; state: ContactPointQualityState; reason_code: string; reason?: string | null; effective_from: string; }; export type AddressQualitySummary = { contact_count: number; contact_point_count: number; quality_counts: Record; duplicate_suggestion_count: number; correction_count: number; corrections: AddressQualityCorrection[]; truncated: boolean; }; export type AddressDistributionChannel = "email" | "postal" | "internal_mail" | "portal"; export type AddressChannelDecision = | "allowed" | "opted_in" | "preferred" | "opted_out" | "suppressed" | "invalid" | "returned" | "temporarily_unavailable"; export type ContactChannelRule = { id: string; tenant_id?: string | null; contact_id: string; channel: AddressDistributionChannel; purpose?: string | null; contact_point_id?: string | null; decision: AddressChannelDecision; legal_basis?: string | null; evidence_ref?: string | null; reason?: string | null; preference_rank?: number | null; locale?: string | null; effective_from?: string | null; effective_until?: string | null; metadata: Record; created_by_account_id?: string | null; created_at: string; updated_at: string; }; export type ContactChannelRulePayload = { channel: AddressDistributionChannel; purpose?: string | null; contact_point_id?: string | null; decision: AddressChannelDecision; legal_basis?: string | null; evidence_ref?: string | null; reason?: string | null; preference_rank?: number | null; locale?: string | null; effective_from?: string | null; effective_until?: string | null; metadata?: Record; }; export type AddressBookCreatePayload = { scope_type: AddressBookScope; group_id?: string | null; name: string; description?: string | null; }; export type AddressBookUpdatePayload = { name?: string | null; description?: string | null; }; export type AddressListPayload = { name: string; description?: string | null; }; export type AddressListEntryPayload = { contact_id: string; contact_email_id?: string | null; contact_postal_address_id?: string | null; label?: string | null; }; export type AddressListEntry = { id: string; address_list_id: string; contact_id: string; contact_email_id?: string | null; contact_postal_address_id?: string | null; target_kind: string; label?: string | null; order_index: number; contact_display_name: string; email?: string | null; postal_address?: string | null; created_at: string; updated_at: string; }; export type ContactPayload = { display_name?: string | null; given_name?: string | null; family_name?: string | null; organization?: string | null; role_title?: string | null; note?: string | null; tags?: string[]; emails?: Array>; phones?: Array>; postal_addresses?: Array>; provenance?: Record; }; export type AddressBookWriteOperation = "create_contact" | "update_contact" | "delete_contact"; export type AddressBookWriteDecision = { address_book_id: string; address_book_label?: string | null; operation: string; allowed: boolean; reason: string; message: string; scope_type?: AddressBookScope | null; scope_id?: string | null; tenant_id?: string | null; source_kind?: string | null; read_only: boolean; required_scopes: string[]; provenance: Record; }; type AddressBookListResponse = { address_books: AddressBook[]; }; type AddressListListResponse = { address_lists: AddressList[]; }; type AddressListEntryListResponse = { entries: AddressListEntry[]; }; export type ContactListResponse = { contacts: Contact[]; total: number; offset: number; limit: number; has_more: boolean; }; type AddressBookWriteTargetsResponse = { targets: AddressBookWriteDecision[]; }; export type VCardImportResult = { imported: number; skipped: number; contacts: Contact[]; issues: Array<{ index: number; message: string; severity: "warning" | "error"; field?: string | null; line?: number | null }>; }; export type AddressSyncSource = { id: string; tenant_id?: string | null; address_book_id: string; connector_type: string; display_name: string; external_account_ref?: string | null; external_address_book_ref?: string | null; sync_direction: string; read_only: boolean; enabled: boolean; status: string; sync_token?: string | null; etag?: string | null; remote_revision?: string | null; last_attempted_at?: string | null; last_success_at?: string | null; last_error?: string | null; last_diagnostic?: Record | null; metadata: Record; created_at: string; updated_at: string; }; export type AddressCardDavAddressBook = { collection_url: string; href: string; display_name?: string | null; description?: string | null; ctag?: string | null; sync_token?: string | null; }; export type AddressCredentialEnvelope = { id: string; scope_type: string; scope_id?: string | null; name: string; description?: string | null; credential_kind: string; public_data: Record; secret_keys: string[]; secret_configured: boolean; allowed_modules: string[]; inherit_to_lower_scopes: boolean; is_active: boolean; revision: string; }; export type AddressSyncPlanStats = { created: number; updated: number; deleted: number; conflicts: number; unchanged: number; errors: number; fetched: number; full_sync: boolean; used_sync_token: boolean; sync_token?: string | null; etag?: string | null; remote_revision?: string | null; }; export type AddressSyncPlanItem = { action: "create" | "update" | "delete" | "remote_create" | "remote_update" | "remote_delete" | "conflict" | "unchanged" | "error"; href?: string | null; remote_uid?: string | null; contact_id?: string | null; display_name?: string | null; etag?: string | null; message?: string | null; }; export type AddressSyncPlan = { sync_source: AddressSyncSource; stats: AddressSyncPlanStats; items: AddressSyncPlanItem[]; }; export type AddressSyncDiagnostic = { id: string; tenant_id?: string | null; sync_source_id: string; severity: string; code: string; message: string; details: Record; created_at: string; updated_at: string; }; export type AddressSyncTombstone = { id: string; tenant_id?: string | null; sync_source_id: string; address_book_id: string; contact_id?: string | null; remote_uid?: string | null; resource_href?: string | null; local_deleted_at?: string | null; remote_deleted_at?: string | null; synced_at?: string | null; metadata: Record; created_at: string; updated_at: string; }; export type AddressSyncConflict = { id: string; tenant_id?: string | null; sync_source_id: string; address_book_id: string; contact_id?: string | null; remote_uid?: string | null; resource_href?: string | null; field_path: string; local_value?: Record | null; remote_value?: Record | null; local_updated_at?: string | null; remote_updated_at?: string | null; status: string; resolution?: string | null; resolved_at?: string | null; resolved_by_account_id?: string | null; metadata: Record; created_at: string; updated_at: string; }; export type AddressImportConfiguration = { field_mappings: Record; delimiter: "," | ";" | "\t" | "|"; encoding: "utf-8" | "utf-8-sig" | "cp1252" | "latin-1"; header_row: number; sheet_name?: string | null; source_key_column?: string | null; duplicate_source_key_policy: "reject" | "first" | "last"; existing_contact_policy: "update" | "ignore" | "reject"; blank_value_policy: "ignore" | "clear" | "reject"; locale?: string | null; default_tags: string[]; max_rows: number; }; export type AddressImportProfile = { id: string; profile_key: string; version: number; tenant_id?: string | null; scope_type: AddressBookScope; scope_id?: string | null; name: string; description?: string | null; source_format: "csv" | "xlsx"; configuration: AddressImportConfiguration; is_current: boolean; created_at: string; updated_at: string; }; export type AddressImportEffect = { row_number: number; action: "create" | "update" | "conflict" | "unchanged" | "ignored"; source_key?: string | null; contact_id?: string | null; display_name?: string | null; changed_fields: string[]; message?: string | null; }; export type AddressImportDiagnostic = { severity: "info" | "warning" | "error"; code: string; message: string; row_number?: number | null; field?: string | null; details: Record; }; export type AddressImportRun = { id: string; address_book_id: string; profile_id: string; source_filename: string; source_format: string; input_hash: string; plan_hash: string; status: string; row_count: number; statistics: Record; diagnostics: AddressImportDiagnostic[]; effects: AddressImportEffect[]; can_apply: boolean; result_evidence: Record; created_at: string; updated_at: string; applied_at?: string | null; rolled_back_at?: string | null; }; export type AddressLdapSourcePayload = { url: string; credential_ref?: string | null; bind_dn?: string | null; start_tls: boolean; connect_timeout?: number; receive_timeout?: number; display_name: string; base_dn: string; search_filter: string; page_size: number; max_entries: number; attribute_map: Record; }; type AddressSyncSourceListResponse = { sync_sources: AddressSyncSource[]; }; type AddressCardDavDiscoveryResponse = { address_books: AddressCardDavAddressBook[]; }; type AddressCredentialEnvelopeListResponse = { credentials: AddressCredentialEnvelope[]; }; type AddressSyncDiagnosticListResponse = { diagnostics: AddressSyncDiagnostic[]; }; type AddressSyncTombstoneListResponse = { tombstones: AddressSyncTombstone[]; }; type AddressSyncConflictListResponse = { conflicts: AddressSyncConflict[]; }; type AddressImportProfileListResponse = { profiles: AddressImportProfile[]; }; type AddressLdapDiscoveryResponse = { base_dns: string[]; }; type ContactChannelRuleListResponse = { rules: ContactChannelRule[]; }; type ContactPointQualityDecisionListResponse = { decisions: ContactPointQualityDecision[]; }; type ContactMergeRecordListResponse = { merges: ContactMergeRecord[]; }; function queryString(params: Record): string { const search = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (value !== null && value !== undefined && value !== "") search.set(key, String(value)); } const serialized = search.toString(); return serialized ? `?${serialized}` : ""; } export async function listAddressBooks(settings: ApiSettings, options: {includeDeleted?: boolean;} = {}): Promise { const response = await apiFetch( settings, `/api/v1/addresses/address-books${queryString({ include_deleted: options.includeDeleted ? "true" : null })}` ); return response.address_books; } export function createAddressBook(settings: ApiSettings, payload: AddressBookCreatePayload): Promise { return apiFetch(settings, "/api/v1/addresses/address-books", { method: "POST", body: JSON.stringify(payload) }); } export function updateAddressBook(settings: ApiSettings, bookId: string, payload: AddressBookUpdatePayload): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${bookId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteAddressBook(settings: ApiSettings, bookId: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${bookId}`, { method: "DELETE" }); } export function restoreAddressBook(settings: ApiSettings, bookId: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${bookId}/restore`, { method: "POST" }); } export async function listAddressLists( settings: ApiSettings, options: { addressBookId?: string | null; includeDeleted?: boolean } = {} ): Promise { const response = await apiFetch( settings, `/api/v1/addresses/address-lists${queryString({ address_book_id: options.addressBookId, include_deleted: options.includeDeleted ? "true" : null })}` ); return response.address_lists; } export function createAddressList(settings: ApiSettings, addressBookId: string, payload: AddressListPayload): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/address-lists`, { method: "POST", body: JSON.stringify(payload) }); } export function updateAddressList(settings: ApiSettings, addressListId: string, payload: Partial): Promise { return apiFetch(settings, `/api/v1/addresses/address-lists/${addressListId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteAddressList(settings: ApiSettings, addressListId: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-lists/${addressListId}`, { method: "DELETE" }); } export function restoreAddressList(settings: ApiSettings, addressListId: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-lists/${addressListId}/restore`, { method: "POST" }); } export async function listAddressListEntries(settings: ApiSettings, addressListId: string): Promise { const response = await apiFetch(settings, `/api/v1/addresses/address-lists/${addressListId}/entries`); return response.entries; } export function createAddressListEntry( settings: ApiSettings, addressListId: string, payload: AddressListEntryPayload ): Promise { return apiFetch(settings, `/api/v1/addresses/address-lists/${addressListId}/entries`, { method: "POST", body: JSON.stringify(payload) }); } export function deleteAddressListEntry(settings: ApiSettings, entryId: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-list-entries/${entryId}`, { method: "DELETE" }); } export async function listAddressBookWriteTargets( settings: ApiSettings, operation: AddressBookWriteOperation = "create_contact" ): Promise { const response = await apiFetch( settings, `/api/v1/addresses/write-targets${queryString({ operation })}` ); return response.targets; } export function getAddressBookWriteDecision( settings: ApiSettings, addressBookId: string, operation: AddressBookWriteOperation = "create_contact" ): Promise { return apiFetch( settings, `/api/v1/addresses/address-books/${addressBookId}/write-decision${queryString({ operation })}` ); } export async function listAddressSyncSources( settings: ApiSettings, options: { addressBookId?: string | null; includeDisabled?: boolean } = {} ): Promise { const response = await apiFetch( settings, `/api/v1/addresses/sync-sources${queryString({ address_book_id: options.addressBookId, include_disabled: options.includeDisabled ? "true" : null })}` ); return response.sync_sources; } export function discoverCardDavAddressBooks( settings: ApiSettings, payload: { url: string; auth_type: "none" | "basic" | "bearer"; username?: string | null; password?: string | null; bearer_token?: string | null; credential_ref?: string | null; } ): Promise { return apiFetch(settings, "/api/v1/addresses/carddav/discover", { method: "POST", body: JSON.stringify(payload) }).then((response) => response.address_books); } export function createCardDavSyncSource( settings: ApiSettings, addressBookId: string, payload: { collection_url: string; display_name?: string | null; auth_type: "none" | "basic" | "bearer"; username?: string | null; password?: string | null; bearer_token?: string | null; credential_ref?: string | null; sync_direction: "read_only" | "import" | "export" | "two_way"; read_only?: boolean | null; sync_token?: string | null; etag?: string | null; remote_revision?: string | null; } ): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/carddav/sources`, { method: "POST", body: JSON.stringify(payload) }); } export function discoverLdapBaseDns( settings: ApiSettings, payload: Pick ): Promise { return apiFetch(settings, "/api/v1/addresses/ldap/discover", { method: "POST", body: JSON.stringify(payload) }).then((response) => response.base_dns); } export function createLdapSyncSource( settings: ApiSettings, addressBookId: string, payload: AddressLdapSourcePayload ): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/ldap/sources`, { method: "POST", body: JSON.stringify(payload) }); } export async function listAddressCredentials( settings: ApiSettings, sourceId?: string | null ): Promise { const response = await apiFetch( settings, `/api/v1/addresses/credentials${queryString({ source_id: sourceId })}` ); return response.credentials; } export function updateAddressSyncSource( settings: ApiSettings, syncSourceId: string, payload: Partial<{ display_name: string | null; external_account_ref: string | null; external_address_book_ref: string | null; sync_direction: "read_only" | "import" | "export" | "two_way"; read_only: boolean | null; enabled: boolean | null; sync_token: string | null; etag: string | null; remote_revision: string | null; metadata: Record | null; }> ): Promise { return apiFetch(settings, `/api/v1/addresses/sync-sources/${syncSourceId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteAddressSyncSource(settings: ApiSettings, syncSourceId: string): Promise { return apiFetch(settings, `/api/v1/addresses/sync-sources/${syncSourceId}`, { method: "DELETE" }); } export function previewAddressSyncSource(settings: ApiSettings, syncSourceId: string, options: { forceFull?: boolean } = {}): Promise { return apiFetch(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/preview`, { method: "POST", body: JSON.stringify({ force_full: Boolean(options.forceFull) }) }); } export function runAddressSyncSource(settings: ApiSettings, syncSourceId: string, options: { forceFull?: boolean } = {}): Promise { return apiFetch(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/run`, { method: "POST", body: JSON.stringify({ force_full: Boolean(options.forceFull) }) }); } export async function listAddressSyncDiagnostics(settings: ApiSettings, syncSourceId: string): Promise { const response = await apiFetch(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/diagnostics`); return response.diagnostics; } export async function listAddressSyncTombstones(settings: ApiSettings, syncSourceId: string): Promise { const response = await apiFetch(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/tombstones`); return response.tombstones; } export async function listAddressSyncConflicts(settings: ApiSettings, syncSourceId: string, status = "open"): Promise { const response = await apiFetch(settings, `/api/v1/addresses/sync-sources/${syncSourceId}/conflicts${queryString({ status })}`); return response.conflicts; } export function resolveAddressSyncConflict( settings: ApiSettings, conflictId: string, resolution = "manual", status = "resolved", options: { mergedPayload?: Record | null } = {} ): Promise { return apiFetch(settings, `/api/v1/addresses/sync-conflicts/${conflictId}/resolve`, { method: "POST", body: JSON.stringify({ resolution, status, merged_payload: options.mergedPayload ?? null }) }); } export function listContactsPage(settings: ApiSettings, options: {addressBookId?: string | null;addressListId?: string | null;query?: string | null;limit?: number;offset?: number;includeDeleted?: boolean;} = {}): Promise { return apiFetch( settings, `/api/v1/addresses/contacts${queryString({ address_book_id: options.addressBookId, address_list_id: options.addressListId, query: options.query, limit: options.limit, offset: options.offset, include_deleted: options.includeDeleted ? "true" : null })}` ); } export async function listContacts(settings: ApiSettings, options: {addressBookId?: string | null;addressListId?: string | null;query?: string | null;limit?: number;offset?: number;includeDeleted?: boolean;} = {}): Promise { const response = await listContactsPage(settings, options); return response.contacts; } export function createContact(settings: ApiSettings, addressBookId: string, payload: ContactPayload): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/contacts`, { method: "POST", body: JSON.stringify(payload) }); } export function updateContact(settings: ApiSettings, contactId: string, payload: ContactPayload): Promise { return apiFetch(settings, `/api/v1/addresses/contacts/${contactId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function deleteContact(settings: ApiSettings, contactId: string): Promise { return apiFetch(settings, `/api/v1/addresses/contacts/${contactId}`, { method: "DELETE" }); } export function restoreContact(settings: ApiSettings, contactId: string): Promise { return apiFetch(settings, `/api/v1/addresses/contacts/${contactId}/restore`, { method: "POST" }); } export function getAddressQualitySummary(settings: ApiSettings, addressBookId: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/quality-summary`); } export function listContactDuplicateSuggestions( settings: ApiSettings, addressBookId: string, options: { contactId?: string | null; minimumScore?: number; limit?: number; scanLimit?: number } = {} ): Promise { return apiFetch( settings, `/api/v1/addresses/address-books/${addressBookId}/duplicate-suggestions${queryString({ contact_id: options.contactId, minimum_score: options.minimumScore, limit: options.limit, scan_limit: options.scanLimit })}` ); } export async function listContactQualityDecisions(settings: ApiSettings, contactId: string): Promise { const response = await apiFetch( settings, `/api/v1/addresses/contacts/${contactId}/quality-decisions` ); return response.decisions; } export function createContactQualityDecision( settings: ApiSettings, contactId: string, payload: { channel: ContactPointQualityDecision["channel"]; contact_point_id?: string | null; state: ContactPointQualityState; reason_code?: string | null; reason?: string | null; evidence_ref?: string | null; } ): Promise { return apiFetch(settings, `/api/v1/addresses/contacts/${contactId}/quality-decisions`, { method: "POST", body: JSON.stringify(payload) }); } export function listContactProvenance( settings: ApiSettings, contactId: string, options: { currentOnly?: boolean; limit?: number } = {} ): Promise { return apiFetch( settings, `/api/v1/addresses/contacts/${contactId}/provenance${queryString({ current_only: options.currentOnly ? "true" : null, limit: options.limit })}` ); } export async function listContactMerges( settings: ApiSettings, options: { addressBookId?: string | null; contactId?: string | null; limit?: number } = {} ): Promise { const response = await apiFetch( settings, `/api/v1/addresses/contact-merges${queryString({ address_book_id: options.addressBookId, contact_id: options.contactId, limit: options.limit })}` ); return response.merges; } export function mergeContacts( settings: ApiSettings, payload: { winner_contact_id: string; duplicate_contact_ids: string[]; reason: string; field_sources?: Record; contact_point_strategy?: "union" | "winner_only"; source_precedence?: string[]; } ): Promise { return apiFetch(settings, "/api/v1/addresses/contact-merges", { method: "POST", body: JSON.stringify(payload) }); } export function recoverContactMerge( settings: ApiSettings, merge: ContactMergeRecord, action: "undo" | "split", reason: string ): Promise { return apiFetch(settings, `/api/v1/addresses/contact-merges/${merge.id}/${action}`, { method: "POST", body: JSON.stringify({ reason, expected_after_hash: merge.after_hash }) }); } export async function listContactChannelRules(settings: ApiSettings, contactId: string): Promise { const response = await apiFetch( settings, `/api/v1/addresses/contacts/${contactId}/channel-rules` ); return response.rules; } export function createContactChannelRule( settings: ApiSettings, contactId: string, payload: ContactChannelRulePayload ): Promise { return apiFetch(settings, `/api/v1/addresses/contacts/${contactId}/channel-rules`, { method: "POST", body: JSON.stringify(payload) }); } export function endContactChannelRule(settings: ApiSettings, ruleId: string): Promise { return apiFetch(settings, `/api/v1/addresses/contact-channel-rules/${ruleId}`, { method: "DELETE" }); } export function importAddressBookVcards(settings: ApiSettings, addressBookId: string, content: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/vcards/import`, { method: "POST", body: JSON.stringify({ content }) }); } export async function listAddressImportProfiles(settings: ApiSettings): Promise { const response = await apiFetch(settings, "/api/v1/addresses/import-profiles"); return response.profiles; } export function createAddressImportProfile( settings: ApiSettings, payload: { scope_type: AddressBookScope; scope_id?: string | null; name: string; description?: string | null; source_format: "csv" | "xlsx"; configuration: AddressImportConfiguration; } ): Promise { return apiFetch(settings, "/api/v1/addresses/import-profiles", { method: "POST", body: JSON.stringify(payload) }); } export function updateAddressImportProfile( settings: ApiSettings, profileId: string, payload: { name?: string; description?: string | null; configuration?: AddressImportConfiguration } ): Promise { return apiFetch(settings, `/api/v1/addresses/import-profiles/${profileId}`, { method: "PATCH", body: JSON.stringify(payload) }); } export function previewAddressImport( settings: ApiSettings, addressBookId: string, payload: { profile_id: string; filename: string; content_base64: string } ): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/imports/preview`, { method: "POST", body: JSON.stringify(payload) }); } export function applyAddressImport(settings: ApiSettings, run: AddressImportRun): Promise { return apiFetch(settings, `/api/v1/addresses/imports/${run.id}/apply`, { method: "POST", body: JSON.stringify({ expected_plan_hash: run.plan_hash }) }); } export function rollbackAddressImport(settings: ApiSettings, runId: string, reason: string): Promise { return apiFetch(settings, `/api/v1/addresses/imports/${runId}/rollback`, { method: "POST", body: JSON.stringify({ reason }) }); } export function exportAddressBookVcards(settings: ApiSettings, addressBookId: string): Promise { return apiFetch(settings, `/api/v1/addresses/address-books/${addressBookId}/vcards/export`); } export function exportContactVcard(settings: ApiSettings, contactId: string): Promise { return apiFetch(settings, `/api/v1/addresses/contacts/${contactId}/vcard`); }