import { apiFetch, apiPath, apiPostJson, apiUrl, authHeaders, type ApiSettings } from "@govoplan/core-webui"; export type PostboxAccessDecision = { allowed: boolean; action: string; postbox_id: string; reason_code: string; explanation: string; organization_unit_id?: string | null; function_id?: string | null; assignment_ids: string[]; assignment_sources: string[]; selected_assignment_id?: string | null; holder_count: number; vacant: boolean; classification: string; classification_allowed: boolean; binding_status: string; }; export type PostboxDirectoryItem = { id: string; tenant_id: string; address: string; address_key: string; name: string; status: string; classification: string; organization_unit_id?: string | null; organization_unit_name?: string | null; function_id?: string | null; function_name?: string | null; context_key?: string | null; template_revision_id?: string | null; holder_count: number; vacant: boolean; encryption_profile: PostboxProtectionProfileId; key_epoch: number; encryption_vault_id?: string | null; protection_policy: PostboxProtectionPolicy; grouping_policy: PostboxGroupingPolicy; access?: PostboxAccessDecision | null; resource_revision: number; etag: string; }; export type PostboxProtectionProfileId = | "plaintext_v1" | "server_envelope_v1" | "external_e2ee_v1"; export type PostboxProtectionPolicy = { new_incumbent_history: "all_retained" | "since_assignment" | "bounded_days"; history_days?: number | null; ordinary_rotation: "rewrap" | "reencrypt"; compromise_rotation: "rewrap" | "reencrypt"; recovery_authority: "disabled" | "user_consent" | "institutional_key_holders" | "dual_control"; recovery_quorum: number; handover_authority: "user_consent" | "institutional_key_holders" | "dual_control"; handover_quorum: number; emergency_access: "disabled" | "dual_control"; emergency_quorum: number; export_authority: "user_consent" | "institutional_key_holders" | "dual_control"; export_quorum: number; destruction_authority: "institutional_key_holders" | "dual_control"; destruction_quorum: number; external_recipient_assurance: "disabled" | "email_otp" | "strong_identity"; vacancy_escalation_content_access: "metadata_only"; }; export type PostboxGroupingPolicy = { mode: "allow" | "same_classification" | "separate"; reason?: string | null; }; export type PostboxProtectionProfile = { id: PostboxProtectionProfileId; label: string; description: string; server_can_decrypt: boolean; requires_encryption_module: boolean; requires_external_client: boolean; available: boolean; standard: boolean; }; export type PostboxProtectionTransition = { id: string; postbox_id: string; source_profile: string; target_profile: string; source_vault_id?: string | null; target_vault_id?: string | null; history_mode: string; authority_mode: string; required_quorum: number; evidence_refs: string[]; reason: string; state: string; message_count: number; completed_count: number; failed_count: number; requested_by?: string | null; activated_at?: string | null; completed_at?: string | null; resource_revision: number; etag: string; configuration_snapshot: Record; items: Array<{ id: string; message_id: string; source_profile: string; target_profile: string; state: string; source_digest?: string | null; target_digest?: string | null; error_code?: string | null; }>; }; export type PostboxParticipant = { kind: string; reference_type: string; reference_id?: string | null; label?: string | null; address?: string | null; }; export type PostboxAttachment = { reference_type: string; reference_id: string; name?: string | null; media_type?: string | null; size_bytes?: number | null; digest?: string | null; metadata: Record; }; export type PostboxAttachmentResolution = PostboxAttachment & { available: boolean; reason_code: string; file_asset_id?: string | null; file_version_id?: string | null; download_path?: string | null; provenance: Record; }; export type PostboxMessage = { id: string; tenant_id: string; postbox_id: string; subject: string; body_text?: string | null; status: string; availability: "available" | "withdrawn" | "expired"; classification: string; sender_label?: string | null; delivered_at: string; read_at?: string | null; acknowledged_at?: string | null; expires_at?: string | null; withdrawn_at?: string | null; producer_module?: string | null; producer_resource_type?: string | null; producer_resource_id?: string | null; in_reply_to_message_id?: string | null; replaces_message_id?: string | null; encryption_profile: PostboxProtectionProfileId; key_epoch: number; ciphertext_ref?: string | null; signed_manifest_ref?: string | null; wrapped_keys: Array<{ recipient_type: string; recipient_id: string; key_epoch: number; wrapped_key_ref: string; algorithm?: string | null; metadata: Record; }>; external_recipient_tokens: Array<{ token_id: string; state: "pending" | "available" | "fetched" | "expired" | "revoked"; expires_at?: string | null; one_time: boolean; key_fetched_at?: string | null; revoked_at?: string | null; assurance_profile?: string | null; metadata: Record; }>; participants: PostboxParticipant[]; attachments: PostboxAttachment[]; metadata: Record; }; export type PostboxGrouping = { id: string; name: string; is_default: boolean; postbox_ids: string[]; total_count: number; unread_count: number; constraints: Array<{ code: "source_requires_separation" | "classification_separation_required"; mode: PostboxGroupingPolicy["mode"]; postbox_id: string; reason?: string | null; enforced_by: "postbox_configuration"; }>; resource_revision: number; etag: string; created_at: string; updated_at: string; }; export type PostboxOrganizationFunction = { id: string; slug: string; name: string; function_type_id?: string | null; delegable: boolean; act_in_place_allowed: boolean; }; export type PostboxOrganizationUnit = { id: string; slug: string; name: string; unit_type_id?: string | null; parent_id?: string | null; functions: PostboxOrganizationFunction[]; }; export type PostboxOrganizationRelationType = { id: string; slug: string; name: string; structure_id?: string | null; is_hierarchical: boolean; status: string; }; export type PostboxOrganizationStructure = { id: string; slug: string; name: string; structure_kind: string; status: string; relation_types: PostboxOrganizationRelationType[]; }; export type PostboxOrganizationTargets = { units: PostboxOrganizationUnit[]; structures: PostboxOrganizationStructure[]; }; export type PostboxRoutingPolicy = { linked_copy: { enabled: boolean; structure_id?: string | null; relation_type_ids: string[]; max_depth: number; stop_unit_id?: string | null; stop_unit_type_id?: string | null; target_function_type_id?: string | null; target_template_id?: string | null; fanout: "nearest" | "all"; allowed_classifications: string[]; allowed_producer_modules: string[]; require_expiry: boolean; max_retention_days?: number | null; }; attention: { mode: "none" | "vacancy_escalation"; delay_minutes?: number | null; }; shared_visibility: { mode: "none"; }; }; export type PostboxTemplateRevision = { id: string; revision: number; function_type_id?: string | null; scope_kind: "tenant" | "unit" | "subtree" | "unit_type"; scope_id?: string | null; scope_structure_id?: string | null; scope_relation_type_ids: string[]; name_pattern: string; address_pattern: string; classification: string; allow_vacant_delivery: boolean; portal_visible: boolean; encryption_profile: string; encryption_vault_id?: string | null; protection_policy: PostboxProtectionPolicy; grouping_policy: PostboxGroupingPolicy; history_policy: Record; routing_policy: PostboxRoutingPolicy; retention_policy: Record; published_at?: string | null; created_at: string; }; export type PostboxTemplate = { id: string; tenant_id: string; slug: string; name: string; description?: string | null; status: string; current_revision: number; resource_revision: number; etag: string; published_revision_id?: string | null; revisions: PostboxTemplateRevision[]; created_at: string; updated_at: string; }; export type PostboxTemplateRevisionPayload = Pick< PostboxTemplateRevision, | "function_type_id" | "scope_kind" | "scope_id" | "scope_structure_id" | "scope_relation_type_ids" | "name_pattern" | "address_pattern" | "classification" | "allow_vacant_delivery" | "portal_visible" | "encryption_profile" | "encryption_vault_id" | "protection_policy" | "grouping_policy" | "routing_policy" >; export type PostboxTemplateCreatePayload = PostboxTemplateRevisionPayload & { slug: string; name: string; description?: string | null; }; export type PostboxTemplatePreviewTarget = { organization_unit_id: string; organization_unit_name: string; function_id: string; function_name: string; address: string; name: string; holder_count: number; vacant: boolean; status: string; existing_postbox_id?: string | null; diagnostics: string[]; }; export type PostboxTemplatePreview = { targets: PostboxTemplatePreviewTarget[]; total: number; ready_count: number; existing_count: number; vacant_count: number; blocked_count: number; truncated: boolean; diagnostics: string[]; }; export type PostboxExactCreatePayload = { name: string; description?: string | null; organization_unit_id: string; function_id: string; address_key?: string | null; classification: string; portal_visible: boolean; encryption_profile: PostboxProtectionProfileId; encryption_vault_id?: string | null; protection_policy: PostboxProtectionPolicy; grouping_policy: PostboxGroupingPolicy; }; export type PostboxMessageAuthoringPayload = { idempotency_key: string; subject: string; body_text?: string | null; ciphertext_ref?: string | null; signed_manifest_ref?: string | null; wrapped_keys?: PostboxMessage["wrapped_keys"]; classification: string; participants: PostboxParticipant[]; attachments: PostboxAttachment[]; metadata: Record; }; type PostboxGroupingPayload = Pick< PostboxGrouping, "name" | "is_default" | "postbox_ids" >; export async function listPostboxes(settings: ApiSettings): Promise { const response = await apiFetch<{ postboxes: PostboxDirectoryItem[] }>( settings, "/api/v1/postbox/directory" ); return response.postboxes; } export async function listPostboxMessages( settings: ApiSettings, postboxIds: string[], limit = 100, offset = 0, query = "", state: "all" | "unread" | "read" | "acknowledged" = "all" ): Promise<{ messages: PostboxMessage[]; total: number; limit: number; offset: number }> { return apiFetch( settings, apiPath("/api/v1/postbox/messages", { postbox_id: postboxIds, limit, offset, q: query || undefined, state }) ); } export function getPostboxMessage( settings: ApiSettings, messageId: string ): Promise { return apiFetch(settings, `/api/v1/postbox/messages/${encodeURIComponent(messageId)}`); } export async function resolvePostboxAttachments( settings: ApiSettings, messageId: string ): Promise { const response = await apiFetch<{ attachments: PostboxAttachmentResolution[] }>( settings, `/api/v1/postbox/messages/${encodeURIComponent(messageId)}/attachment-resolutions` ); return response.attachments; } export async function downloadPostboxAttachment( settings: ApiSettings, attachment: PostboxAttachmentResolution ): Promise { if (!attachment.available || !attachment.download_path) { throw new Error("This attachment payload is not available."); } const response = await fetch(apiUrl(settings, attachment.download_path), { headers: authHeaders(settings), credentials: "include" }); if (!response.ok) { throw new Error(`Attachment download failed (${response.status}).`); } const objectUrl = URL.createObjectURL(await response.blob()); const link = document.createElement("a"); link.href = objectUrl; link.download = attachment.name || attachment.reference_id; document.body.appendChild(link); link.click(); link.remove(); URL.revokeObjectURL(objectUrl); } export function markPostboxMessage( settings: ApiSettings, messageId: string, state: "read" | "acknowledged" ): Promise { return apiFetch( settings, `/api/v1/postbox/messages/${encodeURIComponent(messageId)}/state`, { method: "PATCH", body: JSON.stringify({ state }) } ); } export function createPostboxMessage( settings: ApiSettings, postboxId: string, payload: PostboxMessageAuthoringPayload ): Promise { return apiPostJson(settings, "/api/v1/postbox/messages", { ...payload, postbox_id: postboxId }); } export function replyToPostboxMessage( settings: ApiSettings, messageId: string, payload: PostboxMessageAuthoringPayload ): Promise { return apiPostJson( settings, `/api/v1/postbox/messages/${encodeURIComponent(messageId)}/replies`, payload ); } export async function listPostboxGroupings(settings: ApiSettings): Promise { const response = await apiFetch<{ groupings: PostboxGrouping[] }>( settings, "/api/v1/postbox/groupings" ); return response.groupings; } export function createPostboxGrouping( settings: ApiSettings, payload: PostboxGroupingPayload ): Promise { return apiPostJson(settings, "/api/v1/postbox/groupings", payload); } export function updatePostboxGrouping( settings: ApiSettings, grouping: PostboxGrouping, payload: PostboxGroupingPayload ): Promise { return apiFetch( settings, `/api/v1/postbox/groupings/${encodeURIComponent(grouping.id)}`, { method: "PUT", headers: { "If-Match": grouping.etag }, body: JSON.stringify({ ...payload, base_revision: grouping.resource_revision }) } ); } export function deletePostboxGrouping( settings: ApiSettings, grouping: PostboxGrouping ): Promise { return apiFetch( settings, `/api/v1/postbox/groupings/${encodeURIComponent(grouping.id)}`, { method: "DELETE", headers: { "If-Match": grouping.etag }, body: JSON.stringify({ base_revision: grouping.resource_revision }) } ); } export async function listAdminPostboxes(settings: ApiSettings): Promise { const response = await apiFetch<{ postboxes: PostboxDirectoryItem[] }>( settings, "/api/v1/postbox/admin/postboxes" ); return response.postboxes; } export async function listPostboxProtectionProfiles( settings: ApiSettings ): Promise<{ standard_profile: PostboxProtectionProfileId; profiles: PostboxProtectionProfile[] }> { return apiFetch(settings, "/api/v1/postbox/admin/protection-profiles"); } export async function listPostboxProtectionTransitions( settings: ApiSettings, postboxId: string ): Promise { const response = await apiFetch<{ transitions: PostboxProtectionTransition[] }>( settings, `/api/v1/postbox/admin/postboxes/${encodeURIComponent(postboxId)}/protection-transitions` ); return response.transitions; } export function createPostboxProtectionTransition( settings: ApiSettings, postbox: PostboxDirectoryItem, payload: { idempotency_key: string; target_profile: PostboxProtectionProfileId; target_vault_id?: string | null; history_mode: "future_only" | "migrate_history"; authority_mode: "user_consent" | "institutional_key_holders" | "dual_control"; required_quorum: number; user_consent_refs: string[]; institutional_authorization_refs: string[]; reason: string; acknowledge_irreversibility: boolean; } ): Promise { return apiPostJson( settings, `/api/v1/postbox/admin/postboxes/${encodeURIComponent(postbox.id)}/protection-transitions`, { ...payload, base_revision: postbox.resource_revision }, { headers: { "If-Match": postbox.etag } } ); } export function updatePostboxProtectionPolicy( settings: ApiSettings, postbox: PostboxDirectoryItem, protectionPolicy: PostboxProtectionPolicy ): Promise { return apiFetch( settings, `/api/v1/postbox/admin/postboxes/${encodeURIComponent(postbox.id)}/protection-policy`, { method: "PUT", headers: { "If-Match": postbox.etag }, body: JSON.stringify({ base_revision: postbox.resource_revision, protection_policy: protectionPolicy }) } ); } export function updatePostboxGroupingPolicy( settings: ApiSettings, postbox: PostboxDirectoryItem, groupingPolicy: PostboxGroupingPolicy ): Promise { return apiFetch( settings, `/api/v1/postbox/admin/postboxes/${encodeURIComponent(postbox.id)}/grouping-policy`, { method: "PUT", headers: { "If-Match": postbox.etag }, body: JSON.stringify({ base_revision: postbox.resource_revision, grouping_policy: groupingPolicy }) } ); } export async function listPostboxOrganizationTargets( settings: ApiSettings ): Promise { return apiFetch( settings, "/api/v1/postbox/admin/organization-targets" ); } export function createExactPostbox( settings: ApiSettings, payload: PostboxExactCreatePayload ): Promise { return apiPostJson(settings, "/api/v1/postbox/admin/postboxes", payload); } export function archivePostbox( settings: ApiSettings, postbox: PostboxDirectoryItem ): Promise { return apiFetch( settings, `/api/v1/postbox/admin/postboxes/${encodeURIComponent(postbox.id)}`, { method: "DELETE", headers: { "If-Match": postbox.etag }, body: JSON.stringify({ base_revision: postbox.resource_revision }) } ); } export async function listPostboxTemplates(settings: ApiSettings): Promise { const response = await apiFetch<{ templates: PostboxTemplate[] }>( settings, "/api/v1/postbox/admin/templates" ); return response.templates; } export function createPostboxTemplate( settings: ApiSettings, payload: PostboxTemplateCreatePayload ): Promise { return apiPostJson(settings, "/api/v1/postbox/admin/templates", payload); } export function previewPostboxTemplate( settings: ApiSettings, payload: PostboxTemplateCreatePayload & { template_id?: string | null; context_key?: string | null; limit?: number; } ): Promise { return apiPostJson( settings, "/api/v1/postbox/admin/templates/preview", payload ); } export function revisePostboxTemplate( settings: ApiSettings, template: PostboxTemplate, payload: PostboxTemplateRevisionPayload ): Promise { return apiPostJson( settings, `/api/v1/postbox/admin/templates/${encodeURIComponent(template.id)}/revisions`, { ...payload, base_revision: template.resource_revision }, { headers: { "If-Match": template.etag } } ); } export function publishPostboxTemplate( settings: ApiSettings, template: PostboxTemplate, revision?: number ): Promise { return apiPostJson( settings, `/api/v1/postbox/admin/templates/${encodeURIComponent(template.id)}/publish`, { revision: revision ?? null, base_revision: template.resource_revision }, { headers: { "If-Match": template.etag } } ); } export function retirePostboxTemplate( settings: ApiSettings, template: PostboxTemplate ): Promise { return apiPostJson( settings, `/api/v1/postbox/admin/templates/${encodeURIComponent(template.id)}/retire`, { base_revision: template.resource_revision }, { headers: { "If-Match": template.etag } } ); } export function materializePostboxTemplate( settings: ApiSettings, templateId: string, payload: { organization_unit_id: string; function_id: string; context_key?: string | null; } ): Promise { return apiPostJson( settings, `/api/v1/postbox/admin/templates/${encodeURIComponent(templateId)}/materialize`, payload ); }